Merge pull request #1 from margarita0303/feature/tensors-performance

Feature/tensors performance Adding SVDGolubKahan
This commit is contained in:
Roland Grinis 2022-08-03 08:26:25 +01:00 committed by GitHub
commit 87022d771b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 909 additions and 112 deletions

View File

@ -84,6 +84,11 @@ benchmark {
iterationTimeUnit = "ms" iterationTimeUnit = "ms"
} }
configurations.register("svd") {
commonConfiguration()
include("SVDBenchmark")
}
configurations.register("buffer") { configurations.register("buffer") {
commonConfiguration() commonConfiguration()
include("BufferBenchmark") include("BufferBenchmark")

View File

@ -0,0 +1,141 @@
/*
* Copyright 2018-2021 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.benchmarks
import kotlinx.benchmark.Benchmark
import kotlinx.benchmark.Blackhole
import kotlinx.benchmark.Scope
import kotlinx.benchmark.State
import org.ejml.UtilEjml.assertTrue
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra.diagonalEmbedding
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra.dot
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra.eq
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra.svdGolubKahan
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra.transpose
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra.Companion.svdPowerMethod
@State(Scope.Benchmark)
class SVDBenchmark {
companion object {
val tensorSmall = DoubleTensorAlgebra.randomNormal(intArrayOf(5, 5), 0)
val tensorMedium = DoubleTensorAlgebra.randomNormal(intArrayOf(10, 10), 0)
val tensorLarge = DoubleTensorAlgebra.randomNormal(intArrayOf(50, 50), 0)
val tensorVeryLarge = DoubleTensorAlgebra.randomNormal(intArrayOf(100, 100), 0)
val epsilon = 1e-9
}
@Benchmark
fun svdPowerMethodSmall(blackhole: Blackhole) {
val svd = tensorSmall.svdPowerMethod()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorSmall, epsilon))
blackhole.consume(
tensorSmall.svdPowerMethod()
)
}
@Benchmark
fun svdPowerMethodMedium(blackhole: Blackhole) {
val svd = tensorMedium.svdPowerMethod()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorMedium, epsilon))
blackhole.consume(
tensorMedium.svdPowerMethod()
)
}
@Benchmark
fun svdPowerMethodLarge(blackhole: Blackhole) {
val svd = tensorLarge.svdPowerMethod()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorLarge, epsilon))
blackhole.consume(
tensorLarge.svdPowerMethod()
)
}
@Benchmark
fun svdPowerMethodVeryLarge(blackhole: Blackhole) {
val svd = tensorVeryLarge.svdPowerMethod()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorVeryLarge, epsilon))
blackhole.consume(
tensorVeryLarge.svdPowerMethod()
)
}
@Benchmark
fun svdGolubKahanSmall(blackhole: Blackhole) {
val svd = tensorSmall.svdGolubKahan()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorSmall, epsilon))
blackhole.consume(
tensorSmall.svdGolubKahan()
)
}
@Benchmark
fun svdGolubKahanMedium(blackhole: Blackhole) {
val svd = tensorMedium.svdGolubKahan()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorMedium, epsilon))
blackhole.consume(
tensorMedium.svdGolubKahan()
)
}
@Benchmark
fun svdGolubKahanLarge(blackhole: Blackhole) {
val svd = tensorLarge.svdGolubKahan()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorLarge, epsilon))
blackhole.consume(
tensorLarge.svdGolubKahan()
)
}
@Benchmark
fun svdGolubKahanVeryLarge(blackhole: Blackhole) {
val svd = tensorVeryLarge.svdGolubKahan()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensorSVD.eq(tensorVeryLarge, epsilon))
blackhole.consume(
tensorVeryLarge.svdGolubKahan()
)
}
}

View File

@ -27,7 +27,7 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val newThis = broadcast[0] val newThis = broadcast[0]
val newOther = broadcast[1] val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i -> val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[i] + newOther.mutableBuffer.array()[i] newThis.mutableBuffer[i] + newOther.mutableBuffer[i]
} }
return DoubleTensor(newThis.shape, resBuffer) return DoubleTensor(newThis.shape, resBuffer)
} }
@ -35,8 +35,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) { override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.tensor, tensor.shape) val newOther = broadcastTo(arg.tensor, tensor.shape)
for (i in 0 until tensor.indices.linearSize) { for (i in 0 until tensor.indices.linearSize) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] += tensor.mutableBuffer[tensor.bufferStart + i] +=
newOther.mutableBuffer.array()[tensor.bufferStart + i] newOther.mutableBuffer[tensor.bufferStart + i]
} }
} }
@ -45,7 +45,7 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val newThis = broadcast[0] val newThis = broadcast[0]
val newOther = broadcast[1] val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i -> val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[i] - newOther.mutableBuffer.array()[i] newThis.mutableBuffer[i] - newOther.mutableBuffer[i]
} }
return DoubleTensor(newThis.shape, resBuffer) return DoubleTensor(newThis.shape, resBuffer)
} }
@ -53,8 +53,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) { override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.tensor, tensor.shape) val newOther = broadcastTo(arg.tensor, tensor.shape)
for (i in 0 until tensor.indices.linearSize) { for (i in 0 until tensor.indices.linearSize) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] -= tensor.mutableBuffer[tensor.bufferStart + i] -=
newOther.mutableBuffer.array()[tensor.bufferStart + i] newOther.mutableBuffer[tensor.bufferStart + i]
} }
} }
@ -63,8 +63,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val newThis = broadcast[0] val newThis = broadcast[0]
val newOther = broadcast[1] val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i -> val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[newThis.bufferStart + i] * newThis.mutableBuffer[newThis.bufferStart + i] *
newOther.mutableBuffer.array()[newOther.bufferStart + i] newOther.mutableBuffer[newOther.bufferStart + i]
} }
return DoubleTensor(newThis.shape, resBuffer) return DoubleTensor(newThis.shape, resBuffer)
} }
@ -72,8 +72,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) { override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.tensor, tensor.shape) val newOther = broadcastTo(arg.tensor, tensor.shape)
for (i in 0 until tensor.indices.linearSize) { for (i in 0 until tensor.indices.linearSize) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] *= tensor.mutableBuffer[tensor.bufferStart + i] *=
newOther.mutableBuffer.array()[tensor.bufferStart + i] newOther.mutableBuffer[tensor.bufferStart + i]
} }
} }
@ -82,8 +82,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val newThis = broadcast[0] val newThis = broadcast[0]
val newOther = broadcast[1] val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i -> val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[newOther.bufferStart + i] / newThis.mutableBuffer[newOther.bufferStart + i] /
newOther.mutableBuffer.array()[newOther.bufferStart + i] newOther.mutableBuffer[newOther.bufferStart + i]
} }
return DoubleTensor(newThis.shape, resBuffer) return DoubleTensor(newThis.shape, resBuffer)
} }
@ -91,8 +91,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
override fun Tensor<Double>.divAssign(arg: StructureND<Double>) { override fun Tensor<Double>.divAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.tensor, tensor.shape) val newOther = broadcastTo(arg.tensor, tensor.shape)
for (i in 0 until tensor.indices.linearSize) { for (i in 0 until tensor.indices.linearSize) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] /= tensor.mutableBuffer[tensor.bufferStart + i] /=
newOther.mutableBuffer.array()[tensor.bufferStart + i] newOther.mutableBuffer[tensor.bufferStart + i]
} }
} }
} }

View File

@ -89,7 +89,7 @@ public open class DoubleTensorAlgebra :
} }
override fun StructureND<Double>.valueOrNull(): Double? = if (tensor.shape contentEquals intArrayOf(1)) override fun StructureND<Double>.valueOrNull(): Double? = if (tensor.shape contentEquals intArrayOf(1))
tensor.mutableBuffer.array()[tensor.bufferStart] else null tensor.mutableBuffer[tensor.bufferStart] else null
override fun StructureND<Double>.value(): Double = valueOrNull() override fun StructureND<Double>.value(): Double = valueOrNull()
?: throw IllegalArgumentException("The tensor shape is $shape, but value method is allowed only for shape [1]") ?: throw IllegalArgumentException("The tensor shape is $shape, but value method is allowed only for shape [1]")
@ -208,7 +208,7 @@ public open class DoubleTensorAlgebra :
override fun Double.plus(arg: StructureND<Double>): DoubleTensor { override fun Double.plus(arg: StructureND<Double>): DoubleTensor {
val resBuffer = DoubleArray(arg.tensor.numElements) { i -> val resBuffer = DoubleArray(arg.tensor.numElements) { i ->
arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] + this arg.tensor.mutableBuffer[arg.tensor.bufferStart + i] + this
} }
return DoubleTensor(arg.shape, resBuffer) return DoubleTensor(arg.shape, resBuffer)
} }
@ -218,35 +218,35 @@ public open class DoubleTensorAlgebra :
override fun StructureND<Double>.plus(arg: StructureND<Double>): DoubleTensor { override fun StructureND<Double>.plus(arg: StructureND<Double>): DoubleTensor {
checkShapesCompatible(tensor, arg.tensor) checkShapesCompatible(tensor, arg.tensor)
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[i] + arg.tensor.mutableBuffer.array()[i] tensor.mutableBuffer[i] + arg.tensor.mutableBuffer[i]
} }
return DoubleTensor(tensor.shape, resBuffer) return DoubleTensor(tensor.shape, resBuffer)
} }
override fun Tensor<Double>.plusAssign(value: Double) { override fun Tensor<Double>.plusAssign(value: Double) {
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] += value tensor.mutableBuffer[tensor.bufferStart + i] += value
} }
} }
override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) { override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) {
checkShapesCompatible(tensor, arg.tensor) checkShapesCompatible(tensor, arg.tensor)
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] += tensor.mutableBuffer[tensor.bufferStart + i] +=
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i] arg.tensor.mutableBuffer[tensor.bufferStart + i]
} }
} }
override fun Double.minus(arg: StructureND<Double>): DoubleTensor { override fun Double.minus(arg: StructureND<Double>): DoubleTensor {
val resBuffer = DoubleArray(arg.tensor.numElements) { i -> val resBuffer = DoubleArray(arg.tensor.numElements) { i ->
this - arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] this - arg.tensor.mutableBuffer[arg.tensor.bufferStart + i]
} }
return DoubleTensor(arg.shape, resBuffer) return DoubleTensor(arg.shape, resBuffer)
} }
override fun StructureND<Double>.minus(arg: Double): DoubleTensor { override fun StructureND<Double>.minus(arg: Double): DoubleTensor {
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[tensor.bufferStart + i] - arg tensor.mutableBuffer[tensor.bufferStart + i] - arg
} }
return DoubleTensor(tensor.shape, resBuffer) return DoubleTensor(tensor.shape, resBuffer)
} }
@ -254,28 +254,28 @@ public open class DoubleTensorAlgebra :
override fun StructureND<Double>.minus(arg: StructureND<Double>): DoubleTensor { override fun StructureND<Double>.minus(arg: StructureND<Double>): DoubleTensor {
checkShapesCompatible(tensor, arg) checkShapesCompatible(tensor, arg)
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[i] - arg.tensor.mutableBuffer.array()[i] tensor.mutableBuffer[i] - arg.tensor.mutableBuffer[i]
} }
return DoubleTensor(tensor.shape, resBuffer) return DoubleTensor(tensor.shape, resBuffer)
} }
override fun Tensor<Double>.minusAssign(value: Double) { override fun Tensor<Double>.minusAssign(value: Double) {
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] -= value tensor.mutableBuffer[tensor.bufferStart + i] -= value
} }
} }
override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) { override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) {
checkShapesCompatible(tensor, arg) checkShapesCompatible(tensor, arg)
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] -= tensor.mutableBuffer[tensor.bufferStart + i] -=
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i] arg.tensor.mutableBuffer[tensor.bufferStart + i]
} }
} }
override fun Double.times(arg: StructureND<Double>): DoubleTensor { override fun Double.times(arg: StructureND<Double>): DoubleTensor {
val resBuffer = DoubleArray(arg.tensor.numElements) { i -> val resBuffer = DoubleArray(arg.tensor.numElements) { i ->
arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] * this arg.tensor.mutableBuffer[arg.tensor.bufferStart + i] * this
} }
return DoubleTensor(arg.shape, resBuffer) return DoubleTensor(arg.shape, resBuffer)
} }
@ -285,36 +285,36 @@ public open class DoubleTensorAlgebra :
override fun StructureND<Double>.times(arg: StructureND<Double>): DoubleTensor { override fun StructureND<Double>.times(arg: StructureND<Double>): DoubleTensor {
checkShapesCompatible(tensor, arg) checkShapesCompatible(tensor, arg)
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[tensor.bufferStart + i] * tensor.mutableBuffer[tensor.bufferStart + i] *
arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] arg.tensor.mutableBuffer[arg.tensor.bufferStart + i]
} }
return DoubleTensor(tensor.shape, resBuffer) return DoubleTensor(tensor.shape, resBuffer)
} }
override fun Tensor<Double>.timesAssign(value: Double) { override fun Tensor<Double>.timesAssign(value: Double) {
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] *= value tensor.mutableBuffer[tensor.bufferStart + i] *= value
} }
} }
override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) { override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) {
checkShapesCompatible(tensor, arg) checkShapesCompatible(tensor, arg)
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] *= tensor.mutableBuffer[tensor.bufferStart + i] *=
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i] arg.tensor.mutableBuffer[tensor.bufferStart + i]
} }
} }
override fun Double.div(arg: StructureND<Double>): DoubleTensor { override fun Double.div(arg: StructureND<Double>): DoubleTensor {
val resBuffer = DoubleArray(arg.tensor.numElements) { i -> val resBuffer = DoubleArray(arg.tensor.numElements) { i ->
this / arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] this / arg.tensor.mutableBuffer[arg.tensor.bufferStart + i]
} }
return DoubleTensor(arg.shape, resBuffer) return DoubleTensor(arg.shape, resBuffer)
} }
override fun StructureND<Double>.div(arg: Double): DoubleTensor { override fun StructureND<Double>.div(arg: Double): DoubleTensor {
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[tensor.bufferStart + i] / arg tensor.mutableBuffer[tensor.bufferStart + i] / arg
} }
return DoubleTensor(shape, resBuffer) return DoubleTensor(shape, resBuffer)
} }
@ -322,29 +322,29 @@ public open class DoubleTensorAlgebra :
override fun StructureND<Double>.div(arg: StructureND<Double>): DoubleTensor { override fun StructureND<Double>.div(arg: StructureND<Double>): DoubleTensor {
checkShapesCompatible(tensor, arg) checkShapesCompatible(tensor, arg)
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] / tensor.mutableBuffer[arg.tensor.bufferStart + i] /
arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] arg.tensor.mutableBuffer[arg.tensor.bufferStart + i]
} }
return DoubleTensor(tensor.shape, resBuffer) return DoubleTensor(tensor.shape, resBuffer)
} }
override fun Tensor<Double>.divAssign(value: Double) { override fun Tensor<Double>.divAssign(value: Double) {
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] /= value tensor.mutableBuffer[tensor.bufferStart + i] /= value
} }
} }
override fun Tensor<Double>.divAssign(arg: StructureND<Double>) { override fun Tensor<Double>.divAssign(arg: StructureND<Double>) {
checkShapesCompatible(tensor, arg) checkShapesCompatible(tensor, arg)
for (i in 0 until tensor.numElements) { for (i in 0 until tensor.numElements) {
tensor.mutableBuffer.array()[tensor.bufferStart + i] /= tensor.mutableBuffer[tensor.bufferStart + i] /=
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i] arg.tensor.mutableBuffer[tensor.bufferStart + i]
} }
} }
override fun StructureND<Double>.unaryMinus(): DoubleTensor { override fun StructureND<Double>.unaryMinus(): DoubleTensor {
val resBuffer = DoubleArray(tensor.numElements) { i -> val resBuffer = DoubleArray(tensor.numElements) { i ->
tensor.mutableBuffer.array()[tensor.bufferStart + i].unaryMinus() tensor.mutableBuffer[tensor.bufferStart + i].unaryMinus()
} }
return DoubleTensor(tensor.shape, resBuffer) return DoubleTensor(tensor.shape, resBuffer)
} }
@ -367,8 +367,8 @@ public open class DoubleTensorAlgebra :
newMultiIndex[ii] = newMultiIndex[jj].also { newMultiIndex[jj] = newMultiIndex[ii] } newMultiIndex[ii] = newMultiIndex[jj].also { newMultiIndex[jj] = newMultiIndex[ii] }
val linearIndex = resTensor.indices.offset(newMultiIndex) val linearIndex = resTensor.indices.offset(newMultiIndex)
resTensor.mutableBuffer.array()[linearIndex] = resTensor.mutableBuffer[linearIndex] =
tensor.mutableBuffer.array()[tensor.bufferStart + offset] tensor.mutableBuffer[tensor.bufferStart + offset]
} }
return resTensor return resTensor
} }
@ -833,8 +833,38 @@ public open class DoubleTensorAlgebra :
return qTensor to rTensor return qTensor to rTensor
} }
override fun StructureND<Double>.svd(): Triple<DoubleTensor, DoubleTensor, DoubleTensor> = override fun StructureND<Double>.svd(): Triple<DoubleTensor, DoubleTensor, DoubleTensor> {
svd(epsilon = 1e-10) return this.svdGolubKahan()
}
public fun StructureND<Double>.svdGolubKahan(iterations: Int = 30, epsilon: Double = 1e-10): Triple<DoubleTensor, DoubleTensor, DoubleTensor> {
val size = tensor.dimension
val commonShape = tensor.shape.sliceArray(0 until size - 2)
val (n, m) = tensor.shape.sliceArray(size - 2 until size)
val uTensor = zeros(commonShape + intArrayOf(n, m))
val sTensor = zeros(commonShape + intArrayOf(m))
val vTensor = zeros(commonShape + intArrayOf(m, m))
val matrices = tensor.matrices
val uTensors = uTensor.matrices
val sTensorVectors = sTensor.vectors
val vTensors = vTensor.matrices
for (index in matrices.indices) {
val matrix = matrices[index]
val matrixSize = matrix.shape.reduce { acc, i -> acc * i }
val curMatrix = DoubleTensor(
matrix.shape,
matrix.mutableBuffer.array()
.slice(matrix.bufferStart until matrix.bufferStart + matrixSize)
.toDoubleArray()
)
curMatrix.as2D().svdGolubKahanHelper(uTensors[index].as2D(), sTensorVectors[index], vTensors[index].as2D(),
iterations, epsilon)
}
return Triple(uTensor, sTensor, vTensor)
}
/** /**
* Singular Value Decomposition. * Singular Value Decomposition.
@ -849,7 +879,7 @@ public open class DoubleTensorAlgebra :
* i.e., the precision with which the cosine approaches 1 in an iterative algorithm. * i.e., the precision with which the cosine approaches 1 in an iterative algorithm.
* @return a triple `Triple(U, S, V)`. * @return a triple `Triple(U, S, V)`.
*/ */
public fun StructureND<Double>.svd(epsilon: Double): Triple<DoubleTensor, DoubleTensor, DoubleTensor> { public fun StructureND<Double>.svdPowerMethod(epsilon: Double = 1e-10): Triple<DoubleTensor, DoubleTensor, DoubleTensor> {
val size = tensor.dimension val size = tensor.dimension
val commonShape = tensor.shape.sliceArray(0 until size - 2) val commonShape = tensor.shape.sliceArray(0 until size - 2)
val (n, m) = tensor.shape.sliceArray(size - 2 until size) val (n, m) = tensor.shape.sliceArray(size - 2 until size)
@ -876,7 +906,7 @@ public open class DoubleTensorAlgebra :
.slice(matrix.bufferStart until matrix.bufferStart + matrixSize) .slice(matrix.bufferStart until matrix.bufferStart + matrixSize)
.toDoubleArray() .toDoubleArray()
) )
svdHelper(curMatrix, usv, m, n, epsilon) svdPowerMethodHelper(curMatrix, usv, m, n, epsilon)
} }
return Triple(uTensor.transpose(), sTensor, vTensor.transpose()) return Triple(uTensor.transpose(), sTensor, vTensor.transpose())
@ -907,7 +937,7 @@ public open class DoubleTensorAlgebra :
} }
} }
val (u, s, v) = tensor.svd(epsilon) val (u, s, v) = tensor.svd()
val shp = s.shape + intArrayOf(1) val shp = s.shape + intArrayOf(1)
val utv = u.transpose() dot v val utv = u.transpose() dot v
val n = s.shape.last() val n = s.shape.last()
@ -928,18 +958,21 @@ public open class DoubleTensorAlgebra :
var eigenvalueStart = 0 var eigenvalueStart = 0
var eigenvectorStart = 0 var eigenvectorStart = 0
val eigenvaluesBuffer = eigenvalues.mutableBuffer
val eigenvectorsBuffer = eigenvectors.mutableBuffer
for (matrix in tensor.matrixSequence()) { for (matrix in tensor.matrixSequence()) {
val matrix2D = matrix.as2D() val matrix2D = matrix.as2D()
val (d, v) = matrix2D.jacobiHelper(maxIteration, epsilon) val (d, v) = matrix2D.jacobiHelper(maxIteration, epsilon)
for (i in 0 until matrix2D.rowNum) { for (i in 0 until matrix2D.rowNum) {
for (j in 0 until matrix2D.colNum) { for (j in 0 until matrix2D.colNum) {
eigenvectors.mutableBuffer.array()[eigenvectorStart + i * matrix2D.rowNum + j] = v[i, j] eigenvectorsBuffer[eigenvectorStart + i * matrix2D.rowNum + j] = v[i, j]
} }
} }
for (i in 0 until matrix2D.rowNum) { for (i in 0 until matrix2D.rowNum) {
eigenvalues.mutableBuffer.array()[eigenvalueStart + i] = d[i] eigenvaluesBuffer[eigenvalueStart + i] = d[i]
} }
eigenvalueStart += this.shape.last() eigenvalueStart += this.shape.last()
@ -962,11 +995,11 @@ public open class DoubleTensorAlgebra :
// assume that buffered tensor is square matrix // assume that buffered tensor is square matrix
operator fun BufferedTensor<Double>.get(i: Int, j: Int): Double { operator fun BufferedTensor<Double>.get(i: Int, j: Int): Double {
return this.mutableBuffer.array()[bufferStart + i * this.shape[0] + j] return this.mutableBuffer[bufferStart + i * this.shape[0] + j]
} }
operator fun BufferedTensor<Double>.set(i: Int, j: Int, value: Double) { operator fun BufferedTensor<Double>.set(i: Int, j: Int, value: Double) {
this.mutableBuffer.array()[bufferStart + i * this.shape[0] + j] = value this.mutableBuffer[bufferStart + i * this.shape[0] + j] = value
} }
fun maxOffDiagonal(matrix: BufferedTensor<Double>): Double { fun maxOffDiagonal(matrix: BufferedTensor<Double>): Double {

View File

@ -17,6 +17,7 @@ import space.kscience.kmath.tensors.core.DoubleTensor
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra import space.kscience.kmath.tensors.core.DoubleTensorAlgebra
import space.kscience.kmath.tensors.core.IntTensor import space.kscience.kmath.tensors.core.IntTensor
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.sqrt import kotlin.math.sqrt
@ -299,7 +300,7 @@ internal fun DoubleTensorAlgebra.svd1d(a: DoubleTensor, epsilon: Double = 1e-10)
} }
} }
internal fun DoubleTensorAlgebra.svdHelper( internal fun DoubleTensorAlgebra.svdPowerMethodHelper(
matrix: DoubleTensor, matrix: DoubleTensor,
USV: Triple<BufferedTensor<Double>, BufferedTensor<Double>, BufferedTensor<Double>>, USV: Triple<BufferedTensor<Double>, BufferedTensor<Double>, BufferedTensor<Double>>,
m: Int, n: Int, epsilon: Double, m: Int, n: Int, epsilon: Double,
@ -307,6 +308,14 @@ internal fun DoubleTensorAlgebra.svdHelper(
val res = ArrayList<Triple<Double, DoubleTensor, DoubleTensor>>(0) val res = ArrayList<Triple<Double, DoubleTensor, DoubleTensor>>(0)
val (matrixU, matrixS, matrixV) = USV val (matrixU, matrixS, matrixV) = USV
val matrixUStart = matrixU.bufferStart
val matrixSStart = matrixS.bufferStart
val matrixVStart = matrixV.bufferStart
val matrixUBuffer = matrixU.mutableBuffer
val matrixSBuffer = matrixS.mutableBuffer
val matrixVBuffer = matrixV.mutableBuffer
for (k in 0 until min(n, m)) { for (k in 0 until min(n, m)) {
var a = matrix.copy() var a = matrix.copy()
for ((singularValue, u, v) in res.slice(0 until k)) { for ((singularValue, u, v) in res.slice(0 until k)) {
@ -340,12 +349,307 @@ internal fun DoubleTensorAlgebra.svdHelper(
val uBuffer = res.map { it.second }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray() val uBuffer = res.map { it.second }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
val vBuffer = res.map { it.third }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray() val vBuffer = res.map { it.third }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
for (i in uBuffer.indices) { for (i in uBuffer.indices) {
matrixU.mutableBuffer.array()[matrixU.bufferStart + i] = uBuffer[i] matrixUBuffer[matrixUStart + i] = uBuffer[i]
} }
for (i in s.indices) { for (i in s.indices) {
matrixS.mutableBuffer.array()[matrixS.bufferStart + i] = s[i] matrixSBuffer[matrixSStart + i] = s[i]
} }
for (i in vBuffer.indices) { for (i in vBuffer.indices) {
matrixV.mutableBuffer.array()[matrixV.bufferStart + i] = vBuffer[i] matrixVBuffer[matrixVStart + i] = vBuffer[i]
}
}
private fun pythag(a: Double, b: Double): Double {
val at: Double = abs(a)
val bt: Double = abs(b)
val ct: Double
val result: Double
if (at > bt) {
ct = bt / at
result = at * sqrt(1.0 + ct * ct)
} else if (bt > 0.0) {
ct = at / bt
result = bt * sqrt(1.0 + ct * ct)
} else result = 0.0
return result
}
private fun SIGN(a: Double, b: Double): Double {
if (b >= 0.0)
return abs(a)
else
return -abs(a)
}
internal fun MutableStructure2D<Double>.svdGolubKahanHelper(u: MutableStructure2D<Double>, w: BufferedTensor<Double>,
v: MutableStructure2D<Double>, iterations: Int, epsilon: Double) {
val shape = this.shape
val m = shape.component1()
val n = shape.component2()
var f = 0.0
val rv1 = DoubleArray(n)
var s = 0.0
var scale = 0.0
var anorm = 0.0
var g = 0.0
var l = 0
val wStart = w.bufferStart
val wBuffer = w.mutableBuffer
for (i in 0 until n) {
/* left-hand reduction */
l = i + 1
rv1[i] = scale * g
g = 0.0
s = 0.0
scale = 0.0
if (i < m) {
for (k in i until m) {
scale += abs(this[k, i]);
}
if (abs(scale) > epsilon) {
for (k in i until m) {
this[k, i] = (this[k, i] / scale)
s += this[k, i] * this[k, i]
}
f = this[i, i]
if (f >= 0) {
g = (-1) * abs(sqrt(s))
} else {
g = abs(sqrt(s))
}
val h = f * g - s
this[i, i] = f - g
if (i != n - 1) {
for (j in l until n) {
s = 0.0
for (k in i until m) {
s += this[k, i] * this[k, j]
}
f = s / h
for (k in i until m) {
this[k, j] += f * this[k, i]
}
}
}
for (k in i until m) {
this[k, i] = this[k, i] * scale
}
}
}
wBuffer[wStart + i] = scale * g
/* right-hand reduction */
g = 0.0
s = 0.0
scale = 0.0
if (i < m && i != n - 1) {
for (k in l until n) {
scale += abs(this[i, k])
}
if (abs(scale) > epsilon) {
for (k in l until n) {
this[i, k] = this[i, k] / scale
s += this[i, k] * this[i, k]
}
f = this[i, l]
if (f >= 0) {
g = (-1) * abs(sqrt(s))
} else {
g = abs(sqrt(s))
}
val h = f * g - s
this[i, l] = f - g
for (k in l until n) {
rv1[k] = this[i, k] / h
}
if (i != m - 1) {
for (j in l until m) {
s = 0.0
for (k in l until n) {
s += this[j, k] * this[i, k]
}
for (k in l until n) {
this[j, k] += s * rv1[k]
}
}
}
for (k in l until n) {
this[i, k] = this[i, k] * scale
}
}
}
anorm = max(anorm, (abs(wBuffer[wStart + i]) + abs(rv1[i])));
}
for (i in n - 1 downTo 0) {
if (i < n - 1) {
if (abs(g) > epsilon) {
for (j in l until n) {
v[j, i] = (this[i, j] / this[i, l]) / g
}
for (j in l until n) {
s = 0.0
for (k in l until n)
s += this[i, k] * v[k, j]
for (k in l until n)
v[k, j] += s * v[k, i]
}
}
for (j in l until n) {
v[i, j] = 0.0
v[j, i] = 0.0
}
}
v[i, i] = 1.0
g = rv1[i]
l = i
}
for (i in min(n, m) - 1 downTo 0) {
l = i + 1
g = wBuffer[wStart + i]
for (j in l until n) {
this[i, j] = 0.0
}
if (abs(g) > epsilon) {
g = 1.0 / g
for (j in l until n) {
s = 0.0
for (k in l until m) {
s += this[k, i] * this[k, j]
}
f = (s / this[i, i]) * g
for (k in i until m) {
this[k, j] += f * this[k, i]
}
}
for (j in i until m) {
this[j, i] *= g
}
} else {
for (j in i until m) {
this[j, i] = 0.0
}
}
this[i, i] += 1.0
}
var flag = 0
var nm = 0
var c = 0.0
var h = 0.0
var y = 0.0
var z = 0.0
var x = 0.0
for (k in n - 1 downTo 0) {
for (its in 1 until iterations) {
flag = 1
for (newl in k downTo 0) {
nm = newl - 1
if (abs(rv1[newl]) + anorm == anorm) {
flag = 0
l = newl
break
}
if (abs(wBuffer[wStart + nm]) + anorm == anorm) {
l = newl
break
}
}
if (flag != 0) {
c = 0.0
s = 1.0
for (i in l until k + 1) {
f = s * rv1[i]
rv1[i] = c * rv1[i]
if (abs(f) + anorm == anorm) {
break
}
g = wBuffer[wStart + i]
h = pythag(f, g)
wBuffer[wStart + i] = h
h = 1.0 / h
c = g * h
s = (-f) * h
for (j in 0 until m) {
y = this[j, nm]
z = this[j, i]
this[j, nm] = y * c + z * s
this[j, i] = z * c - y * s
}
}
}
z = wBuffer[wStart + k]
if (l == k) {
if (z < 0.0) {
wBuffer[wStart + k] = -z
for (j in 0 until n)
v[j, k] = -v[j, k]
}
break
}
x = wBuffer[wStart + l]
nm = k - 1
y = wBuffer[wStart + nm]
g = rv1[nm]
h = rv1[k]
f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y)
g = pythag(f, 1.0)
f = ((x - z) * (x + z) + h * ((y / (f + SIGN(g, f))) - h)) / x
c = 1.0
s = 1.0
var i = 0
for (j in l until nm + 1) {
i = j + 1
g = rv1[i]
y = wBuffer[wStart + i]
h = s * g
g = c * g
z = pythag(f, h)
rv1[j] = z
c = f / z
s = h / z
f = x * c + g * s
g = g * c - x * s
h = y * s
y *= c
for (jj in 0 until n) {
x = v[jj, j];
z = v[jj, i];
v[jj, j] = x * c + z * s;
v[jj, i] = z * c - x * s;
}
z = pythag(f, h)
wBuffer[wStart + j] = z
if (abs(z) > epsilon) {
z = 1.0 / z
c = f * z
s = h * z
}
f = c * g + s * y
x = c * y - s * g
for (jj in 0 until m) {
y = this[jj, j]
z = this[jj, i]
this[jj, j] = y * c + z * s
this[jj, i] = z * c - y * s
}
}
rv1[l] = 0.0
rv1[k] = f
wBuffer[wStart + k] = x
}
}
for (i in 0 until n) {
for (j in 0 until m) {
u[j, i] = this[j, i]
}
} }
} }

View File

@ -12,110 +12,250 @@ import kotlin.test.assertTrue
internal class TestDoubleAnalyticTensorAlgebra { internal class TestDoubleAnalyticTensorAlgebra {
val shape = intArrayOf(2, 1, 3, 2) val shapeWithNegative = intArrayOf(4)
val buffer = doubleArrayOf( val bufferWithNegative = doubleArrayOf(9.3348, -7.5889, -1.2005, 1.1584)
27.1, 20.0, 19.84, val tensorWithNegative = DoubleTensor(shapeWithNegative, bufferWithNegative)
23.123, 3.0, 2.0,
3.23, 133.7, 25.3, val shape1 = intArrayOf(4)
100.3, 11.0, 12.012 val buffer1 = doubleArrayOf(1.3348, 1.5889, 1.2005, 1.1584)
) val tensor1 = DoubleTensor(shape1, buffer1)
val tensor = DoubleTensor(shape, buffer)
val shape2 = intArrayOf(2, 2)
val buffer2 = doubleArrayOf(1.0, 9.456, 3.0, 4.0)
val tensor2 = DoubleTensor(shape2, buffer2)
val shape3 = intArrayOf(2, 3, 2)
val buffer3 = doubleArrayOf(1.0, 9.456, 7.0, 2.123, 1.0, 9.456, 30.8888, 6.0, 1.0, 9.456, 3.0, 4.99)
val tensor3 = DoubleTensor(shape3, buffer3)
val shape4 = intArrayOf(2, 1, 3, 2)
val buffer4 = doubleArrayOf(27.1, 20.0, 19.84, 23.123, 3.0, 2.0, 3.23, 133.7, 25.3, 100.3, 11.0, 12.012)
val tensor4 = DoubleTensor(shape4, buffer4)
val bufferWithNegativeMod1 = bufferWithNegative.map { x -> x % 1 }.toDoubleArray()
val tensorWithNegativeMod1 = DoubleTensor(shapeWithNegative, bufferWithNegativeMod1)
val buffer1Mod1 = buffer1.map { x -> x % 1 }.toDoubleArray()
val tensor1Mod1 = DoubleTensor(shape1, buffer1Mod1)
val buffer2Mod1 = buffer2.map { x -> x % 1 }.toDoubleArray()
val tensor2Mod1 = DoubleTensor(shape2, buffer2Mod1)
val buffer3Mod1 = buffer3.map { x -> x % 1 }.toDoubleArray()
val tensor3Mod1 = DoubleTensor(shape3, buffer3Mod1)
val buffer4Mod1 = buffer4.map { x -> x % 1 }.toDoubleArray()
val tensor4Mod1 = DoubleTensor(shape4, buffer4Mod1)
fun DoubleArray.fmap(transform: (Double) -> Double): DoubleArray { fun DoubleArray.fmap(transform: (Double) -> Double): DoubleArray {
return this.map(transform).toDoubleArray() return this.map(transform).toDoubleArray()
} }
fun expectedTensor(transform: (Double) -> Double): DoubleTensor { fun expectedTensorWithNegative(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape, buffer.fmap(transform)) return DoubleTensor(shapeWithNegative, bufferWithNegative.fmap(transform))
}
fun expectedTensor1(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape1, buffer1.fmap(transform))
}
fun expectedTensor2(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape2, buffer2.fmap(transform))
}
fun expectedTensor3(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape3, buffer3.fmap(transform))
}
fun expectedTensor4(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape4, buffer4.fmap(transform))
}
fun expectedTensorWithNegativeMod1(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shapeWithNegative, bufferWithNegativeMod1.fmap(transform))
}
fun expectedTensor1Mod1(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape1, buffer1Mod1.fmap(transform))
}
fun expectedTensor2Mod1(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape2, buffer2Mod1.fmap(transform))
}
fun expectedTensor3Mod1(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape3, buffer3Mod1.fmap(transform))
}
fun expectedTensor4Mod1(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape4, buffer4Mod1.fmap(transform))
} }
@Test @Test
fun testExp() = DoubleTensorAlgebra { fun testExp() = DoubleTensorAlgebra {
assertTrue { tensor.exp() eq expectedTensor(::exp) } assertTrue { tensorWithNegative.exp() eq expectedTensorWithNegative(::exp) }
assertTrue { tensor1.exp() eq expectedTensor1(::exp) }
assertTrue { tensor2.exp() eq expectedTensor2(::exp) }
assertTrue { tensor3.exp() eq expectedTensor3(::exp) }
assertTrue { tensor4.exp() eq expectedTensor4(::exp) }
} }
@Test @Test
fun testLog() = DoubleTensorAlgebra { fun testLog() = DoubleTensorAlgebra {
assertTrue { tensor.ln() eq expectedTensor(::ln) } assertTrue { tensor1.ln() eq expectedTensor1(::ln) }
assertTrue { tensor2.ln() eq expectedTensor2(::ln) }
assertTrue { tensor3.ln() eq expectedTensor3(::ln) }
assertTrue { tensor4.ln() eq expectedTensor4(::ln) }
} }
@Test @Test
fun testSqrt() = DoubleTensorAlgebra { fun testSqrt() = DoubleTensorAlgebra {
assertTrue { tensor.sqrt() eq expectedTensor(::sqrt) } assertTrue { tensor1.sqrt() eq expectedTensor1(::sqrt) }
assertTrue { tensor2.sqrt() eq expectedTensor2(::sqrt) }
assertTrue { tensor3.sqrt() eq expectedTensor3(::sqrt) }
assertTrue { tensor4.sqrt() eq expectedTensor4(::sqrt) }
} }
@Test @Test
fun testCos() = DoubleTensorAlgebra { fun testCos() = DoubleTensorAlgebra {
assertTrue { tensor.cos() eq expectedTensor(::cos) } assertTrue { tensorWithNegative.cos() eq expectedTensorWithNegative(::cos) }
assertTrue { tensor1.cos() eq expectedTensor1(::cos) }
assertTrue { tensor2.cos() eq expectedTensor2(::cos) }
assertTrue { tensor3.cos() eq expectedTensor3(::cos) }
assertTrue { tensor4.cos() eq expectedTensor4(::cos) }
} }
@Test
fun testAcos() = DoubleTensorAlgebra {
assertTrue { tensorWithNegativeMod1.acos() eq expectedTensorWithNegativeMod1(::acos) }
assertTrue { tensor1Mod1.acos() eq expectedTensor1Mod1(::acos) }
assertTrue { tensor2Mod1.acos() eq expectedTensor2Mod1(::acos) }
assertTrue { tensor3Mod1.acos() eq expectedTensor3Mod1(::acos) }
assertTrue { tensor4Mod1.acos() eq expectedTensor4Mod1(::acos) }
}
@Test @Test
fun testCosh() = DoubleTensorAlgebra { fun testCosh() = DoubleTensorAlgebra {
assertTrue { tensor.cosh() eq expectedTensor(::cosh) } assertTrue { tensorWithNegative.cosh() eq expectedTensorWithNegative(::cosh) }
assertTrue { tensor1.cosh() eq expectedTensor1(::cosh) }
assertTrue { tensor2.cosh() eq expectedTensor2(::cosh) }
assertTrue { tensor3.cosh() eq expectedTensor3(::cosh) }
assertTrue { tensor4.cosh() eq expectedTensor4(::cosh) }
} }
@Test @Test
fun testAcosh() = DoubleTensorAlgebra { fun testAcosh() = DoubleTensorAlgebra {
assertTrue { tensor.acosh() eq expectedTensor(::acosh) } assertTrue { tensor1.acosh() eq expectedTensor1(::acosh) }
assertTrue { tensor2.acosh() eq expectedTensor2(::acosh) }
assertTrue { tensor3.acosh() eq expectedTensor3(::acosh) }
assertTrue { tensor4.acosh() eq expectedTensor4(::acosh) }
} }
@Test @Test
fun testSin() = DoubleTensorAlgebra { fun testSin() = DoubleTensorAlgebra {
assertTrue { tensor.sin() eq expectedTensor(::sin) } assertTrue { tensorWithNegative.sin() eq expectedTensorWithNegative(::sin) }
assertTrue { tensor1.sin() eq expectedTensor1(::sin) }
assertTrue { tensor2.sin() eq expectedTensor2(::sin) }
assertTrue { tensor3.sin() eq expectedTensor3(::sin) }
assertTrue { tensor4.sin() eq expectedTensor4(::sin) }
}
@Test
fun testAsin() = DoubleTensorAlgebra {
assertTrue { tensorWithNegativeMod1.asin() eq expectedTensorWithNegativeMod1(::asin) }
assertTrue { tensor1Mod1.asin() eq expectedTensor1Mod1(::asin) }
assertTrue { tensor2Mod1.asin() eq expectedTensor2Mod1(::asin) }
assertTrue { tensor3Mod1.asin() eq expectedTensor3Mod1(::asin) }
assertTrue { tensor4Mod1.asin() eq expectedTensor4Mod1(::asin) }
} }
@Test @Test
fun testSinh() = DoubleTensorAlgebra { fun testSinh() = DoubleTensorAlgebra {
assertTrue { tensor.sinh() eq expectedTensor(::sinh) } assertTrue { tensorWithNegative.sinh() eq expectedTensorWithNegative(::sinh) }
assertTrue { tensor1.sinh() eq expectedTensor1(::sinh) }
assertTrue { tensor2.sinh() eq expectedTensor2(::sinh) }
assertTrue { tensor3.sinh() eq expectedTensor3(::sinh) }
assertTrue { tensor4.sinh() eq expectedTensor4(::sinh) }
} }
@Test @Test
fun testAsinh() = DoubleTensorAlgebra { fun testAsinh() = DoubleTensorAlgebra {
assertTrue { tensor.asinh() eq expectedTensor(::asinh) } assertTrue { tensorWithNegative.asinh() eq expectedTensorWithNegative(::asinh) }
assertTrue { tensor1.asinh() eq expectedTensor1(::asinh) }
assertTrue { tensor2.asinh() eq expectedTensor2(::asinh) }
assertTrue { tensor3.asinh() eq expectedTensor3(::asinh) }
assertTrue { tensor4.asinh() eq expectedTensor4(::asinh) }
} }
@Test @Test
fun testTan() = DoubleTensorAlgebra { fun testTan() = DoubleTensorAlgebra {
assertTrue { tensor.tan() eq expectedTensor(::tan) } assertTrue { tensorWithNegative.tan() eq expectedTensorWithNegative(::tan) }
assertTrue { tensor1.tan() eq expectedTensor1(::tan) }
assertTrue { tensor2.tan() eq expectedTensor2(::tan) }
assertTrue { tensor3.tan() eq expectedTensor3(::tan) }
assertTrue { tensor4.tan() eq expectedTensor4(::tan) }
} }
@Test @Test
fun testAtan() = DoubleTensorAlgebra { fun testAtan() = DoubleTensorAlgebra {
assertTrue { tensor.atan() eq expectedTensor(::atan) } assertTrue { tensorWithNegative.atan() eq expectedTensorWithNegative(::atan) }
assertTrue { tensor1.atan() eq expectedTensor1(::atan) }
assertTrue { tensor2.atan() eq expectedTensor2(::atan) }
assertTrue { tensor3.atan() eq expectedTensor3(::atan) }
assertTrue { tensor4.atan() eq expectedTensor4(::atan) }
} }
@Test @Test
fun testTanh() = DoubleTensorAlgebra { fun testTanh() = DoubleTensorAlgebra {
assertTrue { tensor.tanh() eq expectedTensor(::tanh) } assertTrue { tensorWithNegative.tanh() eq expectedTensorWithNegative(::tanh) }
assertTrue { tensor1.tanh() eq expectedTensor1(::tanh) }
assertTrue { tensor2.tanh() eq expectedTensor2(::tanh) }
assertTrue { tensor3.tanh() eq expectedTensor3(::tanh) }
assertTrue { tensor4.tanh() eq expectedTensor4(::tanh) }
}
@Test
fun testAtanh() = DoubleTensorAlgebra {
assertTrue { tensorWithNegativeMod1.atanh() eq expectedTensorWithNegativeMod1(::atanh) }
assertTrue { tensor1Mod1.atanh() eq expectedTensor1Mod1(::atanh) }
assertTrue { tensor2Mod1.atanh() eq expectedTensor2Mod1(::atanh) }
assertTrue { tensor3Mod1.atanh() eq expectedTensor3Mod1(::atanh) }
assertTrue { tensor4Mod1.atanh() eq expectedTensor4Mod1(::atanh) }
} }
@Test @Test
fun testCeil() = DoubleTensorAlgebra { fun testCeil() = DoubleTensorAlgebra {
assertTrue { tensor.ceil() eq expectedTensor(::ceil) } assertTrue { tensorWithNegative.ceil() eq expectedTensorWithNegative(::ceil) }
assertTrue { tensor1.ceil() eq expectedTensor1(::ceil) }
assertTrue { tensor2.ceil() eq expectedTensor2(::ceil) }
assertTrue { tensor3.ceil() eq expectedTensor3(::ceil) }
assertTrue { tensor4.ceil() eq expectedTensor4(::ceil) }
} }
@Test @Test
fun testFloor() = DoubleTensorAlgebra { fun testFloor() = DoubleTensorAlgebra {
assertTrue { tensor.floor() eq expectedTensor(::floor) } assertTrue { tensorWithNegative.floor() eq expectedTensorWithNegative(::floor) }
assertTrue { tensor1.floor() eq expectedTensor1(::floor) }
assertTrue { tensor2.floor() eq expectedTensor2(::floor) }
assertTrue { tensor3.floor() eq expectedTensor3(::floor) }
assertTrue { tensor4.floor() eq expectedTensor4(::floor) }
} }
val shape2 = intArrayOf(2, 2) val shape5 = intArrayOf(2, 2)
val buffer2 = doubleArrayOf( val buffer5 = doubleArrayOf(
1.0, 2.0, 1.0, 2.0,
-3.0, 4.0 -3.0, 4.0
) )
val tensor2 = DoubleTensor(shape2, buffer2) val tensor5 = DoubleTensor(shape5, buffer5)
@Test @Test
fun testMin() = DoubleTensorAlgebra { fun testMin() = DoubleTensorAlgebra {
assertTrue { tensor2.min() == -3.0 } assertTrue { tensor5.min() == -3.0 }
assertTrue { tensor2.min(0, true) eq fromArray( assertTrue { tensor5.min(0, true) eq fromArray(
intArrayOf(1, 2), intArrayOf(1, 2),
doubleArrayOf(-3.0, 2.0) doubleArrayOf(-3.0, 2.0)
)} )}
assertTrue { tensor2.min(1, false) eq fromArray( assertTrue { tensor5.min(1, false) eq fromArray(
intArrayOf(2), intArrayOf(2),
doubleArrayOf(1.0, -3.0) doubleArrayOf(1.0, -3.0)
)} )}
@ -123,12 +263,12 @@ internal class TestDoubleAnalyticTensorAlgebra {
@Test @Test
fun testMax() = DoubleTensorAlgebra { fun testMax() = DoubleTensorAlgebra {
assertTrue { tensor2.max() == 4.0 } assertTrue { tensor5.max() == 4.0 }
assertTrue { tensor2.max(0, true) eq fromArray( assertTrue { tensor5.max(0, true) eq fromArray(
intArrayOf(1, 2), intArrayOf(1, 2),
doubleArrayOf(1.0, 4.0) doubleArrayOf(1.0, 4.0)
)} )}
assertTrue { tensor2.max(1, false) eq fromArray( assertTrue { tensor5.max(1, false) eq fromArray(
intArrayOf(2), intArrayOf(2),
doubleArrayOf(2.0, 4.0) doubleArrayOf(2.0, 4.0)
)} )}
@ -136,12 +276,12 @@ internal class TestDoubleAnalyticTensorAlgebra {
@Test @Test
fun testSum() = DoubleTensorAlgebra { fun testSum() = DoubleTensorAlgebra {
assertTrue { tensor2.sum() == 4.0 } assertTrue { tensor5.sum() == 4.0 }
assertTrue { tensor2.sum(0, true) eq fromArray( assertTrue { tensor5.sum(0, true) eq fromArray(
intArrayOf(1, 2), intArrayOf(1, 2),
doubleArrayOf(-2.0, 6.0) doubleArrayOf(-2.0, 6.0)
)} )}
assertTrue { tensor2.sum(1, false) eq fromArray( assertTrue { tensor5.sum(1, false) eq fromArray(
intArrayOf(2), intArrayOf(2),
doubleArrayOf(3.0, 1.0) doubleArrayOf(3.0, 1.0)
)} )}
@ -149,15 +289,24 @@ internal class TestDoubleAnalyticTensorAlgebra {
@Test @Test
fun testMean() = DoubleTensorAlgebra { fun testMean() = DoubleTensorAlgebra {
assertTrue { tensor2.mean() == 1.0 } assertTrue { tensor5.mean() == 1.0 }
assertTrue { tensor2.mean(0, true) eq fromArray( assertTrue { tensor5.mean(0, true) eq fromArray(
intArrayOf(1, 2), intArrayOf(1, 2),
doubleArrayOf(-1.0, 3.0) doubleArrayOf(-1.0, 3.0)
)} )}
assertTrue { tensor2.mean(1, false) eq fromArray( assertTrue { tensor5.mean(1, false) eq fromArray(
intArrayOf(2), intArrayOf(2),
doubleArrayOf(1.5, 0.5) doubleArrayOf(1.5, 0.5)
)} )}
} }
@Test
fun testStd() = DoubleTensorAlgebra {
assertTrue { floor(tensor5.std() * 10000 ) / 10000 == 2.9439 }
}
@Test
fun testVariance() = DoubleTensorAlgebra {
assertTrue { floor(tensor5.variance() * 10000 ) / 10000 == 8.6666 }
}
} }

View File

@ -156,23 +156,12 @@ internal class TestDoubleLinearOpsTensorAlgebra {
val res = svd1d(tensor2) val res = svd1d(tensor2)
val resBuffer = res.mutableBuffer
val resStart = res.bufferStart
assertTrue(res.shape contentEquals intArrayOf(2)) assertTrue(res.shape contentEquals intArrayOf(2))
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart]) - 0.386) < 0.01 } assertTrue { abs(abs(resBuffer[resStart]) - 0.386) < 0.01 }
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart + 1]) - 0.922) < 0.01 } assertTrue { abs(abs(resBuffer[resStart + 1]) - 0.922) < 0.01 }
}
@Test
fun testSVD() = DoubleTensorAlgebra{
testSVDFor(fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)))
testSVDFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0)))
}
@Test
fun testBatchedSVD() = DoubleTensorAlgebra {
val tensor = randomNormal(intArrayOf(2, 5, 3), 0)
val (tensorU, tensorS, tensorV) = tensor.svd()
val tensorSVD = tensorU dot (diagonalEmbedding(tensorS) dot tensorV.transpose())
assertTrue(tensor.eq(tensorSVD))
} }
@Test @Test
@ -184,13 +173,130 @@ internal class TestDoubleLinearOpsTensorAlgebra {
assertTrue(tensorSigma.eq(tensorSigmaCalc)) assertTrue(tensorSigma.eq(tensorSigmaCalc))
} }
@Test
fun testSVD() = DoubleTensorAlgebra{
testSVDFor(fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)))
testSVDFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0)))
val buffer1 = doubleArrayOf(
1.000000, 2.000000, 3.000000,
2.000000, 3.000000, 4.000000,
3.000000, 4.000000, 5.000000,
4.000000, 5.000000, 6.000000,
5.000000, 6.000000, 9.000000
)
testSVDFor(fromArray(intArrayOf(5, 3), buffer1))
val buffer2 = doubleArrayOf(
1.0, 2.0, 3.0, 2.0, 3.0,
4.0, 3.0, 4.0, 5.0, 4.0,
5.0, 6.0, 5.0, 6.0, 7.0
)
testSVDFor(fromArray(intArrayOf(3, 5), buffer2))
}
@Test
fun testBatchedSVD() = DoubleTensorAlgebra{
val tensor1 = randomNormal(intArrayOf(2, 5, 3), 0)
testSVDFor(tensor1)
val tensor2 = DoubleTensorAlgebra.randomNormal(intArrayOf(30, 30, 30), 0)
testSVDFor(tensor2)
}
@Test
fun testSVDGolubKahan() = DoubleTensorAlgebra{
testSVDGolubKahanFor(fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)))
testSVDGolubKahanFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0)))
val buffer1 = doubleArrayOf(
1.000000, 2.000000, 3.000000,
2.000000, 3.000000, 4.000000,
3.000000, 4.000000, 5.000000,
4.000000, 5.000000, 6.000000,
5.000000, 6.000000, 9.000000
)
testSVDGolubKahanFor(fromArray(intArrayOf(5, 3), buffer1))
val buffer2 = doubleArrayOf(
1.0, 2.0, 3.0, 2.0, 3.0,
4.0, 3.0, 4.0, 5.0, 4.0,
5.0, 6.0, 5.0, 6.0, 7.0
)
testSVDGolubKahanFor(fromArray(intArrayOf(3, 5), buffer2))
}
@Test
fun testBatchedSVDGolubKahan() = DoubleTensorAlgebra{
val tensor1 = randomNormal(intArrayOf(2, 5, 3), 0)
testSVDGolubKahanFor(tensor1)
val tensor2 = DoubleTensorAlgebra.randomNormal(intArrayOf(30, 30, 30), 0)
testSVDGolubKahanFor(tensor2)
}
@Test
fun testSVDPowerMethod() = DoubleTensorAlgebra{
testSVDPowerMethodFor(fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)))
testSVDPowerMethodFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0)))
val buffer1 = doubleArrayOf(
1.000000, 2.000000, 3.000000,
2.000000, 3.000000, 4.000000,
3.000000, 4.000000, 5.000000,
4.000000, 5.000000, 6.000000,
5.000000, 6.000000, 9.000000
)
testSVDPowerMethodFor(fromArray(intArrayOf(5, 3), buffer1))
val buffer2 = doubleArrayOf(
1.0, 2.0, 3.0, 2.0, 3.0,
4.0, 3.0, 4.0, 5.0, 4.0,
5.0, 6.0, 5.0, 6.0, 7.0
)
testSVDPowerMethodFor(fromArray(intArrayOf(3, 5), buffer2))
}
@Test
fun testBatchedSVDPowerMethod() = DoubleTensorAlgebra {
val tensor1 = randomNormal(intArrayOf(2, 5, 3), 0)
testSVDPowerMethodFor(tensor1)
val tensor2 = DoubleTensorAlgebra.randomNormal(intArrayOf(30, 30, 30), 0)
testSVDPowerMethodFor(tensor2)
}
// @Test
// fun testSVDPowerMethodError() = DoubleTensorAlgebra{
// val buffer = doubleArrayOf(
// 1.000000, 2.000000, 3.000000,
// 2.000000, 3.000000, 4.000000,
// 3.000000, 4.000000, 5.000000,
// 4.000000, 5.000000, 6.000000,
// 5.000000, 6.000000, 7.000000
// )
// testSVDPowerMethodFor(fromArray(intArrayOf(5, 3), buffer))
// }
} }
private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor) {
private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor, epsilon: Double = 1e-10) {
val svd = tensor.svd() val svd = tensor.svd()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensor.eq(tensorSVD))
}
private fun DoubleTensorAlgebra.testSVDGolubKahanFor(tensor: DoubleTensor, epsilon: Double = 1e-10) {
val svd = tensor.svdGolubKahan()
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
)
assertTrue(tensor.eq(tensorSVD, epsilon))
}
private fun DoubleTensorAlgebra.testSVDPowerMethodFor(tensor: DoubleTensor, epsilon: Double = 1e-10) {
val svd = tensor.svdPowerMethod()
val tensorSVD = svd.first val tensorSVD = svd.first
.dot( .dot(
diagonalEmbedding(svd.second) diagonalEmbedding(svd.second)

View File

@ -23,6 +23,44 @@ import kotlin.test.assertTrue
internal class TestDoubleTensor { internal class TestDoubleTensor {
@Test
fun testFullLike() = DoubleTensorAlgebra {
val shape = intArrayOf(2, 3)
val buffer = doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
val tensor = DoubleTensor(shape, buffer)
val value = 12.5
assertTrue { tensor.fullLike(value) eq DoubleTensor(shape, buffer.map { value }.toDoubleArray() ) }
}
@Test
fun testOnesLike() = DoubleTensorAlgebra {
val shape = intArrayOf(2, 3)
val buffer = doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
val tensor = DoubleTensor(shape, buffer)
assertTrue { tensor.onesLike() eq DoubleTensor(shape, buffer.map { 1.0 }.toDoubleArray() ) }
}
@Test
fun testRowsByIndices() = DoubleTensorAlgebra {
val shape = intArrayOf(2, 2)
val buffer = doubleArrayOf(1.0, 2.0, -3.0, 4.0)
val tensor = fromArray(shape, buffer)
assertTrue { tensor.rowsByIndices(intArrayOf(0)) eq DoubleTensor(intArrayOf(1, 2), doubleArrayOf(1.0, 2.0)) }
assertTrue { tensor.rowsByIndices(intArrayOf(0, 1)) eq tensor }
}
@Test
fun testTimes() = DoubleTensorAlgebra {
val shape = intArrayOf(2, 2)
val buffer = doubleArrayOf(1.0, 2.0, -3.0, 4.0)
val tensor = DoubleTensor(shape, buffer)
val value = 3
assertTrue { tensor.times(value).toBufferedTensor() eq DoubleTensor(shape, buffer.map { x -> 3 * x }.toDoubleArray()) }
val buffer2 = doubleArrayOf(7.0, -8.0, -5.0, 2.0)
val tensor2 = DoubleTensor(shape, buffer2)
assertTrue {tensor.times(tensor2).toBufferedTensor() eq DoubleTensor(shape, doubleArrayOf(7.0, -16.0, 15.0, 8.0)) }
}
@Test @Test
fun testValue() = DoubleTensorAlgebra { fun testValue() = DoubleTensorAlgebra {
val value = 12.5 val value = 12.5

View File

@ -132,6 +132,27 @@ internal class TestDoubleTensorAlgebra {
468.0, 501.0, 534.0, 594.0, 636.0, 678.0, 720.0, 771.0, 822.0 468.0, 501.0, 534.0, 594.0, 636.0, 678.0, 720.0, 771.0, 822.0
)) ))
assertTrue(res45.shape contentEquals intArrayOf(2, 3, 3)) assertTrue(res45.shape contentEquals intArrayOf(2, 3, 3))
val oneDimTensor1 = fromArray(intArrayOf(3), doubleArrayOf(1.0, 2.0, 3.0))
val oneDimTensor2 = fromArray(intArrayOf(3), doubleArrayOf(4.0, 5.0, 6.0))
val resOneDimTensors = oneDimTensor1.dot(oneDimTensor2)
assertTrue(resOneDimTensors.mutableBuffer.array() contentEquals doubleArrayOf(32.0))
assertTrue(resOneDimTensors.shape contentEquals intArrayOf(1))
val twoDimTensor1 = fromArray(intArrayOf(2, 2), doubleArrayOf(1.0, 2.0, 3.0, 4.0))
val twoDimTensor2 = fromArray(intArrayOf(2, 2), doubleArrayOf(5.0, 6.0, 7.0, 8.0))
val resTwoDimTensors = twoDimTensor1.dot(twoDimTensor2)
assertTrue(resTwoDimTensors.mutableBuffer.array() contentEquals doubleArrayOf(19.0, 22.0, 43.0, 50.0))
assertTrue(resTwoDimTensors.shape contentEquals intArrayOf(2, 2))
val oneDimTensor3 = fromArray(intArrayOf(2), doubleArrayOf(1.0, 2.0))
val resOneDimTensorOnTwoDimTensor = oneDimTensor3.dot(twoDimTensor1)
assertTrue(resOneDimTensorOnTwoDimTensor.mutableBuffer.array() contentEquals doubleArrayOf(7.0, 10.0))
assertTrue(resOneDimTensorOnTwoDimTensor.shape contentEquals intArrayOf(2))
val resTwoDimTensorOnOneDimTensor = twoDimTensor1.dot(oneDimTensor3)
assertTrue(resTwoDimTensorOnOneDimTensor.mutableBuffer.array() contentEquals doubleArrayOf(5.0, 11.0))
assertTrue(resTwoDimTensorOnOneDimTensor.shape contentEquals intArrayOf(2))
} }
@Test @Test