From e5ffb22126d9cc7a7c16a3d4a55c7940978542bd Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 7 May 2020 09:54:46 +0300 Subject: [PATCH] For-real refactoring and test fix. Never call equals on buffer --- build.gradle.kts | 2 +- .../scientifik/kmath/linear/BufferMatrix.kt | 13 +- .../scientifik/kmath/linear/LinearAlgebra.kt | 6 - .../scientifik/kmath/linear/MatrixBuilder.kt | 38 ++++- .../scientifik/kmath/structures/Buffers.kt | 31 +--- .../kmath/structures/DoubleBuffer.kt | 34 ++++ .../kmath/structures/NDStructure.kt | 10 +- .../kmath/structures/Structure1D.kt | 2 +- .../scientifik/kmath/linear/MatrixTest.kt | 2 +- .../kmath/real/DoubleMatrixOperations.kt | 146 ---------------- .../scientifik/kmath/real/realBuffer.kt | 8 + .../scientifik/kmath/real/realMatrix.kt | 161 ++++++++++++++++++ .../scientific.kmath.real/RealMatrixTest.kt | 133 ++++++++------- 13 files changed, 331 insertions(+), 255 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/DoubleBuffer.kt delete mode 100644 kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt create mode 100644 kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt create mode 100644 kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt diff --git a/build.gradle.kts b/build.gradle.kts index 34e87fa2a..4a7e99d90 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("scientifik.publish") apply false } -val kmathVersion by extra("0.1.4-dev-5") +val kmathVersion by extra("0.1.4-dev-6") val bintrayRepo by extra("scientifik") val githubProject by extra("kmath") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index 624cd44d4..73b18b810 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -27,7 +27,7 @@ class BufferMatrixContext>( @Suppress("OVERRIDE_BY_INLINE") object RealMatrixContext : GenericMatrixContext { - override val elementContext = RealField + override val elementContext get() = RealField override inline fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): Matrix { val buffer = DoubleBuffer(rows * columns) { offset -> initializer(offset / columns, offset % columns) } @@ -101,8 +101,15 @@ infix fun BufferMatrix.dot(other: BufferMatrix): BufferMatrix.unsafeArray(): DoubleArray = if (this is DoubleBuffer) { + 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)) { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt index 0456ffebb..abd8603f4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt @@ -1,12 +1,8 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.Field -import scientifik.kmath.operations.Norm -import scientifik.kmath.operations.RealField import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.VirtualBuffer -import scientifik.kmath.structures.asSequence typealias Point = Buffer @@ -19,8 +15,6 @@ interface LinearSolver { fun inverse(a: Matrix): Matrix } -typealias RealMatrix = Matrix - /** * Convert matrix to vector if it is possible */ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt index 466dbea6e..516f65bb8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt @@ -1,14 +1,46 @@ package scientifik.kmath.linear +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory import scientifik.kmath.structures.Structure2D import scientifik.kmath.structures.asBuffer -class MatrixBuilder(val rows: Int, val columns: Int) { - operator fun invoke(vararg elements: T): FeaturedMatrix { +class MatrixBuilder(val rows: Int, val columns: Int) { + operator fun invoke(vararg elements: T): FeaturedMatrix { if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns") val buffer = elements.asBuffer() return BufferMatrix(rows, columns, buffer) } + + //TODO add specific matrix builder functions like diagonal, etc } -fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) \ No newline at end of file +fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) + +fun Structure2D.Companion.row(vararg values: T): FeaturedMatrix { + val buffer = values.asBuffer() + return BufferMatrix(1, values.size, buffer) +} + +inline fun Structure2D.Companion.row( + size: Int, + factory: BufferFactory = Buffer.Companion::auto, + noinline builder: (Int) -> T +): FeaturedMatrix { + val buffer = factory(size, builder) + return BufferMatrix(1, size, buffer) +} + +fun Structure2D.Companion.column(vararg values: T): FeaturedMatrix { + val buffer = values.asBuffer() + return BufferMatrix(values.size, 1, buffer) +} + +inline fun Structure2D.Companion.column( + size: Int, + factory: BufferFactory = Buffer.Companion::auto, + noinline builder: (Int) -> T +): FeaturedMatrix { + val buffer = factory(size, builder) + return BufferMatrix(size, 1, buffer) +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index ab8891ab9..613a0d7ca 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -161,36 +161,7 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) } -fun Array.asBuffer() = ArrayBuffer(this) - -inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer { - override val size: Int get() = array.size - - override fun get(index: Int): Double = array[index] - - override fun set(index: Int, value: Double) { - array[index] = value - } - - override fun iterator() = array.iterator() - - override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) -} - -@Suppress("FunctionName") -inline fun DoubleBuffer(size: Int, init: (Int) -> Double) = DoubleBuffer(DoubleArray(size) { init(it) }) - -/** - * Transform buffer of doubles into array for high performance operations - */ -val Buffer.array: DoubleArray - get() = if (this is DoubleBuffer) { - array - } else { - DoubleArray(size) { get(it) } - } - -fun DoubleArray.asBuffer() = DoubleBuffer(this) +fun Array.asBuffer(): ArrayBuffer = ArrayBuffer(this) inline class ShortBuffer(val array: ShortArray) : MutableBuffer { override val size: Int get() = array.size diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/DoubleBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/DoubleBuffer.kt new file mode 100644 index 000000000..c0b7f713b --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/DoubleBuffer.kt @@ -0,0 +1,34 @@ +package scientifik.kmath.structures + +inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer { + override val size: Int get() = array.size + + override fun get(index: Int): Double = array[index] + + override fun set(index: Int, value: Double) { + array[index] = value + } + + override fun iterator() = array.iterator() + + override fun copy(): MutableBuffer = + DoubleBuffer(array.copyOf()) +} + +@Suppress("FunctionName") +inline fun DoubleBuffer(size: Int, init: (Int) -> Double): DoubleBuffer = DoubleBuffer(DoubleArray(size) { init(it) }) + +@Suppress("FunctionName") +fun DoubleBuffer(vararg doubles: Double): DoubleBuffer = DoubleBuffer(doubles) + +/** + * Transform buffer of doubles into array for high performance operations + */ +val MutableBuffer.array: DoubleArray + get() = if (this is DoubleBuffer) { + array + } else { + DoubleArray(size) { get(it) } + } + +fun DoubleArray.asBuffer() = DoubleBuffer(this) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 25d736b92..236a6f366 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -20,10 +20,14 @@ interface NDStructure { companion object { fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean { - if(st1===st2) return true + if (st1 === st2) return true // fast comparison of buffers if possible - if(st1 is NDBuffer && st2 is NDBuffer && st1.strides == st2.strides){ + if ( + st1 is NDBuffer && + st2 is NDBuffer && + st1.strides == st2.strides + ) { return st1.buffer.contentEquals(st2.buffer) } @@ -210,7 +214,7 @@ abstract class NDBuffer : NDStructure { class BufferNDStructure( override val strides: Strides, override val buffer: Buffer -) : NDBuffer(){ +) : NDBuffer() { init { if (strides.linearSize != buffer.size) { error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt index df56017a3..9974538ec 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt @@ -17,7 +17,7 @@ interface Structure1D : NDStructure, Buffer { /** * A 1D wrapper for nd-structure */ -private inline class Structure1DWrapper(val structure: NDStructure) : Structure1D { +private inline class Structure1DWrapper(val structure: NDStructure) : Structure1D{ override val shape: IntArray get() = structure.shape override val size: Int get() = structure.shape[0] 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 7d1209963..dcd510e32 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -17,7 +17,7 @@ class MatrixTest { @Test fun testBuilder() { - val matrix = Matrix.build(2, 3)( + val matrix = Matrix.build(2, 3)( 1.0, 0.0, 0.0, 0.0, 1.0, 2.0 ) 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 deleted file mode 100644 index 7eeba3031..000000000 --- a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt +++ /dev/null @@ -1,146 +0,0 @@ -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 - -/* - * 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 - * - */ - -/* - * 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] } -} - -fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { - row, col -> get(if (row == 0) 0 else row % rowNum, col) -} - -/* - * 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[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[row, col] / double -} - -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 -> this + matrix[row, col] -} - -operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { - row, col -> this - matrix[row, col] -} - -// 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) -} - -fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { - i, j -> this[i, j].pow(n) -} - -/* - * 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.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { - row, col -> this[row,col] - other[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] - 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.sumByColumn() = MatrixContext.real.produce(1, colNum) { _, j -> - val column = columns[j] - with(elementContext) { - sum(column.asSequence()) - } -} - -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) { - _, j -> columns[j].asSequence().max() ?: throw Exception("Cannot produce min on empty column") -} - -fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { - _, j -> columns[j].asSequence().average() -} - -/* - * Operations processing all elements - */ - -fun Matrix.sum() = elements().map { (_, value) -> value }.sum() - -fun Matrix.min() = elements().map { (_, value) -> value }.min() - -fun Matrix.max() = elements().map { (_, value) -> value }.max() - -fun Matrix.average() = elements().map { (_, value) -> value }.average() diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt new file mode 100644 index 000000000..af3569439 --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realBuffer.kt @@ -0,0 +1,8 @@ +package scientifik.kmath.real + +import scientifik.kmath.structures.DoubleBuffer + +/** + * C + */ +fun DoubleBuffer.contentEquals(vararg doubles: Double) = array.contentEquals(doubles) \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt new file mode 100644 index 000000000..813d89577 --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/realMatrix.kt @@ -0,0 +1,161 @@ +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.DoubleBuffer +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.asIterable +import kotlin.math.pow + +/* + * 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 + * + */ + +/* + * Functions that help create a real (Double) matrix + */ + +typealias RealMatrix = Matrix + +fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum, initializer) + +fun Sequence.toMatrix(): RealMatrix = toList().let { + MatrixContext.real.produce(it.size, it[0].size) { row, col -> it[row][col] } +} + +fun Matrix.repeatStackVertical(n: Int): RealMatrix = + VirtualMatrix(rowNum * n, colNum) { row, col -> + get(if (row == 0) 0 else row % rowNum, col) + } + +/* + * Operations for matrix and real number + */ + +operator fun Matrix.times(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] * double + } + +operator fun Matrix.plus(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] + double + } + +operator fun Matrix.minus(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] - double + } + +operator fun Matrix.div(double: Double): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] / double + } + +operator fun Double.times(matrix: Matrix): RealMatrix = + MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + this * matrix[row, col] + } + +operator fun Double.plus(matrix: Matrix): RealMatrix = + MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + this + matrix[row, col] + } + +operator fun Double.minus(matrix: Matrix): RealMatrix = + MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + this - matrix[row, col] + } + +// 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(): RealMatrix = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col].pow(2) +} + +fun Matrix.pow(n: Int): RealMatrix = MatrixContext.real.produce(rowNum, colNum) { i, j -> + this[i, j].pow(n) +} + +/* + * Operations on two matrices (per-element!) + */ + +operator fun Matrix.times(other: Matrix): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] * other[row, col] + } + +operator fun Matrix.plus(other: Matrix): RealMatrix = + MatrixContext.real.add(this, other) + +operator fun Matrix.minus(other: Matrix): RealMatrix = + MatrixContext.real.produce(rowNum, colNum) { row, col -> + this[row, col] - other[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] + else + mapper(rows[row]) + } + +fun Matrix.extractColumns(columnRange: IntRange): RealMatrix = + MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> + this[row, columnRange.first + col] + } + +fun Matrix.extractColumn(columnIndex: Int): RealMatrix = + extractColumns(columnIndex..columnIndex) + +fun Matrix.sumByColumn(): DoubleBuffer = DoubleBuffer(colNum) { j -> + val column = columns[j] + with(elementContext) { + sum(column.asIterable()) + } +} + +fun Matrix.minByColumn(): DoubleBuffer = DoubleBuffer(colNum) { j -> + columns[j].asIterable().min() ?: throw Exception("Cannot produce min on empty column") +} + +fun Matrix.maxByColumn(): DoubleBuffer = DoubleBuffer(colNum) { j -> + columns[j].asIterable().max() ?: throw Exception("Cannot produce min on empty column") +} + +fun Matrix.averageByColumn(): DoubleBuffer = DoubleBuffer(colNum) { j -> + columns[j].asIterable().average() +} + +/* + * Operations processing all elements + */ + +fun Matrix.sum() = elements().map { (_, value) -> value }.sum() + +fun Matrix.min() = elements().map { (_, value) -> value }.min() + +fun Matrix.max() = elements().map { (_, value) -> value }.max() + +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 index e72711490..8918fb300 100644 --- a/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt @@ -1,11 +1,12 @@ package scientific.kmath.real -import scientifik.kmath.real.* import scientifik.kmath.linear.VirtualMatrix import scientifik.kmath.linear.build +import scientifik.kmath.real.* import scientifik.kmath.structures.Matrix import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class RealMatrixTest { @Test @@ -19,72 +20,72 @@ class RealMatrixTest { fun testSequenceToMatrix() { val m = Sequence { listOf( - DoubleArray(10) { 10.0 }, - DoubleArray(10) { 20.0 }, - DoubleArray(10) { 30.0 }).iterator() + DoubleArray(10) { 10.0 }, + DoubleArray(10) { 20.0 }, + DoubleArray(10) { 30.0 }).iterator() }.toMatrix() assertEquals(m.sum(), 20.0 * 30) } @Test fun testRepeatStackVertical() { - val matrix1 = Matrix.build(2, 3)( - 1.0, 0.0, 0.0, - 0.0, 1.0, 2.0 + val matrix1 = Matrix.build(2, 3)( + 1.0, 0.0, 0.0, + 0.0, 1.0, 2.0 ) - val matrix2 = Matrix.build(6, 3)( - 1.0, 0.0, 0.0, - 0.0, 1.0, 2.0, - 1.0, 0.0, 0.0, - 0.0, 1.0, 2.0, - 1.0, 0.0, 0.0, - 0.0, 1.0, 2.0 + val matrix2 = Matrix.build(6, 3)( + 1.0, 0.0, 0.0, + 0.0, 1.0, 2.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 2.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 2.0 ) assertEquals(VirtualMatrix.wrap(matrix2), matrix1.repeatStackVertical(3)) } @Test fun testMatrixAndDouble() { - val matrix1 = Matrix.build(2, 3)( - 1.0, 0.0, 3.0, - 4.0, 6.0, 2.0 + val matrix1 = Matrix.build(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)( - 0.75, -0.5, 3.25, - 4.5, 7.0, 2.0 + val expectedResult = Matrix.build(2, 3)( + 0.75, -0.5, 3.25, + 4.5, 7.0, 2.0 ) assertEquals(matrix2, expectedResult) } @Test fun testDoubleAndMatrix() { - val matrix1 = Matrix.build(2, 3)( - 1.0, 0.0, 3.0, - 4.0, 6.0, 2.0 + val matrix1 = Matrix.build(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)( - 5.0, 10.0, -5.0, - -10.0, -20.0, 0.0 + val expectedResult = Matrix.build(2, 3)( + 5.0, 10.0, -5.0, + -10.0, -20.0, 0.0 ) assertEquals(matrix2, expectedResult) } @Test fun testSquareAndPower() { - val matrix1 = Matrix.build(2, 3)( - -1.0, 0.0, 3.0, - 4.0, -6.0, -2.0 + val matrix1 = Matrix.build(2, 3)( + -1.0, 0.0, 3.0, + 4.0, -6.0, -2.0 ) - val matrix2 = Matrix.build(2, 3)( - 1.0, 0.0, 9.0, - 16.0, 36.0, 4.0 + val matrix2 = Matrix.build(2, 3)( + 1.0, 0.0, 9.0, + 16.0, 36.0, 4.0 ) - val matrix3 = Matrix.build(2, 3)( - -1.0, 0.0, 27.0, - 64.0, -216.0, -8.0 + val matrix3 = Matrix.build(2, 3)( + -1.0, 0.0, 27.0, + 64.0, -216.0, -8.0 ) assertEquals(matrix1.square(), matrix2) assertEquals(matrix1.pow(3), matrix3) @@ -92,51 +93,61 @@ class RealMatrixTest { @Test fun testTwoMatrixOperations() { - val matrix1 = Matrix.build(2, 3)( - -1.0, 0.0, 3.0, - 4.0, -6.0, 7.0 + val matrix1 = Matrix.build(2, 3)( + -1.0, 0.0, 3.0, + 4.0, -6.0, 7.0 ) - val matrix2 = Matrix.build(2, 3)( - 1.0, 0.0, 3.0, - 4.0, 6.0, -2.0 + val matrix2 = Matrix.build(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)( - -3.0, 0.0, 9.0, - 16.0, -48.0, -5.0 + val expectedResult = Matrix.build(2, 3)( + -3.0, 0.0, 9.0, + 16.0, -48.0, -5.0 ) assertEquals(result, expectedResult) } @Test fun testColumnOperations() { - val matrix1 = Matrix.build(2, 4)( - -1.0, 0.0, 3.0, 15.0, - 4.0, -6.0, 7.0, -11.0 + val matrix1 = Matrix.build(2, 4)( + -1.0, 0.0, 3.0, 15.0, + 4.0, -6.0, 7.0, -11.0 ) - val matrix2 = Matrix.build(2, 5)( - -1.0, 0.0, 3.0, 15.0, -1.0, - 4.0, -6.0, 7.0, -11.0, 4.0 + val matrix2 = Matrix.build(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)( - 0.0, 3.0, - -6.0, 7.0 + val col1 = Matrix.build(2, 1)(0.0, -6.0) + val cols1to2 = Matrix.build(2, 2)( + 0.0, 3.0, + -6.0, 7.0 ) + assertEquals(matrix1.appendColumn { it[0] }, matrix2) assertEquals(matrix1.extractColumn(1), col1) assertEquals(matrix1.extractColumns(1..2), cols1to2) - assertEquals(matrix1.sumByColumn(), Matrix.build(1, 4)(3.0, -6.0, 10.0, 4.0)) - assertEquals(matrix1.minByColumn(), Matrix.build(1, 4)(-1.0, -6.0, 3.0, -11.0)) - assertEquals(matrix1.maxByColumn(), Matrix.build(1, 4)(4.0, 0.0, 7.0, 15.0)) - assertEquals(matrix1.averageByColumn(), Matrix.build(1, 4)(1.5, -3.0, 5.0, 2.0)) + //equals should never be called on buffers + assertTrue { + matrix1.sumByColumn().contentEquals(3.0, -6.0, 10.0, 4.0) + } //assertEquals(matrix1.sumByColumn(), DoubleBuffer(3.0, -6.0, 10.0, 4.0)) + assertTrue { + matrix1.minByColumn().contentEquals(-1.0, -6.0, 3.0, -11.0) + } //assertEquals(matrix1.minByColumn(), DoubleBuffer(-1.0, -6.0, 3.0, -11.0)) + assertTrue { + matrix1.maxByColumn().contentEquals(4.0, 0.0, 7.0, 15.0) + } //assertEquals(matrix1.maxByColumn(), DoubleBuffer(4.0, 0.0, 7.0, 15.0)) + assertTrue { + matrix1.averageByColumn().contentEquals(1.5, -3.0, 5.0, 2.0) + } //assertEquals(matrix1.averageByColumn(), DoubleBuffer(1.5, -3.0, 5.0, 2.0)) } @Test fun testAllElementOperations() { - val matrix1 = Matrix.build(2, 4)( - -1.0, 0.0, 3.0, 15.0, - 4.0, -6.0, 7.0, -11.0 + val matrix1 = Matrix.build(2, 4)( + -1.0, 0.0, 3.0, 15.0, + 4.0, -6.0, 7.0, -11.0 ) assertEquals(matrix1.sum(), 11.0) assertEquals(matrix1.min(), -11.0)