From 1619a490170b4e84aa1f32fb604ef0700cfa536e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 18 Aug 2024 22:45:33 +0300 Subject: [PATCH] Add proper test for symmetric matrices eigenValueDecomposition --- .../kmath/linear/eigenValueDecomposition.kt | 40 +++++++++++++++++++ .../kmath/linear/EigenDecomposition.kt | 2 + kmath-ejml/build.gradle.kts | 27 ++++++------- .../kscience/kmath/ejml/EjmlLinearSpace.kt | 0 .../space/kscience/kmath/ejml/EjmlMatrix.kt | 0 .../space/kscience/kmath/ejml/EjmlVector.kt | 0 .../kscience/kmath/ejml/implementations.kt | 4 +- .../kscience/kmath/ejml/EjmlMatrixTest.kt | 13 ++++-- .../kscience/kmath/ejml/EjmlVectorTest.kt | 0 test-utils/src/commonMain/kotlin/asserts.kt | 10 +++++ 10 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt rename kmath-ejml/src/{main => jvmMain}/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt (100%) rename kmath-ejml/src/{main => jvmMain}/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt (100%) rename kmath-ejml/src/{main => jvmMain}/kotlin/space/kscience/kmath/ejml/EjmlVector.kt (100%) rename kmath-ejml/src/{main => jvmMain}/kotlin/space/kscience/kmath/ejml/implementations.kt (99%) rename kmath-ejml/src/{test => jvmTest}/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt (85%) rename kmath-ejml/src/{test => jvmTest}/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt (100%) diff --git a/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt b/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt new file mode 100644 index 000000000..1e7211757 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/linear/eigenValueDecomposition.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2018-2024 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.linear + +import space.kscience.kmath.commons.linear.CMLinearSpace +import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM +import space.kscience.kmath.nd.StructureND +import space.kscience.kmath.operations.algebra +import space.kscience.kmath.structures.Float64 +import kotlin.random.Random + +fun main() { + val dim = 46 + + val random = Random(123) + + val u = Float64.algebra.linearSpace.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + + listOf(CMLinearSpace, EjmlLinearSpaceDDRM).forEach { algebra -> + with(algebra) { + //create a simmetric matrix + val matrix = buildMatrix(dim, dim) { row, col -> + if (row >= col) u[row, col] else u[col, row] + } + val eigen = matrix.getOrComputeAttribute(EIG) ?: error("Failed to compute eigenvalue decomposition") + check( + StructureND.contentEquals( + matrix, + eigen.v dot eigen.d dot eigen.v.transposed(), + 1e-4 + ) + ) { "$algebra decomposition failed" } + println("$algebra eigenvalue decomposition complete and checked" ) + } + } + +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/EigenDecomposition.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/EigenDecomposition.kt index 4675509bd..dcb97d24a 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/EigenDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/EigenDecomposition.kt @@ -7,6 +7,7 @@ package space.kscience.kmath.linear import space.kscience.attributes.PolymorphicAttribute import space.kscience.attributes.safeTypeOf +import space.kscience.kmath.UnstableKMathAPI public interface EigenDecomposition { /** @@ -24,5 +25,6 @@ public class EigenDecompositionAttribute : PolymorphicAttribute>(safeTypeOf()), MatrixAttribute> +@UnstableKMathAPI public val MatrixScope.EIG: EigenDecompositionAttribute get() = EigenDecompositionAttribute() diff --git a/kmath-ejml/build.gradle.kts b/kmath-ejml/build.gradle.kts index de0399244..003d5d56e 100644 --- a/kmath-ejml/build.gradle.kts +++ b/kmath-ejml/build.gradle.kts @@ -1,13 +1,20 @@ plugins { - id("space.kscience.gradle.jvm") + id("space.kscience.gradle.mpp") } val ejmlVerision = "0.43.1" -dependencies { - api(projects.kmathCore) - api(projects.kmathComplex) - api("org.ejml:ejml-all:$ejmlVerision") +kscience { + jvm() + jvmMain { + api(projects.kmathCore) + api(projects.kmathComplex) + api("org.ejml:ejml-all:$ejmlVerision") + } + + jvmTest { + implementation(projects.testUtils) + } } readme { @@ -28,12 +35,4 @@ readme { id = "ejml-linear-space", ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt" ) { "LinearSpace implementations." } -} - -//kotlin.sourceSets.main { -// val codegen by tasks.creating { -// ejmlCodegen(kotlin.srcDirs.first().absolutePath + "/space/kscience/kmath/ejml/_generated.kt") -// } -// -// kotlin.srcDirs(files().builtBy(codegen)) -//} +} \ No newline at end of file diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt similarity index 100% rename from kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt rename to kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt similarity index 100% rename from kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt rename to kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/EjmlVector.kt similarity index 100% rename from kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt rename to kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/EjmlVector.kt diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/implementations.kt b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt similarity index 99% rename from kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/implementations.kt rename to kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt index d84eeb589..5657f94e3 100644 --- a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/implementations.kt +++ b/kmath-ejml/src/jvmMain/kotlin/space/kscience/kmath/ejml/implementations.kt @@ -278,8 +278,8 @@ public object EjmlLinearSpaceDDRM : EjmlLinearSpace by lazy { - val eigenvectors = List(origin.numRows) { cmEigen.getEigenVector(it) } - buildMatrix(origin.numRows, origin.numCols) { row, column -> + val eigenvectors = List(origin.numRows) { cmEigen.getEigenVector(it) }.filterNotNull() + buildMatrix(eigenvectors.size, origin.numCols) { row, column -> eigenvectors[row][column] } } diff --git a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt b/kmath-ejml/src/jvmTest/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt similarity index 85% rename from kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt rename to kmath-ejml/src/jvmTest/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt index dda42d42c..239889e04 100644 --- a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt +++ b/kmath-ejml/src/jvmTest/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt @@ -18,12 +18,15 @@ import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.toArray import space.kscience.kmath.operations.algebra import space.kscience.kmath.structures.Float64 +import space.kscience.kmath.testutils.assertStructureEquals import kotlin.random.Random import kotlin.random.asJavaRandom import kotlin.test.* internal fun assertMatrixEquals(expected: StructureND, actual: StructureND) { - assertTrue { StructureND.contentEquals(expected, actual) } + expected.elements().forEach { (index, value) -> + assertEquals(value, actual[index], "Structure element with index ${index.toList()} should be equal to $value but is ${actual[index]}") + } } @OptIn(UnstableKMathAPI::class) @@ -108,8 +111,12 @@ internal class EjmlMatrixTest { @Test fun eigenValueDecomposition() = EjmlLinearSpaceDDRM { - val matrix = EjmlDoubleMatrix(randomMatrix) + val dim = 46 + val u = buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val matrix = buildMatrix(dim, dim) { row, col -> + if (row >= col) u[row, col] else u[col, row] + } val eigen = matrix.getOrComputeAttribute(EIG) ?: fail() - assertMatrixEquals(matrix, eigen.v dot eigen.d dot eigen.v.transposed()) + assertStructureEquals(matrix, eigen.v dot eigen.d dot eigen.v.transposed()) } } diff --git a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt b/kmath-ejml/src/jvmTest/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt similarity index 100% rename from kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt rename to kmath-ejml/src/jvmTest/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt diff --git a/test-utils/src/commonMain/kotlin/asserts.kt b/test-utils/src/commonMain/kotlin/asserts.kt index f4716f9f6..a3f8a28fd 100644 --- a/test-utils/src/commonMain/kotlin/asserts.kt +++ b/test-utils/src/commonMain/kotlin/asserts.kt @@ -5,6 +5,8 @@ package space.kscience.kmath.testutils +import space.kscience.kmath.PerformancePitfall +import space.kscience.kmath.nd.StructureND import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Float64 import space.kscience.kmath.structures.indices @@ -18,4 +20,12 @@ public fun assertBufferEquals(expected: Buffer, result: Buffer expected.indices.forEach { assertEquals(expected[it], result[it], tolerance) } +} + +@OptIn(PerformancePitfall::class) +public fun assertStructureEquals(expected: StructureND, result: StructureND, tolerance: Double = 1e-4) { + assertEquals(expected.shape, result.shape, "Structure shape mismatch") + expected.indices.forEach { + assertEquals(expected[it], result[it], tolerance) + } } \ No newline at end of file