From aff7bbab4131380929fb906ec57901b776c395e3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 24 Jan 2021 17:07:19 +0300 Subject: [PATCH] Matrix performance optimization --- .../kscience/kmath/benchmarks/DotBenchmark.kt | 15 ++--- .../benchmarks/LinearAlgebraBenchmark.kt | 4 +- .../kmath/benchmarks/ViktorBenchmark.kt | 6 +- .../kmath/benchmarks/ViktorLogBenchmark.kt | 11 +++- .../kmath/structures/ParallelRealNDField.kt | 40 +++++++------ .../kscience/kmath/linear/BufferMatrix.kt | 57 +++++++++++++++++++ .../kscience/kmath/linear/MatrixWrapper.kt | 6 -- .../kmath/linear/RealMatrixContext.kt | 28 +++++---- .../kscience/kmath/nd/BufferNDAlgebra.kt | 10 ++-- .../kscience/kmath/nd/ComplexNDField.kt | 30 +++++----- .../kotlin/kscience/kmath/nd/NDAlgebra.kt | 24 ++++---- .../kotlin/kscience/kmath/nd/RealNDField.kt | 46 +++++++-------- .../kotlin/kscience/kmath/nd/Structure2D.kt | 7 ++- .../kotlin/kscience/kmath/real/RealMatrix.kt | 2 +- .../kscience.kmath.nd4j/Nd4jArrayAlgebra.kt | 11 ++-- .../kmath/nd4j/Nd4jArrayAlgebraTest.kt | 4 +- .../kmath/viktor/ViktorNDStructure.kt | 43 ++++++++++---- 17 files changed, 215 insertions(+), 129 deletions(-) diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt index cc8043664..2256a3e02 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/DotBenchmark.kt @@ -31,38 +31,35 @@ class DotBenchmark { } @Benchmark - fun commonsMathMultiplication() { + fun cmDot() { CMMatrixContext { cmMatrix1 dot cmMatrix2 } } @Benchmark - fun ejmlMultiplication() { + fun ejmlDot() { EjmlMatrixContext { ejmlMatrix1 dot ejmlMatrix2 } } @Benchmark - fun ejmlMultiplicationwithConversion() { + fun ejmlDotWithConversion() { EjmlMatrixContext { - val ejmlMatrix1 = matrix1.toEjml() - val ejmlMatrix2 = matrix2.toEjml() - - ejmlMatrix1 dot ejmlMatrix2 + matrix1 dot matrix2 } } @Benchmark - fun bufferedMultiplication() { + fun bufferedDot() { BufferMatrixContext(RealField, Buffer.Companion::real).invoke { matrix1 dot matrix2 } } @Benchmark - fun realMultiplication() { + fun realDot() { RealMatrixContext { matrix1 dot matrix2 } diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt index 9e5f76112..4a06724ec 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/LinearAlgebraBenchmark.kt @@ -26,7 +26,9 @@ class LinearAlgebraBenchmark { @Benchmark fun kmathLUPInversion() { - MatrixContext.real.inverseWithLUP(matrix) + MatrixContext.real{ + inverseWithLUP(matrix) + } } @Benchmark diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt index 85c5d8289..e246936f0 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorBenchmark.kt @@ -23,7 +23,7 @@ internal class ViktorBenchmark { fun automaticFieldAddition() { autoField { var res: NDStructure = one - repeat(n) { res += one } + repeat(n) { res += 1.0 } } } @@ -31,7 +31,7 @@ internal class ViktorBenchmark { fun realFieldAddition() { realField { var res: NDStructure = one - repeat(n) { res += one } + repeat(n) { res += 1.0 } } } @@ -39,7 +39,7 @@ internal class ViktorBenchmark { fun viktorFieldAddition() { viktorField { var res = one - repeat(n) { res += one } + repeat(n) { res += 1.0 } } } diff --git a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt index e841c53c9..b9c39b088 100644 --- a/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt +++ b/examples/src/benchmarks/kotlin/kscience/kmath/benchmarks/ViktorLogBenchmark.kt @@ -15,7 +15,7 @@ internal class ViktorLogBenchmark { final val n: Int = 100 // automatically build context most suited for given type. - final val autoField: BufferedNDField = NDAlgebra.auto(RealField, dim, dim) + final val autoField: NDField = NDAlgebra.auto(RealField, dim, dim) final val realField: RealNDField = NDAlgebra.real(dim, dim) final val viktorField: ViktorNDField = ViktorNDField(intArrayOf(dim, dim)) @@ -29,6 +29,15 @@ internal class ViktorLogBenchmark { } } + @Benchmark + fun viktorFieldLog() { + viktorField { + val fortyTwo = produce { 42.0 } + var res = one + repeat(n) { res = ln(fortyTwo) } + } + } + @Benchmark fun rawViktorLog() { val fortyTwo = F64Array.full(dim, dim, init = 42.0) diff --git a/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt b/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt index e13f5dd47..48286c140 100644 --- a/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt +++ b/examples/src/main/kotlin/kscience/kmath/structures/ParallelRealNDField.kt @@ -48,22 +48,20 @@ class StreamRealNDField( return NDBuffer(strides, array.asBuffer()) } - override fun map( - arg: NDStructure, + override fun NDStructure.map( transform: RealField.(Double) -> Double, ): NDBuffer { - val array = Arrays.stream(arg.buffer.array).parallel().map { RealField.transform(it) }.toArray() + val array = Arrays.stream(buffer.array).parallel().map { RealField.transform(it) }.toArray() return NDBuffer(strides, array.asBuffer()) } - override fun mapIndexed( - arg: NDStructure, + override fun NDStructure.mapIndexed( transform: RealField.(index: IntArray, Double) -> Double, ): NDBuffer { val array = IntStream.range(0, strides.linearSize).parallel().mapToDouble { offset -> RealField.transform( strides.index(offset), - arg.buffer.array[offset] + buffer.array[offset] ) }.toArray() @@ -81,25 +79,25 @@ class StreamRealNDField( return NDBuffer(strides, array.asBuffer()) } - override fun power(arg: NDStructure, pow: Number): NDBuffer = map(arg) { power(it, pow) } + override fun power(arg: NDStructure, pow: Number): NDBuffer = arg.map() { power(it, pow) } - override fun exp(arg: NDStructure): NDBuffer = map(arg) { exp(it) } + override fun exp(arg: NDStructure): NDBuffer = arg.map() { exp(it) } - override fun ln(arg: NDStructure): NDBuffer = map(arg) { ln(it) } + override fun ln(arg: NDStructure): NDBuffer = arg.map() { ln(it) } - override fun sin(arg: NDStructure): NDBuffer = map(arg) { sin(it) } - override fun cos(arg: NDStructure): NDBuffer = map(arg) { cos(it) } - override fun tan(arg: NDStructure): NDBuffer = map(arg) { tan(it) } - override fun asin(arg: NDStructure): NDBuffer = map(arg) { asin(it) } - override fun acos(arg: NDStructure): NDBuffer = map(arg) { acos(it) } - override fun atan(arg: NDStructure): NDBuffer = map(arg) { atan(it) } + override fun sin(arg: NDStructure): NDBuffer = arg.map() { sin(it) } + override fun cos(arg: NDStructure): NDBuffer = arg.map() { cos(it) } + override fun tan(arg: NDStructure): NDBuffer = arg.map() { tan(it) } + override fun asin(arg: NDStructure): NDBuffer = arg.map() { asin(it) } + override fun acos(arg: NDStructure): NDBuffer = arg.map() { acos(it) } + override fun atan(arg: NDStructure): NDBuffer = arg.map() { atan(it) } - override fun sinh(arg: NDStructure): NDBuffer = map(arg) { sinh(it) } - override fun cosh(arg: NDStructure): NDBuffer = map(arg) { cosh(it) } - override fun tanh(arg: NDStructure): NDBuffer = map(arg) { tanh(it) } - override fun asinh(arg: NDStructure): NDBuffer = map(arg) { asinh(it) } - override fun acosh(arg: NDStructure): NDBuffer = map(arg) { acosh(it) } - override fun atanh(arg: NDStructure): NDBuffer = map(arg) { atanh(it) } + override fun sinh(arg: NDStructure): NDBuffer = arg.map() { sinh(it) } + override fun cosh(arg: NDStructure): NDBuffer = arg.map() { cosh(it) } + override fun tanh(arg: NDStructure): NDBuffer = arg.map() { tanh(it) } + override fun asinh(arg: NDStructure): NDBuffer = arg.map() { asinh(it) } + override fun acosh(arg: NDStructure): NDBuffer = arg.map() { acosh(it) } + override fun atanh(arg: NDStructure): NDBuffer = arg.map() { atanh(it) } } fun NDAlgebra.Companion.realWithStream(vararg shape: Int): StreamRealNDField = StreamRealNDField(shape) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt index bcd1c9976..6a66e91c8 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/BufferMatrix.kt @@ -3,6 +3,7 @@ package kscience.kmath.linear import kscience.kmath.nd.NDStructure import kscience.kmath.nd.Structure2D import kscience.kmath.operations.Ring +import kscience.kmath.operations.invoke import kscience.kmath.structures.Buffer import kscience.kmath.structures.BufferFactory import kscience.kmath.structures.asSequence @@ -28,6 +29,62 @@ public class BufferMatrixContext>( public override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) + private fun Matrix.toBufferMatrix(): BufferMatrix = if (this is BufferMatrix) this else { + produce(rowNum, colNum) { i, j -> get(i, j) } + } + + public fun one(rows: Int, columns: Int): Matrix = VirtualMatrix(rows, columns) { i, j -> + if (i == j) 1.0 else 0.0 + } + DiagonalFeature + + public override infix fun Matrix.dot(other: Matrix): BufferMatrix { + require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } + val bufferMatrix = toBufferMatrix() + val otherBufferMatrix = other.toBufferMatrix() + return elementContext { + produce(rowNum, other.colNum) { i, j -> + var res = one + for (l in 0 until colNum) { + res += bufferMatrix[i, l] * otherBufferMatrix[l, j] + } + res + } + } + } + + public override infix fun Matrix.dot(vector: Point): Point { + require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" } + val bufferMatrix = toBufferMatrix() + return elementContext { + bufferFactory(rowNum) { i -> + var res = one + for (j in 0 until colNum) { + res += bufferMatrix[i, j] * vector[j] + } + res + } + } + } + + override fun add(a: Matrix, b: Matrix): BufferMatrix { + require(a.rowNum == b.rowNum) { "Row number mismatch in matrix addition. Left side: ${a.rowNum}, right side: ${b.rowNum}" } + require(a.colNum == b.colNum) { "Column number mismatch in matrix addition. Left side: ${a.colNum}, right side: ${b.colNum}" } + val aBufferMatrix = a.toBufferMatrix() + val bBufferMatrix = b.toBufferMatrix() + return elementContext { + produce(a.rowNum, a.colNum) { i, j -> + aBufferMatrix[i, j] + bBufferMatrix[i, j] + } + } + } + + override fun multiply(a: Matrix, k: Number): BufferMatrix { + val aBufferMatrix = a.toBufferMatrix() + return elementContext { + produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() } + } + } + public companion object } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt index 726a91e97..df967e3c1 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/MatrixWrapper.kt @@ -60,12 +60,6 @@ public operator fun Matrix.plus(newFeatures: Collection Double, -): BufferMatrix = MatrixContext.real.produce(rows, columns, initializer) - /** * Build a square matrix from given elements. */ diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt index 3d544a9af..da795f56b 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/linear/RealMatrixContext.kt @@ -2,10 +2,9 @@ package kscience.kmath.linear import kscience.kmath.structures.RealBuffer -@Suppress("OVERRIDE_BY_INLINE") public object RealMatrixContext : MatrixContext> { - public override inline fun produce( + public override fun produce( rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double, @@ -14,7 +13,7 @@ public object RealMatrixContext : MatrixContext> { return BufferMatrix(rows, columns, buffer) } - private fun Matrix.wrap(): BufferMatrix = if (this is BufferMatrix) this else { + public fun Matrix.toBufferMatrix(): BufferMatrix = if (this is BufferMatrix) this else { produce(rowNum, colNum) { i, j -> get(i, j) } } @@ -24,10 +23,12 @@ public object RealMatrixContext : MatrixContext> { public override infix fun Matrix.dot(other: Matrix): BufferMatrix { require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } + val bufferMatrix = toBufferMatrix() + val otherBufferMatrix = other.toBufferMatrix() return produce(rowNum, other.colNum) { i, j -> var res = 0.0 for (l in 0 until colNum) { - res += get(i, l) * other.get(l, j) + res += bufferMatrix[i, l] * otherBufferMatrix[l, j] } res } @@ -35,10 +36,11 @@ public object RealMatrixContext : MatrixContext> { public override infix fun Matrix.dot(vector: Point): Point { require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" } + val bufferMatrix = toBufferMatrix() return RealBuffer(rowNum) { i -> var res = 0.0 for (j in 0 until colNum) { - res += get(i, j) * vector[j] + res += bufferMatrix[i, j] * vector[j] } res } @@ -47,17 +49,23 @@ public object RealMatrixContext : MatrixContext> { override fun add(a: Matrix, b: Matrix): BufferMatrix { require(a.rowNum == b.rowNum) { "Row number mismatch in matrix addition. Left side: ${a.rowNum}, right side: ${b.rowNum}" } require(a.colNum == b.colNum) { "Column number mismatch in matrix addition. Left side: ${a.colNum}, right side: ${b.colNum}" } + val aBufferMatrix = a.toBufferMatrix() + val bBufferMatrix = b.toBufferMatrix() return produce(a.rowNum, a.colNum) { i, j -> - a[i, j] + b[i, j] + aBufferMatrix[i, j] + bBufferMatrix[i, j] } } - override fun Matrix.times(value: Double): BufferMatrix = - produce(rowNum, colNum) { i, j -> get(i, j) * value } + override fun Matrix.times(value: Double): BufferMatrix { + val bufferMatrix = toBufferMatrix() + return produce(rowNum, colNum) { i, j -> bufferMatrix[i, j] * value } + } - override fun multiply(a: Matrix, k: Number): BufferMatrix = - produce(a.rowNum, a.colNum) { i, j -> a[i, j] * k.toDouble() } + override fun multiply(a: Matrix, k: Number): BufferMatrix { + val aBufferMatrix = a.toBufferMatrix() + return produce(a.rowNum, a.colNum) { i, j -> aBufferMatrix[i, j] * k.toDouble() } + } } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt index 93add36eb..2d72162a6 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/BufferNDAlgebra.kt @@ -28,24 +28,24 @@ public interface BufferNDAlgebra : NDAlgebra { else -> bufferFactory(strides.linearSize) { offset -> get(strides.index(offset)) } } - override fun map(arg: NDStructure, transform: C.(T) -> T): NDBuffer { + override fun NDStructure.map(transform: C.(T) -> T): NDBuffer { val buffer = bufferFactory(strides.linearSize) { offset -> - elementContext.transform(arg.buffer[offset]) + elementContext.transform(buffer[offset]) } return NDBuffer(strides, buffer) } - override fun mapIndexed(arg: NDStructure, transform: C.(index: IntArray, T) -> T): NDStructure { + override fun NDStructure.mapIndexed(transform: C.(index: IntArray, T) -> T): NDBuffer { val buffer = bufferFactory(strides.linearSize) { offset -> elementContext.transform( strides.index(offset), - arg.buffer[offset] + buffer[offset] ) } return NDBuffer(strides, buffer) } - override fun combine(a: NDStructure, b: NDStructure, transform: C.(T, T) -> T): NDStructure { + override fun combine(a: NDStructure, b: NDStructure, transform: C.(T, T) -> T): NDBuffer { val buffer = bufferFactory(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt index 074a1185b..00e79f2e4 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/ComplexNDField.kt @@ -70,25 +70,25 @@ public class ComplexNDField( // return BufferedNDFieldElement(this, buffer) // } - override fun power(arg: NDStructure, pow: Number): NDBuffer = map(arg) { power(it, pow) } + override fun power(arg: NDStructure, pow: Number): NDBuffer = arg.map() { power(it, pow) } - override fun exp(arg: NDStructure): NDBuffer = map(arg) { exp(it) } + override fun exp(arg: NDStructure): NDBuffer = arg.map() { exp(it) } - override fun ln(arg: NDStructure): NDBuffer = map(arg) { ln(it) } + override fun ln(arg: NDStructure): NDBuffer = arg.map() { ln(it) } - override fun sin(arg: NDStructure): NDBuffer = map(arg) { sin(it) } - override fun cos(arg: NDStructure): NDBuffer = map(arg) { cos(it) } - override fun tan(arg: NDStructure): NDBuffer = map(arg) { tan(it) } - override fun asin(arg: NDStructure): NDBuffer = map(arg) { asin(it) } - override fun acos(arg: NDStructure): NDBuffer = map(arg) { acos(it) } - override fun atan(arg: NDStructure): NDBuffer = map(arg) { atan(it) } + override fun sin(arg: NDStructure): NDBuffer = arg.map() { sin(it) } + override fun cos(arg: NDStructure): NDBuffer = arg.map() { cos(it) } + override fun tan(arg: NDStructure): NDBuffer = arg.map() { tan(it) } + override fun asin(arg: NDStructure): NDBuffer = arg.map() { asin(it) } + override fun acos(arg: NDStructure): NDBuffer = arg.map() { acos(it) } + override fun atan(arg: NDStructure): NDBuffer = arg.map() { atan(it) } - override fun sinh(arg: NDStructure): NDBuffer = map(arg) { sinh(it) } - override fun cosh(arg: NDStructure): NDBuffer = map(arg) { cosh(it) } - override fun tanh(arg: NDStructure): NDBuffer = map(arg) { tanh(it) } - override fun asinh(arg: NDStructure): NDBuffer = map(arg) { asinh(it) } - override fun acosh(arg: NDStructure): NDBuffer = map(arg) { acosh(it) } - override fun atanh(arg: NDStructure): NDBuffer = map(arg) { atanh(it) } + override fun sinh(arg: NDStructure): NDBuffer = arg.map() { sinh(it) } + override fun cosh(arg: NDStructure): NDBuffer = arg.map() { cosh(it) } + override fun tanh(arg: NDStructure): NDBuffer = arg.map() { tanh(it) } + override fun asinh(arg: NDStructure): NDBuffer = arg.map() { asinh(it) } + override fun acosh(arg: NDStructure): NDBuffer = arg.map() { acosh(it) } + override fun atanh(arg: NDStructure): NDBuffer = arg.map() { atanh(it) } } diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt index facc7eb38..749fb1a13 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/NDAlgebra.kt @@ -40,12 +40,12 @@ public interface NDAlgebra { /** * Maps elements from one structure to another one by applying [transform] to them. */ - public fun map(arg: NDStructure, transform: C.(T) -> T): NDStructure + public fun NDStructure.map(transform: C.(T) -> T): NDStructure /** * Maps elements from one structure to another one by applying [transform] to them alongside with their indices. */ - public fun mapIndexed(arg: NDStructure, transform: C.(index: IntArray, T) -> T): NDStructure + public fun NDStructure.mapIndexed(transform: C.(index: IntArray, T) -> T): NDStructure /** * Combines two structures into one. @@ -56,7 +56,7 @@ public interface NDAlgebra { * Element-wise invocation of function working on [T] on a [NDStructure]. */ public operator fun Function1.invoke(structure: NDStructure): NDStructure = - map(structure) { value -> this@invoke(value) } + structure.map() { value -> this@invoke(value) } public companion object } @@ -109,7 +109,7 @@ public interface NDSpace> : Space>, NDAlgebra, k: Number): NDStructure = map(a) { multiply(it, k) } + public override fun multiply(a: NDStructure, k: Number): NDStructure = a.map() { multiply(it, k) } // TODO move to extensions after KEEP-176 @@ -120,7 +120,7 @@ public interface NDSpace> : Space>, NDAlgebra.plus(arg: T): NDStructure = map(this) { value -> add(arg, value) } + public operator fun NDStructure.plus(arg: T): NDStructure = this.map() { value -> add(arg, value) } /** * Subtracts an element from ND structure of it. @@ -129,7 +129,7 @@ public interface NDSpace> : Space>, NDAlgebra.minus(arg: T): NDStructure = map(this) { value -> add(arg, -value) } + public operator fun NDStructure.minus(arg: T): NDStructure = this.map() { value -> add(arg, -value) } /** * Adds an element to ND structure of it. @@ -138,7 +138,7 @@ public interface NDSpace> : Space>, NDAlgebra): NDStructure = map(arg) { value -> add(this@plus, value) } + public operator fun T.plus(arg: NDStructure): NDStructure = arg.map() { value -> add(this@plus, value) } /** * Subtracts an ND structure from an element of it. @@ -147,7 +147,7 @@ public interface NDSpace> : Space>, NDAlgebra): NDStructure = map(arg) { value -> add(-this@minus, value) } + public operator fun T.minus(arg: NDStructure): NDStructure = arg.map() { value -> add(-this@minus, value) } public companion object } @@ -179,7 +179,7 @@ public interface NDRing> : Ring>, NDSpace { * @param arg the multiplier. * @return the product. */ - public operator fun NDStructure.times(arg: T): NDStructure = map(this) { value -> multiply(arg, value) } + public operator fun NDStructure.times(arg: T): NDStructure = this.map() { value -> multiply(arg, value) } /** * Multiplies an element by a ND structure of it. @@ -188,7 +188,7 @@ public interface NDRing> : Ring>, NDSpace { * @param arg the multiplier. * @return the product. */ - public operator fun T.times(arg: NDStructure): NDStructure = map(arg) { value -> multiply(this@times, value) } + public operator fun T.times(arg: NDStructure): NDStructure = arg.map() { value -> multiply(this@times, value) } public companion object } @@ -219,7 +219,7 @@ public interface NDField> : Field>, NDRing * @param arg the divisor. * @return the quotient. */ - public operator fun NDStructure.div(arg: T): NDStructure = map(this) { value -> divide(arg, value) } + public operator fun NDStructure.div(arg: T): NDStructure = this.map() { value -> divide(arg, value) } /** * Divides an element by an ND structure of it. @@ -228,7 +228,7 @@ public interface NDField> : Field>, NDRing * @param arg the divisor. * @return the quotient. */ - public operator fun T.div(arg: NDStructure): NDStructure = map(arg) { divide(it, this@div) } + public operator fun T.div(arg: NDStructure): NDStructure = arg.map() { divide(it, this@div) } // @ThreadLocal // public companion object { diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt index 91b5500cd..8e0f9e171 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/RealNDField.kt @@ -35,16 +35,15 @@ public class RealNDField( } @Suppress("OVERRIDE_BY_INLINE") - override inline fun map( - arg: NDStructure, + override inline fun NDStructure.map( transform: RealField.(Double) -> Double, ): NDBuffer { - val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(arg.buffer.array[offset]) } + val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(buffer.array[offset]) } return NDBuffer(strides, buffer) } @Suppress("OVERRIDE_BY_INLINE") - override inline fun produce(initializer: RealField.(IntArray) -> Double): NDBuffer { + override inline fun produce(initializer: RealField.(IntArray) -> Double): NDBuffer { val array = DoubleArray(strides.linearSize) { offset -> val index = strides.index(offset) RealField.initializer(index) @@ -53,15 +52,14 @@ public class RealNDField( } @Suppress("OVERRIDE_BY_INLINE") - override inline fun mapIndexed( - arg: NDStructure, + override inline fun NDStructure.mapIndexed( transform: RealField.(index: IntArray, Double) -> Double, ): NDBuffer = NDBuffer( strides, buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform( strides.index(offset), - arg.buffer.array[offset] + buffer.array[offset] ) }) @@ -70,32 +68,32 @@ public class RealNDField( a: NDStructure, b: NDStructure, transform: RealField.(Double, Double) -> Double, - ): NDBuffer { + ): NDBuffer { val buffer = RealBuffer(strides.linearSize) { offset -> RealField.transform(a.buffer.array[offset], b.buffer.array[offset]) } - return NDBuffer(strides, buffer) + return NDBuffer(strides, buffer) } - override fun power(arg: NDStructure, pow: Number): NDBuffer = map(arg) { power(it, pow) } + override fun power(arg: NDStructure, pow: Number): NDBuffer = arg.map { power(it, pow) } - override fun exp(arg: NDStructure): NDBuffer = map(arg) { exp(it) } + override fun exp(arg: NDStructure): NDBuffer = arg.map { exp(it) } - override fun ln(arg: NDStructure): NDBuffer = map(arg) { ln(it) } + override fun ln(arg: NDStructure): NDBuffer = arg.map { ln(it) } - override fun sin(arg: NDStructure): NDBuffer = map(arg) { sin(it) } - override fun cos(arg: NDStructure): NDBuffer = map(arg) { cos(it) } - override fun tan(arg: NDStructure): NDBuffer = map(arg) { tan(it) } - override fun asin(arg: NDStructure): NDBuffer = map(arg) { asin(it) } - override fun acos(arg: NDStructure): NDBuffer = map(arg) { acos(it) } - override fun atan(arg: NDStructure): NDBuffer = map(arg) { atan(it) } + override fun sin(arg: NDStructure): NDBuffer = arg.map { sin(it) } + override fun cos(arg: NDStructure): NDBuffer = arg.map { cos(it) } + override fun tan(arg: NDStructure): NDBuffer = arg.map { tan(it) } + override fun asin(arg: NDStructure): NDBuffer = arg.map { asin(it) } + override fun acos(arg: NDStructure): NDBuffer = arg.map { acos(it) } + override fun atan(arg: NDStructure): NDBuffer = arg.map { atan(it) } - override fun sinh(arg: NDStructure): NDBuffer = map(arg) { sinh(it) } - override fun cosh(arg: NDStructure): NDBuffer = map(arg) { cosh(it) } - override fun tanh(arg: NDStructure): NDBuffer = map(arg) { tanh(it) } - override fun asinh(arg: NDStructure): NDBuffer = map(arg) { asinh(it) } - override fun acosh(arg: NDStructure): NDBuffer = map(arg) { acosh(it) } - override fun atanh(arg: NDStructure): NDBuffer = map(arg) { atanh(it) } + override fun sinh(arg: NDStructure): NDBuffer = arg.map { sinh(it) } + override fun cosh(arg: NDStructure): NDBuffer = arg.map { cosh(it) } + override fun tanh(arg: NDStructure): NDBuffer = arg.map { tanh(it) } + override fun asinh(arg: NDStructure): NDBuffer = arg.map { asinh(it) } + override fun acosh(arg: NDStructure): NDBuffer = arg.map { acosh(it) } + override fun atanh(arg: NDStructure): NDBuffer = arg.map { atanh(it) } } public fun NDAlgebra.Companion.real(vararg shape: Int): RealNDField = RealNDField(shape) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt index 42a643db1..32e0704fc 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/nd/Structure2D.kt @@ -1,6 +1,7 @@ package kscience.kmath.nd -import kscience.kmath.linear.Matrix +import kscience.kmath.linear.BufferMatrix +import kscience.kmath.linear.RealMatrixContext import kscience.kmath.structures.Buffer import kscience.kmath.structures.VirtualBuffer @@ -58,9 +59,9 @@ public interface Structure2D : NDStructure { rows: Int, columns: Int, crossinline init: (i: Int, j: Int) -> Double, - ): Matrix = NDAlgebra.real(rows, columns).produce { (i, j) -> + ): BufferMatrix = RealMatrixContext.produce(rows,columns) { i, j -> init(i, j) - }.as2D() + } } } diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt index e7bde9980..044cafb0b 100644 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt +++ b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt @@ -140,7 +140,7 @@ public fun RealMatrix.min(): Double? = elements().map { (_, value) -> value }.mi public fun RealMatrix.max(): Double? = elements().map { (_, value) -> value }.maxOrNull() public fun RealMatrix.average(): Double = elements().map { (_, value) -> value }.average() -public inline fun RealMatrix.map(transform: (Double) -> Double): RealMatrix = +public inline fun RealMatrix.map(crossinline transform: (Double) -> Double): RealMatrix = MatrixContext.real.produce(rowNum, colNum) { i, j -> transform(get(i, j)) } diff --git a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt index 91d45dccd..b9c95034e 100644 --- a/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt +++ b/kmath-nd4j/src/main/kotlin/kscience.kmath.nd4j/Nd4jArrayAlgebra.kt @@ -44,18 +44,17 @@ public interface Nd4jArrayAlgebra : NDAlgebra { return struct } - public override fun map(arg: NDStructure, transform: C.(T) -> T): Nd4jArrayStructure { - val newStruct = arg.ndArray.dup().wrap() + public override fun NDStructure.map(transform: C.(T) -> T): Nd4jArrayStructure { + val newStruct = ndArray.dup().wrap() newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) } return newStruct } - public override fun mapIndexed( - arg: NDStructure, + public override fun NDStructure.mapIndexed( transform: C.(index: IntArray, T) -> T, ): Nd4jArrayStructure { - val new = Nd4j.create(*shape).wrap() - new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, arg[idx]) } + val new = Nd4j.create(*this@Nd4jArrayAlgebra.shape).wrap() + new.indicesIterator().forEach { idx -> new[idx] = elementContext.transform(idx, this[idx]) } return new } diff --git a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt index 650d5670c..04959d290 100644 --- a/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt +++ b/kmath-nd4j/src/test/kotlin/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt @@ -1,7 +1,7 @@ package kscience.kmath.nd4j -import org.nd4j.linalg.factory.Nd4j import kscience.kmath.operations.invoke +import org.nd4j.linalg.factory.Nd4j import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.fail @@ -20,7 +20,7 @@ internal class Nd4jArrayAlgebraTest { @Test fun testMap() { - val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { map(one) { it + it * 2 } } + val res = (IntNd4jArrayRing(intArrayOf(2, 2))) { one.map() { it + it * 2 } } val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure() expected[intArrayOf(0, 0)] = 3 expected[intArrayOf(0, 1)] = 3 diff --git a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt index f91359a0c..d3e4806b0 100644 --- a/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt +++ b/kmath-viktor/src/main/kotlin/kscience/kmath/viktor/ViktorNDStructure.kt @@ -1,7 +1,10 @@ package kscience.kmath.viktor +import kscience.kmath.misc.UnstableKMathAPI import kscience.kmath.nd.* +import kscience.kmath.operations.ExtendedField import kscience.kmath.operations.RealField +import kscience.kmath.operations.RingWithNumbers import org.jetbrains.bio.viktor.F64Array @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") @@ -20,8 +23,10 @@ public inline class ViktorNDStructure(public val f64Buffer: F64Array) : MutableN public fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this) +@OptIn(UnstableKMathAPI::class) @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") -public class ViktorNDField(public override val shape: IntArray) : NDField { +public class ViktorNDField(public override val shape: IntArray) : NDField, + RingWithNumbers>, ExtendedField> { public val NDStructure.f64Buffer: F64Array get() = when { @@ -50,26 +55,25 @@ public class ViktorNDField(public override val shape: IntArray) : NDField, transform: RealField.(Double) -> Double): ViktorNDStructure = - F64Array(*shape).apply { + public override fun NDStructure.map(transform: RealField.(Double) -> Double): ViktorNDStructure = + F64Array(*this@ViktorNDField.shape).apply { this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(arg[index]), indices = index) + set(value = RealField.transform(this@map[index]), indices = index) } }.asStructure() - public override fun mapIndexed( - arg: NDStructure, - transform: RealField.(index: IntArray, Double) -> Double - ): ViktorNDStructure = F64Array(*shape).apply { + public override fun NDStructure.mapIndexed( + transform: RealField.(index: IntArray, Double) -> Double, + ): ViktorNDStructure = F64Array(*this@ViktorNDField.shape).apply { this@ViktorNDField.strides.indices().forEach { index -> - set(value = RealField.transform(index, arg[index]), indices = index) + set(value = RealField.transform(index, this@mapIndexed[index]), indices = index) } }.asStructure() public override fun combine( a: NDStructure, b: NDStructure, - transform: RealField.(Double, Double) -> Double + transform: RealField.(Double, Double) -> Double, ): ViktorNDStructure = F64Array(*shape).apply { this@ViktorNDField.strides.indices().forEach { index -> set(value = RealField.transform(a[index], b[index]), indices = index) @@ -93,6 +97,25 @@ public class ViktorNDField(public override val shape: IntArray) : NDField.plus(arg: Double): ViktorNDStructure = (f64Buffer.plus(arg)).asStructure() + + override fun number(value: Number): ViktorNDStructure = + F64Array.full(init = value.toDouble(), shape = shape).asStructure() + + override fun sin(arg: NDStructure): ViktorNDStructure = arg.map { sin(it) } + + override fun cos(arg: NDStructure): ViktorNDStructure = arg.map { cos(it) } + + override fun asin(arg: NDStructure): ViktorNDStructure = arg.map { asin(it) } + + override fun acos(arg: NDStructure): ViktorNDStructure = arg.map { acos(it) } + + override fun atan(arg: NDStructure): ViktorNDStructure = arg.map { atan(it) } + + override fun power(arg: NDStructure, pow: Number): ViktorNDStructure = arg.map { it.pow(pow) } + + override fun exp(arg: NDStructure): ViktorNDStructure = arg.f64Buffer.exp().asStructure() + + override fun ln(arg: NDStructure): ViktorNDStructure = arg.f64Buffer.log().asStructure() } public fun ViktorNDField(vararg shape: Int): ViktorNDField = ViktorNDField(shape) \ No newline at end of file