diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt index 4bfbe5863..f315f6b51 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt @@ -45,7 +45,7 @@ public class BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() { val newThis = broadcast[0] val newOther = broadcast[1] val resBuffer = DoubleArray(newThis.linearStructure.size) { i -> - newThis.buffer.array()[newOther.bufferStart + i] * + newThis.buffer.array()[newThis.bufferStart + i] * newOther.buffer.array()[newOther.bufferStart + i] } return DoubleTensor(newThis.shape, resBuffer) @@ -85,7 +85,6 @@ public inline fun BroadcastDoubleTensorAlgebra(block: BroadcastDoubleTensorA internal inline fun broadcastShapes(vararg shapes: IntArray): IntArray { - println(shapes) var totalDim = 0 for (shape in shapes) { totalDim = max(totalDim, shape.size) @@ -179,5 +178,59 @@ internal inline fun broadcastTensors(vararg tensors: DoubleTensor): List { + val onlyTwoDims = tensors.asSequence().onEach { + require(it.shape.size >= 2) { + throw RuntimeException("Tensors must have at least 2 dimensions") + } + }.any { it.shape.size != 2 } + + if (!onlyTwoDims) { + return tensors.asList() + } + + val totalShape = broadcastShapes(*(tensors.map { it.shape.sliceArray(0..it.shape.size - 3) }).toTypedArray()) + val n = totalShape.reduce { acc, i -> acc * i } + + val res = ArrayList(0) + for (tensor in tensors) { + val matrixShape = tensor.shape.sliceArray(tensor.shape.size - 2 until tensor.shape.size).copyOf() + val matrixSize = matrixShape[0] * matrixShape[1] + val matrix = DoubleTensor(matrixShape, DoubleArray(matrixSize)) + + val outerTensor = DoubleTensor(totalShape, DoubleArray(n)) + val resTensor = DoubleTensor(totalShape + matrixShape, DoubleArray(n * matrixSize)) + + for (linearIndex in 0 until n) { + val totalMultiIndex = outerTensor.linearStructure.index(linearIndex) + var curMultiIndex = tensor.shape.sliceArray(0..tensor.shape.size - 3).copyOf() + curMultiIndex = IntArray(totalMultiIndex.size - curMultiIndex.size) {1} + curMultiIndex + + val newTensor = DoubleTensor(curMultiIndex + matrixShape, tensor.buffer.array()) + + for (i in curMultiIndex.indices) { + if (curMultiIndex[i] != 1) { + curMultiIndex[i] = totalMultiIndex[i] + } else { + curMultiIndex[i] = 0 + } + } + + for (i in 0 until matrixSize) { + val curLinearIndex = newTensor.linearStructure.offset(curMultiIndex + + matrix.linearStructure.index(i)) + val newLinearIndex = resTensor.linearStructure.offset(totalMultiIndex + + matrix.linearStructure.index(i)) + + resTensor.buffer.array()[resTensor.bufferStart + newLinearIndex] = + newTensor.buffer.array()[newTensor.bufferStart + curLinearIndex] + } + } + res += resTensor + } + return res } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt index c67687a09..feb8d11fa 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt @@ -132,7 +132,7 @@ public open class DoubleTensorAlgebra : TensorPartialDivisionAlgebra - this.buffer.array()[other.bufferStart + i] * + this.buffer.array()[this.bufferStart + i] * other.buffer.array()[other.bufferStart + i] } return DoubleTensor(this.shape, resBuffer) @@ -242,7 +242,63 @@ public open class DoubleTensorAlgebra : TensorPartialDivisionAlgebra acc * i } + val resTensor = DoubleTensor(resShape, DoubleArray(resSize)) + + for ((res, ab) in resTensor.matrixSequence().zip(newThis.matrixSequence().zip(newOther.matrixSequence()))) { + val (a, b) = ab + + for (i in 0 until l) { + for (j in 0 until n) { + var curr = 0.0 + for (k in 0 until m) { + curr += a[i, k] * b[k, j] + } + res[i, j] = curr + } + } + } + + if (penultimateDim) { + return resTensor.view(resTensor.shape.dropLast(2).toIntArray() + + intArrayOf(resTensor.shape.last())) + } + if (lastDim) { + return resTensor.view(resTensor.shape.dropLast(1).toIntArray()) + } + return resTensor } override fun diagonalEmbedding(diagonalEntries: DoubleTensor, offset: Int, dim1: Int, dim2: Int): DoubleTensor { diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt index 73e3993a1..41c9b72f7 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt @@ -47,6 +47,36 @@ class TestBroadcasting { assertTrue(res[2].buffer.array() contentEquals doubleArrayOf(500.0, 500.0, 500.0, 500.0, 500.0, 500.0)) } + @Test + fun broadcastOuterTensors() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(1, 3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor3 = fromArray(intArrayOf(1, 1, 1), doubleArrayOf(500.0)) + + val res = broadcastOuterTensors(tensor1, tensor2, tensor3) + + assertTrue(res[0].shape contentEquals intArrayOf(1, 2, 3)) + assertTrue(res[1].shape contentEquals intArrayOf(1, 1, 3)) + assertTrue(res[2].shape contentEquals intArrayOf(1, 1, 1)) + + assertTrue(res[0].buffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + assertTrue(res[1].buffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0)) + assertTrue(res[2].buffer.array() contentEquals doubleArrayOf(500.0)) + } + + @Test + fun broadcastOuterTensorsShapes() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 1, 3, 2, 3), DoubleArray(2 * 1 * 3 * 2 * 3) {0.0}) + val tensor2 = fromArray(intArrayOf(4, 2, 5, 1, 3, 3), DoubleArray(4 * 2 * 5 * 1 * 3 * 3) {0.0}) + val tensor3 = fromArray(intArrayOf(1, 1), doubleArrayOf(500.0)) + + val res = broadcastOuterTensors(tensor1, tensor2, tensor3) + + assertTrue(res[0].shape contentEquals intArrayOf(4, 2, 5, 3, 2, 3)) + assertTrue(res[1].shape contentEquals intArrayOf(4, 2, 5, 3, 3, 3)) + assertTrue(res[2].shape contentEquals intArrayOf(4, 2, 5, 3, 1, 1)) + } + @Test fun minusTensor() = BroadcastDoubleTensorAlgebra { val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt index 06aa3ebf7..168d80a9d 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt @@ -79,6 +79,42 @@ class TestDoubleTensorAlgebra { assertTrue(expected.buffer.array() contentEquals assignResult.buffer.array()) } + @Test + fun dot() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor11 = fromArray(intArrayOf(3, 2), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor3 = fromArray(intArrayOf(1, 1, 3), doubleArrayOf(-1.0, -2.0, -3.0)) + + val res12 = tensor1.dot(tensor2) + assertTrue(res12.buffer.array() contentEquals doubleArrayOf(140.0, 320.0)) + assertTrue(res12.shape contentEquals intArrayOf(2)) + + val res32 = tensor3.dot(tensor2) + assertTrue(res32.buffer.array() contentEquals doubleArrayOf(-140.0)) + assertTrue(res32.shape contentEquals intArrayOf(1, 1)) + + val res22 = tensor2.dot(tensor2) + assertTrue(res22.buffer.array() contentEquals doubleArrayOf(1400.0)) + assertTrue(res22.shape contentEquals intArrayOf(1)) + + val res11 = tensor1.dot(tensor11) + assertTrue(res11.buffer.array() contentEquals doubleArrayOf(22.0, 28.0, 49.0, 64.0)) + assertTrue(res11.shape contentEquals intArrayOf(2, 2)) + + var tensor4 = fromArray(intArrayOf(10, 3, 4), DoubleArray(10 * 3 * 4) {0.0}) + var tensor5 = fromArray(intArrayOf(10, 4, 5), DoubleArray(10 * 4 * 5) {0.0}) + assertTrue(tensor4.dot(tensor5).shape contentEquals intArrayOf(10, 3, 5)) + + tensor4 = fromArray(intArrayOf(10, 3, 4), DoubleArray(10 * 3 * 4) {0.0}) + tensor5 = fromArray(intArrayOf(4, 5), DoubleArray(4 * 5) {0.0}) + assertTrue(tensor4.dot(tensor5).shape contentEquals intArrayOf(10, 3, 5)) + + tensor4 = fromArray(intArrayOf(4, 2, 1, 3, 8, 1), DoubleArray(4 * 2 * 1 * 3 * 8 * 1) {0.0}) + tensor5 = fromArray(intArrayOf(5, 1, 2, 8, 3, 1, 5), DoubleArray(5 * 1 * 2 * 8 * 3 * 1 * 5) {0.0}) + assertTrue(tensor4.dot(tensor5).shape contentEquals intArrayOf(5, 4, 2, 8, 3, 8, 5)) + } + @Test fun testContentEqual() = DoubleTensorAlgebra { //TODO()