package jaci.openrio.toast.lib.math; /** * The Matrix Class is a math object that exists in the form of a 2 Dimensional Matrix. These matrices follow * standard mathematical matrix rules. * * @author Jaci */ public class Matrix { int rows, columns; double[][] values; public static Matrix IDENTITY = new Matrix(2, 1, 0, 0, 1); /** * Create a new, empty matrix with a specified number of rows and columns */ public Matrix(int rows, int columns) { this.rows = rows; this.columns = columns; values = new double[rows][columns]; } /** * Create a new matrix with a specified dataset. * @param data The data to insert into the matrix * @param columns How many columns for each row (used to split the dataset into a 2d matrix) */ public Matrix(double[] data, int columns) { this(data.length / columns, columns); for (int i = 0; i < rows; i++) for (int j = 0; j < columns; j++) values[i][j] = data[(i * columns) + j]; } /** * Create a new matrix with a specified dataset. * @param data The data to insert into the matrix * @param columns How many columns for each row (used to split the dataset into a 2d matrix) */ public Matrix(int columns, double... data) { this(data, columns); } /** * Create a new matrix with the specified dataset. * @param data The data to use for the matrix. This 2d array is the data of the 2d matrix */ public Matrix(double[][] data) { values = data; rows = data.length; columns = data[0].length; } /** * Get the values inside of the matrix in the form of a 2d double matrix */ public double[][] getValues() { return values; } /** * Get the amount of rows in the matrix */ public int getRows() { return rows; } /** * Get the amount of columns per row in the matrix */ public int getColumns() { return columns; } /** * Set a value in the matrix to a specified value * @param rowID The row number to update * @param columnID The column number to update * @param value The value to update to */ public void set(int rowID, int columnID, double value) { values[rowID][columnID] = value; } /** * Get a value in the matrix * @param rowID The row number to get * @param columnID The column number to get */ public double get(int rowID, int columnID) { return values[rowID][columnID]; } /** * Returns true if this matrix equals another matrix or a 2d double array */ public boolean equals(Object o) { double[][] va; if (o instanceof Matrix) { va = ((Matrix) o).values; } else if (o instanceof double[][]) { va = (double[][]) o; } else return false; if (va == null) return false; if (va.length != values.length || va[0].length != values[0].length) return false; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) if (get(i,j) != va[i][j]) return false; } return true; } /** * Returns true if the matrix is square (the row count and column count are equal) */ public boolean isSquare() { return rows == columns; } /** * Clone the 2d double array for manipulation */ private double[][] cloneValues() { double[][] newDoubles = new double[values.length][]; for(int i = 0; i < values.length; i++) newDoubles[i] = values[i].clone(); return newDoubles; } /** * Get the determinant of the matrix. For a 2x2 matrix, this is 'ad-bc'. Determinants can only be * found if the matrix is square. */ public double determinant() { if (!isSquare()) throw new IllegalArgumentException("Determinant Matrices must be Square"); return determinant(cloneValues(), rows); } /** * Returns true if the matrix is singular (the determinant is 0). Singular matrices cannot be inverted. */ public boolean isSingular() { return determinant() == 0; } /** * Convert the matrix into a nicely formatted string */ public String asString() { String s = rows + " * " + columns + " matrix\n"; for (int i = 0; i < rows; i++) { String build = ""; for (int j = 0; j < columns; j++) { build += "\t " + MathHelper.round(values[i][j], 2); } s += build; if (i != rows - 1) s += "\n"; } return s; } /** * Invert the matrix. This cannot occur if the matrix is singular (determinant is 0) */ public Matrix invert() { if (isSingular()) throw new IllegalStateException("Cannot invert singular matrix!"); return new Matrix(invert(cloneValues())); } /** * Multiply this matrix by another matrix. * The object this method is called on is treated as the pre-multiplied matrix, and the parameter is the * post-multiplied. * e.g. * A.multiply(B) => AB * @param mx The matrix to multiply with */ public Matrix multiply(Matrix mx) { return new Matrix(multiply(values, mx.values)); } /** * Multiply this matrix element-wise with a scalar quantity */ public Matrix multiply(double scalar) { double[][] newMatrix = new double[rows][columns]; for (int i = 0; i < rows; i++) for (int j = 0; j < columns; j++) newMatrix[i][j] = values[i][j] * scalar; return new Matrix(newMatrix); } /** * Multiply this matrix by another matrix. * The object this method is called on is treated as the post-multiplied matrix, and the parameter is the * pre-multiplied. * e.g. * A.postmultiplyBy(B) => BA * @param mx The matrix to multiply with */ public Matrix premultiplyBy(Matrix mx) { return mx.multiply(this); } /** * Alias for {@link #multiply(Matrix)} */ public Matrix postmultiplyBy(Matrix mx) { return this.multiply(mx); } /** * Add this matrix element-wise with another matrix */ public Matrix add(Matrix mx) { double[][] newMatrix = new double[rows][columns]; for (int i = 0; i < rows; i++) for (int j = 0; j < columns; j++) newMatrix[i][j] = values[i][j] + mx.get(i, j); return new Matrix(newMatrix); } /** * Subtract the parameter matrix from this matrix */ public Matrix subtract(Matrix mx) { double[][] newMatrix = new double[rows][columns]; for (int i = 0; i < rows; i++) for (int j = 0; j < columns; j++) newMatrix[i][j] = values[i][j] - mx.get(i, j); return new Matrix(newMatrix); } // -- STATICS -- // public static double[][] multiply(double[][] a, double[][] b) { int aRows = a.length; int aColumns = a[0].length; int bRows = b.length; int bColumns = b[0].length; if (aColumns != bRows) throw new IllegalArgumentException("Matrices of size " + aRows+"*"+aColumns + " and " + bRows+"*"+bColumns +" cannot be multiplied!"); double[][] result = new double[aRows][bColumns]; for (int i = 0; i < aRows; i++) for (int j = 0; j < bColumns; j++) for (int k = 0; k < aColumns; k++) result[i][j] += a[i][k] * b[k][j]; return result; } public static double determinant(double[][] a, int n){ double det = 0; int p = 0, q = 0, sign = 1; if(n==1) det = a[0][0]; else { double b[][] = new double[n-1][n-1]; for(int x = 0 ; x < n ; x++){ p=0; q=0; for(int i = 1;i < n; i++){ for(int j = 0; j < n;j++){ if(j != x){ b[p][q++] = a[i][j]; if(q % (n-1) == 0){ p++; q=0; } } } } det = det + a[0][x] * determinant(b, n-1) * sign; sign = -sign; } } return det; } // Thanks to http://www.sanfoundry.com/java-program-find-inverse-matrix/ for inversion algorithm public static double[][] invert(double[][] a) { int n = a.length; double x[][] = new double[n][n]; double b[][] = new double[n][n]; int index[] = new int[n]; for (int i=0; i<n; ++i) b[i][i] = 1; gaussian(a, index); for (int i=0; i<n-1; ++i) for (int j=i+1; j<n; ++j) for (int k=0; k<n; ++k) b[index[j]][k] -= a[index[j]][i]*b[index[i]][k]; for (int i=0; i<n; ++i) { x[n-1][i] = b[index[n-1]][i]/a[index[n-1]][n-1]; for (int j=n-2; j>=0; --j) { x[j][i] = b[index[j]][i]; for (int k=j+1; k<n; ++k) x[j][i] -= a[index[j]][k]*x[k][i]; x[j][i] /= a[index[j]][j]; } } return x; } public static void gaussian(double[][] a, int index[]) { int n = index.length; double c[] = new double[n]; for (int i=0; i<n; ++i) index[i] = i; for (int i=0; i<n; ++i) { double c1 = 0; for (int j=0; j<n; ++j) { double c0 = Math.abs(a[i][j]); if (c0 > c1) c1 = c0; } c[i] = c1; } int k = 0; for (int j=0; j<n-1; ++j) { double pi1 = 0; for (int i=j; i<n; ++i) { double pi0 = Math.abs(a[index[i]][j]); pi0 /= c[index[i]]; if (pi0 > pi1) { pi1 = pi0; k = i; } } int itmp = index[j]; index[j] = index[k]; index[k] = itmp; for (int i=j+1; i<n; ++i) { double pj = a[index[i]][j]/a[index[j]][j]; a[index[i]][j] = pj; for (int l=j+1; l<n; ++l) a[index[i]][l] -= pj*a[index[j]][l]; } } } }