From 5e6f65a181c1a04cf5b992ab350972cb41d6ac93 Mon Sep 17 00:00:00 2001 From: darksnake Date: Sat, 13 Mar 2021 18:19:10 +0300 Subject: [PATCH] WIP Matrix refactor --- .../kmath/structures/typeSafeDimensions.kt | 4 +- .../kscience/kmath/commons/linear/CMMatrix.kt | 4 +- .../kscience/kmath/commons/linear/CMSolver.kt | 4 +- .../kscience/kmath/linear/MatrixBuilder.kt | 25 ++++++++++-- .../space/kscience/kmath/linear/MatrixTest.kt | 2 +- .../kscience/kmath/linear/RealLUSolverTest.kt | 14 +++---- .../kmath/structures/NumberNDFieldTest.kt | 5 ++- .../kotlin/space/kscience/kmath/real/dot.kt | 19 ---------- .../kaceince/kmath/real/RealMatrixTest.kt | 38 +++++++++---------- .../kaceince/kmath/real/RealVectorTest.kt | 4 +- 10 files changed, 60 insertions(+), 59 deletions(-) diff --git a/examples/src/main/kotlin/space/kscience/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/space/kscience/kmath/structures/typeSafeDimensions.kt index fdd631238..d2d130ab4 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/structures/typeSafeDimensions.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/structures/typeSafeDimensions.kt @@ -5,7 +5,7 @@ import space.kscience.kmath.dimensions.D3 import space.kscience.kmath.dimensions.DMatrixContext import space.kscience.kmath.dimensions.Dimension -private fun DMatrixContext.simple() { +private fun DMatrixContext.simple() { val m1 = produce { i, j -> (i + j).toDouble() } val m2 = produce { i, j -> (i + j).toDouble() } @@ -17,7 +17,7 @@ private object D5 : Dimension { override val dim: UInt = 5u } -private fun DMatrixContext.custom() { +private fun DMatrixContext.custom() { val m1 = produce { i, j -> (i + j).toDouble() } val m2 = produce { i, j -> (i - j).toDouble() } val m3 = produce { i, j -> (i - j).toDouble() } diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt index c1c3d716a..b462a1a36 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt @@ -99,8 +99,8 @@ public object CMLinearSpace : LinearSpace { ArrayRealVector(array).wrap() } - private fun RealMatrix.wrap(): CMMatrix = CMMatrix(this) - private fun RealVector.wrap(): CMVector = CMVector(this) + internal fun RealMatrix.wrap(): CMMatrix = CMMatrix(this) + internal fun RealVector.wrap(): CMVector = CMVector(this) override fun buildVector(size: Int, initializer: RealField.(Int) -> Double): Vector = ArrayRealVector(DoubleArray(size) { RealField.initializer(it) }).wrap() diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt index ff4727aa2..b5fd0154e 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt @@ -27,7 +27,7 @@ public fun CMLinearSpace.solve( a: Matrix, b: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP -): CMMatrix = solver(a, decomposition).solve(b.toCM().origin).asMatrix() +): CMMatrix = solver(a, decomposition).solve(b.toCM().origin).wrap() public fun CMLinearSpace.solve( a: Matrix, @@ -38,4 +38,4 @@ public fun CMLinearSpace.solve( public fun CMLinearSpace.inverse( a: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP -): CMMatrix = solver(a, decomposition).inverse.asMatrix() +): CMMatrix = solver(a, decomposition).inverse.wrap() diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt index 28df78dad..6814cb561 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt @@ -3,11 +3,30 @@ package space.kscience.kmath.linear import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Ring +public class MatrixBuilder>( + public val linearSpace: LinearSpace, + public val rows: Int, + public val columns: Int, +) { + public operator fun invoke(vararg elements: T): Matrix { + require(rows * columns == elements.size) { "The number of elements ${elements.size} is not equal $rows * $columns" } + return linearSpace.buildMatrix(rows, columns) { i, j -> elements[i * columns + j] } + } + + //TODO add specific matrix builder functions like diagonal, etc +} @UnstableKMathAPI -public fun LinearSpace>.matrix(rows: Int, columns: Int, vararg elements: T): Matrix { - require(rows * columns == elements.size) { "The number of elements ${elements.size} is not equal $rows * $columns" } - return buildMatrix(rows, columns) { i, j -> elements[i * columns + j] } +public fun > LinearSpace.matrix(rows: Int, columns: Int): MatrixBuilder = + MatrixBuilder(this, rows, columns) + +/** + * Build a square matrix from given elements. + */ +@UnstableKMathAPI +public fun LinearSpace>.square(vararg elements: T): Matrix { + val size: Int = kotlin.math.sqrt(elements.size.toDouble()).toInt() + return matrix(size,size)(*elements) } @UnstableKMathAPI diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt index 27374e93f..53cdd4522 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt @@ -38,7 +38,7 @@ class MatrixTest { infix fun Matrix.pow(power: Int): Matrix { var res = this repeat(power - 1) { - res = RealLinearSpace.invoke { res dot this@pow } + res = LinearSpace.real.run { res dot this@pow } } return res } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/RealLUSolverTest.kt index 4d6b8f5be..802849a1e 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/RealLUSolverTest.kt @@ -14,12 +14,12 @@ class RealLUSolverTest { @Test fun testDecomposition() { - val matrix = Matrix.square( - 3.0, 1.0, - 1.0, 3.0 - ) - LinearSpace.real.run { + val matrix = square( + 3.0, 1.0, + 1.0, 3.0 + ) + val lup = lup(matrix) //Check determinant @@ -31,14 +31,14 @@ class RealLUSolverTest { @Test fun testInvert() { - val matrix = Matrix.square( + val matrix = LinearSpace.real.square( 3.0, 1.0, 1.0, 3.0 ) val inverted = LinearSpace.real.inverseWithLup(matrix) - val expected = Matrix.square( + val expected = LinearSpace.real.square( 0.375, -0.125, -0.125, 0.375 ) diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt index 23b0e7348..dd7871f9a 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt @@ -1,5 +1,6 @@ package space.kscience.kmath.structures +import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.nd.* import space.kscience.kmath.operations.Norm import space.kscience.kmath.operations.invoke @@ -33,7 +34,9 @@ class NumberNDFieldTest { @Test fun testGeneration() { - val array = Structure2D.real(3, 3) { i, j -> (i * 10 + j).toDouble() } + val array = LinearSpace.real.buildMatrix(3, 3) { i, j -> + (i * 10 + j).toDouble() + } for (i in 0..2) { for (j in 0..2) { diff --git a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt index 5a66b55bd..1a7c1213c 100644 --- a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt +++ b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt @@ -9,23 +9,4 @@ import space.kscience.kmath.nd.Matrix */ public infix fun Matrix.dot(other: Matrix): Matrix = LinearSpace.real.run{ this@dot dot other -// require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } -// val resultArray = DoubleArray(this.rowNum * other.colNum) -// -// //convert to array to insure there is no memory indirection -// fun Buffer.unsafeArray() = if (this is RealBuffer) -// this.array -// else -// DoubleArray(size) { get(it) } -// -// val a = this.buffer.unsafeArray() -// val b = other.buffer.unsafeArray() -// -// for (i in (0 until rowNum)) -// for (j in (0 until other.colNum)) -// for (k in (0 until colNum)) -// resultArray[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j] -// -// val buffer = RealBuffer(resultArray) -// return BufferMatrix(rowNum, other.colNum, buffer) } \ No newline at end of file diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt index 9e2778be9..6ab11b03b 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt @@ -1,7 +1,7 @@ package kaceince.kmath.real -import space.kscience.kmath.linear.Matrix -import space.kscience.kmath.linear.build +import space.kscience.kmath.linear.LinearSpace +import space.kscience.kmath.linear.matrix import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.real.* import space.kscience.kmath.structures.contentEquals @@ -30,11 +30,11 @@ internal class RealMatrixTest { @Test fun testRepeatStackVertical() { - val matrix1 = Matrix.build(2, 3)( + val matrix1 = LinearSpace.real.matrix(2, 3)( 1.0, 0.0, 0.0, 0.0, 1.0, 2.0 ) - val matrix2 = Matrix.build(6, 3)( + val matrix2 = LinearSpace.real.matrix(6, 3)( 1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0, 0.0, 0.0, @@ -47,12 +47,12 @@ internal class RealMatrixTest { @Test fun testMatrixAndDouble() { - val matrix1 = Matrix.build(2, 3)( + val matrix1 = LinearSpace.real.matrix(2, 3)( 1.0, 0.0, 3.0, 4.0, 6.0, 2.0 ) val matrix2 = (matrix1 * 2.5 + 1.0 - 2.0) / 2.0 - val expectedResult = Matrix.build(2, 3)( + val expectedResult = LinearSpace.real.matrix(2, 3)( 0.75, -0.5, 3.25, 4.5, 7.0, 2.0 ) @@ -61,13 +61,13 @@ internal class RealMatrixTest { @Test fun testDoubleAndMatrix() { - val matrix1 = Matrix.build(2, 3)( + val matrix1 = LinearSpace.real.matrix(2, 3)( 1.0, 0.0, 3.0, 4.0, 6.0, 2.0 ) val matrix2 = 20.0 - (10.0 + (5.0 * matrix1)) //val matrix2 = 10.0 + (5.0 * matrix1) - val expectedResult = Matrix.build(2, 3)( + val expectedResult = LinearSpace.real.matrix(2, 3)( 5.0, 10.0, -5.0, -10.0, -20.0, 0.0 ) @@ -76,15 +76,15 @@ internal class RealMatrixTest { @Test fun testSquareAndPower() { - val matrix1 = Matrix.build(2, 3)( + val matrix1 = LinearSpace.real.matrix(2, 3)( -1.0, 0.0, 3.0, 4.0, -6.0, -2.0 ) - val matrix2 = Matrix.build(2, 3)( + val matrix2 = LinearSpace.real.matrix(2, 3)( 1.0, 0.0, 9.0, 16.0, 36.0, 4.0 ) - val matrix3 = Matrix.build(2, 3)( + val matrix3 = LinearSpace.real.matrix(2, 3)( -1.0, 0.0, 27.0, 64.0, -216.0, -8.0 ) @@ -95,16 +95,16 @@ internal class RealMatrixTest { @OptIn(UnstableKMathAPI::class) @Test fun testTwoMatrixOperations() { - val matrix1 = Matrix.build(2, 3)( + val matrix1 = LinearSpace.real.matrix(2, 3)( -1.0, 0.0, 3.0, 4.0, -6.0, 7.0 ) - val matrix2 = Matrix.build(2, 3)( + val matrix2 = LinearSpace.real.matrix(2, 3)( 1.0, 0.0, 3.0, 4.0, 6.0, -2.0 ) val result = matrix1 * matrix2 + matrix1 - matrix2 - val expectedResult = Matrix.build(2, 3)( + val expectedResult = LinearSpace.real.matrix(2, 3)( -3.0, 0.0, 9.0, 16.0, -48.0, -5.0 ) @@ -113,16 +113,16 @@ internal class RealMatrixTest { @Test fun testColumnOperations() { - val matrix1 = Matrix.build(2, 4)( + val matrix1 = LinearSpace.real.matrix(2, 4)( -1.0, 0.0, 3.0, 15.0, 4.0, -6.0, 7.0, -11.0 ) - val matrix2 = Matrix.build(2, 5)( + val matrix2 = LinearSpace.real.matrix(2, 5)( -1.0, 0.0, 3.0, 15.0, -1.0, 4.0, -6.0, 7.0, -11.0, 4.0 ) - val col1 = Matrix.build(2, 1)(0.0, -6.0) - val cols1to2 = Matrix.build(2, 2)( + val col1 = LinearSpace.real.matrix(2, 1)(0.0, -6.0) + val cols1to2 = LinearSpace.real.matrix(2, 2)( 0.0, 3.0, -6.0, 7.0 ) @@ -147,7 +147,7 @@ internal class RealMatrixTest { @Test fun testAllElementOperations() { - val matrix1 = Matrix.build(2, 4)( + val matrix1 = LinearSpace.real.matrix(2, 4)( -1.0, 0.0, 3.0, 15.0, 4.0, -6.0, 7.0, -11.0 ) diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt index 5d032de67..59d55110f 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt @@ -2,9 +2,7 @@ package kaceince.kmath.real import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.asMatrix -import space.kscience.kmath.linear.real import space.kscience.kmath.linear.transpose -import space.kscience.kmath.operations.invoke import space.kscience.kmath.real.plus import space.kscience.kmath.structures.Buffer import kotlin.test.Test @@ -32,7 +30,7 @@ internal class RealVectorTest { val vector2 = Buffer.real(5) { 5 - it.toDouble() } val matrix1 = vector1.asMatrix() val matrix2 = vector2.asMatrix().transpose() - val product = LinearSpace.real { matrix1 dot matrix2 } + val product = LinearSpace.real.run { matrix1 dot matrix2 } assertEquals(5.0, product[1, 0]) assertEquals(6.0, product[2, 2]) }