package main.java.model;
import java.time.LocalDate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Main Matrix class for mathematical matrix operations.
* @author Isaac Jordan
*/
public class Matrix {
private StringProperty name;
private final IntegerProperty numRows;
private final IntegerProperty numCols;
private final ObjectProperty<LocalDate> createdDate;
private final ObjectProperty<double[][]> data;
private Double determinant;
private Matrix inverse;
private RREFMatrix RREForm;
private Matrix cofactor;
/**
* Default constructor. Creates an empty, unnamed matrix, with current date.
*
* @throws Exception
*/
public Matrix() {
this(null, new double[0][0], null);
}
/**
* Constructor with some initial data.
*
* @param name
* @param data
* @throws Exception
*/
public Matrix(String name, double[][] data, LocalDate date) {
this.name = new SimpleStringProperty(name);
this.data = new SimpleObjectProperty<double[][]>(data);
numRows = new SimpleIntegerProperty(data.length);
numCols = new SimpleIntegerProperty(data[0].length);
if (date != null)
createdDate = new SimpleObjectProperty<LocalDate>(date);
else
createdDate = new SimpleObjectProperty<LocalDate>(LocalDate.now());
}
// Getters/Setters
// name
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name;
}
// numRows
public int getNumRows() {
return numRows.get();
}
public IntegerProperty numRowsProperty() {
return numRows;
}
// numCols
public int getNumCols() {
return numCols.get();
}
public IntegerProperty numColsProperty() {
return numCols;
}
// createdDateget
public LocalDate getCreatedDate() {
return createdDate.get();
}
public ObjectProperty<LocalDate> createdDateProperty() {
return createdDate;
}
// data
public double[][] getData() {
return data.get();
}
public double[] getRow(int row) {
return data.get()[row];
}
public double[] getCol(int colNum) {
double[] column = new double[getNumRows()];
for (int i = 0; i < getNumRows(); i++)
column[i] = data.get()[i][colNum];
return column;
}
public double getCell(int row, int col) {
return data.get()[row][col];
}
public double[][] cloneData() {
double[][] result = new double[getNumRows()][getNumCols()];
for (int i = 0; i < getNumRows(); i++) {
for (int j = 0; j < getNumCols(); j++) {
result[i][j] = getData()[i][j];
}
}
return result;
}
/**
* Changes matrix's data to positive 0's and 10 decimal places.
*
* @return
*/
public Matrix normalise() {
double[][] data = getData();
for (int i = 0; i < getNumRows(); i++) {
for (int j = 0; j < getNumCols(); j++) {
if (data[i][j] == -0.0)
data[i][j] = 0.0;
// Round number to 10 decimal places.
data[i][j] = Math.round(data[i][j] * 10000000000.0) / 10000000000.0;
}
}
return this;
}
/**
* Checks whether it is possible to multiply two matrices together.
*
* @param A
* @param B
* @return
*/
public static Boolean checkMultCompatibility(Matrix A, Matrix B) {
if (A.getData().length != B.getData()[0].length)
return false;
return true;
}
/**
* Uses naive method to calculate the product of two matrices.
* Throws IllegalArgumentException if the matrices are not compatible.
*
* @param A
* @param B
* @return
*/
public static Matrix multiplyMatrices(Matrix A, Matrix B) {
if (checkMultCompatibility(A, B)) {
double[][] data = new double[A.getNumRows()][B.getNumCols()];
int i = 0;
int j = 0;
int k = 0;
while (i < A.getNumRows()) {
while (j < B.getNumCols()) {
while (k < B.getNumRows()) {
data[i][j] = data[i][j] + A.getData()[i][k] * B.getData()[k][j];
k += 1;
}
j += 1;
k = 0;
}
i += 1;
j = 0;
}
return (new Matrix(null, data, null));
}
throw new IllegalArgumentException("Matrices are not compatible");
}
/**
* Multiples a matrix by a scalar.
* @param c - A double value to multiple all entries of the matrix by.
* @return
*/
public Matrix multiplyScalar(double c) {
for (int i = 0; i < getNumRows(); i++)
for (int j = 0; j < getNumCols(); j++)
getData()[i][j] *= c;
return this;
}
/**
* Raises matrix to the power n using naive method.
* Can use a lot of resources if matrix, or n, is large.
* @param n
* @return
*/
public Matrix toPower(int n) {
Matrix resultMatrix = new Matrix(null, cloneData(), null);
for (int i = 1; i < n; i++)
resultMatrix = Matrix.multiplyMatrices(this, resultMatrix);
return resultMatrix;
}
/**
* Static method that adds two matrices A, and B. If the matrices cannot be added, an
* IllegalArgumentException is thrown.
*
* @param A
* @param B
* @return
*/
public static Matrix addMatrices(Matrix A, Matrix B) {
if (A.getNumRows() == B.getNumRows() && A.getNumCols() == B.getNumCols()) {
double[][] data = new double[A.getNumRows()][A.getNumCols()];
for (int i = 0; i < A.getNumRows(); i++)
for (int j = 0; j < A.getNumCols(); j++)
data[i][j] = A.getData()[i][j] + B.getData()[i][j];
return (new Matrix(null, data, null));
} else
throw new IllegalArgumentException("Matrices are not compatible.");
}
/**
* Returns the nested array after having removed the values in the given row, and column.
* http://en.wikipedia.org/wiki/Minor_(linear_algebra)
*
* @param initialData
* @param returnData - The object to fill with the result.
* @param row - Which row's data to remove
* @param column - Which column's data to remove.
* @param numRows - How many rows initialData has.
* @return
*/
private static double[][] reduce(double[][] initialData, double[][] returnData, int row,
int column, int numRows) {
for (int h = 0, j = 0; h < numRows; h++) {
if (h == row)
continue;
for (int i = 0, k = 0; i < numRows; i++) {
if (i == column)
continue;
returnData[j][k] = initialData[h][i];
k++;
}
j++;
}
return returnData;
}
/**
* Returns the full cofactor matrix of this matrix.
* Warning: This is very computationally heavy.
* @return
*/
public Matrix cofactorMatrix() {
if (cofactor != null) {
return cofactor;
}
double[][] data = cloneData();
double[][] cofactorData = new double[getNumRows()][getNumCols()];
double det;
for (int i = 0; i < getNumRows(); i++) {
for (int j = 0; j < getNumCols(); j++) {
double[][] reduced = new double[getNumRows() - 1][getNumCols() - 1];
det = determinant(reduce(data, reduced, i, j, getNumRows()));
cofactorData[i][j] = ((i + j) % 2 == 0 ? det : -det);
}
}
cofactor = new Matrix(null, cofactorData, null);
return cofactor;
}
/**
* Private static method that deals with the recursion of determinant calculation. Inspired by
* http://en.wikibooks.org/wiki/Algorithm_Implementation/Linear_Algebra/Determinant_of_a_Matrix
*
* @param data
* @return
*/
private static double determinant(double[][] data) {
int numRows = data.length;
double ret = 0;
if (numRows == 2)
return data[0][0] * data[1][1] - data[0][1] * data[1][0];
if (numRows < 4) {
double prod1 = 1, prod2 = 1;
for (int i = 0; i < numRows; i++) {
prod1 = 1;
prod2 = 1;
for (int j = 0; j < numRows; j++) {
prod1 *= data[(j + i + 1) % numRows][j];
prod2 *= data[(j + i + 1) % numRows][numRows - j - 1];
}
ret += prod1 - prod2;
}
return ret;
}
double[][] reduced = new double[numRows - 1][numRows - 1];
for (int h = 0; h < numRows; h++) {
if (data[h][0] == 0)
continue;
reduce(data, reduced, h, 0, numRows);
if (h % 2 == 0)
ret -= determinant(reduced) * data[h][0];
if (h % 2 == 1)
ret += determinant(reduced) * data[h][0];
}
return ret;
}
/**
* Public method for determinant calculation.
*
* @return
*/
public double determinant() {
if (determinant != null) {
return determinant;
}
determinant = determinant(getData());
return determinant;
}
/**
* Swaps row1 and row2
* @param A
* @param row1
* @param row2
* @return
*/
public static Matrix ERO1(Matrix A, int row1, int row2) {
double[] temp = A.getData()[row1];
A.getData()[row1] = A.getData()[row2];
A.getData()[row2] = temp;
return A;
}
/**
* Multiply every element of row by scalar
* @param A
* @param row
* @param scalar
* @return
*/
public static Matrix ERO2(Matrix A, int row, double scalar) {
for (int i = 0; i < A.getNumCols(); i++) {
A.getData()[row][i] *= scalar;
}
return A;
}
/**
* Executes: row1 = row1 + scalar*row2
* @param A
* @param row1
* @param row2
* @param scalar
* @return
*/
public static Matrix ERO3(Matrix A, int row1, int row2, double scalar) {
for (int i = 0; i < A.getNumCols(); i++) {
A.getData()[row1][i] += scalar * A.getData()[row2][i];
}
return A;
}
/**
* Returns the transpose of this matrix in a new Matrix object.
* @return
*/
public Matrix transpose() {
double[][] data = new double[this.getNumCols()][this.getNumRows()];
for (int i = 0; i < this.getNumRows(); i++) {
data[i] = this.getCol(i);
}
return new Matrix(null, data, null);
}
/**
* Returns the inverse of this matrix in a new matrix object.
* The inverse is only calculated the first time this method is called,
* and stored for subsequent calls.
* @return
*/
public Matrix inverse() {
if (inverse != null) {
return inverse;
}
double det = determinant();
if (det != 0) {
inverse = cofactorMatrix().transpose().multiplyScalar(1 / det).normalise();
return inverse;
} else
return null;
}
/**
* Returns the sum of all values on the main diagonal of this matrix.
* @return
*/
public double trace() {
double total = 0;
double[][] data = getData();
for (int i = 0; i < getNumRows(); i++) {
total += data[i][i];
}
return total;
}
public RREFMatrix reducedEchelonForm() {
if (RREForm != null) {
return RREForm;
}
RREForm = new RREFMatrix(this);
return RREForm;
}
/**
* Currently only works for 2x2 matrices.
* http://en.wikipedia.org/wiki/Eigenvalue_algorithm#Direct_calculation
* @return
*/
public double[] eigenvalues() {
if (getNumRows() == 2 && getNumCols() == 2){
double lambda1 = (this.trace() + Math.sqrt(Math.pow(this.trace(), 2) - 4 * this.determinant()))/2;
double lambda2 = (this.trace() - Math.sqrt(Math.pow(this.trace(), 2) - 4 * this.determinant()))/2;
double[] returnData = {lambda1,lambda2};
return returnData;
} else {
return null;
}
}
}