//Matrix/src/main/java/com/mattrixwv/matrix/ModMatrix.java //Mattrixwv // Created: 02-09-22 //Modified: 08-11-24 package com.mattrixwv.matrix; import java.util.Arrays; import com.mattrixwv.matrix.exceptions.InvalidCoordinatesException; import com.mattrixwv.matrix.exceptions.InvalidGeometryException; import com.mattrixwv.matrix.exceptions.InvalidRowSizeException; import com.mattrixwv.matrix.exceptions.InvalidScalarException; import com.mattrixwv.matrix.exceptions.NullMatrixException; /** * Represents a matrix of integers that have been run through a modulus function, and provides various matrix operations. */ public class ModMatrix extends IntegerMatrix{ /** * The mod used on each element of the matrix */ protected int mod; //?Helper functions /** * Set the mod values * * @param mod The new mod value * @throws InvalidScalarException If the mod value is less than or equal to 0 */ protected void setMod(int mod){ if(mod <= 0){ throw new InvalidScalarException("The mod must be > 0"); } this.mod = mod; } /** * Get the mod value of a number * * @param value The value to mod * @return The modded value */ protected int modValue(int value){ int newValue = value % mod; if(newValue < 0){ newValue += mod; } return newValue; } /** * Get the mod value of each element in an array * * @param values The array of values * @return The array of modded values * @throws NullMatrixException If the array is null */ protected int[] modValues(int[] values){ if(values == null){ throw new NullMatrixException("Array cannot be null"); } int[] newValues = new int[values.length]; for(int cnt = 0;cnt < values.length;++cnt){ newValues[cnt] = modValue(values[cnt]); } return newValues; } /** * Get the mod value of each element in the matrix */ protected void modGrid(){ for(int row = 0;row < getNumRows();++row){ for(int col = 0;col < getNumCols();++col){ grid[row][col] = modValue(grid[row][col]); } } } /** * Sets the matrix grid to the specified 2D array. Validates the input to ensure * all rows are of equal length. * * @param grid The 2D array to set as the matrix grid. * @throws InvalidRowSizeException If the rows of the matrix are not all the same length. */ @Override protected void setGrid(int[][] grid){ super.setGrid(grid); modGrid(); } //?Constructors /** * Constructs an empty matrix (0x0). * * @param mod The mod value to use. */ public ModMatrix(int mod){ super(); setMod(mod); modGrid(); } /** * Constructs a matrix with the specified grid. * * @param grid The 2D array to initialize the matrix with. * @param mod The mod value to use. */ public ModMatrix(int[][] grid, int mod){ super(); setMod(mod); setGrid(grid); } /** * Constructs a copy of the specified matrix. * * @param matrix The matrix to copy. */ public ModMatrix(ModMatrix matrix){ super(); setMod(matrix.mod); setGrid(matrix.grid); } /** * Constructs a ModMatrix based on the given IntegerMatrix * * @param matrix The matrix to copy. * @param mod The mod value to use. */ public ModMatrix(IntegerMatrix matrix, int mod){ super(); setMod(mod); setGrid(matrix.grid); } /** * Constructs a matrix with the specified number of rows and columns, filled with the specified value. * * @param rows The number of rows. * @param cols The number of columns. * @param fill The value to fill the matrix with. * @param mod The mod value to use. * @throws InvalidGeometryException If the number of rows or columns is less than or equal to zero. */ public ModMatrix(int rows, int cols, int fill, int mod){ super(rows, cols, fill); setMod(mod); modGrid(); } //?Gets /** * Get the mod value of the matrix * * @return The mod value */ public int getMod(){ return mod; } /** * Returns a new matrix that is a copy of the specified row. * * @param row The index of the row to retrieve. * @return A new ModMatrix instance containing the specified row. * @throws InvalidCoordinatesException If the row index is out of bounds. */ @Override public ModMatrix getRow(int row){ return new ModMatrix(super.getRow(row), mod); } /** * Returns the number of rows in the matrix. * * @return The number of rows. */ @Override public ModMatrix getCol(int col){ return new ModMatrix(super.getCol(col), mod); } //?Sets /** * Sets the value at the specified row and column. * * @param row The row index. * @param col The column index. * @param value The value to set. * @throws InvalidCoordinatesException If the row or column index is out of bounds. */ @Override public void set(int row, int col, int value){ super.set(row, col, modValue(value)); } /** * Sets the specified row with the given array of elements. * * @param row The row index. * @param elements The array of elements to set. * @throws InvalidCoordinatesException If the row index is out of bounds. * @throws InvalidGeometryException If the length of the elements array does not match the number of columns. */ @Override public void setRow(int row, int[] elements){ super.setRow(row, modValues(elements)); } /** * Sets the specified row with the given matrix containing a single row. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param row The row index. * @param matrix The matrix containing a single row to set. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single row. */ @Override public void setRow(int row, IntegerMatrix matrix){ setRow(row, new ModMatrix(matrix, mod)); } /** * Sets the specified row with the given matrix containing a single row. * * @param row The row index. * @param matrix The matrix containing a single row to set. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single row. */ public void setRow(int row, ModMatrix matrix){ super.setRow(row, matrix); modGrid(); } /** * Sets the specified column with the given array of elements. * * @param col The column index. * @param elements The array of elements to set. * @throws InvalidCoordinatesException If the column index is out of bounds. * @throws InvalidGeometryException If the length of the elements array does not match the number of rows. */ @Override public void setCol(int col, int[] elements){ super.setCol(col, elements); modGrid(); } /** * Sets the specified column with the given matrix containing a single column. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param col The column index. * @param matrix The matrix containing a single column to set. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single column. */ @Override public void setCol(int col, IntegerMatrix matrix){ setCol(col, new ModMatrix(matrix, mod)); } /** * Sets the specified column with the given matrix containing a single column. * * @param col The column index. * @param matrix The matrix containing a single column to set. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single column. */ public void setCol(int col, ModMatrix matrix){ super.setCol(col, matrix); modGrid(); } //?Adds /** * Adds a new row with the specified array of elements to the matrix. * * @param elements The array of elements to add as a new row. * @throws NullMatrixException If the elements array is null. * @throws InvalidGeometryException If the length of the elements array does not match the number of columns. */ @Override public void addRow(int[] elements){ super.addRow(modValues(elements)); } /** * Adds a new row with the given matrix containing a single row to the matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param matrix The matrix containing a single row to add. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single row. */ @Override public void addRow(IntegerMatrix matrix){ addRow(new ModMatrix(matrix, mod)); } /** * Adds a new row with the given matrix containing a single row to the matrix. * * @param matrix The matrix containing a single row to add. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single row. */ public void addRow(ModMatrix matrix){ super.addRow(matrix); modGrid(); } /** * Adds a new column with the specified array of elements to the matrix. * * @param elements The array of elements to add as a new column. * @throws NullMatrixException If the elements array is null. * @throws InvalidGeometryException If the length of the elements array does not match the number of rows. */ @Override public void addCol(int[] elements){ super.addCol(modValues(elements)); } /** * Adds a new column with the given matrix containing a single column to the matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param matrix The matrix containing a single column to add. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single column. */ @Override public void addCol(IntegerMatrix matrix){ addCol(new ModMatrix(matrix, mod)); } /** * Adds a new column with the given matrix containing a single column to the matrix. * * @param matrix The matrix containing a single column to add. * @throws NullMatrixException If the matrix is null. * @throws InvalidGeometryException If the matrix does not contain a single column. */ public void addCol(ModMatrix matrix){ super.addCol(matrix); modGrid(); } /** * Appends the specified matrix to the right side of the current matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param rightSide The matrix to append to the right side. * @return A new ModMatrix instance with the right-side matrix appended. * @throws NullMatrixException If the right-side matrix is null. * @throws InvalidGeometryException If the number of rows does not match. */ @Override public ModMatrix appendRight(IntegerMatrix rightSide){ return appendRight(new ModMatrix(rightSide, mod)); } /** * Appends the specified matrix to the right side of the current matrix. * * @param rightSide The matrix to append to the right side. * @return A new ModMatrix instance with the right-side matrix appended. * @throws NullMatrixException If the right-side matrix is null. * @throws InvalidGeometryException If the number of rows does not match. */ public ModMatrix appendRight(ModMatrix rightSide){ return new ModMatrix(super.appendRight(rightSide), mod); } /** * Appends the specified matrix to the bottom of the current matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param bottomSide The matrix to append to the bottom. * @return A new ModMatrix instance with the bottom matrix appended. * @throws NullMatrixException If the bottom matrix is null. * @throws InvalidGeometryException If the number of columns does not match. */ @Override public ModMatrix appendBottom(IntegerMatrix bottomSide){ return appendBottom(new ModMatrix(bottomSide, mod)); } /** * Appends the specified matrix to the bottom of the current matrix. * * @param bottomSide The matrix to append to the bottom. * @return A new ModMatrix instance with the bottom matrix appended. * @throws NullMatrixException If the bottom matrix is null. * @throws InvalidGeometryException If the number of columns does not match. */ public ModMatrix appendBottom(ModMatrix bottomSide){ return new ModMatrix(super.appendBottom(bottomSide), mod); } //?Simple operations /** * Generates an identity matrix of the given size. * * @param size The size of the identity matrix. * @return A new ModMatrix instance representing the identity matrix. * @throws InvalidGeometryException If the size is less than or equal to zero. */ public static ModMatrix generateIdentity(int size){ return generateIdentity(size, Integer.MAX_VALUE); } /** * Generates an identity matrix of the given size. * * @param size The size of the identity matrix. * @param mod The mod value to use. * @return A new ModMatrix instance representing the identity matrix. * @throws InvalidGeometryException If the size is less than or equal to zero. */ public static ModMatrix generateIdentity(int size, int mod){ return new ModMatrix(IntegerMatrix.generateIdentity(size), mod); } /** * Adds the specified matrix to the current matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param rightSide The matrix to add. * @return A new ModMatrix instance with the result of the addition. * @throws InvalidGeometryException If the matrices do not have the same dimensions. */ @Override public ModMatrix add(IntegerMatrix rightSide){ return add(new ModMatrix(rightSide, mod)); } /** * Adds the specified matrix to the current matrix. * * @param rightSide The matrix to add. * @return A new ModMatrix instance with the result of the addition. * @throws InvalidGeometryException If the matrices do not have the same dimensions. */ public ModMatrix add(ModMatrix rightSide){ return new ModMatrix(super.add(rightSide), mod); } /** * Adds the scalar to every element in the matrix. * * @param scalar The scalar to add. * @return A new ModMatrix instance with the result of the addition. */ @Override public ModMatrix add(int scalar){ return new ModMatrix(super.add(scalar), mod); } /** * Subtracts the specified matrix from the current matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param rightSide The matrix to subtract. * @return A new ModMatrix instance with the result of the subtraction. * @throws InvalidGeometryException If the matrices do not have the same dimensions. */ @Override public ModMatrix subtract(IntegerMatrix rightSide){ return subtract(new ModMatrix(rightSide, mod)); } /** * Subtracts the specified matrix from the current matrix. * * @param rightSide The matrix to subtract. * @return A new ModMatrix instance with the result of the subtraction. * @throws InvalidGeometryException If the matrices do not have the same dimensions. */ public ModMatrix subtract(ModMatrix rightSide){ return new ModMatrix(super.subtract(rightSide), mod); } /** * Subtracts the scalar from every element in the matrix. * * @param scalar The scalar to subtract. * @return A new ModMatrix instance with the result of the subtraction. */ @Override public ModMatrix subtract(int scalar){ return new ModMatrix(super.subtract(scalar), mod); } /** * Multiplies the current matrix by the specified matrix. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param rightSide The matrix to multiply by. * @return A new ModMatrix instance with the result of the multiplication. * @throws InvalidGeometryException If the number of columns in the current matrix does not match the number of rows in the right-side matrix. */ @Override public ModMatrix multiply(IntegerMatrix rightSide){ return multiply(new ModMatrix(rightSide, mod)); } /** * Multiplies the current matrix by the specified matrix. * * @param rightSide The matrix to multiply by. * @return A new ModMatrix instance with the result of the multiplication. * @throws InvalidGeometryException If the number of columns in the current matrix does not match the number of rows in the right-side matrix. */ public ModMatrix multiply(ModMatrix rightSide){ return new ModMatrix(super.multiply(rightSide), mod); } /** * Multiplies every element in the matrix by the scalar. * * @param scalar the scalar to multiply * @return A new ModMatrix instance with the result of the multiplication */ @Override public ModMatrix multiply(int scalar){ return new ModMatrix(super.multiply(scalar), mod); } /** * Multiplies the current matrix by itself the given number of times. * * @param power The number of times to multiply the matrix by itself * @return A new ModMatrix instance with the result of the multiplication * @throws InvalidScalarException If the power is negative * @throws InvalidGeometryException If the matrix is not square */ @Override public ModMatrix pow(int power){ //Make sure the matrix is square so it can be multiplied if(!isSquare()){ throw new InvalidGeometryException("The matrix must be square to raise it to a power"); } //Make sure the power is positive if(power < 0){ throw new InvalidScalarException("The power must be >= 0"); } else if(power == 0){ return new ModMatrix(getNumRows(), getNumCols(), 1, mod); } //Create a new matrix for the product ModMatrix newMatrix = new ModMatrix(this); //Multiply the current grid power times for(int currentPower = 1;currentPower < power;++currentPower){ newMatrix = newMatrix.multiply(this); } //Return the new grid return newMatrix; } /** * Calculates the dot product of the two matrices. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param rightSide The matrix to use on the right side of the calculation * @return The dot product of the two matrices * @throws NullMatrixException If the right matrix is null * @throws InvalidGeometryException If the matrices do not have compatible dimensions */ @Override public int dotProduct(IntegerMatrix rightSide){ return dotProduct(new ModMatrix(rightSide, mod)); } /** * Calculates the dot product of the two matrices. * * @param rightSide The matrix to use on the right side of the calculation * @return The dot product of the two matrices * @throws NullMatrixException If the right matrix is null * @throws InvalidGeometryException If the matrices do not have compatible dimensions */ public int dotProduct(ModMatrix rightSide){ return super.dotProduct(rightSide) % mod; } /** * Calculates the Hadamard product of the two matrices. * Converts the IntegerMatrix to a ModMatrix before the operation. * * @param rightSide The matrix to use on the right side of the calculation * @return The Hadamard product of the two matrices * @throws NullMatrixException If the right matrix is null * @throws InvalidGeometryException If the matrices do not have compatible dimensions */ @Override public ModMatrix hadamardProduct(IntegerMatrix rightSide){ return hadamardProduct(new ModMatrix(rightSide, mod)); } /** * Calculates the Hadamard product of the two matrices. * * @param rightSide The matrix to use on the right side of the calculation * @return The Hadamard product of the two matrices * @throws NullMatrixException If the right matrix is null * @throws InvalidGeometryException If the matrices do not have compatible dimensions */ public ModMatrix hadamardProduct(ModMatrix rightSide){ return new ModMatrix(super.hadamardProduct(rightSide), mod); } //?Complex operations /** * Transposes the current matrix (i.e., swaps rows and columns). * * @return A new ModMatrix instance representing the transposed matrix. */ @Override public ModMatrix transpose(){ return new ModMatrix(super.transpose(), mod); } /** * Calculates the cofactor matrix of the current matrix. * * @return A new ModMatrix instance representing the cofactor matrix. * @throws InvalidGeometryException If the matrix is not square. * @see #cofactor() */ @Override public ModMatrix cof(){ return cofactor(); } /** * Calculates the cofactor matrix of the current matrix. * * @return A new ModMatrix instance representing the cofactor matrix. * @throws InvalidGeometryException If the matrix is not square. */ @Override public ModMatrix cofactor(){ //Make sure the matrix is square if(!isSquare()){ throw new InvalidGeometryException("A matrix must be square to find the cofactor matrix"); } //Create a new grid int[][] newGrid = new int[getNumRows()][getNumCols()]; //If the grid is 1x1 return the grid if(getNumRows() == 1){ newGrid[0][0] = 1; } //Use the formula to find the cofactor matrix else{ for(int row = 0;row < getNumRows();++row){ int multiplier = ((row % 2) == 0) ? 1 : -1; for(int col = 0;col < getNumCols();++col){ newGrid[row][col] = multiplier * laplaceExpansionHelper(row, col).determinant(); multiplier = -multiplier; } } } //Return the new matrix return new ModMatrix(newGrid, mod); } /** * Calculates the adjoint matrix of the current matrix. * * @return A new ModMatrix instance representing the adjoint matrix. * @throws InvalidGeometryException If the matrix is not square. * @see #adjoint() */ @Override public ModMatrix adj(){ return adjoint(); } /** * Calculates the adjoint matrix of the current matrix. * * @return A new ModMatrix instance representing the adjoint matrix. * @throws InvalidGeometryException If the matrix is not square. */ @Override public ModMatrix adjoint(){ return cofactor().transpose(); } /** * Calculates the inverse of the current matrix. * * @return A new ModMatrix instance representing the inverse matrix. * @throws InvalidGeometryException If the matrix is not square or if the determinant is 0. */ @Override public ModMatrix inverse(){ //Make sure the matrix is square if(!isSquare()){ throw new InvalidGeometryException("A matrix must be square for it to have an inverse"); } //Make sure the determinant is not 0 int determinant = determinant(); if(determinant == 0){ throw new InvalidScalarException("The determinant cannot be 0"); } //Find the inverse of determinant % mod int determinantInverse = -1; for(int num = 1;num < mod;++num){ if(modValue(determinant * num) == 1){ determinantInverse = num; break; } } if(determinantInverse < 0){ throw new InvalidScalarException("There is no inverse for the determinant"); } //Find the matrix of cofactors and multiply the inverse determinant by cofactors and return return adjoint().multiply(determinantInverse); } //?Object functions /** * Determines whether the given object is equal to the current matrix. * Can determine equality using ModMatrix or int[][]. * * @param rightSide The object to compare to the current matrix. * @return True if the objects are equal, false otherwise. * @see #equals(ModMatrix) */ @Override public boolean equals(Object rightSide){ if(rightSide == null){ return false; } else if(rightSide.getClass().equals(this.getClass())){ return equals((ModMatrix)rightSide); } else if(rightSide.getClass().equals(int[][].class)){ int[][] rightMatrix = (int[][])rightSide; return equals(new ModMatrix(rightMatrix, mod)); } else{ return false; } } /** * Determines whether the given ModMatrix is equal to the current matrix. * * @param rightMatrix The ModMatrix to compare to the current matrix. * @return True if the matrices are equal, false otherwise. */ public boolean equals(ModMatrix rightMatrix){ if(rightMatrix == null){ return false; } //Make sure they have the same number of elements if((getNumRows() != rightMatrix.getNumRows()) || (getNumCols() != rightMatrix.getNumCols())){ return false; } //Check every element for(int row = 0;row < getNumRows();++row){ for(int col = 0;col < getNumCols();++col){ if(grid[row][col] != rightMatrix.grid[row][col]){ return false; } } } //If false hasn't been return yet then they are equal return true; } /** * Calculates a hash code for the current matrix. * * @return The hash code for the current matrix. */ @Override public int hashCode(){ return Arrays.hashCode(grid); } /** * Returns a string representation of the matrix, with rows and columns formatted for readability. * * @return A string representation of the matrix. */ @Override public String toString(){ return super.toString() + "\nmod(" + mod + ")"; } }