Golub-Kahan SVD algorithm for KMP tensors #499
@ -84,6 +84,11 @@ benchmark {
|
||||
iterationTimeUnit = "ms"
|
||||
}
|
||||
|
||||
configurations.register("svd") {
|
||||
commonConfiguration()
|
||||
include("SVDBenchmark")
|
||||
}
|
||||
|
||||
configurations.register("buffer") {
|
||||
commonConfiguration()
|
||||
include("BufferBenchmark")
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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) {
|
||||
blackhole.consume(
|
||||
tensorSmall.svdPowerMethod()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdPowerMethodMedium(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorMedium.svdPowerMethod()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdPowerMethodLarge(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorLarge.svdPowerMethod()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdPowerMethodVeryLarge(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorVeryLarge.svdPowerMethod()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdGolubKahanSmall(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorSmall.svdGolubKahan()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdGolubKahanMedium(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorMedium.svdGolubKahan()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdGolubKahanLarge(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorLarge.svdGolubKahan()
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun svdGolubKahanVeryLarge(blackhole: Blackhole) {
|
||||
blackhole.consume(
|
||||
tensorVeryLarge.svdGolubKahan()
|
||||
)
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
val newThis = broadcast[0]
|
||||
val newOther = broadcast[1]
|
||||
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)
|
||||
}
|
||||
@ -35,8 +35,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) {
|
||||
val newOther = broadcastTo(arg.tensor, tensor.shape)
|
||||
for (i in 0 until tensor.indices.linearSize) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] +=
|
||||
newOther.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] +=
|
||||
newOther.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
val newThis = broadcast[0]
|
||||
val newOther = broadcast[1]
|
||||
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)
|
||||
}
|
||||
@ -53,8 +53,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) {
|
||||
val newOther = broadcastTo(arg.tensor, tensor.shape)
|
||||
for (i in 0 until tensor.indices.linearSize) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] -=
|
||||
newOther.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] -=
|
||||
newOther.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,8 +63,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
val newThis = broadcast[0]
|
||||
val newOther = broadcast[1]
|
||||
val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
|
||||
newThis.mutableBuffer.array()[newThis.bufferStart + i] *
|
||||
newOther.mutableBuffer.array()[newOther.bufferStart + i]
|
||||
newThis.mutableBuffer[newThis.bufferStart + i] *
|
||||
newOther.mutableBuffer[newOther.bufferStart + i]
|
||||
}
|
||||
return DoubleTensor(newThis.shape, resBuffer)
|
||||
}
|
||||
@ -72,8 +72,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) {
|
||||
val newOther = broadcastTo(arg.tensor, tensor.shape)
|
||||
for (i in 0 until tensor.indices.linearSize) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] *=
|
||||
newOther.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] *=
|
||||
newOther.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,8 +82,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
val newThis = broadcast[0]
|
||||
val newOther = broadcast[1]
|
||||
val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
|
||||
newThis.mutableBuffer.array()[newOther.bufferStart + i] /
|
||||
newOther.mutableBuffer.array()[newOther.bufferStart + i]
|
||||
newThis.mutableBuffer[newOther.bufferStart + i] /
|
||||
newOther.mutableBuffer[newOther.bufferStart + i]
|
||||
}
|
||||
return DoubleTensor(newThis.shape, resBuffer)
|
||||
}
|
||||
@ -91,8 +91,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
||||
override fun Tensor<Double>.divAssign(arg: StructureND<Double>) {
|
||||
val newOther = broadcastTo(arg.tensor, tensor.shape)
|
||||
for (i in 0 until tensor.indices.linearSize) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] /=
|
||||
newOther.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] /=
|
||||
newOther.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ public open class DoubleTensorAlgebra :
|
||||
}
|
||||
|
||||
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()
|
||||
?: 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 {
|
||||
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)
|
||||
}
|
||||
@ -218,35 +218,35 @@ public open class DoubleTensorAlgebra :
|
||||
override fun StructureND<Double>.plus(arg: StructureND<Double>): DoubleTensor {
|
||||
checkShapesCompatible(tensor, arg.tensor)
|
||||
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)
|
||||
}
|
||||
|
||||
override fun Tensor<Double>.plusAssign(value: Double) {
|
||||
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>) {
|
||||
checkShapesCompatible(tensor, arg.tensor)
|
||||
for (i in 0 until tensor.numElements) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] +=
|
||||
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] +=
|
||||
arg.tensor.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
override fun Double.minus(arg: StructureND<Double>): DoubleTensor {
|
||||
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)
|
||||
}
|
||||
|
||||
override fun StructureND<Double>.minus(arg: Double): DoubleTensor {
|
||||
val resBuffer = DoubleArray(tensor.numElements) { i ->
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] - arg
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] - arg
|
||||
}
|
||||
return DoubleTensor(tensor.shape, resBuffer)
|
||||
}
|
||||
@ -254,28 +254,28 @@ public open class DoubleTensorAlgebra :
|
||||
override fun StructureND<Double>.minus(arg: StructureND<Double>): DoubleTensor {
|
||||
checkShapesCompatible(tensor, arg)
|
||||
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)
|
||||
}
|
||||
|
||||
override fun Tensor<Double>.minusAssign(value: Double) {
|
||||
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>) {
|
||||
checkShapesCompatible(tensor, arg)
|
||||
for (i in 0 until tensor.numElements) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] -=
|
||||
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] -=
|
||||
arg.tensor.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
override fun Double.times(arg: StructureND<Double>): DoubleTensor {
|
||||
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)
|
||||
}
|
||||
@ -285,36 +285,36 @@ public open class DoubleTensorAlgebra :
|
||||
override fun StructureND<Double>.times(arg: StructureND<Double>): DoubleTensor {
|
||||
checkShapesCompatible(tensor, arg)
|
||||
val resBuffer = DoubleArray(tensor.numElements) { i ->
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] *
|
||||
arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] *
|
||||
arg.tensor.mutableBuffer[arg.tensor.bufferStart + i]
|
||||
}
|
||||
return DoubleTensor(tensor.shape, resBuffer)
|
||||
}
|
||||
|
||||
override fun Tensor<Double>.timesAssign(value: Double) {
|
||||
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>) {
|
||||
checkShapesCompatible(tensor, arg)
|
||||
for (i in 0 until tensor.numElements) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] *=
|
||||
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] *=
|
||||
arg.tensor.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
override fun Double.div(arg: StructureND<Double>): DoubleTensor {
|
||||
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)
|
||||
}
|
||||
|
||||
override fun StructureND<Double>.div(arg: Double): DoubleTensor {
|
||||
val resBuffer = DoubleArray(tensor.numElements) { i ->
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] / arg
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] / arg
|
||||
}
|
||||
return DoubleTensor(shape, resBuffer)
|
||||
}
|
||||
@ -322,29 +322,29 @@ public open class DoubleTensorAlgebra :
|
||||
override fun StructureND<Double>.div(arg: StructureND<Double>): DoubleTensor {
|
||||
checkShapesCompatible(tensor, arg)
|
||||
val resBuffer = DoubleArray(tensor.numElements) { i ->
|
||||
tensor.mutableBuffer.array()[arg.tensor.bufferStart + i] /
|
||||
arg.tensor.mutableBuffer.array()[arg.tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[arg.tensor.bufferStart + i] /
|
||||
arg.tensor.mutableBuffer[arg.tensor.bufferStart + i]
|
||||
}
|
||||
return DoubleTensor(tensor.shape, resBuffer)
|
||||
}
|
||||
|
||||
override fun Tensor<Double>.divAssign(value: Double) {
|
||||
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>) {
|
||||
checkShapesCompatible(tensor, arg)
|
||||
for (i in 0 until tensor.numElements) {
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i] /=
|
||||
arg.tensor.mutableBuffer.array()[tensor.bufferStart + i]
|
||||
tensor.mutableBuffer[tensor.bufferStart + i] /=
|
||||
arg.tensor.mutableBuffer[tensor.bufferStart + i]
|
||||
}
|
||||
}
|
||||
|
||||
override fun StructureND<Double>.unaryMinus(): DoubleTensor {
|
||||
val resBuffer = DoubleArray(tensor.numElements) { i ->
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + i].unaryMinus()
|
||||
tensor.mutableBuffer[tensor.bufferStart + i].unaryMinus()
|
||||
}
|
||||
return DoubleTensor(tensor.shape, resBuffer)
|
||||
}
|
||||
@ -367,8 +367,8 @@ public open class DoubleTensorAlgebra :
|
||||
newMultiIndex[ii] = newMultiIndex[jj].also { newMultiIndex[jj] = newMultiIndex[ii] }
|
||||
|
||||
val linearIndex = resTensor.indices.offset(newMultiIndex)
|
||||
resTensor.mutableBuffer.array()[linearIndex] =
|
||||
tensor.mutableBuffer.array()[tensor.bufferStart + offset]
|
||||
resTensor.mutableBuffer[linearIndex] =
|
||||
tensor.mutableBuffer[tensor.bufferStart + offset]
|
||||
}
|
||||
return resTensor
|
||||
}
|
||||
@ -833,8 +833,38 @@ public open class DoubleTensorAlgebra :
|
||||
return qTensor to rTensor
|
||||
}
|
||||
|
||||
override fun StructureND<Double>.svd(): Triple<DoubleTensor, DoubleTensor, DoubleTensor> =
|
||||
svd(epsilon = 1e-10)
|
||||
override fun StructureND<Double>.svd(): Triple<DoubleTensor, DoubleTensor, DoubleTensor> {
|
||||
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.
|
||||
@ -849,7 +879,7 @@ public open class DoubleTensorAlgebra :
|
||||
* i.e., the precision with which the cosine approaches 1 in an iterative algorithm.
|
||||
* @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 commonShape = tensor.shape.sliceArray(0 until size - 2)
|
||||
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)
|
||||
.toDoubleArray()
|
||||
)
|
||||
svdHelper(curMatrix, usv, m, n, epsilon)
|
||||
svdPowerMethodHelper(curMatrix, usv, m, n, epsilon)
|
||||
}
|
||||
|
||||
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 utv = u.transpose() dot v
|
||||
val n = s.shape.last()
|
||||
@ -928,18 +958,21 @@ public open class DoubleTensorAlgebra :
|
||||
|
||||
var eigenvalueStart = 0
|
||||
var eigenvectorStart = 0
|
||||
val eigenvaluesBuffer = eigenvalues.mutableBuffer
|
||||
val eigenvectorsBuffer = eigenvectors.mutableBuffer
|
||||
|
||||
for (matrix in tensor.matrixSequence()) {
|
||||
val matrix2D = matrix.as2D()
|
||||
val (d, v) = matrix2D.jacobiHelper(maxIteration, epsilon)
|
||||
|
||||
for (i in 0 until matrix2D.rowNum) {
|
||||
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) {
|
||||
eigenvalues.mutableBuffer.array()[eigenvalueStart + i] = d[i]
|
||||
eigenvaluesBuffer[eigenvalueStart + i] = d[i]
|
||||
}
|
||||
|
||||
eigenvalueStart += this.shape.last()
|
||||
@ -962,11 +995,11 @@ public open class DoubleTensorAlgebra :
|
||||
|
||||
// assume that buffered tensor is square matrix
|
||||
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) {
|
||||
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 {
|
||||
|
@ -17,6 +17,7 @@ import space.kscience.kmath.tensors.core.DoubleTensor
|
||||
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra
|
||||
import space.kscience.kmath.tensors.core.IntTensor
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
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,
|
||||
USV: Triple<BufferedTensor<Double>, BufferedTensor<Double>, BufferedTensor<Double>>,
|
||||
m: Int, n: Int, epsilon: Double,
|
||||
@ -307,6 +308,14 @@ internal fun DoubleTensorAlgebra.svdHelper(
|
||||
val res = ArrayList<Triple<Double, DoubleTensor, DoubleTensor>>(0)
|
||||
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)) {
|
||||
var a = matrix.copy()
|
||||
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 vBuffer = res.map { it.third }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
|
||||
for (i in uBuffer.indices) {
|
||||
matrixU.mutableBuffer.array()[matrixU.bufferStart + i] = uBuffer[i]
|
||||
matrixUBuffer[matrixUStart + i] = uBuffer[i]
|
||||
}
|
||||
for (i in s.indices) {
|
||||
matrixS.mutableBuffer.array()[matrixS.bufferStart + i] = s[i]
|
||||
matrixSBuffer[matrixSStart + i] = s[i]
|
||||
}
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,110 +12,250 @@ import kotlin.test.assertTrue
|
||||
|
||||
internal class TestDoubleAnalyticTensorAlgebra {
|
||||
|
||||
val shape = intArrayOf(2, 1, 3, 2)
|
||||
val buffer = doubleArrayOf(
|
||||
27.1, 20.0, 19.84,
|
||||
23.123, 3.0, 2.0,
|
||||
val shapeWithNegative = intArrayOf(4)
|
||||
val bufferWithNegative = doubleArrayOf(9.3348, -7.5889, -1.2005, 1.1584)
|
||||
val tensorWithNegative = DoubleTensor(shapeWithNegative, bufferWithNegative)
|
||||
|
||||
3.23, 133.7, 25.3,
|
||||
100.3, 11.0, 12.012
|
||||
)
|
||||
val tensor = DoubleTensor(shape, buffer)
|
||||
val shape1 = intArrayOf(4)
|
||||
val buffer1 = doubleArrayOf(1.3348, 1.5889, 1.2005, 1.1584)
|
||||
val tensor1 = DoubleTensor(shape1, buffer1)
|
||||
|
||||
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 {
|
||||
return this.map(transform).toDoubleArray()
|
||||
}
|
||||
|
||||
fun expectedTensor(transform: (Double) -> Double): DoubleTensor {
|
||||
return DoubleTensor(shape, buffer.fmap(transform))
|
||||
fun expectedTensorWithNegative(transform: (Double) -> Double): DoubleTensor {
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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 buffer2 = doubleArrayOf(
|
||||
val shape5 = intArrayOf(2, 2)
|
||||
val buffer5 = doubleArrayOf(
|
||||
1.0, 2.0,
|
||||
-3.0, 4.0
|
||||
)
|
||||
val tensor2 = DoubleTensor(shape2, buffer2)
|
||||
val tensor5 = DoubleTensor(shape5, buffer5)
|
||||
|
||||
@Test
|
||||
fun testMin() = DoubleTensorAlgebra {
|
||||
assertTrue { tensor2.min() == -3.0 }
|
||||
assertTrue { tensor2.min(0, true) eq fromArray(
|
||||
assertTrue { tensor5.min() == -3.0 }
|
||||
assertTrue { tensor5.min(0, true) eq fromArray(
|
||||
intArrayOf(1, 2),
|
||||
doubleArrayOf(-3.0, 2.0)
|
||||
)}
|
||||
assertTrue { tensor2.min(1, false) eq fromArray(
|
||||
assertTrue { tensor5.min(1, false) eq fromArray(
|
||||
intArrayOf(2),
|
||||
doubleArrayOf(1.0, -3.0)
|
||||
)}
|
||||
@ -123,12 +263,12 @@ internal class TestDoubleAnalyticTensorAlgebra {
|
||||
|
||||
@Test
|
||||
fun testMax() = DoubleTensorAlgebra {
|
||||
assertTrue { tensor2.max() == 4.0 }
|
||||
assertTrue { tensor2.max(0, true) eq fromArray(
|
||||
assertTrue { tensor5.max() == 4.0 }
|
||||
assertTrue { tensor5.max(0, true) eq fromArray(
|
||||
intArrayOf(1, 2),
|
||||
doubleArrayOf(1.0, 4.0)
|
||||
)}
|
||||
assertTrue { tensor2.max(1, false) eq fromArray(
|
||||
assertTrue { tensor5.max(1, false) eq fromArray(
|
||||
intArrayOf(2),
|
||||
doubleArrayOf(2.0, 4.0)
|
||||
)}
|
||||
@ -136,12 +276,12 @@ internal class TestDoubleAnalyticTensorAlgebra {
|
||||
|
||||
@Test
|
||||
fun testSum() = DoubleTensorAlgebra {
|
||||
assertTrue { tensor2.sum() == 4.0 }
|
||||
assertTrue { tensor2.sum(0, true) eq fromArray(
|
||||
assertTrue { tensor5.sum() == 4.0 }
|
||||
assertTrue { tensor5.sum(0, true) eq fromArray(
|
||||
intArrayOf(1, 2),
|
||||
doubleArrayOf(-2.0, 6.0)
|
||||
)}
|
||||
assertTrue { tensor2.sum(1, false) eq fromArray(
|
||||
assertTrue { tensor5.sum(1, false) eq fromArray(
|
||||
intArrayOf(2),
|
||||
doubleArrayOf(3.0, 1.0)
|
||||
)}
|
||||
@ -149,15 +289,24 @@ internal class TestDoubleAnalyticTensorAlgebra {
|
||||
|
||||
@Test
|
||||
fun testMean() = DoubleTensorAlgebra {
|
||||
assertTrue { tensor2.mean() == 1.0 }
|
||||
assertTrue { tensor2.mean(0, true) eq fromArray(
|
||||
assertTrue { tensor5.mean() == 1.0 }
|
||||
assertTrue { tensor5.mean(0, true) eq fromArray(
|
||||
intArrayOf(1, 2),
|
||||
doubleArrayOf(-1.0, 3.0)
|
||||
)}
|
||||
assertTrue { tensor2.mean(1, false) eq fromArray(
|
||||
assertTrue { tensor5.mean(1, false) eq fromArray(
|
||||
intArrayOf(2),
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
@ -156,23 +156,12 @@ internal class TestDoubleLinearOpsTensorAlgebra {
|
||||
|
||||
val res = svd1d(tensor2)
|
||||
|
||||
val resBuffer = res.mutableBuffer
|
||||
val resStart = res.bufferStart
|
||||
|
||||
assertTrue(res.shape contentEquals intArrayOf(2))
|
||||
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart]) - 0.386) < 0.01 }
|
||||
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart + 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))
|
||||
assertTrue { abs(abs(resBuffer[resStart]) - 0.386) < 0.01 }
|
||||
assertTrue { abs(abs(resBuffer[resStart + 1]) - 0.922) < 0.01 }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -184,13 +173,118 @@ internal class TestDoubleLinearOpsTensorAlgebra {
|
||||
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, epsilon: Double = 1e-10) {
|
||||
private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor) {
|
||||
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)
|
||||
@ -199,3 +293,15 @@ private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor, epsilon: Double
|
||||
|
||||
assertTrue(tensor.eq(tensorSVD, epsilon))
|
||||
}
|
||||
|
||||
private fun DoubleTensorAlgebra.testSVDPowerMethodFor(tensor: DoubleTensor, epsilon: Double = 1e-10) {
|
||||
val svd = tensor.svdPowerMethod()
|
||||
|
||||
val tensorSVD = svd.first
|
||||
.dot(
|
||||
diagonalEmbedding(svd.second)
|
||||
.dot(svd.third.transpose())
|
||||
)
|
||||
|
||||
assertTrue(tensor.eq(tensorSVD, epsilon))
|
||||
}
|
@ -23,6 +23,44 @@ import kotlin.test.assertTrue
|
||||
|
||||
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
|
||||
fun testValue() = DoubleTensorAlgebra {
|
||||
val value = 12.5
|
||||
|
@ -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
|
||||
))
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user