diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 852ba15..f37e372 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,10 +37,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'adopt' # Initializes the CodeQL tools for scanning. diff --git a/example/build.gradle b/example/build.gradle index 5a6a596..c0e9d52 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -7,7 +7,7 @@ version = '1.0.5' java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } diff --git a/lib/build.gradle b/lib/build.gradle index 434ab42..d6a62c2 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -9,7 +9,7 @@ version = '1.0.5' java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } @@ -18,6 +18,9 @@ repositories { } dependencies { + implementation 'org.ejml:ejml-core:0.43.1' + implementation 'org.ejml:ejml-ddense:0.43.1' + implementation 'org.ejml:ejml-simple:0.43.1' implementation 'com.opencsv:opencsv:5.8' implementation 'org.apache.logging.log4j:log4j-api:2.20.0' implementation 'org.apache.logging.log4j:log4j-core:2.20.0' @@ -38,12 +41,6 @@ testing { } } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - task sourceJar(type: Jar) { from sourceSets.main.allSource archiveClassifier.set('sources') diff --git a/lib/src/main/java/de/edux/util/math/ConcurrentMatrixMultiplication.java b/lib/src/main/java/de/edux/util/math/ConcurrentMatrixMultiplication.java new file mode 100644 index 0000000..05a255e --- /dev/null +++ b/lib/src/main/java/de/edux/util/math/ConcurrentMatrixMultiplication.java @@ -0,0 +1,17 @@ +package de.edux.util.math; + +public interface ConcurrentMatrixMultiplication { + + /** + * Multiplies two matrices and returns the resulting matrix. + * + * @param a The first matrix. + * @param b The second matrix. + * @return The product of the two matrices. + * @throws IllegalArgumentException If the matrices cannot be multiplied due to incompatible dimensions. + */ + double[][] multiplyMatrices(double[][] a, double[][] b) throws IllegalArgumentException, IncompatibleDimensionsException; + + + +} diff --git a/lib/src/main/java/de/edux/util/math/IncompatibleDimensionsException.java b/lib/src/main/java/de/edux/util/math/IncompatibleDimensionsException.java new file mode 100644 index 0000000..e236e74 --- /dev/null +++ b/lib/src/main/java/de/edux/util/math/IncompatibleDimensionsException.java @@ -0,0 +1,7 @@ +package de.edux.util.math; + +public class IncompatibleDimensionsException extends Exception{ + public IncompatibleDimensionsException(String message) { + super(message); + } +} diff --git a/lib/src/main/java/de/edux/util/math/MathMatrix.java b/lib/src/main/java/de/edux/util/math/MathMatrix.java new file mode 100644 index 0000000..ccace34 --- /dev/null +++ b/lib/src/main/java/de/edux/util/math/MathMatrix.java @@ -0,0 +1,58 @@ +package de.edux.util.math; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class MathMatrix implements ConcurrentMatrixMultiplication { + private static final Logger LOG = LoggerFactory.getLogger(MathMatrix.class); + + @Override + public double[][] multiplyMatrices(double[][] a, double[][] b) throws IncompatibleDimensionsException { + LOG.info("Multiplying matrices of size {}x{} and {}x{}", a.length, a[0].length, b.length, b[0].length); + int aRows = a.length; + int aCols = a[0].length; + int bCols = b[0].length; + + if (aCols != b.length) { + throw new IncompatibleDimensionsException("Cannot multiply matrices with incompatible dimensions"); + } + + double[][] result = new double[aRows][bCols]; + + try(var executor = Executors.newVirtualThreadPerTaskExecutor()) { + List> futures = new ArrayList<>(aRows); + + for (int i = 0; i < aRows; i++) { + final int rowIndex = i; + futures.add(executor.submit(() -> { + for (int colIndex = 0; colIndex < bCols; colIndex++) { + result[rowIndex][colIndex] = multiplyMatrixRowByColumn(a, b, rowIndex, colIndex); + } + return null; + })); + } + for (var future : futures) { + future.get(); + } + } catch (ExecutionException | InterruptedException e) { + LOG.error("Error while multiplying matrices", e); + } + + LOG.info("Finished multiplying matrices"); + return result; + } + + private double multiplyMatrixRowByColumn(double[][] a, double[][] b, int row, int col) { + double sum = 0; + for (int i = 0; i < a[0].length; i++) { + sum += a[row][i] * b[i][col]; + } + return sum; + } +} diff --git a/lib/src/main/java/de/edux/util/math/MatrixOperations.java b/lib/src/main/java/de/edux/util/math/MatrixOperations.java new file mode 100644 index 0000000..af9bf6c --- /dev/null +++ b/lib/src/main/java/de/edux/util/math/MatrixOperations.java @@ -0,0 +1,49 @@ +package de.edux.util.math; + +public interface MatrixOperations { + /** + * Adds two matrices and returns the resulting matrix. + * + * @param a The first matrix. + * @param b The second matrix. + * @return The sum of the two matrices. + * @throws IllegalArgumentException If the matrices are not of the same dimension. + */ + double[][] addMatrices(double[][] a, double[][] b) throws IllegalArgumentException; + + /** + * Subtracts matrix b from matrix a and returns the resulting matrix. + * + * @param a The first matrix. + * @param b The second matrix. + * @return The result of a - b. + * @throws IllegalArgumentException If the matrices are not of the same dimension. + */ + double[][] subtractMatrices(double[][] a, double[][] b) throws IllegalArgumentException; + + /** + * Transposes the given matrix and returns the resulting matrix. + * + * @param a The matrix to transpose. + * @return The transposed matrix. + */ + double[][] transposeMatrix(double[][] a); + + /** + * Inverts the given matrix and returns the resulting matrix. + * + * @param a The matrix to invert. + * @return The inverted matrix. + * @throws IllegalArgumentException If the matrix is not invertible. + */ + double[][] invertMatrix(double[][] a) throws IllegalArgumentException; + + /** + * Calculates and returns the determinant of the given matrix. + * + * @param a The matrix. + * @return The determinant of the matrix. + * @throws IllegalArgumentException If the matrix is not square. + */ + double determinant(double[][] a) throws IllegalArgumentException; +} diff --git a/lib/src/test/java/de/edux/ml/nn/network/MultilayerPerceptronTest.java b/lib/src/test/java/de/edux/ml/nn/network/MultilayerPerceptronTest.java index 9d32ff6..b4d4763 100644 --- a/lib/src/test/java/de/edux/ml/nn/network/MultilayerPerceptronTest.java +++ b/lib/src/test/java/de/edux/ml/nn/network/MultilayerPerceptronTest.java @@ -1,6 +1,5 @@ package de.edux.ml.nn.network; -import de.edux.data.provider.Penguin; import de.edux.data.provider.SeabornDataProcessor; import de.edux.data.provider.SeabornProvider; import de.edux.functions.activation.ActivationFunction; @@ -9,7 +8,6 @@ import de.edux.ml.nn.config.NetworkConfiguration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; import java.io.File; import java.net.URL; diff --git a/lib/src/test/java/de/edux/util/math/MathMatrixTest.java b/lib/src/test/java/de/edux/util/math/MathMatrixTest.java new file mode 100644 index 0000000..d24fe20 --- /dev/null +++ b/lib/src/test/java/de/edux/util/math/MathMatrixTest.java @@ -0,0 +1,115 @@ +package de.edux.util.math; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MathMatrixTest { + private static final long someMaximumValue = 1_000_000_000; // Example value + private static final Logger LOG = LoggerFactory.getLogger(MathMatrixTest.class); + + @Test + void multiplyMatrices() throws IncompatibleDimensionsException { + long startTime = System.currentTimeMillis(); + int size = 500; + + double[][] matrixA = generateMatrix(size); + double[][] matrixB = generateMatrix(size); + + ConcurrentMatrixMultiplication matrixMultiplier = new MathMatrix(); + double[][] resultMatrix = matrixMultiplier.multiplyMatrices(matrixA, matrixB); + + assertEquals(size, resultMatrix.length); + assertEquals(size, resultMatrix[0].length); + + long endTime = System.currentTimeMillis(); + long timeElapsed = endTime - startTime; + LOG.info("Time elapsed: " + timeElapsed / 1000 + " seconds"); + } + + @Test + void multiplyMatricesSmall() throws IncompatibleDimensionsException { + double[][] matrixA = { + {1, 2}, + {3, 4} + }; + + double[][] matrixB = { + {2, 0}, + {1, 3} + }; + + ConcurrentMatrixMultiplication matrixMultiplier = new MathMatrix(); + double[][] resultMatrix = matrixMultiplier.multiplyMatrices(matrixA, matrixB); + + double[][] expectedMatrix = { + {4, 6}, + {10, 12} + }; + + assertArrayEquals(expectedMatrix, resultMatrix); + } + + static void assertArrayEquals(double[][] expected, double[][] actual) { + assertEquals(expected.length, actual.length); + + for (int i = 0; i < expected.length; i++) { + assertArrayEquals(expected[i], actual[i]); + } + } + + static void assertArrayEquals(double[] expected, double[] actual) { + assertEquals(expected.length, actual.length); + + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], actual[i]); + } + } + + double[][] generateMatrix(int size) { + double[][] matrix = new double[size][size]; + final int MAX_THREADS = 32; + + ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + List> futures = new ArrayList<>(); + + try { + int rowsPerThread = Math.max(size / MAX_THREADS, 1); + + for (int i = 0; i < MAX_THREADS && i * rowsPerThread < size; i++) { + final int startRow = i * rowsPerThread; + final int endRow = Math.min((i + 1) * rowsPerThread, size); + + futures.add(executor.submit(() -> { + for (int row = startRow; row < endRow; row++) { + for (int col = 0; col < size; col++) { + matrix[row][col] = Math.random() * 10; // Random values between 0 and 10 + } + } + return null; + })); + } + + for (Future future : futures) { + future.get(); + } + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } finally { + executor.shutdown(); + } + + LOG.info("Generated matrix with size: " + size); + return matrix; + } + +} \ No newline at end of file