From b65197f577d0d0dbebd3d8dc1f8b14797bf346f9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 1 Nov 2021 19:45:02 +0300 Subject: [PATCH] revert parts of tensor api to tensors --- .../kscience/kmath/benchmarks/DotBenchmark.kt | 11 ++ .../space/kscience/kmath/nd/AlgebraND.kt | 2 +- .../space/kscience/kmath/nd/BufferND.kt | 6 - .../kscience/kmath/operations/Algebra.kt | 10 ++ .../kmath/multik/MultikDoubleAlgebra.kt | 136 ++++++++++++++++++ .../kmath/multik/MultikTensorAlgebra.kt | 39 +++-- .../kscience/kmath/nd4j/Nd4jTensorAlgebra.kt | 4 +- .../tensors/api/AnalyticTensorAlgebra.kt | 6 +- .../kmath/tensors/api/TensorAlgebra.kt | 4 +- .../kmath/tensors/core/DoubleTensorAlgebra.kt | 4 +- 10 files changed, 188 insertions(+), 34 deletions(-) create mode 100644 kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikDoubleAlgebra.kt diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt index 64f9b5dff..9203c269e 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt @@ -13,6 +13,7 @@ import space.kscience.kmath.commons.linear.CMLinearSpace import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM import space.kscience.kmath.linear.invoke import space.kscience.kmath.linear.linearSpace +import space.kscience.kmath.multik.multikAlgebra import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.structures.Buffer import kotlin.random.Random @@ -58,6 +59,16 @@ internal class DotBenchmark { blackhole.consume(matrix1 dot matrix2) } +// @Benchmark +// fun tensorDot(blackhole: Blackhole) = with(Double.tensorAlgebra) { +// blackhole.consume(matrix1 dot matrix2) +// } + + @Benchmark + fun multikDot(blackhole: Blackhole) = with(Double.multikAlgebra) { + blackhole.consume(matrix1 dot matrix2) + } + @Benchmark fun bufferedDot(blackhole: Blackhole) = with(DoubleField.linearSpace(Buffer.Companion::auto)) { blackhole.consume(matrix1 dot matrix2) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt index 4e52c8ba9..113bd4c52 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt @@ -35,7 +35,7 @@ public interface WithShape { * @param T the type of ND-structure element. * @param C the type of the element context. */ -public interface AlgebraND> { +public interface AlgebraND>: Algebra> { /** * The algebra over elements of ND structure. */ diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt index 19924616d..515988159 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt @@ -5,7 +5,6 @@ package space.kscience.kmath.nd -import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.BufferFactory import space.kscience.kmath.structures.MutableBuffer @@ -27,11 +26,6 @@ public open class BufferND( override val shape: IntArray get() = indices.shape - @PerformancePitfall - override fun elements(): Sequence> = indices.asSequence().map { - it to this[it] - } - override fun toString(): String = StructureND.toString(this) } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index 0e5c6de1f..992c0e015 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.operations import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.misc.UnstableKMathAPI /** * Stub for DSL the [Algebra] is. @@ -99,6 +100,14 @@ public interface Algebra { */ public fun binaryOperation(operation: String, left: T, right: T): T = binaryOperationFunction(operation)(left, right) + + /** + * Export an algebra element, so it could be accessed even after algebra scope is closed. + * This method must be used on algebras where data is stored externally or any local algebra state is used. + * By default (if not overridden), exports the object itself. + */ + @UnstableKMathAPI + public fun export(arg: T): T = arg } public fun Algebra.bindSymbolOrNull(symbol: Symbol): T? = bindSymbolOrNull(symbol.identity) @@ -162,6 +171,7 @@ public interface GroupOps : Algebra { * @return the difference. */ public operator fun T.minus(arg: T): T = add(this, -arg) + // Dynamic dispatch of operations override fun unaryOperationFunction(operation: String): (arg: T) -> T = when (operation) { PLUS_OPERATION -> { arg -> +arg } diff --git a/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikDoubleAlgebra.kt b/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikDoubleAlgebra.kt new file mode 100644 index 000000000..e5fef6c1e --- /dev/null +++ b/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikDoubleAlgebra.kt @@ -0,0 +1,136 @@ +package space.kscience.kmath.multik + +import org.jetbrains.kotlinx.multik.ndarray.data.DN +import org.jetbrains.kotlinx.multik.ndarray.data.DataType +import space.kscience.kmath.nd.StructureND +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra +import space.kscience.kmath.tensors.api.LinearOpsTensorAlgebra +import space.kscience.kmath.tensors.api.Tensor + +public object MultikDoubleAlgebra : MultikDivisionTensorAlgebra(), + AnalyticTensorAlgebra, LinearOpsTensorAlgebra { + override val elementAlgebra: DoubleField get() = DoubleField + override val type: DataType get() = DataType.DoubleDataType + + override fun StructureND.mean(): Double = multikStat.mean(asMultik().array) + + override fun StructureND.mean(dim: Int, keepDim: Boolean): Tensor = + multikStat.mean(asMultik().array, dim).wrap() + + override fun StructureND.std(): Double { + TODO("Not yet implemented") + } + + override fun StructureND.std(dim: Int, keepDim: Boolean): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.variance(): Double { + TODO("Not yet implemented") + } + + override fun StructureND.variance(dim: Int, keepDim: Boolean): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.exp(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.ln(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.sqrt(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.cos(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.acos(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.cosh(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.acosh(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.sin(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.asin(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.sinh(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.asinh(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.tan(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.atan(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.tanh(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.atanh(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.ceil(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.floor(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.det(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.inv(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.cholesky(): Tensor { + TODO("Not yet implemented") + } + + override fun StructureND.qr(): Pair, Tensor> { + TODO("Not yet implemented") + } + + override fun StructureND.lu(): Triple, Tensor, Tensor> { + TODO("Not yet implemented") + } + + override fun StructureND.svd(): Triple, Tensor, Tensor> { + TODO("Not yet implemented") + } + + override fun StructureND.symEig(): Pair, Tensor> { + TODO("Not yet implemented") + } +} + +public val Double.Companion.multikAlgebra: MultikTensorAlgebra get() = MultikDoubleAlgebra +public val DoubleField.multikAlgebra: MultikTensorAlgebra get() = MultikDoubleAlgebra + diff --git a/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikTensorAlgebra.kt b/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikTensorAlgebra.kt index c8fc23a2c..e3b940e3c 100644 --- a/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikTensorAlgebra.kt +++ b/kmath-multik/src/main/kotlin/space/kscience/kmath/multik/MultikTensorAlgebra.kt @@ -7,11 +7,9 @@ package space.kscience.kmath.multik -import org.jetbrains.kotlinx.multik.api.Multik -import org.jetbrains.kotlinx.multik.api.linalg.dot -import org.jetbrains.kotlinx.multik.api.mk -import org.jetbrains.kotlinx.multik.api.ndarrayOf -import org.jetbrains.kotlinx.multik.api.zeros +import org.jetbrains.kotlinx.multik.api.* +import org.jetbrains.kotlinx.multik.api.linalg.LinAlg +import org.jetbrains.kotlinx.multik.api.math.Math import org.jetbrains.kotlinx.multik.ndarray.data.* import org.jetbrains.kotlinx.multik.ndarray.operations.* import space.kscience.kmath.misc.PerformancePitfall @@ -52,10 +50,16 @@ private fun MultiArray.asD2Array(): D2Array { else throw ClassCastException("Cannot cast MultiArray to NDArray.") } -public abstract class MultikTensorAlgebra> : TensorAlgebra where T : Number, T : Comparable { +public abstract class MultikTensorAlgebra> : TensorAlgebra + where T : Number, T : Comparable { public abstract val type: DataType + protected val multikMath: Math = mk.math + protected val multikLinAl: LinAlg = mk.linalg + protected val multikStat: Statistics = mk.stat + + override fun structureND(shape: Shape, initializer: A.(IntArray) -> T): MultikTensor { val strides = DefaultStrides(shape) val memoryView = initMemoryView(strides.linearSize, type) @@ -65,6 +69,7 @@ public abstract class MultikTensorAlgebra> : TensorAlgebra return MultikTensor(NDArray(memoryView, shape = shape, dim = DN(shape.size))) } + @OptIn(PerformancePitfall::class) override fun StructureND.map(transform: A.(T) -> T): MultikTensor = if (this is MultikTensor) { val data = initMemoryView(array.size, type) var count = 0 @@ -76,6 +81,7 @@ public abstract class MultikTensorAlgebra> : TensorAlgebra } } + @OptIn(PerformancePitfall::class) override fun StructureND.mapIndexed(transform: A.(index: IntArray, T) -> T): MultikTensor = if (this is MultikTensor) { val array = asMultik().array @@ -96,6 +102,7 @@ public abstract class MultikTensorAlgebra> : TensorAlgebra } } + @OptIn(PerformancePitfall::class) override fun zip(left: StructureND, right: StructureND, transform: A.(T, T) -> T): MultikTensor { require(left.shape.contentEquals(right.shape)) { "ND array shape mismatch" } //TODO replace by ShapeMismatchException val leftArray = left.asMultik().array @@ -208,9 +215,9 @@ public abstract class MultikTensorAlgebra> : TensorAlgebra override fun StructureND.unaryMinus(): MultikTensor = asMultik().array.unaryMinus().wrap() - override fun StructureND.get(i: Int): MultikTensor = asMultik().array.mutableView(i).wrap() + override fun Tensor.get(i: Int): MultikTensor = asMultik().array.mutableView(i).wrap() - override fun StructureND.transpose(i: Int, j: Int): MultikTensor = asMultik().array.transpose(i, j).wrap() + override fun Tensor.transpose(i: Int, j: Int): MultikTensor = asMultik().array.transpose(i, j).wrap() override fun Tensor.view(shape: IntArray): MultikTensor { require(shape.all { it > 0 }) @@ -236,12 +243,12 @@ public abstract class MultikTensorAlgebra> : TensorAlgebra override fun StructureND.dot(other: StructureND): MultikTensor = if (this.shape.size == 1 && other.shape.size == 1) { Multik.ndarrayOf( - asMultik().array.asD1Array() dot other.asMultik().array.asD1Array() - ).asDNArray().wrap() + multikLinAl.linAlgEx.dotVV(asMultik().array.asD1Array(), other.asMultik().array.asD1Array()) + ).wrap() } else if (this.shape.size == 2 && other.shape.size == 2) { - (asMultik().array.asD2Array() dot other.asMultik().array.asD2Array()).asDNArray().wrap() + multikLinAl.linAlgEx.dotMM(asMultik().array.asD2Array(), other.asMultik().array.asD2Array()).wrap() } else if (this.shape.size == 2 && other.shape.size == 1) { - (asMultik().array.asD2Array() dot other.asMultik().array.asD1Array()).asDNArray().wrap() + multikLinAl.linAlgEx.dotMV(asMultik().array.asD2Array(), other.asMultik().array.asD1Array()).wrap() } else { TODO("Not implemented for broadcasting") } @@ -303,14 +310,6 @@ public abstract class MultikDivisionTensorAlgebra> } } -public object MultikDoubleAlgebra : MultikDivisionTensorAlgebra() { - override val elementAlgebra: DoubleField get() = DoubleField - override val type: DataType get() = DataType.DoubleDataType -} - -public val Double.Companion.multikAlgebra: MultikTensorAlgebra get() = MultikDoubleAlgebra -public val DoubleField.multikAlgebra: MultikTensorAlgebra get() = MultikDoubleAlgebra - public object MultikFloatAlgebra : MultikDivisionTensorAlgebra() { override val elementAlgebra: FloatField get() = FloatField override val type: DataType get() = DataType.FloatDataType diff --git a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt index a537c0629..58309736d 100644 --- a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt +++ b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt @@ -92,8 +92,8 @@ public sealed interface Nd4jTensorAlgebra> : AnalyticTe } override fun StructureND.unaryMinus(): Nd4jArrayStructure = ndArray.neg().wrap() - override fun StructureND.get(i: Int): Nd4jArrayStructure = ndArray.slice(i.toLong()).wrap() - override fun StructureND.transpose(i: Int, j: Int): Nd4jArrayStructure = ndArray.swapAxes(i, j).wrap() + override fun Tensor.get(i: Int): Nd4jArrayStructure = ndArray.slice(i.toLong()).wrap() + override fun Tensor.transpose(i: Int, j: Int): Nd4jArrayStructure = ndArray.swapAxes(i, j).wrap() override fun StructureND.dot(other: StructureND): Nd4jArrayStructure = ndArray.mmul(other.ndArray).wrap() override fun StructureND.min(dim: Int, keepDim: Boolean): Nd4jArrayStructure = diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt index debfb3ef0..c756584a4 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.tensors.api +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureND import space.kscience.kmath.operations.Field @@ -121,4 +122,7 @@ public interface AnalyticTensorAlgebra> : TensorPartialDivisionA //For information: https://pytorch.org/docs/stable/generated/torch.floor.html#torch.floor public fun StructureND.floor(): Tensor -} \ No newline at end of file +} + +@UnstableKMathAPI +public fun > ATA.exp(arg: StructureND): Tensor = arg.exp() \ No newline at end of file diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt index 8c445cf2d..e12bce209 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt @@ -166,7 +166,7 @@ public interface TensorAlgebra> : RingOpsND { * @param i index of the extractable tensor * @return subtensor of the original tensor with index [i] */ - public operator fun StructureND.get(i: Int): Tensor + public operator fun Tensor.get(i: Int): Tensor /** * Returns a tensor that is a transposed version of this tensor. The given dimensions [i] and [j] are swapped. @@ -176,7 +176,7 @@ public interface TensorAlgebra> : RingOpsND { * @param j the second dimension to be transposed * @return transposed tensor */ - public fun StructureND.transpose(i: Int = -2, j: Int = -1): Tensor + public fun Tensor.transpose(i: Int = -2, j: Int = -1): Tensor /** * Returns a new tensor with the same data as the self tensor but of a different shape. diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt index d7aa18c3d..35df2df8a 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt @@ -115,7 +115,7 @@ public open class DoubleTensorAlgebra : TensorLinearStructure(shape).asSequence().map { DoubleField.initializer(it) }.toMutableList().toDoubleArray() ) - override operator fun StructureND.get(i: Int): DoubleTensor { + override operator fun Tensor.get(i: Int): DoubleTensor { val lastShape = tensor.shape.drop(1).toIntArray() val newShape = if (lastShape.isNotEmpty()) lastShape else intArrayOf(1) val newStart = newShape.reduce(Int::times) * i + tensor.bufferStart @@ -344,7 +344,7 @@ public open class DoubleTensorAlgebra : return DoubleTensor(tensor.shape, resBuffer) } - override fun StructureND.transpose(i: Int, j: Int): DoubleTensor { + override fun Tensor.transpose(i: Int, j: Int): DoubleTensor { val ii = tensor.minusIndex(i) val jj = tensor.minusIndex(j) checkTranspose(tensor.dimension, ii, jj)