From 22a3c6e4b9cc5ccb56972af66122ff0d86e77995 Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Sun, 14 Jul 2019 18:22:22 +0300 Subject: [PATCH 1/3] Initial implementation of kmath-for-real module based on https://github.com/thomasnield/numky --- kmath-for-real/build.gradle.kts | 9 ++ .../kmath/real/DoubleMatrixOperations.kt | 117 ++++++++++++++++++ settings.gradle.kts | 1 + 3 files changed, 127 insertions(+) create mode 100644 kmath-for-real/build.gradle.kts create mode 100644 kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt diff --git a/kmath-for-real/build.gradle.kts b/kmath-for-real/build.gradle.kts new file mode 100644 index 000000000..7eaa5e174 --- /dev/null +++ b/kmath-for-real/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + `npm-multiplatform` +} + +kotlin.sourceSets.commonMain { + dependencies { + api(project(":kmath-core")) + } +} \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt new file mode 100644 index 000000000..54e711a7b --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt @@ -0,0 +1,117 @@ +package scientifik.kmath.real + +import scientifik.kmath.linear.MatrixContext +import scientifik.kmath.linear.RealMatrixContext.elementContext +import scientifik.kmath.linear.VirtualMatrix +import scientifik.kmath.operations.sum +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.asSequence +import kotlin.math.pow + +// Initial implementation of these functions is taken from: +// https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt + +fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double) = MatrixContext.real.produce(rowNum, colNum, initializer) + +fun Sequence.toMatrix() = toList().let { + MatrixContext.real.produce(it.size,it[0].size) { row, col -> it[row][col] } +} + +operator fun Matrix.times(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@times[row, col] * double +} + +fun Matrix.square() = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@square[row,col].pow(2) +} + +operator fun Matrix.plus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@plus[row,col] + double +} + +operator fun Matrix.minus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@minus[row,col] - double +} + +operator fun Matrix.div(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@div[row,col] / double +} + +operator fun Double.times(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] * this +} + +operator fun Double.plus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] + this +} + +operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] - this +} + +operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] / this +} + +operator fun Matrix.times(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@times[row,col] * other[row,col] +} + +operator fun Matrix.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@minus[row,col] - other[row,col] +} + +operator fun Matrix.plus(other: Matrix) = MatrixContext.real.add(this,other) + +fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { row, col -> + get(if (row == 0) 0 else row % rowNum, col) +} + +inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = + MatrixContext.real.produce(rowNum,colNum+1) { row,col -> + if (col < colNum) + this[row,col] + else + mapper(rows[row]) + } + +fun Matrix.extractColumn(columnIndex: Int) = extractColumns(columnIndex..columnIndex) + +fun Matrix.extractColumns(columnRange: IntRange) = MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> + this@extractColumns[row, columnRange.start + col] +} + +fun Matrix.sumByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + with(elementContext) { + sum(column.asSequence()) + } +} + +fun Matrix.minByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + column.asSequence().min()?:throw Exception("Cannot produce min on empty column") +} + +fun Matrix.maxByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + column.asSequence().max()?:throw Exception("Cannot produce min on empty column") +} + +fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + column.asSequence().average() +} + +fun Matrix.sum() = this.elements().map { (_,value) -> value }.sum() + +fun Matrix.min() = this.elements().map { (_,value) -> value }.min() + +fun Matrix.max() = this.elements().map { (_,value) -> value }.max() + +fun Matrix.average() = this.elements().map { (_,value) -> value }.average() + +fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { i, j -> + this@pow[i,j].pow(n) +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 004b432fd..e7f27714c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,5 +29,6 @@ include( ":kmath-commons", ":kmath-koma", ":kmath-prob", + ":kmath-for-real", ":examples" ) From c31c9a9532d86aa30768763c286b030230f8d61c Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Wed, 7 Aug 2019 21:47:50 +0300 Subject: [PATCH 2/3] Move RealVector to kmath-for-real and use scientifik plugin --- .../scientifik/kmath/linear/MatrixTest.kt | 30 ---------------- kmath-for-real/build.gradle.kts | 10 +++--- .../scientifik/kmath/linear/RealVector.kt | 0 .../scientifik/kmath/linear/VectorTest.kt | 36 +++++++++++++++++++ kmath-histograms/build.gradle.kts | 1 + 5 files changed, 43 insertions(+), 34 deletions(-) rename {kmath-core => kmath-for-real}/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt (100%) create mode 100644 kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index de13c9888..d27aa665c 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -6,21 +6,6 @@ import kotlin.test.assertEquals class MatrixTest { - @Test - fun testSum() { - val vector1 = RealVector(5) { it.toDouble() } - val vector2 = RealVector(5) { 5 - it.toDouble() } - val sum = vector1 + vector2 - assertEquals(5.0, sum[2]) - } - - @Test - fun testVectorToMatrix() { - val vector = RealVector(5) { it.toDouble() } - val matrix = vector.asMatrix() - assertEquals(4.0, matrix[4, 0]) - } - @Test fun testTranspose() { val matrix = MatrixContext.real.one(3, 3) @@ -28,21 +13,6 @@ class MatrixTest { assertEquals(matrix, transposed) } - - @Test - fun testDot() { - val vector1 = RealVector(5) { it.toDouble() } - val vector2 = RealVector(5) { 5 - it.toDouble() } - - val matrix1 = vector1.asMatrix() - val matrix2 = vector2.asMatrix().transpose() - val product = MatrixContext.real.run { matrix1 dot matrix2 } - - - assertEquals(5.0, product[1, 0]) - assertEquals(6.0, product[2, 2]) - } - @Test fun testBuilder() { val matrix = Matrix.build(2, 3)( diff --git a/kmath-for-real/build.gradle.kts b/kmath-for-real/build.gradle.kts index 7eaa5e174..a8a8975bc 100644 --- a/kmath-for-real/build.gradle.kts +++ b/kmath-for-real/build.gradle.kts @@ -1,9 +1,11 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } -kotlin.sourceSets.commonMain { - dependencies { - api(project(":kmath-core")) +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + } } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt similarity index 100% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt rename to kmath-for-real/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt diff --git a/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt b/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt new file mode 100644 index 000000000..0e73ee4a5 --- /dev/null +++ b/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt @@ -0,0 +1,36 @@ +package scientifik.kmath.linear + +import kotlin.test.Test +import kotlin.test.assertEquals + +class VectorTest { + @Test + fun testSum() { + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } + val sum = vector1 + vector2 + assertEquals(5.0, sum[2]) + } + + @Test + fun testVectorToMatrix() { + val vector = RealVector(5) { it.toDouble() } + val matrix = vector.asMatrix() + assertEquals(4.0, matrix[4, 0]) + } + + @Test + fun testDot() { + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } + + val matrix1 = vector1.asMatrix() + val matrix2 = vector2.asMatrix().transpose() + val product = MatrixContext.real.run { matrix1 dot matrix2 } + + + assertEquals(5.0, product[1, 0]) + assertEquals(6.0, product[2, 2]) + } + +} \ No newline at end of file diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 39aa833ad..993bfed8e 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -5,5 +5,6 @@ plugins { kotlin.sourceSets.commonMain { dependencies { api(project(":kmath-core")) + api(project(":kmath-for-real")) } } \ No newline at end of file From fbe7988bd03156f5dd59b023deb0772e041cacda Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Tue, 3 Sep 2019 20:35:50 +0300 Subject: [PATCH 3/3] Some refactoring, add test file --- .../kmath/real/DoubleMatrixOperations.kt | 137 +++++++++++------- .../scientific.kmath.real/RealMatrixTest.kt | 16 ++ kmath-for-real/src/jvmMain/kotlin/.gitkeep | 0 3 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt create mode 100644 kmath-for-real/src/jvmMain/kotlin/.gitkeep diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt index 54e711a7b..d9c6b5eab 100644 --- a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt @@ -9,109 +9,138 @@ import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.asSequence import kotlin.math.pow -// Initial implementation of these functions is taken from: -// https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt +/* + * Functions for convenient "numpy-like" operations with Double matrices. + * + * Initial implementation of these functions is taken from: + * https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt + * + */ -fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double) = MatrixContext.real.produce(rowNum, colNum, initializer) +/* + * Functions that help create a real (Double) matrix + */ + +fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double) = + MatrixContext.real.produce(rowNum, colNum, initializer) fun Sequence.toMatrix() = toList().let { - MatrixContext.real.produce(it.size,it[0].size) { row, col -> it[row][col] } + MatrixContext.real.produce(it.size, it[0].size) { row, col -> it[row][col] } } -operator fun Matrix.times(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@times[row, col] * double +fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { + row, col -> get(if (row == 0) 0 else row % rowNum, col) } -fun Matrix.square() = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@square[row,col].pow(2) +/* + * Operations for matrix and real number + */ + +operator fun Matrix.times(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] * double } -operator fun Matrix.plus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@plus[row,col] + double +operator fun Matrix.plus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] + double } -operator fun Matrix.minus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@minus[row,col] - double +operator fun Matrix.minus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] - double } -operator fun Matrix.div(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@div[row,col] / double +operator fun Matrix.div(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] / double } -operator fun Double.times(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] * this +operator fun Double.times(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { + row, col -> this * matrix[row, col] } -operator fun Double.plus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] + this +operator fun Double.plus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { + row, col -> this * matrix[row, col] } -operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] - this +operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { + row, col -> this - matrix[row, col] } -operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] / this +// TODO: does this operation make sense? Should it be 'this/matrix[row, col]'? +//operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { +// row, col -> matrix[row, col] / this +//} + +/* + * Per-element (!) square and power operations + */ + +fun Matrix.square() = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col].pow(2) } -operator fun Matrix.times(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@times[row,col] * other[row,col] +fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { + i, j -> this[i, j].pow(n) } -operator fun Matrix.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@minus[row,col] - other[row,col] +/* + * Operations on two matrices (per-element!) + */ + +operator fun Matrix.times(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] * other[row, col] } -operator fun Matrix.plus(other: Matrix) = MatrixContext.real.add(this,other) +operator fun Matrix.plus(other: Matrix) = MatrixContext.real.add(this, other) -fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { row, col -> - get(if (row == 0) 0 else row % rowNum, col) +operator fun Matrix.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row,col] - other[row,col] } -inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = - MatrixContext.real.produce(rowNum,colNum+1) { row,col -> +/* + * Operations on columns + */ + +inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = + MatrixContext.real.produce(rowNum,colNum+1) { + row, col -> if (col < colNum) - this[row,col] + this[row, col] else mapper(rows[row]) - } + } + +fun Matrix.extractColumns(columnRange: IntRange) = MatrixContext.real.produce(rowNum, columnRange.count()) { + row, col -> this[row, columnRange.first + col] +} fun Matrix.extractColumn(columnIndex: Int) = extractColumns(columnIndex..columnIndex) -fun Matrix.extractColumns(columnRange: IntRange) = MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> - this@extractColumns[row, columnRange.start + col] -} - -fun Matrix.sumByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> +fun Matrix.sumByColumn() = MatrixContext.real.produce(1, colNum) { _, j -> val column = columns[j] with(elementContext) { sum(column.asSequence()) } } -fun Matrix.minByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> - val column = columns[j] - column.asSequence().min()?:throw Exception("Cannot produce min on empty column") +fun Matrix.minByColumn() = MatrixContext.real.produce(1, colNum) { + _, j -> columns[j].asSequence().min() ?: throw Exception("Cannot produce min on empty column") } -fun Matrix.maxByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> - val column = columns[j] - column.asSequence().max()?:throw Exception("Cannot produce min on empty column") +fun Matrix.maxByColumn() = MatrixContext.real.produce(1, colNum) { + _, j -> columns[j].asSequence().max() ?: throw Exception("Cannot produce min on empty column") } -fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> - val column = columns[j] - column.asSequence().average() +fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { + _, j -> columns[j].asSequence().average() } -fun Matrix.sum() = this.elements().map { (_,value) -> value }.sum() +/* + * Operations processing all elements + */ -fun Matrix.min() = this.elements().map { (_,value) -> value }.min() +fun Matrix.sum() = elements().map { (_, value) -> value }.sum() -fun Matrix.max() = this.elements().map { (_,value) -> value }.max() +fun Matrix.min() = elements().map { (_, value) -> value }.min() -fun Matrix.average() = this.elements().map { (_,value) -> value }.average() +fun Matrix.max() = elements().map { (_, value) -> value }.max() -fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { i, j -> - this@pow[i,j].pow(n) -} \ No newline at end of file +fun Matrix.average() = elements().map { (_, value) -> value }.average() diff --git a/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt new file mode 100644 index 000000000..808794442 --- /dev/null +++ b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt @@ -0,0 +1,16 @@ +package scientific.kmath.real + +import scientifik.kmath.real.average +import scientifik.kmath.real.realMatrix +import scientifik.kmath.real.sum +import kotlin.test.Test +import kotlin.test.assertEquals + +class RealMatrixTest { + @Test + fun testSum() { + val m = realMatrix(10, 10) { i, j -> (i + j).toDouble() } + assertEquals(m.sum(), 900.0) + assertEquals(m.average(), 9.0) + } +} diff --git a/kmath-for-real/src/jvmMain/kotlin/.gitkeep b/kmath-for-real/src/jvmMain/kotlin/.gitkeep new file mode 100644 index 000000000..e69de29bb