Global refactor of tensors
This commit is contained in:
parent
3729faf49b
commit
b5d04ba02c
@ -7,6 +7,7 @@
|
|||||||
- Algebra now has an obligatory `bufferFactory` (#477).
|
- Algebra now has an obligatory `bufferFactory` (#477).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Major refactor of tensors (only minor API changes)
|
||||||
- Kotlin 1.7.20
|
- Kotlin 1.7.20
|
||||||
- `LazyStructure` `deffered` -> `async` to comply with coroutines code style
|
- `LazyStructure` `deffered` -> `async` to comply with coroutines code style
|
||||||
- Default `dot` operation in tensor algebra no longer support broadcasting. Instead `matmul` operation is added to `DoubleTensorAlgebra`.
|
- Default `dot` operation in tensor algebra no longer support broadcasting. Instead `matmul` operation is added to `DoubleTensorAlgebra`.
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import space.kscience.gradle.isInDevelopment
|
||||||
import space.kscience.gradle.useApache2Licence
|
import space.kscience.gradle.useApache2Licence
|
||||||
import space.kscience.gradle.useSPCTeam
|
import space.kscience.gradle.useSPCTeam
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("space.kscience.gradle.project")
|
id("space.kscience.gradle.project")
|
||||||
id("org.jetbrains.kotlinx.kover") version "0.5.0"
|
id("org.jetbrains.kotlinx.kover") version "0.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@ -14,7 +15,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.3.1-dev-3"
|
version = "0.3.1-dev-4"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
@ -75,8 +76,14 @@ ksciencePublish {
|
|||||||
useApache2Licence()
|
useApache2Licence()
|
||||||
useSPCTeam()
|
useSPCTeam()
|
||||||
}
|
}
|
||||||
github("kmath", "SciProgCentre", addToRelease = false)
|
github("kmath", "SciProgCentre")
|
||||||
space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/dev")
|
space(
|
||||||
|
if (isInDevelopment) {
|
||||||
|
"https://maven.pkg.jetbrains.space/mipt-npm/p/sci/dev"
|
||||||
|
} else {
|
||||||
|
"https://maven.pkg.jetbrains.space/mipt-npm/p/sci/release"
|
||||||
|
}
|
||||||
|
)
|
||||||
sonatype()
|
sonatype()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ fun main() {
|
|||||||
// inverse Sigma matrix can be restored from singular values with diagonalEmbedding function
|
// inverse Sigma matrix can be restored from singular values with diagonalEmbedding function
|
||||||
val sigma = diagonalEmbedding(singValues.map{ if (abs(it) < 1e-3) 0.0 else 1.0/it })
|
val sigma = diagonalEmbedding(singValues.map{ if (abs(it) < 1e-3) 0.0 else 1.0/it })
|
||||||
|
|
||||||
val alphaOLS = v dot sigma dot u.transpose() dot y
|
val alphaOLS = v dot sigma dot u.transposed() dot y
|
||||||
println("Estimated alpha:\n" +
|
println("Estimated alpha:\n" +
|
||||||
"$alphaOLS")
|
"$alphaOLS")
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ fun main(): Unit = Double.tensorAlgebra.withBroadcast { // work in context with
|
|||||||
println("y:\n$y")
|
println("y:\n$y")
|
||||||
|
|
||||||
// stack them into single dataset
|
// stack them into single dataset
|
||||||
val dataset = stack(listOf(x, y)).transpose()
|
val dataset = stack(listOf(x, y)).transposed()
|
||||||
|
|
||||||
// normalize both x and y
|
// normalize both x and y
|
||||||
val xMean = x.mean()
|
val xMean = x.mean()
|
||||||
|
@ -75,7 +75,7 @@ fun main() = Double.tensorAlgebra.withBroadcast {// work in context with linear
|
|||||||
|
|
||||||
// solveLT(l, b) function can be easily adapted for upper triangular matrix by the permutation matrix revMat
|
// solveLT(l, b) function can be easily adapted for upper triangular matrix by the permutation matrix revMat
|
||||||
// create it by placing ones on side diagonal
|
// create it by placing ones on side diagonal
|
||||||
val revMat = u.zeroesLike()
|
val revMat = zeroesLike(u)
|
||||||
val n = revMat.shape[0]
|
val n = revMat.shape[0]
|
||||||
for (i in 0 until n) {
|
for (i in 0 until n) {
|
||||||
revMat[intArrayOf(i, n - 1 - i)] = 1.0
|
revMat[intArrayOf(i, n - 1 - i)] = 1.0
|
||||||
|
@ -3,16 +3,14 @@
|
|||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:OptIn(PerformancePitfall::class)
|
|
||||||
|
|
||||||
package space.kscience.kmath.tensors
|
package space.kscience.kmath.tensors
|
||||||
|
|
||||||
import space.kscience.kmath.misc.PerformancePitfall
|
import space.kscience.kmath.operations.asIterable
|
||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra
|
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensor
|
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.copyArray
|
import space.kscience.kmath.tensors.core.toDoubleTensor
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
const val seed = 100500L
|
const val seed = 100500L
|
||||||
@ -82,9 +80,9 @@ class Dense(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun backward(input: DoubleTensor, outputError: DoubleTensor): DoubleTensor = DoubleTensorAlgebra {
|
override fun backward(input: DoubleTensor, outputError: DoubleTensor): DoubleTensor = DoubleTensorAlgebra {
|
||||||
val gradInput = outputError dot weights.transpose()
|
val gradInput = outputError dot weights.transposed()
|
||||||
|
|
||||||
val gradW = input.transpose() dot outputError
|
val gradW = input.transposed() dot outputError
|
||||||
val gradBias = outputError.mean(dim = 0, keepDim = false) * input.shape[0].toDouble()
|
val gradBias = outputError.mean(dim = 0, keepDim = false) * input.shape[0].toDouble()
|
||||||
|
|
||||||
weights -= learningRate * gradW
|
weights -= learningRate * gradW
|
||||||
@ -109,12 +107,11 @@ fun accuracy(yPred: DoubleTensor, yTrue: DoubleTensor): Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// neural network class
|
// neural network class
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
class NeuralNetwork(private val layers: List<Layer>) {
|
class NeuralNetwork(private val layers: List<Layer>) {
|
||||||
private fun softMaxLoss(yPred: DoubleTensor, yTrue: DoubleTensor): DoubleTensor = BroadcastDoubleTensorAlgebra {
|
private fun softMaxLoss(yPred: DoubleTensor, yTrue: DoubleTensor): DoubleTensor = BroadcastDoubleTensorAlgebra {
|
||||||
|
|
||||||
val onesForAnswers = yPred.zeroesLike()
|
val onesForAnswers = zeroesLike(yPred)
|
||||||
yTrue.copyArray().forEachIndexed { index, labelDouble ->
|
yTrue.source.asIterable().forEachIndexed { index, labelDouble ->
|
||||||
val label = labelDouble.toInt()
|
val label = labelDouble.toInt()
|
||||||
onesForAnswers[intArrayOf(index, label)] = 1.0
|
onesForAnswers[intArrayOf(index, label)] = 1.0
|
||||||
}
|
}
|
||||||
@ -166,7 +163,7 @@ class NeuralNetwork(private val layers: List<Layer>) {
|
|||||||
for ((xBatch, yBatch) in iterBatch(xTrain, yTrain)) {
|
for ((xBatch, yBatch) in iterBatch(xTrain, yTrain)) {
|
||||||
train(xBatch, yBatch)
|
train(xBatch, yBatch)
|
||||||
}
|
}
|
||||||
println("Accuracy:${accuracy(yTrain, predict(xTrain).argMax(1, true).asDouble())}")
|
println("Accuracy:${accuracy(yTrain, predict(xTrain).argMax(1, true).toDoubleTensor())}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +230,7 @@ fun main() = BroadcastDoubleTensorAlgebra {
|
|||||||
val prediction = model.predict(xTest)
|
val prediction = model.predict(xTest)
|
||||||
|
|
||||||
// process raw prediction via argMax
|
// process raw prediction via argMax
|
||||||
val predictionLabels = prediction.argMax(1, true).asDouble()
|
val predictionLabels = prediction.argMax(1, true).toDoubleTensor()
|
||||||
|
|
||||||
// find out accuracy
|
// find out accuracy
|
||||||
val acc = accuracy(yTest, predictionLabels)
|
val acc = accuracy(yTest, predictionLabels)
|
||||||
|
@ -238,18 +238,3 @@ public interface MutableStructureND<T> : StructureND<T> {
|
|||||||
public operator fun <T> MutableStructureND<T>.set(vararg index: Int, value: T) {
|
public operator fun <T> MutableStructureND<T>.set(vararg index: Int, value: T) {
|
||||||
set(index, value)
|
set(index, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform a structure element-by element in place.
|
|
||||||
*/
|
|
||||||
@OptIn(PerformancePitfall::class)
|
|
||||||
public inline fun <T> MutableStructureND<T>.mapInPlace(action: (index: IntArray, t: T) -> T): Unit =
|
|
||||||
elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) }
|
|
||||||
|
|
||||||
public inline fun <reified T : Any> StructureND<T>.zip(
|
|
||||||
struct: StructureND<T>,
|
|
||||||
crossinline block: (T, T) -> T,
|
|
||||||
): StructureND<T> {
|
|
||||||
require(shape.contentEquals(struct.shape)) { "Shape mismatch in structure combination" }
|
|
||||||
return StructureND.auto(shape) { block(this[it], struct[it]) }
|
|
||||||
}
|
|
||||||
|
@ -114,7 +114,6 @@ public interface Buffer<out T> {
|
|||||||
*
|
*
|
||||||
* The [size] is specified, and each element is calculated by calling the specified [initializer] function.
|
* The [size] is specified, and each element is calculated by calling the specified [initializer] function.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
public inline fun <reified T : Any> auto(size: Int, initializer: (Int) -> T): Buffer<T> =
|
public inline fun <reified T : Any> auto(size: Int, initializer: (Int) -> T): Buffer<T> =
|
||||||
auto(T::class, size, initializer)
|
auto(T::class, size, initializer)
|
||||||
}
|
}
|
||||||
|
@ -47,11 +47,6 @@ public inline fun DoubleBuffer(size: Int, init: (Int) -> Double): DoubleBuffer =
|
|||||||
*/
|
*/
|
||||||
public fun DoubleBuffer(vararg doubles: Double): DoubleBuffer = DoubleBuffer(doubles)
|
public fun DoubleBuffer(vararg doubles: Double): DoubleBuffer = DoubleBuffer(doubles)
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplified [DoubleBuffer] to array comparison
|
|
||||||
*/
|
|
||||||
public fun DoubleBuffer.contentEquals(vararg doubles: Double): Boolean = array.contentEquals(doubles)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new [DoubleArray] containing all the elements of this [Buffer].
|
* Returns a new [DoubleArray] containing all the elements of this [Buffer].
|
||||||
*/
|
*/
|
||||||
|
@ -24,8 +24,7 @@ public value class IntBuffer(public val array: IntArray) : MutableBuffer<Int> {
|
|||||||
|
|
||||||
override operator fun iterator(): IntIterator = array.iterator()
|
override operator fun iterator(): IntIterator = array.iterator()
|
||||||
|
|
||||||
override fun copy(): MutableBuffer<Int> =
|
override fun copy(): IntBuffer = IntBuffer(array.copyOf())
|
||||||
IntBuffer(array.copyOf())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +74,9 @@ class NumberNDFieldTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun combineTest() {
|
fun combineTest() {
|
||||||
val division = array1.zip(array2, Double::div)
|
algebra {
|
||||||
|
val division = zip(array1, array2) { l, r -> l / r }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object L2Norm : Norm<StructureND<Number>, Double> {
|
object L2Norm : Norm<StructureND<Number>, Double> {
|
||||||
|
@ -4,11 +4,11 @@ plugins {
|
|||||||
|
|
||||||
kscience {
|
kscience {
|
||||||
native()
|
native()
|
||||||
}
|
|
||||||
|
|
||||||
kotlin.sourceSets.commonMain {
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":kmath-core"))
|
api(projects.kmathCore)
|
||||||
|
}
|
||||||
|
dependencies("commonTest") {
|
||||||
|
implementation(projects.testUtils)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import space.kscience.kmath.misc.PerformancePitfall
|
|||||||
import space.kscience.kmath.misc.UnstableKMathAPI
|
import space.kscience.kmath.misc.UnstableKMathAPI
|
||||||
import space.kscience.kmath.nd.StructureND
|
import space.kscience.kmath.nd.StructureND
|
||||||
import space.kscience.kmath.operations.algebra
|
import space.kscience.kmath.operations.algebra
|
||||||
import space.kscience.kmath.structures.contentEquals
|
import space.kscience.kmath.testutils.contentEquals
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
@ -72,6 +72,20 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a structure element-by element in place.
|
||||||
|
*/
|
||||||
|
public inline fun <T> MutableStructureND<T>.mapIndexedInPlace(operation: (index: IntArray, t: T) -> T): Unit {
|
||||||
|
if (this is MultikTensor) {
|
||||||
|
array.multiIndices.iterator().forEach {
|
||||||
|
set(it, operation(it, get(it)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
indices.forEach { set(it, operation(it, get(it))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(PerformancePitfall::class)
|
@OptIn(PerformancePitfall::class)
|
||||||
override fun zip(left: StructureND<T>, right: StructureND<T>, transform: A.(T, T) -> T): MultikTensor<T> {
|
override fun zip(left: StructureND<T>, right: StructureND<T>, transform: A.(T, T) -> T): MultikTensor<T> {
|
||||||
require(left.shape.contentEquals(right.shape)) { "ND array shape mismatch" } //TODO replace by ShapeMismatchException
|
require(left.shape.contentEquals(right.shape)) { "ND array shape mismatch" } //TODO replace by ShapeMismatchException
|
||||||
@ -121,7 +135,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.plusAssign(value)
|
array.plusAssign(value)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { _, t -> elementAlgebra.add(t, value) }
|
mapIndexedInPlace { _, t -> elementAlgebra.add(t, value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +143,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.plusAssign(arg.asMultik().array)
|
array.plusAssign(arg.asMultik().array)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { index, t -> elementAlgebra.add(t, arg[index]) }
|
mapIndexedInPlace { index, t -> elementAlgebra.add(t, arg[index]) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +159,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.minusAssign(value)
|
array.minusAssign(value)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { _, t -> elementAlgebra.run { t - value } }
|
mapIndexedInPlace { _, t -> elementAlgebra.run { t - value } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +167,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.minusAssign(arg.asMultik().array)
|
array.minusAssign(arg.asMultik().array)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { index, t -> elementAlgebra.run { t - arg[index] } }
|
mapIndexedInPlace { index, t -> elementAlgebra.run { t - arg[index] } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +184,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.timesAssign(value)
|
array.timesAssign(value)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { _, t -> elementAlgebra.multiply(t, value) }
|
mapIndexedInPlace { _, t -> elementAlgebra.multiply(t, value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +192,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.timesAssign(arg.asMultik().array)
|
array.timesAssign(arg.asMultik().array)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { index, t -> elementAlgebra.multiply(t, arg[index]) }
|
mapIndexedInPlace { index, t -> elementAlgebra.multiply(t, arg[index]) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +201,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
|
|||||||
|
|
||||||
override fun Tensor<T>.getTensor(i: Int): MultikTensor<T> = asMultik().array.mutableView(i).wrap()
|
override fun Tensor<T>.getTensor(i: Int): MultikTensor<T> = asMultik().array.mutableView(i).wrap()
|
||||||
|
|
||||||
override fun Tensor<T>.transpose(i: Int, j: Int): MultikTensor<T> = asMultik().array.transpose(i, j).wrap()
|
override fun Tensor<T>.transposed(i: Int, j: Int): MultikTensor<T> = asMultik().array.transpose(i, j).wrap()
|
||||||
|
|
||||||
override fun Tensor<T>.view(shape: IntArray): MultikTensor<T> {
|
override fun Tensor<T>.view(shape: IntArray): MultikTensor<T> {
|
||||||
require(shape.all { it > 0 })
|
require(shape.all { it > 0 })
|
||||||
@ -283,7 +297,7 @@ public abstract class MultikDivisionTensorAlgebra<T, A : Field<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.divAssign(value)
|
array.divAssign(value)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { _, t -> elementAlgebra.divide(t, value) }
|
mapIndexedInPlace { _, t -> elementAlgebra.divide(t, value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +305,7 @@ public abstract class MultikDivisionTensorAlgebra<T, A : Field<T>>(
|
|||||||
if (this is MultikTensor) {
|
if (this is MultikTensor) {
|
||||||
array.divAssign(arg.asMultik().array)
|
array.divAssign(arg.asMultik().array)
|
||||||
} else {
|
} else {
|
||||||
mapInPlace { index, t -> elementAlgebra.divide(t, arg[index]) }
|
mapIndexedInPlace { index, t -> elementAlgebra.divide(t, arg[index]) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -96,7 +96,7 @@ public sealed interface Nd4jTensorAlgebra<T : Number, A : Field<T>> : AnalyticTe
|
|||||||
|
|
||||||
override fun StructureND<T>.unaryMinus(): Nd4jArrayStructure<T> = ndArray.neg().wrap()
|
override fun StructureND<T>.unaryMinus(): Nd4jArrayStructure<T> = ndArray.neg().wrap()
|
||||||
override fun Tensor<T>.getTensor(i: Int): Nd4jArrayStructure<T> = ndArray.slice(i.toLong()).wrap()
|
override fun Tensor<T>.getTensor(i: Int): Nd4jArrayStructure<T> = ndArray.slice(i.toLong()).wrap()
|
||||||
override fun Tensor<T>.transpose(i: Int, j: Int): Nd4jArrayStructure<T> = ndArray.swapAxes(i, j).wrap()
|
override fun Tensor<T>.transposed(i: Int, j: Int): Nd4jArrayStructure<T> = ndArray.swapAxes(i, j).wrap()
|
||||||
override fun StructureND<T>.dot(other: StructureND<T>): Nd4jArrayStructure<T> = ndArray.mmul(other.ndArray).wrap()
|
override fun StructureND<T>.dot(other: StructureND<T>): Nd4jArrayStructure<T> = ndArray.mmul(other.ndArray).wrap()
|
||||||
|
|
||||||
override fun StructureND<T>.min(dim: Int, keepDim: Boolean): Nd4jArrayStructure<T> =
|
override fun StructureND<T>.min(dim: Int, keepDim: Boolean): Nd4jArrayStructure<T> =
|
||||||
|
@ -188,7 +188,7 @@ public abstract class TensorFlowAlgebra<T, TT : TNumber, A : Ring<T>> internal c
|
|||||||
StridedSliceHelper.stridedSlice(ops.scope(), it, Indices.at(i.toLong()))
|
StridedSliceHelper.stridedSlice(ops.scope(), it, Indices.at(i.toLong()))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<T>.transpose(i: Int, j: Int): Tensor<T> = operate {
|
override fun Tensor<T>.transposed(i: Int, j: Int): Tensor<T> = operate {
|
||||||
ops.linalg.transpose(it, ops.constant(intArrayOf(i, j)))
|
ops.linalg.transpose(it, ops.constant(intArrayOf(i, j)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,12 @@ kotlin.sourceSets {
|
|||||||
api(project(":kmath-stat"))
|
api(project(":kmath-stat"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonTest{
|
||||||
|
dependencies{
|
||||||
|
implementation(projects.testUtils)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readme {
|
readme {
|
||||||
|
@ -180,7 +180,7 @@ public interface TensorAlgebra<T, A : Ring<T>> : RingOpsND<T, A> {
|
|||||||
* @param j the second dimension to be transposed
|
* @param j the second dimension to be transposed
|
||||||
* @return transposed tensor
|
* @return transposed tensor
|
||||||
*/
|
*/
|
||||||
public fun Tensor<T>.transpose(i: Int = -2, j: Int = -1): Tensor<T>
|
public fun Tensor<T>.transposed(i: Int = -2, j: Int = -1): Tensor<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new tensor with the same data as the self tensor but of a different shape.
|
* Returns a new tensor with the same data as the self tensor but of a different shape.
|
||||||
|
@ -7,8 +7,8 @@ package space.kscience.kmath.tensors.core
|
|||||||
|
|
||||||
import space.kscience.kmath.misc.UnstableKMathAPI
|
import space.kscience.kmath.misc.UnstableKMathAPI
|
||||||
import space.kscience.kmath.nd.StructureND
|
import space.kscience.kmath.nd.StructureND
|
||||||
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
import space.kscience.kmath.tensors.api.Tensor
|
import space.kscience.kmath.tensors.api.Tensor
|
||||||
import space.kscience.kmath.tensors.core.internal.array
|
|
||||||
import space.kscience.kmath.tensors.core.internal.broadcastTensors
|
import space.kscience.kmath.tensors.core.internal.broadcastTensors
|
||||||
import space.kscience.kmath.tensors.core.internal.broadcastTo
|
import space.kscience.kmath.tensors.core.internal.broadcastTo
|
||||||
|
|
||||||
@ -22,8 +22,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
|||||||
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
||||||
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 = DoubleBuffer(newThis.indices.linearSize) { i ->
|
||||||
newThis.mutableBuffer.array()[i] + newOther.mutableBuffer.array()[i]
|
newThis.source[i] + newOther.source[i]
|
||||||
}
|
}
|
||||||
return DoubleTensor(newThis.shape, resBuffer)
|
return DoubleTensor(newThis.shape, resBuffer)
|
||||||
}
|
}
|
||||||
@ -31,8 +31,7 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
|||||||
override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) {
|
override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) {
|
||||||
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
||||||
for (i in 0 until asDoubleTensor().indices.linearSize) {
|
for (i in 0 until asDoubleTensor().indices.linearSize) {
|
||||||
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] +=
|
asDoubleTensor().source[i] += newOther.source[i]
|
||||||
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,17 +39,16 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
|||||||
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
||||||
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 = DoubleBuffer(newThis.indices.linearSize) { i ->
|
||||||
newThis.mutableBuffer.array()[i] - newOther.mutableBuffer.array()[i]
|
newThis.source[i] - newOther.source[i]
|
||||||
}
|
}
|
||||||
return DoubleTensor(newThis.shape, resBuffer)
|
return DoubleTensor(newThis.shape, resBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) {
|
override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) {
|
||||||
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
||||||
for (i in 0 until asDoubleTensor().indices.linearSize) {
|
for (i in 0 until indices.linearSize) {
|
||||||
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] -=
|
asDoubleTensor().source[i] -= newOther.source[i]
|
||||||
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,18 +56,16 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
|||||||
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
||||||
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 = DoubleBuffer(newThis.indices.linearSize) { i ->
|
||||||
newThis.mutableBuffer.array()[newThis.bufferStart + i] *
|
newThis.source[i] * newOther.source[i]
|
||||||
newOther.mutableBuffer.array()[newOther.bufferStart + i]
|
|
||||||
}
|
}
|
||||||
return DoubleTensor(newThis.shape, resBuffer)
|
return DoubleTensor(newThis.shape, resBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) {
|
override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) {
|
||||||
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
||||||
for (i in 0 until asDoubleTensor().indices.linearSize) {
|
for (i in 0 until indices.linearSize) {
|
||||||
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] *=
|
asDoubleTensor().source[+i] *= newOther.source[i]
|
||||||
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,18 +73,16 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
|
|||||||
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
|
||||||
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 = DoubleBuffer(newThis.indices.linearSize) { i ->
|
||||||
newThis.mutableBuffer.array()[newOther.bufferStart + i] /
|
newThis.source[i] / newOther.source[i]
|
||||||
newOther.mutableBuffer.array()[newOther.bufferStart + i]
|
|
||||||
}
|
}
|
||||||
return DoubleTensor(newThis.shape, resBuffer)
|
return DoubleTensor(newThis.shape, resBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Double>.divAssign(arg: StructureND<Double>) {
|
override fun Tensor<Double>.divAssign(arg: StructureND<Double>) {
|
||||||
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
|
||||||
for (i in 0 until asDoubleTensor().indices.linearSize) {
|
for (i in 0 until indices.linearSize) {
|
||||||
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] /=
|
asDoubleTensor().source[i] /= newOther.source[i]
|
||||||
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,12 @@ import space.kscience.kmath.tensors.api.Tensor
|
|||||||
/**
|
/**
|
||||||
* Represents [Tensor] over a [MutableBuffer] intended to be used through [DoubleTensor] and [IntTensor]
|
* Represents [Tensor] over a [MutableBuffer] intended to be used through [DoubleTensor] and [IntTensor]
|
||||||
*/
|
*/
|
||||||
public open class BufferedTensor<T> internal constructor(
|
public abstract class BufferedTensor<T>(
|
||||||
override val shape: IntArray,
|
override val shape: IntArray,
|
||||||
@PublishedApi internal val mutableBuffer: MutableBuffer<T>,
|
|
||||||
@PublishedApi internal val bufferStart: Int,
|
|
||||||
) : Tensor<T> {
|
) : Tensor<T> {
|
||||||
|
|
||||||
|
public abstract val source: MutableBuffer<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buffer strides based on [TensorLinearStructure] implementation
|
* Buffer strides based on [TensorLinearStructure] implementation
|
||||||
*/
|
*/
|
||||||
@ -27,14 +27,8 @@ public open class BufferedTensor<T> internal constructor(
|
|||||||
/**
|
/**
|
||||||
* Number of elements in tensor
|
* Number of elements in tensor
|
||||||
*/
|
*/
|
||||||
public val numElements: Int
|
public val linearSize: Int get() = indices.linearSize
|
||||||
get() = indices.linearSize
|
|
||||||
|
|
||||||
override fun get(index: IntArray): T = mutableBuffer[bufferStart + indices.offset(index)]
|
|
||||||
|
|
||||||
override fun set(index: IntArray, value: T) {
|
|
||||||
mutableBuffer[bufferStart + indices.offset(index)] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
@PerformancePitfall
|
@PerformancePitfall
|
||||||
override fun elements(): Sequence<Pair<IntArray, T>> = indices.asSequence().map {
|
override fun elements(): Sequence<Pair<IntArray, T>> = indices.asSequence().map {
|
||||||
|
@ -5,16 +5,88 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.tensors.core
|
package space.kscience.kmath.tensors.core
|
||||||
|
|
||||||
import space.kscience.kmath.structures.DoubleBuffer
|
import space.kscience.kmath.structures.*
|
||||||
import space.kscience.kmath.tensors.core.internal.toPrettyString
|
import space.kscience.kmath.tensors.core.internal.toPrettyString
|
||||||
|
|
||||||
|
public class OffsetDoubleBuffer(
|
||||||
|
private val source: DoubleBuffer,
|
||||||
|
private val offset: Int,
|
||||||
|
override val size: Int,
|
||||||
|
) : MutableBuffer<Double> {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(offset >= 0) { "Offset must be non-negative" }
|
||||||
|
require(size >= 0) { "Size must be non-negative" }
|
||||||
|
require(offset + size <= source.size) { "Maximum index must be inside source dimension" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(index: Int, value: Double) {
|
||||||
|
require(index in 0 until size) { "Index must be in [0, size)" }
|
||||||
|
source[index + offset] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(index: Int): Double = source[index + offset]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy only a part of buffer that belongs to this tensor
|
||||||
|
*/
|
||||||
|
override fun copy(): DoubleBuffer = source.array.copyOfRange(offset, offset + size).asBuffer()
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<Double> = iterator {
|
||||||
|
for (i in indices) {
|
||||||
|
yield(get(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = Buffer.toString(this)
|
||||||
|
|
||||||
|
public fun view(addOffset: Int, newSize: Int = size - addOffset): OffsetDoubleBuffer =
|
||||||
|
OffsetDoubleBuffer(source, offset + addOffset, newSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun OffsetDoubleBuffer.slice(range: IntRange): OffsetDoubleBuffer = view(range.first, range.last - range.first)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map only operable content of the offset buffer
|
||||||
|
*/
|
||||||
|
public inline fun OffsetDoubleBuffer.map(operation: (Double) -> Double): DoubleBuffer =
|
||||||
|
DoubleBuffer(size) { operation(get(it)) }
|
||||||
|
|
||||||
|
public inline fun OffsetDoubleBuffer.zip(
|
||||||
|
other: OffsetDoubleBuffer,
|
||||||
|
operation: (l: Double, r: Double) -> Double,
|
||||||
|
): DoubleBuffer {
|
||||||
|
require(size == other.size) { "The sizes of zipped buffers must be the same" }
|
||||||
|
return DoubleBuffer(size) { operation(get(it), other[it]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map in place
|
||||||
|
*/
|
||||||
|
public inline fun OffsetDoubleBuffer.mapInPlace(operation: (Double) -> Double) {
|
||||||
|
indices.forEach { set(it, operation(get(it))) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default [BufferedTensor] implementation for [Double] values
|
* Default [BufferedTensor] implementation for [Double] values
|
||||||
*/
|
*/
|
||||||
public class DoubleTensor @PublishedApi internal constructor(
|
public class DoubleTensor(
|
||||||
shape: IntArray,
|
shape: IntArray,
|
||||||
buffer: DoubleArray,
|
override val source: OffsetDoubleBuffer,
|
||||||
offset: Int = 0
|
) : BufferedTensor<Double>(shape) {
|
||||||
) : BufferedTensor<Double>(shape, DoubleBuffer(buffer), offset) {
|
|
||||||
|
init {
|
||||||
|
require(linearSize == source.size) { "Source buffer size must be equal tensor size" }
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(shape: IntArray, buffer: DoubleBuffer) : this(shape, OffsetDoubleBuffer(buffer, 0, buffer.size))
|
||||||
|
|
||||||
|
override fun get(index: IntArray): Double = this.source[indices.offset(index)]
|
||||||
|
|
||||||
|
override fun set(index: IntArray, value: Double) {
|
||||||
|
source[indices.offset(index)] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String = toPrettyString()
|
override fun toString(): String = toPrettyString()
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,17 +5,87 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.tensors.core
|
package space.kscience.kmath.tensors.core
|
||||||
|
|
||||||
import space.kscience.kmath.structures.IntBuffer
|
import space.kscience.kmath.structures.*
|
||||||
import space.kscience.kmath.tensors.core.internal.array
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default [BufferedTensor] implementation for [Int] values
|
* Default [BufferedTensor] implementation for [Int] values
|
||||||
*/
|
*/
|
||||||
public class IntTensor @PublishedApi internal constructor(
|
public class OffsetIntBuffer(
|
||||||
shape: IntArray,
|
private val source: IntBuffer,
|
||||||
buffer: IntArray,
|
private val offset: Int,
|
||||||
offset: Int = 0,
|
override val size: Int,
|
||||||
) : BufferedTensor<Int>(shape, IntBuffer(buffer), offset) {
|
) : MutableBuffer<Int> {
|
||||||
public fun asDouble(): DoubleTensor =
|
|
||||||
DoubleTensor(shape, mutableBuffer.array().map { it.toDouble() }.toDoubleArray(), bufferStart)
|
init {
|
||||||
|
require(offset >= 0) { "Offset must be non-negative" }
|
||||||
|
require(size >= 0) { "Size must be non-negative" }
|
||||||
|
require(offset + size <= source.size) { "Maximum index must be inside source dimension" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(index: Int, value: Int) {
|
||||||
|
require(index in 0 until size) { "Index must be in [0, size)" }
|
||||||
|
source[index + offset] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(index: Int): Int = source[index + offset]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy only a part of buffer that belongs to this tensor
|
||||||
|
*/
|
||||||
|
override fun copy(): IntBuffer = source.array.copyOfRange(offset, offset + size).asBuffer()
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<Int> = iterator {
|
||||||
|
for (i in indices) {
|
||||||
|
yield(get(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = Buffer.toString(this)
|
||||||
|
|
||||||
|
public fun view(addOffset: Int, newSize: Int = size - addOffset): OffsetIntBuffer =
|
||||||
|
OffsetIntBuffer(source, offset + addOffset, newSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun OffsetIntBuffer.slice(range: IntRange): OffsetIntBuffer = view(range.first, range.last - range.first)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map only operable content of the offset buffer
|
||||||
|
*/
|
||||||
|
public inline fun OffsetIntBuffer.map(operation: (Int) -> Int): IntBuffer =
|
||||||
|
IntBuffer(size) { operation(get(it)) }
|
||||||
|
|
||||||
|
public inline fun OffsetIntBuffer.zip(
|
||||||
|
other: OffsetIntBuffer,
|
||||||
|
operation: (l: Int, r: Int) -> Int,
|
||||||
|
): IntBuffer {
|
||||||
|
require(size == other.size) { "The sizes of zipped buffers must be the same" }
|
||||||
|
return IntBuffer(size) { operation(get(it), other[it]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map in place
|
||||||
|
*/
|
||||||
|
public inline fun OffsetIntBuffer.mapInPlace(operation: (Int) -> Int) {
|
||||||
|
indices.forEach { set(it, operation(get(it))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default [BufferedTensor] implementation for [Int] values
|
||||||
|
*/
|
||||||
|
public class IntTensor(
|
||||||
|
shape: IntArray,
|
||||||
|
override val source: OffsetIntBuffer,
|
||||||
|
) : BufferedTensor<Int>(shape) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(linearSize == source.size) { "Source buffer size must be equal tensor size" }
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(shape: IntArray, buffer: IntBuffer) : this(shape, OffsetIntBuffer(buffer, 0, buffer.size))
|
||||||
|
|
||||||
|
override fun get(index: IntArray): Int = this.source[indices.offset(index)]
|
||||||
|
|
||||||
|
override fun set(index: IntArray, value: Int) {
|
||||||
|
source[indices.offset(index)] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ package space.kscience.kmath.tensors.core
|
|||||||
import space.kscience.kmath.misc.PerformancePitfall
|
import space.kscience.kmath.misc.PerformancePitfall
|
||||||
import space.kscience.kmath.nd.*
|
import space.kscience.kmath.nd.*
|
||||||
import space.kscience.kmath.operations.IntRing
|
import space.kscience.kmath.operations.IntRing
|
||||||
import space.kscience.kmath.structures.MutableBuffer
|
import space.kscience.kmath.structures.*
|
||||||
import space.kscience.kmath.tensors.api.*
|
import space.kscience.kmath.tensors.api.*
|
||||||
import space.kscience.kmath.tensors.core.internal.*
|
import space.kscience.kmath.tensors.core.internal.*
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
@ -23,10 +23,6 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
|
|
||||||
public companion object : IntTensorAlgebra()
|
public companion object : IntTensorAlgebra()
|
||||||
|
|
||||||
override fun StructureND<Int>.dot(other: StructureND<Int>): Tensor<Int> {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override val elementAlgebra: IntRing get() = IntRing
|
override val elementAlgebra: IntRing get() = IntRing
|
||||||
|
|
||||||
|
|
||||||
@ -36,56 +32,64 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
* @param transform the function to be applied to each element of the tensor.
|
* @param transform the function to be applied to each element of the tensor.
|
||||||
* @return the resulting tensor after applying the function.
|
* @return the resulting tensor after applying the function.
|
||||||
*/
|
*/
|
||||||
@PerformancePitfall
|
|
||||||
@Suppress("OVERRIDE_BY_INLINE")
|
@Suppress("OVERRIDE_BY_INLINE")
|
||||||
final override inline fun StructureND<Int>.map(transform: IntRing.(Int) -> Int): IntTensor {
|
final override inline fun StructureND<Int>.map(transform: IntRing.(Int) -> Int): IntTensor {
|
||||||
val tensor = this.asIntTensor()
|
val tensor = this.asIntTensor()
|
||||||
//TODO remove additional copy
|
//TODO remove additional copy
|
||||||
val sourceArray = tensor.copyArray()
|
val array = IntBuffer(tensor.source.size) { IntRing.transform(tensor.source[it]) }
|
||||||
val array = IntArray(tensor.numElements) { IntRing.transform(sourceArray[it]) }
|
|
||||||
return IntTensor(
|
return IntTensor(
|
||||||
tensor.shape,
|
tensor.shape,
|
||||||
array,
|
array,
|
||||||
tensor.bufferStart
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PerformancePitfall
|
public inline fun Tensor<Int>.mapInPlace(operation: (Int) -> Int) {
|
||||||
|
if (this is IntTensor) {
|
||||||
|
source.mapInPlace(operation)
|
||||||
|
} else {
|
||||||
|
indices.forEach { set(it, operation(get(it))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun Tensor<Int>.mapIndexedInPlace(operation: (IntArray, Int) -> Int) {
|
||||||
|
indices.forEach { set(it, operation(it, get(it))) }
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("OVERRIDE_BY_INLINE")
|
@Suppress("OVERRIDE_BY_INLINE")
|
||||||
final override inline fun StructureND<Int>.mapIndexed(transform: IntRing.(index: IntArray, Int) -> Int): IntTensor {
|
final override inline fun StructureND<Int>.mapIndexed(transform: IntRing.(index: IntArray, Int) -> Int): IntTensor {
|
||||||
val tensor = this.asIntTensor()
|
val tensor = this.asIntTensor()
|
||||||
//TODO remove additional copy
|
//TODO remove additional copy
|
||||||
val sourceArray = tensor.copyArray()
|
val buffer = IntBuffer(tensor.source.size) {
|
||||||
val array = IntArray(tensor.numElements) { IntRing.transform(tensor.indices.index(it), sourceArray[it]) }
|
IntRing.transform(tensor.indices.index(it), tensor.source[it])
|
||||||
return IntTensor(
|
}
|
||||||
tensor.shape,
|
return IntTensor(tensor.shape, buffer)
|
||||||
array,
|
|
||||||
tensor.bufferStart
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PerformancePitfall
|
@Suppress("OVERRIDE_BY_INLINE")
|
||||||
override fun zip(
|
final override inline fun zip(
|
||||||
left: StructureND<Int>,
|
left: StructureND<Int>,
|
||||||
right: StructureND<Int>,
|
right: StructureND<Int>,
|
||||||
transform: IntRing.(Int, Int) -> Int,
|
transform: IntRing.(Int, Int) -> Int,
|
||||||
): IntTensor {
|
): IntTensor {
|
||||||
require(left.shape.contentEquals(right.shape)) {
|
checkShapesCompatible(left, right)
|
||||||
"The shapes in zip are not equal: left - ${left.shape}, right - ${right.shape}"
|
|
||||||
}
|
|
||||||
val leftTensor = left.asIntTensor()
|
val leftTensor = left.asIntTensor()
|
||||||
val leftArray = leftTensor.copyArray()
|
|
||||||
val rightTensor = right.asIntTensor()
|
val rightTensor = right.asIntTensor()
|
||||||
val rightArray = rightTensor.copyArray()
|
val buffer = IntBuffer(leftTensor.source.size) {
|
||||||
val array = IntArray(leftTensor.numElements) { IntRing.transform(leftArray[it], rightArray[it]) }
|
IntRing.transform(leftTensor.source[it], rightTensor.source[it])
|
||||||
return IntTensor(
|
}
|
||||||
leftTensor.shape,
|
return IntTensor(leftTensor.shape, buffer)
|
||||||
array
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun StructureND<Int>.valueOrNull(): Int? = if (asIntTensor().shape contentEquals intArrayOf(1))
|
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart] else null
|
public inline fun StructureND<Int>.reduceElements(transform: (IntBuffer) -> Int): Int =
|
||||||
|
transform(asIntTensor().source.copy())
|
||||||
|
//TODO do we need protective copy?
|
||||||
|
|
||||||
|
override fun StructureND<Int>.valueOrNull(): Int? {
|
||||||
|
val dt = asIntTensor()
|
||||||
|
return if (dt.shape contentEquals intArrayOf(1)) dt.source[0] else null
|
||||||
|
}
|
||||||
|
|
||||||
override fun StructureND<Int>.value(): Int = valueOrNull()
|
override fun StructureND<Int>.value(): Int = 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]")
|
||||||
@ -94,16 +98,16 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
* Constructs a tensor with the specified shape and data.
|
* Constructs a tensor with the specified shape and data.
|
||||||
*
|
*
|
||||||
* @param shape the desired shape for the tensor.
|
* @param shape the desired shape for the tensor.
|
||||||
* @param buffer one-dimensional data array.
|
* @param array one-dimensional data array.
|
||||||
* @return tensor with the [shape] shape and [buffer] data.
|
* @return tensor with the [shape] shape and [array] data.
|
||||||
*/
|
*/
|
||||||
public fun fromArray(shape: IntArray, buffer: IntArray): IntTensor {
|
public fun fromArray(shape: IntArray, array: IntArray): IntTensor {
|
||||||
checkEmptyShape(shape)
|
checkNotEmptyShape(shape)
|
||||||
check(buffer.isNotEmpty()) { "Illegal empty buffer provided" }
|
check(array.isNotEmpty()) { "Illegal empty buffer provided" }
|
||||||
check(buffer.size == shape.reduce(Int::times)) {
|
check(array.size == shape.reduce(Int::times)) {
|
||||||
"Inconsistent shape ${shape.toList()} for buffer of size ${buffer.size} provided"
|
"Inconsistent shape ${shape.toList()} for buffer of size ${array.size} provided"
|
||||||
}
|
}
|
||||||
return IntTensor(shape, buffer, 0)
|
return IntTensor(shape, array.asBuffer())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,10 +123,10 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
override fun Tensor<Int>.getTensor(i: Int): IntTensor {
|
override fun Tensor<Int>.getTensor(i: Int): IntTensor {
|
||||||
val lastShape = asIntTensor().shape.drop(1).toIntArray()
|
val dt = asIntTensor()
|
||||||
|
val lastShape = shape.drop(1).toIntArray()
|
||||||
val newShape = if (lastShape.isNotEmpty()) lastShape else intArrayOf(1)
|
val newShape = if (lastShape.isNotEmpty()) lastShape else intArrayOf(1)
|
||||||
val newStart = newShape.reduce(Int::times) * i + asIntTensor().bufferStart
|
return IntTensor(newShape, dt.source.view(newShape.reduce(Int::times) * i))
|
||||||
return IntTensor(newShape, asIntTensor().mutableBuffer.array(), newStart)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,8 +137,8 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
* @return tensor with the [shape] shape and filled with [value].
|
* @return tensor with the [shape] shape and filled with [value].
|
||||||
*/
|
*/
|
||||||
public fun full(value: Int, shape: IntArray): IntTensor {
|
public fun full(value: Int, shape: IntArray): IntTensor {
|
||||||
checkEmptyShape(shape)
|
checkNotEmptyShape(shape)
|
||||||
val buffer = IntArray(shape.reduce(Int::times)) { value }
|
val buffer = IntBuffer(shape.reduce(Int::times)) { value }
|
||||||
return IntTensor(shape, buffer)
|
return IntTensor(shape, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,9 +148,9 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
* @param value the value to fill the output tensor with.
|
* @param value the value to fill the output tensor with.
|
||||||
* @return tensor with the `input` tensor shape and filled with [value].
|
* @return tensor with the `input` tensor shape and filled with [value].
|
||||||
*/
|
*/
|
||||||
public fun Tensor<Int>.fullLike(value: Int): IntTensor {
|
public fun fullLike(structureND: StructureND<*>, value: Int): IntTensor {
|
||||||
val shape = asIntTensor().shape
|
val shape = structureND.shape
|
||||||
val buffer = IntArray(asIntTensor().numElements) { value }
|
val buffer = IntBuffer(structureND.indices.linearSize) { value }
|
||||||
return IntTensor(shape, buffer)
|
return IntTensor(shape, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +167,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
*
|
*
|
||||||
* @return tensor filled with the scalar value `0`, with the same shape as `input` tensor.
|
* @return tensor filled with the scalar value `0`, with the same shape as `input` tensor.
|
||||||
*/
|
*/
|
||||||
public fun StructureND<Int>.zeroesLike(): IntTensor = asIntTensor().fullLike(0)
|
public fun zeroesLike(structureND: StructureND<Int>): IntTensor = fullLike(structureND.asIntTensor(), 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a tensor filled with the scalar value `1`, with the shape defined by the variable argument [shape].
|
* Returns a tensor filled with the scalar value `1`, with the shape defined by the variable argument [shape].
|
||||||
@ -178,7 +182,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
*
|
*
|
||||||
* @return tensor filled with the scalar value `1`, with the same shape as `input` tensor.
|
* @return tensor filled with the scalar value `1`, with the same shape as `input` tensor.
|
||||||
*/
|
*/
|
||||||
public fun Tensor<Int>.onesLike(): IntTensor = asIntTensor().fullLike(1)
|
public fun onesLike(structureND: Tensor<*>): IntTensor = fullLike(structureND, 1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a 2D tensor with shape ([n], [n]), with ones on the diagonal and zeros elsewhere.
|
* Returns a 2D tensor with shape ([n], [n]), with ones on the diagonal and zeros elsewhere.
|
||||||
@ -188,7 +192,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
*/
|
*/
|
||||||
public fun eye(n: Int): IntTensor {
|
public fun eye(n: Int): IntTensor {
|
||||||
val shape = intArrayOf(n, n)
|
val shape = intArrayOf(n, n)
|
||||||
val buffer = IntArray(n * n) { 0 }
|
val buffer = IntBuffer(n * n) { 0 }
|
||||||
val res = IntTensor(shape, buffer)
|
val res = IntTensor(shape, buffer)
|
||||||
for (i in 0 until n) {
|
for (i in 0 until n) {
|
||||||
res[intArrayOf(i, i)] = 1
|
res[intArrayOf(i, i)] = 1
|
||||||
@ -196,151 +200,92 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun Int.plus(arg: StructureND<Int>): IntTensor = arg.map { this@plus + it }
|
||||||
* Return a copy of the tensor.
|
|
||||||
*
|
|
||||||
* @return a copy of the `input` tensor with a copied buffer.
|
|
||||||
*/
|
|
||||||
public fun StructureND<Int>.copy(): IntTensor =
|
|
||||||
IntTensor(asIntTensor().shape, asIntTensor().mutableBuffer.array().copyOf(), asIntTensor().bufferStart)
|
|
||||||
|
|
||||||
override fun Int.plus(arg: StructureND<Int>): IntTensor {
|
override fun StructureND<Int>.plus(arg: Int): IntTensor = map { it + arg }
|
||||||
val resBuffer = IntArray(arg.asIntTensor().numElements) { i ->
|
|
||||||
arg.asIntTensor().mutableBuffer.array()[arg.asIntTensor().bufferStart + i] + this
|
|
||||||
}
|
|
||||||
return IntTensor(arg.shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun StructureND<Int>.plus(arg: Int): IntTensor = arg + asIntTensor()
|
override fun StructureND<Int>.plus(arg: StructureND<Int>): IntTensor = zip(this, arg) { l, r -> l + r }
|
||||||
|
|
||||||
override fun StructureND<Int>.plus(arg: StructureND<Int>): IntTensor {
|
|
||||||
checkShapesCompatible(asIntTensor(), arg.asIntTensor())
|
|
||||||
val resBuffer = IntArray(asIntTensor().numElements) { i ->
|
|
||||||
asIntTensor().mutableBuffer.array()[i] + arg.asIntTensor().mutableBuffer.array()[i]
|
|
||||||
}
|
|
||||||
return IntTensor(asIntTensor().shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Tensor<Int>.plusAssign(value: Int) {
|
override fun Tensor<Int>.plusAssign(value: Int) {
|
||||||
for (i in 0 until asIntTensor().numElements) {
|
mapInPlace { it + value }
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] += value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Int>.plusAssign(arg: StructureND<Int>) {
|
override fun Tensor<Int>.plusAssign(arg: StructureND<Int>) {
|
||||||
checkShapesCompatible(asIntTensor(), arg.asIntTensor())
|
checkShapesCompatible(asIntTensor(), arg.asIntTensor())
|
||||||
for (i in 0 until asIntTensor().numElements) {
|
mapIndexedInPlace { index, value ->
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] +=
|
value + arg[index]
|
||||||
arg.asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Int.minus(arg: StructureND<Int>): IntTensor {
|
override fun Int.minus(arg: StructureND<Int>): IntTensor = arg.map { this@minus - it }
|
||||||
val resBuffer = IntArray(arg.asIntTensor().numElements) { i ->
|
|
||||||
this - arg.asIntTensor().mutableBuffer.array()[arg.asIntTensor().bufferStart + i]
|
|
||||||
}
|
|
||||||
return IntTensor(arg.shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun StructureND<Int>.minus(arg: Int): IntTensor {
|
override fun StructureND<Int>.minus(arg: Int): IntTensor = map { it - arg }
|
||||||
val resBuffer = IntArray(asIntTensor().numElements) { i ->
|
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] - arg
|
|
||||||
}
|
|
||||||
return IntTensor(asIntTensor().shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun StructureND<Int>.minus(arg: StructureND<Int>): IntTensor {
|
override fun StructureND<Int>.minus(arg: StructureND<Int>): IntTensor = zip(this, arg) { l, r -> l + r }
|
||||||
checkShapesCompatible(asIntTensor(), arg)
|
|
||||||
val resBuffer = IntArray(asIntTensor().numElements) { i ->
|
|
||||||
asIntTensor().mutableBuffer.array()[i] - arg.asIntTensor().mutableBuffer.array()[i]
|
|
||||||
}
|
|
||||||
return IntTensor(asIntTensor().shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Tensor<Int>.minusAssign(value: Int) {
|
override fun Tensor<Int>.minusAssign(value: Int) {
|
||||||
for (i in 0 until asIntTensor().numElements) {
|
mapInPlace { it - value }
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] -= value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Int>.minusAssign(arg: StructureND<Int>) {
|
override fun Tensor<Int>.minusAssign(arg: StructureND<Int>) {
|
||||||
checkShapesCompatible(asIntTensor(), arg)
|
checkShapesCompatible(this, arg)
|
||||||
for (i in 0 until asIntTensor().numElements) {
|
mapIndexedInPlace { index, value -> value - arg[index] }
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] -=
|
|
||||||
arg.asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Int.times(arg: StructureND<Int>): IntTensor {
|
override fun Int.times(arg: StructureND<Int>): IntTensor = arg.map { this@times * it }
|
||||||
val resBuffer = IntArray(arg.asIntTensor().numElements) { i ->
|
|
||||||
arg.asIntTensor().mutableBuffer.array()[arg.asIntTensor().bufferStart + i] * this
|
|
||||||
}
|
|
||||||
return IntTensor(arg.shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun StructureND<Int>.times(arg: Int): IntTensor = arg * asIntTensor()
|
override fun StructureND<Int>.times(arg: Int): IntTensor = arg * asIntTensor()
|
||||||
|
|
||||||
override fun StructureND<Int>.times(arg: StructureND<Int>): IntTensor {
|
override fun StructureND<Int>.times(arg: StructureND<Int>): IntTensor = zip(this, arg) { l, r -> l * r }
|
||||||
checkShapesCompatible(asIntTensor(), arg)
|
|
||||||
val resBuffer = IntArray(asIntTensor().numElements) { i ->
|
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] *
|
|
||||||
arg.asIntTensor().mutableBuffer.array()[arg.asIntTensor().bufferStart + i]
|
|
||||||
}
|
|
||||||
return IntTensor(asIntTensor().shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Tensor<Int>.timesAssign(value: Int) {
|
override fun Tensor<Int>.timesAssign(value: Int) {
|
||||||
for (i in 0 until asIntTensor().numElements) {
|
mapInPlace { it * value }
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] *= value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Int>.timesAssign(arg: StructureND<Int>) {
|
override fun Tensor<Int>.timesAssign(arg: StructureND<Int>) {
|
||||||
checkShapesCompatible(asIntTensor(), arg)
|
checkShapesCompatible(this, arg)
|
||||||
for (i in 0 until asIntTensor().numElements) {
|
mapIndexedInPlace { index, value -> value * arg[index] }
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] *=
|
|
||||||
arg.asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun StructureND<Int>.unaryMinus(): IntTensor {
|
override fun StructureND<Int>.unaryMinus(): IntTensor = map { -it }
|
||||||
val resBuffer = IntArray(asIntTensor().numElements) { i ->
|
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i].unaryMinus()
|
|
||||||
}
|
|
||||||
return IntTensor(asIntTensor().shape, resBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Tensor<Int>.transpose(i: Int, j: Int): IntTensor {
|
override fun Tensor<Int>.transposed(i: Int, j: Int): IntTensor {
|
||||||
val ii = asIntTensor().minusIndex(i)
|
// TODO change strides instead of changing content
|
||||||
val jj = asIntTensor().minusIndex(j)
|
val dt = asIntTensor()
|
||||||
checkTranspose(asIntTensor().dimension, ii, jj)
|
val ii = dt.minusIndex(i)
|
||||||
val n = asIntTensor().numElements
|
val jj = dt.minusIndex(j)
|
||||||
|
checkTranspose(dt.dimension, ii, jj)
|
||||||
|
val n = dt.linearSize
|
||||||
val resBuffer = IntArray(n)
|
val resBuffer = IntArray(n)
|
||||||
|
|
||||||
val resShape = asIntTensor().shape.copyOf()
|
val resShape = dt.shape.copyOf()
|
||||||
resShape[ii] = resShape[jj].also { resShape[jj] = resShape[ii] }
|
resShape[ii] = resShape[jj].also { resShape[jj] = resShape[ii] }
|
||||||
|
|
||||||
val resTensor = IntTensor(resShape, resBuffer)
|
val resTensor = IntTensor(resShape, resBuffer.asBuffer())
|
||||||
|
|
||||||
for (offset in 0 until n) {
|
for (offset in 0 until n) {
|
||||||
val oldMultiIndex = asIntTensor().indices.index(offset)
|
val oldMultiIndex = dt.indices.index(offset)
|
||||||
val newMultiIndex = oldMultiIndex.copyOf()
|
val newMultiIndex = oldMultiIndex.copyOf()
|
||||||
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.source[linearIndex] = dt.source[offset]
|
||||||
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + offset]
|
|
||||||
}
|
}
|
||||||
return resTensor
|
return resTensor
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Int>.view(shape: IntArray): IntTensor {
|
override fun Tensor<Int>.view(shape: IntArray): IntTensor {
|
||||||
checkView(asIntTensor(), shape)
|
checkView(asIntTensor(), shape)
|
||||||
return IntTensor(shape, asIntTensor().mutableBuffer.array(), asIntTensor().bufferStart)
|
return IntTensor(shape, asIntTensor().source)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Tensor<Int>.viewAs(other: StructureND<Int>): IntTensor =
|
override fun Tensor<Int>.viewAs(other: StructureND<Int>): IntTensor =
|
||||||
asIntTensor().view(other.shape)
|
view(other.shape)
|
||||||
|
|
||||||
|
override fun StructureND<Int>.dot(other: StructureND<Int>): IntTensor {
|
||||||
|
return if (dimension in 0..2 && other.dimension in 0..2) TODO("not implemented")
|
||||||
|
else error("Only vectors and matrices are allowed in non-broadcasting dot operation")
|
||||||
|
}
|
||||||
|
|
||||||
override fun diagonalEmbedding(
|
override fun diagonalEmbedding(
|
||||||
diagonalEntries: Tensor<Int>,
|
diagonalEntries: Tensor<Int>,
|
||||||
@ -374,7 +319,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
diagonalEntries.shape.slice(greaterDim - 1 until n - 1).toIntArray()
|
diagonalEntries.shape.slice(greaterDim - 1 until n - 1).toIntArray()
|
||||||
val resTensor = zeros(resShape)
|
val resTensor = zeros(resShape)
|
||||||
|
|
||||||
for (i in 0 until diagonalEntries.asIntTensor().numElements) {
|
for (i in 0 until diagonalEntries.asIntTensor().linearSize) {
|
||||||
val multiIndex = diagonalEntries.asIntTensor().indices.index(i)
|
val multiIndex = diagonalEntries.asIntTensor().indices.index(i)
|
||||||
|
|
||||||
var offset1 = 0
|
var offset1 = 0
|
||||||
@ -394,16 +339,27 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
return resTensor.asIntTensor()
|
return resTensor.asIntTensor()
|
||||||
}
|
}
|
||||||
|
|
||||||
private infix fun Tensor<Int>.eq(
|
/**
|
||||||
|
* Compares element-wise two int tensors
|
||||||
|
*
|
||||||
|
* @param other the tensor to compare with `input` tensor.
|
||||||
|
* @param epsilon permissible error when comparing two Int values.
|
||||||
|
* @return true if two tensors have the same shape and elements, false otherwise.
|
||||||
|
*/
|
||||||
|
public fun Tensor<Int>.eq(other: Tensor<Int>): Boolean =
|
||||||
|
asIntTensor().eq(other) { x, y -> x == y }
|
||||||
|
|
||||||
|
private fun Tensor<Int>.eq(
|
||||||
other: Tensor<Int>,
|
other: Tensor<Int>,
|
||||||
|
eqFunction: (Int, Int) -> Boolean,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
checkShapesCompatible(asIntTensor(), other)
|
checkShapesCompatible(asIntTensor(), other)
|
||||||
val n = asIntTensor().numElements
|
val n = asIntTensor().linearSize
|
||||||
if (n != other.asIntTensor().numElements) {
|
if (n != other.asIntTensor().linearSize) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for (i in 0 until n) {
|
for (i in 0 until n) {
|
||||||
if (asIntTensor().mutableBuffer[asIntTensor().bufferStart + i] != other.asIntTensor().mutableBuffer[other.asIntTensor().bufferStart + i]) {
|
if (!eqFunction(asIntTensor().source[i], other.asIntTensor().source[i])) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -421,10 +377,12 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
val shape = tensors[0].shape
|
val shape = tensors[0].shape
|
||||||
check(tensors.all { it.shape contentEquals shape }) { "Tensors must have same shapes" }
|
check(tensors.all { it.shape contentEquals shape }) { "Tensors must have same shapes" }
|
||||||
val resShape = intArrayOf(tensors.size) + shape
|
val resShape = intArrayOf(tensors.size) + shape
|
||||||
val resBuffer = tensors.flatMap {
|
// val resBuffer: List<Int> = tensors.flatMap {
|
||||||
it.asIntTensor().mutableBuffer.array().drop(it.asIntTensor().bufferStart).take(it.asIntTensor().numElements)
|
// it.asIntTensor().source.array.drop(it.asIntTensor().bufferStart)
|
||||||
}.toIntArray()
|
// .take(it.asIntTensor().linearSize)
|
||||||
return IntTensor(resShape, resBuffer, 0)
|
// }
|
||||||
|
val resBuffer = tensors.map { it.asIntTensor().source }.concat()
|
||||||
|
return IntTensor(resShape, resBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -435,14 +393,11 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
*/
|
*/
|
||||||
public fun Tensor<Int>.rowsByIndices(indices: IntArray): IntTensor = stack(indices.map { getTensor(it) })
|
public fun Tensor<Int>.rowsByIndices(indices: IntArray): IntTensor = stack(indices.map { getTensor(it) })
|
||||||
|
|
||||||
private inline fun StructureND<Int>.fold(foldFunction: (IntArray) -> Int): Int =
|
private inline fun StructureND<Int>.foldDimToInt(
|
||||||
foldFunction(asIntTensor().copyArray())
|
|
||||||
|
|
||||||
private inline fun <reified R : Any> StructureND<Int>.foldDim(
|
|
||||||
dim: Int,
|
dim: Int,
|
||||||
keepDim: Boolean,
|
keepDim: Boolean,
|
||||||
foldFunction: (IntArray) -> R,
|
foldFunction: (IntArray) -> Int,
|
||||||
): BufferedTensor<R> {
|
): IntTensor {
|
||||||
check(dim < dimension) { "Dimension $dim out of range $dimension" }
|
check(dim < dimension) { "Dimension $dim out of range $dimension" }
|
||||||
val resShape = if (keepDim) {
|
val resShape = if (keepDim) {
|
||||||
shape.take(dim).toIntArray() + intArrayOf(1) + shape.takeLast(dimension - dim - 1).toIntArray()
|
shape.take(dim).toIntArray() + intArrayOf(1) + shape.takeLast(dimension - dim - 1).toIntArray()
|
||||||
@ -451,9 +406,9 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
}
|
}
|
||||||
val resNumElements = resShape.reduce(Int::times)
|
val resNumElements = resShape.reduce(Int::times)
|
||||||
val init = foldFunction(IntArray(1) { 0 })
|
val init = foldFunction(IntArray(1) { 0 })
|
||||||
val resTensor = BufferedTensor(
|
val resTensor = IntTensor(
|
||||||
resShape,
|
resShape,
|
||||||
MutableBuffer.auto(resNumElements) { init }, 0
|
IntBuffer(resNumElements) { init }
|
||||||
)
|
)
|
||||||
for (index in resTensor.indices) {
|
for (index in resTensor.indices) {
|
||||||
val prefix = index.take(dim).toIntArray()
|
val prefix = index.take(dim).toIntArray()
|
||||||
@ -465,31 +420,33 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
|
|||||||
return resTensor
|
return resTensor
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun StructureND<Int>.sum(): Int = asIntTensor().fold { it.sum() }
|
|
||||||
|
override fun StructureND<Int>.sum(): Int = reduceElements { it.array.sum() }
|
||||||
|
|
||||||
override fun StructureND<Int>.sum(dim: Int, keepDim: Boolean): IntTensor =
|
override fun StructureND<Int>.sum(dim: Int, keepDim: Boolean): IntTensor =
|
||||||
foldDim(dim, keepDim) { x -> x.sum() }.asIntTensor()
|
foldDimToInt(dim, keepDim) { x -> x.sum() }
|
||||||
|
|
||||||
override fun StructureND<Int>.min(): Int = this.fold { it.minOrNull()!! }
|
override fun StructureND<Int>.min(): Int = reduceElements { it.array.min() }
|
||||||
|
|
||||||
override fun StructureND<Int>.min(dim: Int, keepDim: Boolean): IntTensor =
|
override fun StructureND<Int>.min(dim: Int, keepDim: Boolean): IntTensor =
|
||||||
foldDim(dim, keepDim) { x -> x.minOrNull()!! }.asIntTensor()
|
foldDimToInt(dim, keepDim) { x -> x.minOrNull()!! }
|
||||||
|
|
||||||
override fun StructureND<Int>.argMin(dim: Int, keepDim: Boolean): IntTensor =
|
override fun StructureND<Int>.argMin(dim: Int, keepDim: Boolean): Tensor<Int> = foldDimToInt(dim, keepDim) { x ->
|
||||||
foldDim(dim, keepDim) { x ->
|
x.withIndex().minBy { it.value }.index
|
||||||
x.withIndex().minByOrNull { it.value }?.index!!
|
}
|
||||||
}.asIntTensor()
|
|
||||||
|
|
||||||
override fun StructureND<Int>.max(): Int = this.fold { it.maxOrNull()!! }
|
override fun StructureND<Int>.max(): Int = reduceElements { it.array.max() }
|
||||||
|
|
||||||
override fun StructureND<Int>.max(dim: Int, keepDim: Boolean): IntTensor =
|
override fun StructureND<Int>.max(dim: Int, keepDim: Boolean): IntTensor =
|
||||||
foldDim(dim, keepDim) { x -> x.maxOrNull()!! }.asIntTensor()
|
foldDimToInt(dim, keepDim) { x -> x.max() }
|
||||||
|
|
||||||
|
|
||||||
override fun StructureND<Int>.argMax(dim: Int, keepDim: Boolean): IntTensor =
|
override fun StructureND<Int>.argMax(dim: Int, keepDim: Boolean): IntTensor =
|
||||||
foldDim(dim, keepDim) { x ->
|
foldDimToInt(dim, keepDim) { x ->
|
||||||
x.withIndex().maxByOrNull { it.value }?.index!!
|
x.withIndex().maxBy { it.value }.index
|
||||||
}.asIntTensor()
|
}
|
||||||
|
|
||||||
|
public fun StructureND<Int>.mean(): Double = sum().toDouble() / indices.linearSize
|
||||||
}
|
}
|
||||||
|
|
||||||
public val Int.Companion.tensorAlgebra: IntTensorAlgebra get() = IntTensorAlgebra
|
public val Int.Companion.tensorAlgebra: IntTensorAlgebra get() = IntTensorAlgebra
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.tensors.core.internal
|
package space.kscience.kmath.tensors.core.internal
|
||||||
|
|
||||||
|
import space.kscience.kmath.structures.asBuffer
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensor
|
import space.kscience.kmath.tensors.core.DoubleTensor
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@ -24,8 +25,8 @@ internal fun multiIndexBroadCasting(tensor: DoubleTensor, resTensor: DoubleTenso
|
|||||||
}
|
}
|
||||||
|
|
||||||
val curLinearIndex = tensor.indices.offset(curMultiIndex)
|
val curLinearIndex = tensor.indices.offset(curMultiIndex)
|
||||||
resTensor.mutableBuffer.array()[linearIndex] =
|
resTensor.source[linearIndex] =
|
||||||
tensor.mutableBuffer.array()[tensor.bufferStart + curLinearIndex]
|
tensor.source[curLinearIndex]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ internal fun broadcastTo(tensor: DoubleTensor, newShape: IntArray): DoubleTensor
|
|||||||
}
|
}
|
||||||
|
|
||||||
val n = newShape.reduce { acc, i -> acc * i }
|
val n = newShape.reduce { acc, i -> acc * i }
|
||||||
val resTensor = DoubleTensor(newShape, DoubleArray(n))
|
val resTensor = DoubleTensor(newShape, DoubleArray(n).asBuffer())
|
||||||
|
|
||||||
for (i in tensor.shape.indices) {
|
for (i in tensor.shape.indices) {
|
||||||
val curDim = tensor.shape[i]
|
val curDim = tensor.shape[i]
|
||||||
@ -82,7 +83,7 @@ internal fun broadcastTensors(vararg tensors: DoubleTensor): List<DoubleTensor>
|
|||||||
val n = totalShape.reduce { acc, i -> acc * i }
|
val n = totalShape.reduce { acc, i -> acc * i }
|
||||||
|
|
||||||
return tensors.map { tensor ->
|
return tensors.map { tensor ->
|
||||||
val resTensor = DoubleTensor(totalShape, DoubleArray(n))
|
val resTensor = DoubleTensor(totalShape, DoubleArray(n).asBuffer())
|
||||||
multiIndexBroadCasting(tensor, resTensor, n)
|
multiIndexBroadCasting(tensor, resTensor, n)
|
||||||
resTensor
|
resTensor
|
||||||
}
|
}
|
||||||
@ -106,17 +107,17 @@ internal fun broadcastOuterTensors(vararg tensors: DoubleTensor): List<DoubleTen
|
|||||||
for (tensor in tensors) {
|
for (tensor in tensors) {
|
||||||
val matrixShape = tensor.shape.sliceArray(tensor.shape.size - 2 until tensor.shape.size).copyOf()
|
val matrixShape = tensor.shape.sliceArray(tensor.shape.size - 2 until tensor.shape.size).copyOf()
|
||||||
val matrixSize = matrixShape[0] * matrixShape[1]
|
val matrixSize = matrixShape[0] * matrixShape[1]
|
||||||
val matrix = DoubleTensor(matrixShape, DoubleArray(matrixSize))
|
val matrix = DoubleTensor(matrixShape, DoubleArray(matrixSize).asBuffer())
|
||||||
|
|
||||||
val outerTensor = DoubleTensor(totalShape, DoubleArray(n))
|
val outerTensor = DoubleTensor(totalShape, DoubleArray(n).asBuffer())
|
||||||
val resTensor = DoubleTensor(totalShape + matrixShape, DoubleArray(n * matrixSize))
|
val resTensor = DoubleTensor(totalShape + matrixShape, DoubleArray(n * matrixSize).asBuffer())
|
||||||
|
|
||||||
for (linearIndex in 0 until n) {
|
for (linearIndex in 0 until n) {
|
||||||
val totalMultiIndex = outerTensor.indices.index(linearIndex)
|
val totalMultiIndex = outerTensor.indices.index(linearIndex)
|
||||||
var curMultiIndex = tensor.shape.sliceArray(0..tensor.shape.size - 3).copyOf()
|
var curMultiIndex = tensor.shape.sliceArray(0..tensor.shape.size - 3).copyOf()
|
||||||
curMultiIndex = IntArray(totalMultiIndex.size - curMultiIndex.size) { 1 } + curMultiIndex
|
curMultiIndex = IntArray(totalMultiIndex.size - curMultiIndex.size) { 1 } + curMultiIndex
|
||||||
|
|
||||||
val newTensor = DoubleTensor(curMultiIndex + matrixShape, tensor.mutableBuffer.array())
|
val newTensor = DoubleTensor(curMultiIndex + matrixShape, tensor.source)
|
||||||
|
|
||||||
for (i in curMultiIndex.indices) {
|
for (i in curMultiIndex.indices) {
|
||||||
if (curMultiIndex[i] != 1) {
|
if (curMultiIndex[i] != 1) {
|
||||||
@ -136,8 +137,8 @@ internal fun broadcastOuterTensors(vararg tensors: DoubleTensor): List<DoubleTen
|
|||||||
matrix.indices.index(i)
|
matrix.indices.index(i)
|
||||||
)
|
)
|
||||||
|
|
||||||
resTensor.mutableBuffer.array()[resTensor.bufferStart + newLinearIndex] =
|
resTensor.source[newLinearIndex] =
|
||||||
newTensor.mutableBuffer.array()[newTensor.bufferStart + curLinearIndex]
|
newTensor.source[curLinearIndex]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add(resTensor)
|
add(resTensor)
|
||||||
|
@ -9,9 +9,10 @@ import space.kscience.kmath.nd.StructureND
|
|||||||
import space.kscience.kmath.tensors.api.Tensor
|
import space.kscience.kmath.tensors.api.Tensor
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensor
|
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.asDoubleTensor
|
||||||
|
|
||||||
|
|
||||||
internal fun checkEmptyShape(shape: IntArray) =
|
internal fun checkNotEmptyShape(shape: IntArray) =
|
||||||
check(shape.isNotEmpty()) {
|
check(shape.isNotEmpty()) {
|
||||||
"Illegal empty shape provided"
|
"Illegal empty shape provided"
|
||||||
}
|
}
|
||||||
@ -25,7 +26,8 @@ internal fun checkBufferShapeConsistency(shape: IntArray, buffer: DoubleArray) =
|
|||||||
"Inconsistent shape ${shape.toList()} for buffer of size ${buffer.size} provided"
|
"Inconsistent shape ${shape.toList()} for buffer of size ${buffer.size} provided"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <T> checkShapesCompatible(a: StructureND<T>, b: StructureND<T>) =
|
@PublishedApi
|
||||||
|
internal fun <T> checkShapesCompatible(a: StructureND<T>, b: StructureND<T>): Unit =
|
||||||
check(a.shape contentEquals b.shape) {
|
check(a.shape contentEquals b.shape) {
|
||||||
"Incompatible shapes ${a.shape.toList()} and ${b.shape.toList()} "
|
"Incompatible shapes ${a.shape.toList()} and ${b.shape.toList()} "
|
||||||
}
|
}
|
||||||
@ -50,15 +52,14 @@ internal fun checkSquareMatrix(shape: IntArray) {
|
|||||||
|
|
||||||
internal fun DoubleTensorAlgebra.checkSymmetric(
|
internal fun DoubleTensorAlgebra.checkSymmetric(
|
||||||
tensor: Tensor<Double>, epsilon: Double = 1e-6,
|
tensor: Tensor<Double>, epsilon: Double = 1e-6,
|
||||||
) =
|
) = check(tensor.eq(tensor.transposed(), epsilon)) {
|
||||||
check(tensor.eq(tensor.transpose(), epsilon)) {
|
|
||||||
"Tensor is not symmetric about the last 2 dimensions at precision $epsilon"
|
"Tensor is not symmetric about the last 2 dimensions at precision $epsilon"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun DoubleTensorAlgebra.checkPositiveDefinite(tensor: DoubleTensor, epsilon: Double = 1e-6) {
|
internal fun DoubleTensorAlgebra.checkPositiveDefinite(tensor: DoubleTensor, epsilon: Double = 1e-6) {
|
||||||
checkSymmetric(tensor, epsilon)
|
checkSymmetric(tensor, epsilon)
|
||||||
for (mat in tensor.matrixSequence())
|
for (mat in tensor.matrixSequence())
|
||||||
check(mat.toTensor().detLU().value() > 0.0) {
|
check(mat.asDoubleTensor().detLU().value() > 0.0) {
|
||||||
"Tensor contains matrices which are not positive definite ${mat.toTensor().detLU().value()}"
|
"Tensor contains matrices which are not positive definite ${mat.asDoubleTensor().detLU().value()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2022 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.tensors.core.internal
|
||||||
|
|
||||||
|
import space.kscience.kmath.nd.MutableStructure2D
|
||||||
|
import space.kscience.kmath.nd.Structure2D
|
||||||
|
import space.kscience.kmath.nd.as2D
|
||||||
|
import space.kscience.kmath.nd.get
|
||||||
|
import space.kscience.kmath.operations.asSequence
|
||||||
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
|
import space.kscience.kmath.structures.VirtualBuffer
|
||||||
|
import space.kscience.kmath.structures.asBuffer
|
||||||
|
import space.kscience.kmath.structures.indices
|
||||||
|
import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra.eye
|
||||||
|
import space.kscience.kmath.tensors.core.BufferedTensor
|
||||||
|
import space.kscience.kmath.tensors.core.DoubleTensor
|
||||||
|
import space.kscience.kmath.tensors.core.OffsetDoubleBuffer
|
||||||
|
import space.kscience.kmath.tensors.core.copyToTensor
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
|
||||||
|
internal fun MutableStructure2D<Double>.jacobiHelper(
|
||||||
|
maxIteration: Int,
|
||||||
|
epsilon: Double,
|
||||||
|
): Pair<DoubleBuffer, Structure2D<Double>> {
|
||||||
|
val n = rowNum
|
||||||
|
val A_ = copyToTensor()
|
||||||
|
val V = eye(n)
|
||||||
|
val D = DoubleBuffer(n) { get(it, it) }
|
||||||
|
val B = DoubleBuffer(n) { get(it, it) }
|
||||||
|
val Z = DoubleBuffer(n) { 0.0 }
|
||||||
|
|
||||||
|
// assume that buffered tensor is square matrix
|
||||||
|
operator fun DoubleTensor.get(i: Int, j: Int): Double {
|
||||||
|
return source[i * shape[0] + j]
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun BufferedTensor<Double>.set(i: Int, j: Int, value: Double) {
|
||||||
|
source[i * shape[0] + j] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maxOffDiagonal(matrix: BufferedTensor<Double>): Double {
|
||||||
|
var maxOffDiagonalElement = 0.0
|
||||||
|
for (i in 0 until n - 1) {
|
||||||
|
for (j in i + 1 until n) {
|
||||||
|
maxOffDiagonalElement = max(maxOffDiagonalElement, abs(matrix[i, j]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxOffDiagonalElement
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rotate(a: BufferedTensor<Double>, s: Double, tau: Double, i: Int, j: Int, k: Int, l: Int) {
|
||||||
|
val g = a[i, j]
|
||||||
|
val h = a[k, l]
|
||||||
|
a[i, j] = g - s * (h + g * tau)
|
||||||
|
a[k, l] = h + s * (g - h * tau)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun jacobiIteration(
|
||||||
|
a: BufferedTensor<Double>,
|
||||||
|
v: BufferedTensor<Double>,
|
||||||
|
d: DoubleBuffer,
|
||||||
|
z: DoubleBuffer,
|
||||||
|
) {
|
||||||
|
for (ip in 0 until n - 1) {
|
||||||
|
for (iq in ip + 1 until n) {
|
||||||
|
val g = 100.0 * abs(a[ip, iq])
|
||||||
|
|
||||||
|
if (g <= epsilon * abs(d[ip]) && g <= epsilon * abs(d[iq])) {
|
||||||
|
a[ip, iq] = 0.0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var h = d[iq] - d[ip]
|
||||||
|
val t = when {
|
||||||
|
g <= epsilon * abs(h) -> (a[ip, iq]) / h
|
||||||
|
else -> {
|
||||||
|
val theta = 0.5 * h / (a[ip, iq])
|
||||||
|
val denominator = abs(theta) + sqrt(1.0 + theta * theta)
|
||||||
|
if (theta < 0.0) -1.0 / denominator else 1.0 / denominator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val c = 1.0 / sqrt(1 + t * t)
|
||||||
|
val s = t * c
|
||||||
|
val tau = s / (1.0 + c)
|
||||||
|
h = t * a[ip, iq]
|
||||||
|
z[ip] -= h
|
||||||
|
z[iq] += h
|
||||||
|
d[ip] -= h
|
||||||
|
d[iq] += h
|
||||||
|
a[ip, iq] = 0.0
|
||||||
|
|
||||||
|
for (j in 0 until ip) {
|
||||||
|
rotate(a, s, tau, j, ip, j, iq)
|
||||||
|
}
|
||||||
|
for (j in (ip + 1) until iq) {
|
||||||
|
rotate(a, s, tau, ip, j, j, iq)
|
||||||
|
}
|
||||||
|
for (j in (iq + 1) until n) {
|
||||||
|
rotate(a, s, tau, ip, j, iq, j)
|
||||||
|
}
|
||||||
|
for (j in 0 until n) {
|
||||||
|
rotate(v, s, tau, j, ip, j, iq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDiagonal(
|
||||||
|
d: DoubleBuffer,
|
||||||
|
z: DoubleBuffer,
|
||||||
|
b: DoubleBuffer,
|
||||||
|
) {
|
||||||
|
for (ip in 0 until d.size) {
|
||||||
|
b[ip] += z[ip]
|
||||||
|
d[ip] = b[ip]
|
||||||
|
z[ip] = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sm = maxOffDiagonal(A_)
|
||||||
|
for (iteration in 0 until maxIteration) {
|
||||||
|
if (sm < epsilon) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
jacobiIteration(A_, V, D, Z)
|
||||||
|
updateDiagonal(D, Z, B)
|
||||||
|
sm = maxOffDiagonal(A_)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO sort eigenvalues
|
||||||
|
return D to V.as2D()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenate a list of arrays
|
||||||
|
*/
|
||||||
|
internal fun List<OffsetDoubleBuffer>.concat(): DoubleBuffer {
|
||||||
|
val array = DoubleArray(sumOf { it.size })
|
||||||
|
var pointer = 0
|
||||||
|
while (pointer < array.size) {
|
||||||
|
for (bufferIndex in indices) {
|
||||||
|
val buffer = get(bufferIndex)
|
||||||
|
for (innerIndex in buffer.indices) {
|
||||||
|
array[pointer] = buffer[innerIndex]
|
||||||
|
pointer++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array.asBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val DoubleTensor.vectors: VirtualBuffer<DoubleTensor>
|
||||||
|
get() {
|
||||||
|
val n = shape.size
|
||||||
|
val vectorOffset = shape[n - 1]
|
||||||
|
val vectorShape = intArrayOf(shape.last())
|
||||||
|
|
||||||
|
return VirtualBuffer(linearSize / vectorOffset) { index ->
|
||||||
|
val offset = index * vectorOffset
|
||||||
|
DoubleTensor(vectorShape, source.view(offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun DoubleTensor.vectorSequence(): Sequence<DoubleTensor> = vectors.asSequence()
|
||||||
|
|
||||||
|
|
||||||
|
internal val DoubleTensor.matrices: VirtualBuffer<DoubleTensor>
|
||||||
|
get(){
|
||||||
|
val n = shape.size
|
||||||
|
check(n >= 2) { "Expected tensor with 2 or more dimensions, got size $n" }
|
||||||
|
val matrixOffset = shape[n - 1] * shape[n - 2]
|
||||||
|
val matrixShape = intArrayOf(shape[n - 2], shape[n - 1])
|
||||||
|
|
||||||
|
return VirtualBuffer(linearSize / matrixOffset) { index ->
|
||||||
|
val offset = index * matrixOffset
|
||||||
|
DoubleTensor(matrixShape, source.view(offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun DoubleTensor.matrixSequence(): Sequence<DoubleTensor> = matrices.asSequence()
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2022 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.tensors.core.internal
|
||||||
|
|
||||||
|
import space.kscience.kmath.operations.asSequence
|
||||||
|
import space.kscience.kmath.structures.IntBuffer
|
||||||
|
import space.kscience.kmath.structures.VirtualBuffer
|
||||||
|
import space.kscience.kmath.structures.asBuffer
|
||||||
|
import space.kscience.kmath.structures.indices
|
||||||
|
import space.kscience.kmath.tensors.core.IntTensor
|
||||||
|
import space.kscience.kmath.tensors.core.OffsetIntBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenate a list of arrays
|
||||||
|
*/
|
||||||
|
internal fun List<OffsetIntBuffer>.concat(): IntBuffer {
|
||||||
|
val array = IntArray(sumOf { it.size })
|
||||||
|
var pointer = 0
|
||||||
|
while (pointer < array.size) {
|
||||||
|
for (bufferIndex in indices) {
|
||||||
|
val buffer = get(bufferIndex)
|
||||||
|
for (innerIndex in buffer.indices) {
|
||||||
|
array[pointer] = buffer[innerIndex]
|
||||||
|
pointer++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array.asBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal val IntTensor.vectors: VirtualBuffer<IntTensor>
|
||||||
|
get() {
|
||||||
|
val n = shape.size
|
||||||
|
val vectorOffset = shape[n - 1]
|
||||||
|
val vectorShape = intArrayOf(shape.last())
|
||||||
|
|
||||||
|
return VirtualBuffer(linearSize / vectorOffset) { index ->
|
||||||
|
val offset = index * vectorOffset
|
||||||
|
IntTensor(vectorShape, source.view(offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun IntTensor.vectorSequence(): Sequence<IntTensor> = vectors.asSequence()
|
||||||
|
|
||||||
|
|
||||||
|
internal val IntTensor.matrices: VirtualBuffer<IntTensor>
|
||||||
|
get(){
|
||||||
|
val n = shape.size
|
||||||
|
check(n >= 2) { "Expected tensor with 2 or more dimensions, got size $n" }
|
||||||
|
val matrixOffset = shape[n - 1] * shape[n - 2]
|
||||||
|
val matrixShape = intArrayOf(shape[n - 2], shape[n - 1])
|
||||||
|
|
||||||
|
return VirtualBuffer(linearSize / matrixOffset) { index ->
|
||||||
|
val offset = index * matrixOffset
|
||||||
|
IntTensor(matrixShape, source.view(offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IntTensor.matrixSequence(): Sequence<IntTensor> = matrices.asSequence()
|
@ -5,75 +5,33 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.tensors.core.internal
|
package space.kscience.kmath.tensors.core.internal
|
||||||
|
|
||||||
import space.kscience.kmath.nd.MutableStructure1D
|
import space.kscience.kmath.nd.*
|
||||||
import space.kscience.kmath.nd.MutableStructure2D
|
|
||||||
import space.kscience.kmath.nd.as1D
|
|
||||||
import space.kscience.kmath.nd.as2D
|
|
||||||
import space.kscience.kmath.operations.asSequence
|
|
||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
import space.kscience.kmath.structures.VirtualBuffer
|
import space.kscience.kmath.structures.IntBuffer
|
||||||
import space.kscience.kmath.tensors.core.BufferedTensor
|
import space.kscience.kmath.structures.asBuffer
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensor
|
import space.kscience.kmath.structures.indices
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra
|
import space.kscience.kmath.tensors.core.*
|
||||||
import space.kscience.kmath.tensors.core.IntTensor
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
internal val <T> BufferedTensor<T>.vectors: VirtualBuffer<BufferedTensor<T>>
|
|
||||||
get() {
|
|
||||||
val n = shape.size
|
|
||||||
val vectorOffset = shape[n - 1]
|
|
||||||
val vectorShape = intArrayOf(shape.last())
|
|
||||||
|
|
||||||
return VirtualBuffer(numElements / vectorOffset) { index ->
|
|
||||||
val offset = index * vectorOffset
|
|
||||||
BufferedTensor(vectorShape, mutableBuffer, bufferStart + offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun <T> BufferedTensor<T>.vectorSequence(): Sequence<BufferedTensor<T>> = vectors.asSequence()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A random access alternative to [matrixSequence]
|
|
||||||
*/
|
|
||||||
internal val <T> BufferedTensor<T>.matrices: VirtualBuffer<BufferedTensor<T>>
|
|
||||||
get() {
|
|
||||||
val n = shape.size
|
|
||||||
check(n >= 2) { "Expected tensor with 2 or more dimensions, got size $n" }
|
|
||||||
val matrixOffset = shape[n - 1] * shape[n - 2]
|
|
||||||
val matrixShape = intArrayOf(shape[n - 2], shape[n - 1])
|
|
||||||
|
|
||||||
return VirtualBuffer(numElements / matrixOffset) { index ->
|
|
||||||
val offset = index * matrixOffset
|
|
||||||
BufferedTensor(matrixShape, mutableBuffer, bufferStart + offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <T> BufferedTensor<T>.matrixSequence(): Sequence<BufferedTensor<T>> = matrices.asSequence()
|
|
||||||
|
|
||||||
internal fun dotTo(
|
internal fun dotTo(
|
||||||
a: BufferedTensor<Double>,
|
a: BufferedTensor<Double>,
|
||||||
b: BufferedTensor<Double>,
|
b: BufferedTensor<Double>,
|
||||||
res: BufferedTensor<Double>,
|
res: BufferedTensor<Double>,
|
||||||
l: Int, m: Int, n: Int,
|
l: Int, m: Int, n: Int,
|
||||||
) {
|
) {
|
||||||
val aStart = a.bufferStart
|
val aBuffer = a.source
|
||||||
val bStart = b.bufferStart
|
val bBuffer = b.source
|
||||||
val resStart = res.bufferStart
|
val resBuffer = res.source
|
||||||
|
|
||||||
val aBuffer = a.mutableBuffer
|
|
||||||
val bBuffer = b.mutableBuffer
|
|
||||||
val resBuffer = res.mutableBuffer
|
|
||||||
|
|
||||||
for (i in 0 until l) {
|
for (i in 0 until l) {
|
||||||
for (j in 0 until n) {
|
for (j in 0 until n) {
|
||||||
var curr = 0.0
|
var curr = 0.0
|
||||||
for (k in 0 until m) {
|
for (k in 0 until m) {
|
||||||
curr += aBuffer[aStart + i * m + k] * bBuffer[bStart + k * n + j]
|
curr += aBuffer[i * m + k] * bBuffer[k * n + j]
|
||||||
}
|
}
|
||||||
resBuffer[resStart + i * n + j] = curr
|
resBuffer[i * n + j] = curr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,7 +87,7 @@ internal fun luHelper(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <T> BufferedTensor<T>.setUpPivots(): IntTensor {
|
internal fun <T> StructureND<T>.setUpPivots(): IntTensor {
|
||||||
val n = this.shape.size
|
val n = this.shape.size
|
||||||
val m = this.shape.last()
|
val m = this.shape.last()
|
||||||
val pivotsShape = IntArray(n - 1) { i -> this.shape[i] }
|
val pivotsShape = IntArray(n - 1) { i -> this.shape[i] }
|
||||||
@ -137,17 +95,17 @@ internal fun <T> BufferedTensor<T>.setUpPivots(): IntTensor {
|
|||||||
|
|
||||||
return IntTensor(
|
return IntTensor(
|
||||||
pivotsShape,
|
pivotsShape,
|
||||||
IntArray(pivotsShape.reduce(Int::times)) { 0 }
|
IntBuffer(pivotsShape.reduce(Int::times)) { 0 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun DoubleTensorAlgebra.computeLU(
|
internal fun DoubleTensorAlgebra.computeLU(
|
||||||
tensor: DoubleTensor,
|
tensor: StructureND<Double>,
|
||||||
epsilon: Double,
|
epsilon: Double,
|
||||||
): Pair<DoubleTensor, IntTensor>? {
|
): Pair<DoubleTensor, IntTensor>? {
|
||||||
|
|
||||||
checkSquareMatrix(tensor.shape)
|
checkSquareMatrix(tensor.shape)
|
||||||
val luTensor = tensor.copy()
|
val luTensor = tensor.copyToTensor()
|
||||||
val pivotsTensor = tensor.setUpPivots()
|
val pivotsTensor = tensor.setUpPivots()
|
||||||
|
|
||||||
for ((lu, pivots) in luTensor.matrixSequence().zip(pivotsTensor.vectorSequence()))
|
for ((lu, pivots) in luTensor.matrixSequence().zip(pivotsTensor.vectorSequence()))
|
||||||
@ -253,8 +211,8 @@ internal fun DoubleTensorAlgebra.qrHelper(
|
|||||||
checkSquareMatrix(matrix.shape)
|
checkSquareMatrix(matrix.shape)
|
||||||
val n = matrix.shape[0]
|
val n = matrix.shape[0]
|
||||||
val qM = q.as2D()
|
val qM = q.as2D()
|
||||||
val matrixT = matrix.transpose(0, 1)
|
val matrixT = matrix.transposed(0, 1)
|
||||||
val qT = q.transpose(0, 1)
|
val qT = q.transposed(0, 1)
|
||||||
|
|
||||||
for (j in 0 until n) {
|
for (j in 0 until n) {
|
||||||
val v = matrixT.getTensor(j)
|
val v = matrixT.getTensor(j)
|
||||||
@ -280,10 +238,10 @@ internal fun DoubleTensorAlgebra.svd1d(a: DoubleTensor, epsilon: Double = 1e-10)
|
|||||||
var v: DoubleTensor
|
var v: DoubleTensor
|
||||||
val b: DoubleTensor
|
val b: DoubleTensor
|
||||||
if (n > m) {
|
if (n > m) {
|
||||||
b = a.transpose(0, 1).dot(a)
|
b = a.transposed(0, 1).dot(a)
|
||||||
v = DoubleTensor(intArrayOf(m), getRandomUnitVector(m, 0))
|
v = DoubleTensor(intArrayOf(m), getRandomUnitVector(m, 0))
|
||||||
} else {
|
} else {
|
||||||
b = a.dot(a.transpose(0, 1))
|
b = a.dot(a.transposed(0, 1))
|
||||||
v = DoubleTensor(intArrayOf(n), getRandomUnitVector(n, 0))
|
v = DoubleTensor(intArrayOf(n), getRandomUnitVector(n, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +266,7 @@ internal fun DoubleTensorAlgebra.svdHelper(
|
|||||||
val (matrixU, matrixS, matrixV) = USV
|
val (matrixU, matrixS, matrixV) = USV
|
||||||
|
|
||||||
for (k in 0 until min(n, m)) {
|
for (k in 0 until min(n, m)) {
|
||||||
var a = matrix.copy()
|
var a = matrix.copyToTensor()
|
||||||
for ((singularValue, u, v) in res.slice(0 until k)) {
|
for ((singularValue, u, v) in res.slice(0 until k)) {
|
||||||
val outerProduct = DoubleArray(u.shape[0] * v.shape[0])
|
val outerProduct = DoubleArray(u.shape[0] * v.shape[0])
|
||||||
for (i in 0 until u.shape[0]) {
|
for (i in 0 until u.shape[0]) {
|
||||||
@ -316,7 +274,7 @@ internal fun DoubleTensorAlgebra.svdHelper(
|
|||||||
outerProduct[i * v.shape[0] + j] = u.getTensor(i).value() * v.getTensor(j).value()
|
outerProduct[i * v.shape[0] + j] = u.getTensor(i).value() * v.getTensor(j).value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a = a - singularValue.times(DoubleTensor(intArrayOf(u.shape[0], v.shape[0]), outerProduct))
|
a = a - singularValue.times(DoubleTensor(intArrayOf(u.shape[0], v.shape[0]), outerProduct.asBuffer()))
|
||||||
}
|
}
|
||||||
var v: DoubleTensor
|
var v: DoubleTensor
|
||||||
var u: DoubleTensor
|
var u: DoubleTensor
|
||||||
@ -328,7 +286,7 @@ internal fun DoubleTensorAlgebra.svdHelper(
|
|||||||
u = u.times(1.0 / norm)
|
u = u.times(1.0 / norm)
|
||||||
} else {
|
} else {
|
||||||
u = svd1d(a, epsilon)
|
u = svd1d(a, epsilon)
|
||||||
v = matrix.transpose(0, 1).dot(u)
|
v = matrix.transposed(0, 1).dot(u)
|
||||||
norm = DoubleTensorAlgebra { (v dot v).sqrt().value() }
|
norm = DoubleTensorAlgebra { (v dot v).sqrt().value() }
|
||||||
v = v.times(1.0 / norm)
|
v = v.times(1.0 / norm)
|
||||||
}
|
}
|
||||||
@ -337,15 +295,15 @@ internal fun DoubleTensorAlgebra.svdHelper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val s = res.map { it.first }.toDoubleArray()
|
val s = res.map { it.first }.toDoubleArray()
|
||||||
val uBuffer = res.map { it.second }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
|
val uBuffer = res.map { it.second.source }.concat()
|
||||||
val vBuffer = res.map { it.third }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
|
val vBuffer = res.map { it.third.source }.concat()
|
||||||
for (i in uBuffer.indices) {
|
for (i in uBuffer.indices) {
|
||||||
matrixU.mutableBuffer.array()[matrixU.bufferStart + i] = uBuffer[i]
|
matrixU.source[i] = uBuffer[i]
|
||||||
}
|
}
|
||||||
for (i in s.indices) {
|
for (i in s.indices) {
|
||||||
matrixS.mutableBuffer.array()[matrixS.bufferStart + i] = s[i]
|
matrixS.source[i] = s[i]
|
||||||
}
|
}
|
||||||
for (i in vBuffer.indices) {
|
for (i in vBuffer.indices) {
|
||||||
matrixV.mutableBuffer.array()[matrixV.bufferStart + i] = vBuffer[i]
|
matrixV.source[i] = vBuffer[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018-2022 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.tensors.core.internal
|
|
||||||
|
|
||||||
import space.kscience.kmath.nd.MutableBufferND
|
|
||||||
import space.kscience.kmath.nd.StructureND
|
|
||||||
import space.kscience.kmath.structures.asMutableBuffer
|
|
||||||
import space.kscience.kmath.tensors.core.BufferedTensor
|
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensor
|
|
||||||
import space.kscience.kmath.tensors.core.IntTensor
|
|
||||||
import space.kscience.kmath.tensors.core.TensorLinearStructure
|
|
||||||
|
|
||||||
internal fun BufferedTensor<Int>.toTensor(): IntTensor =
|
|
||||||
IntTensor(this.shape, this.mutableBuffer.array(), this.bufferStart)
|
|
||||||
|
|
||||||
internal fun BufferedTensor<Double>.toTensor(): DoubleTensor =
|
|
||||||
DoubleTensor(this.shape, this.mutableBuffer.array(), this.bufferStart)
|
|
||||||
|
|
||||||
internal fun <T> StructureND<T>.copyToBufferedTensor(): BufferedTensor<T> =
|
|
||||||
BufferedTensor(
|
|
||||||
this.shape,
|
|
||||||
TensorLinearStructure(this.shape).asSequence().map(this::get).toMutableList().asMutableBuffer(),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
internal fun <T> StructureND<T>.toBufferedTensor(): BufferedTensor<T> = when (this) {
|
|
||||||
is BufferedTensor<T> -> this
|
|
||||||
is MutableBufferND<T> -> if (this.indices == TensorLinearStructure(this.shape)) {
|
|
||||||
BufferedTensor(this.shape, this.buffer, 0)
|
|
||||||
} else {
|
|
||||||
this.copyToBufferedTensor()
|
|
||||||
}
|
|
||||||
else -> this.copyToBufferedTensor()
|
|
||||||
}
|
|
@ -6,41 +6,25 @@
|
|||||||
package space.kscience.kmath.tensors.core.internal
|
package space.kscience.kmath.tensors.core.internal
|
||||||
|
|
||||||
import space.kscience.kmath.nd.as1D
|
import space.kscience.kmath.nd.as1D
|
||||||
|
import space.kscience.kmath.operations.DoubleBufferOps.Companion.map
|
||||||
import space.kscience.kmath.operations.toMutableList
|
import space.kscience.kmath.operations.toMutableList
|
||||||
import space.kscience.kmath.samplers.GaussianSampler
|
import space.kscience.kmath.samplers.GaussianSampler
|
||||||
import space.kscience.kmath.stat.RandomGenerator
|
import space.kscience.kmath.stat.RandomGenerator
|
||||||
import space.kscience.kmath.structures.*
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
import space.kscience.kmath.tensors.core.BufferedTensor
|
import space.kscience.kmath.tensors.core.BufferedTensor
|
||||||
import space.kscience.kmath.tensors.core.DoubleTensor
|
import space.kscience.kmath.tensors.core.DoubleTensor
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
/**
|
internal fun getRandomNormals(n: Int, seed: Long): DoubleBuffer {
|
||||||
* Returns a reference to [IntArray] containing all the elements of this [Buffer] or copy the data.
|
|
||||||
*/
|
|
||||||
internal fun Buffer<Int>.array(): IntArray = when (this) {
|
|
||||||
is IntBuffer -> array
|
|
||||||
else -> this.toIntArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reference to [DoubleArray] containing all the elements of this [Buffer] or copy the data.
|
|
||||||
*/
|
|
||||||
@PublishedApi
|
|
||||||
internal fun Buffer<Double>.array(): DoubleArray = when (this) {
|
|
||||||
is DoubleBuffer -> array
|
|
||||||
else -> this.toDoubleArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun getRandomNormals(n: Int, seed: Long): DoubleArray {
|
|
||||||
val distribution = GaussianSampler(0.0, 1.0)
|
val distribution = GaussianSampler(0.0, 1.0)
|
||||||
val generator = RandomGenerator.default(seed)
|
val generator = RandomGenerator.default(seed)
|
||||||
return distribution.sample(generator).nextBufferBlocking(n).toDoubleArray()
|
return distribution.sample(generator).nextBufferBlocking(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getRandomUnitVector(n: Int, seed: Long): DoubleArray {
|
internal fun getRandomUnitVector(n: Int, seed: Long): DoubleBuffer {
|
||||||
val unnorm = getRandomNormals(n, seed)
|
val unnorm: DoubleBuffer = getRandomNormals(n, seed)
|
||||||
val norm = sqrt(unnorm.sumOf { it * it })
|
val norm = sqrt(unnorm.array.sumOf { it * it })
|
||||||
return unnorm.map { it / norm }.toDoubleArray()
|
return unnorm.map { it / norm }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun minusIndexFrom(n: Int, i: Int): Int = if (i >= 0) i else {
|
internal fun minusIndexFrom(n: Int, i: Int): Int = if (i >= 0) i else {
|
||||||
@ -71,6 +55,7 @@ internal fun format(value: Double, digits: Int = 4): String = buildString {
|
|||||||
append("e+")
|
append("e+")
|
||||||
append(order)
|
append(order)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
append('e')
|
append('e')
|
||||||
append(order)
|
append(order)
|
||||||
@ -116,7 +101,7 @@ internal fun DoubleTensor.toPrettyString(): String = buildString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
offset += vectorSize
|
offset += vectorSize
|
||||||
if (this@toPrettyString.numElements == offset) {
|
if (this@toPrettyString.linearSize == offset) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018-2022 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.tensors.core
|
|
||||||
|
|
||||||
import space.kscience.kmath.nd.StructureND
|
|
||||||
import space.kscience.kmath.tensors.api.Tensor
|
|
||||||
import space.kscience.kmath.tensors.core.internal.toBufferedTensor
|
|
||||||
import space.kscience.kmath.tensors.core.internal.toTensor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Casts [Tensor] of [Double] to [DoubleTensor]
|
|
||||||
*/
|
|
||||||
public fun StructureND<Double>.asDoubleTensor(): DoubleTensor = when (this) {
|
|
||||||
is DoubleTensor -> this
|
|
||||||
else -> this.toBufferedTensor().toTensor()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Casts [Tensor] of [Int] to [IntTensor]
|
|
||||||
*/
|
|
||||||
public fun StructureND<Int>.asIntTensor(): IntTensor = when (this) {
|
|
||||||
is IntTensor -> this
|
|
||||||
else -> this.toBufferedTensor().toTensor()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy-protected [DoubleArray] of tensor elements
|
|
||||||
*/
|
|
||||||
public fun DoubleTensor.copyArray(): DoubleArray {
|
|
||||||
//TODO use ArrayCopy
|
|
||||||
return DoubleArray(numElements) { i ->
|
|
||||||
mutableBuffer[bufferStart + i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy-protected [IntArray] of tensor elements
|
|
||||||
*/
|
|
||||||
public fun IntTensor.copyArray(): IntArray {
|
|
||||||
return IntArray(numElements) { i ->
|
|
||||||
mutableBuffer[bufferStart + i]
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2022 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.tensors.core
|
||||||
|
|
||||||
|
import space.kscience.kmath.nd.StructureND
|
||||||
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
|
import space.kscience.kmath.structures.asBuffer
|
||||||
|
import space.kscience.kmath.tensors.api.Tensor
|
||||||
|
|
||||||
|
|
||||||
|
public fun StructureND<Double>.copyToTensor(): DoubleTensor = if (this is DoubleTensor) {
|
||||||
|
DoubleTensor(shape, source.copy())
|
||||||
|
} else {
|
||||||
|
DoubleTensor(
|
||||||
|
shape,
|
||||||
|
TensorLinearStructure(this.shape).map(this::get).toDoubleArray().asBuffer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun StructureND<Int>.toDoubleTensor(): DoubleTensor {
|
||||||
|
return if (this is IntTensor) {
|
||||||
|
DoubleTensor(
|
||||||
|
shape,
|
||||||
|
DoubleBuffer(linearSize) { source[it].toDouble() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val tensor = DoubleTensorAlgebra.zeroesLike(this)
|
||||||
|
indices.forEach {
|
||||||
|
tensor[it] = get(it).toDouble()
|
||||||
|
}
|
||||||
|
return tensor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts [Tensor] of [Double] to [DoubleTensor]
|
||||||
|
*/
|
||||||
|
public fun StructureND<Double>.asDoubleTensor(): DoubleTensor = when (this) {
|
||||||
|
is DoubleTensor -> this
|
||||||
|
else -> copyToTensor()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts [Tensor] of [Int] to [IntTensor]
|
||||||
|
*/
|
||||||
|
public fun StructureND<Int>.asIntTensor(): IntTensor = when (this) {
|
||||||
|
is IntTensor -> this
|
||||||
|
else -> IntTensor(
|
||||||
|
this.shape,
|
||||||
|
TensorLinearStructure(this.shape).map(this::get).toIntArray().asBuffer()
|
||||||
|
)
|
||||||
|
}
|
@ -6,7 +6,10 @@
|
|||||||
package space.kscience.kmath.tensors.core
|
package space.kscience.kmath.tensors.core
|
||||||
|
|
||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
import space.kscience.kmath.tensors.core.internal.*
|
import space.kscience.kmath.tensors.core.internal.broadcastOuterTensors
|
||||||
|
import space.kscience.kmath.tensors.core.internal.broadcastShapes
|
||||||
|
import space.kscience.kmath.tensors.core.internal.broadcastTensors
|
||||||
|
import space.kscience.kmath.tensors.core.internal.broadcastTo
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ -34,7 +37,7 @@ internal class TestBroadcasting {
|
|||||||
|
|
||||||
val res = broadcastTo(tensor2, tensor1.shape)
|
val res = broadcastTo(tensor2, tensor1.shape)
|
||||||
assertTrue(res.shape contentEquals intArrayOf(2, 3))
|
assertTrue(res.shape contentEquals intArrayOf(2, 3))
|
||||||
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0))
|
assertTrue(res.source contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -49,9 +52,9 @@ internal class TestBroadcasting {
|
|||||||
assertTrue(res[1].shape contentEquals intArrayOf(1, 2, 3))
|
assertTrue(res[1].shape contentEquals intArrayOf(1, 2, 3))
|
||||||
assertTrue(res[2].shape contentEquals intArrayOf(1, 2, 3))
|
assertTrue(res[2].shape contentEquals intArrayOf(1, 2, 3))
|
||||||
|
|
||||||
assertTrue(res[0].mutableBuffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
assertTrue(res[0].source contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
||||||
assertTrue(res[1].mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0))
|
assertTrue(res[1].source contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0))
|
||||||
assertTrue(res[2].mutableBuffer.array() contentEquals doubleArrayOf(500.0, 500.0, 500.0, 500.0, 500.0, 500.0))
|
assertTrue(res[2].source contentEquals doubleArrayOf(500.0, 500.0, 500.0, 500.0, 500.0, 500.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -66,9 +69,9 @@ internal class TestBroadcasting {
|
|||||||
assertTrue(res[1].shape contentEquals intArrayOf(1, 1, 3))
|
assertTrue(res[1].shape contentEquals intArrayOf(1, 1, 3))
|
||||||
assertTrue(res[2].shape contentEquals intArrayOf(1, 1, 1))
|
assertTrue(res[2].shape contentEquals intArrayOf(1, 1, 1))
|
||||||
|
|
||||||
assertTrue(res[0].mutableBuffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
assertTrue(res[0].source contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
||||||
assertTrue(res[1].mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0))
|
assertTrue(res[1].source contentEquals doubleArrayOf(10.0, 20.0, 30.0))
|
||||||
assertTrue(res[2].mutableBuffer.array() contentEquals doubleArrayOf(500.0))
|
assertTrue(res[2].source contentEquals doubleArrayOf(500.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -95,16 +98,16 @@ internal class TestBroadcasting {
|
|||||||
val tensor32 = tensor3 - tensor2
|
val tensor32 = tensor3 - tensor2
|
||||||
|
|
||||||
assertTrue(tensor21.shape contentEquals intArrayOf(2, 3))
|
assertTrue(tensor21.shape contentEquals intArrayOf(2, 3))
|
||||||
assertTrue(tensor21.mutableBuffer.array() contentEquals doubleArrayOf(9.0, 18.0, 27.0, 6.0, 15.0, 24.0))
|
assertTrue(tensor21.source contentEquals doubleArrayOf(9.0, 18.0, 27.0, 6.0, 15.0, 24.0))
|
||||||
|
|
||||||
assertTrue(tensor31.shape contentEquals intArrayOf(1, 2, 3))
|
assertTrue(tensor31.shape contentEquals intArrayOf(1, 2, 3))
|
||||||
assertTrue(
|
assertTrue(
|
||||||
tensor31.mutableBuffer.array()
|
tensor31.source
|
||||||
contentEquals doubleArrayOf(499.0, 498.0, 497.0, 496.0, 495.0, 494.0)
|
contentEquals doubleArrayOf(499.0, 498.0, 497.0, 496.0, 495.0, 494.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(tensor32.shape contentEquals intArrayOf(1, 1, 3))
|
assertTrue(tensor32.shape contentEquals intArrayOf(1, 1, 3))
|
||||||
assertTrue(tensor32.mutableBuffer.array() contentEquals doubleArrayOf(490.0, 480.0, 470.0))
|
assertTrue(tensor32.source contentEquals doubleArrayOf(490.0, 480.0, 470.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package space.kscience.kmath.tensors.core
|
package space.kscience.kmath.tensors.core
|
||||||
|
|
||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
|
import space.kscience.kmath.structures.asBuffer
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -20,14 +21,14 @@ internal class TestDoubleAnalyticTensorAlgebra {
|
|||||||
3.23, 133.7, 25.3,
|
3.23, 133.7, 25.3,
|
||||||
100.3, 11.0, 12.012
|
100.3, 11.0, 12.012
|
||||||
)
|
)
|
||||||
val tensor = DoubleTensor(shape, buffer)
|
val tensor = DoubleTensor(shape, buffer.asBuffer())
|
||||||
|
|
||||||
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 expectedTensor(transform: (Double) -> Double): DoubleTensor {
|
||||||
return DoubleTensor(shape, buffer.fmap(transform))
|
return DoubleTensor(shape, buffer.fmap(transform).asBuffer())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -106,58 +107,74 @@ internal class TestDoubleAnalyticTensorAlgebra {
|
|||||||
1.0, 2.0,
|
1.0, 2.0,
|
||||||
-3.0, 4.0
|
-3.0, 4.0
|
||||||
)
|
)
|
||||||
val tensor2 = DoubleTensor(shape2, buffer2)
|
val tensor2 = DoubleTensor(shape2, buffer2.asBuffer())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMin() = DoubleTensorAlgebra {
|
fun testMin() = DoubleTensorAlgebra {
|
||||||
assertTrue { tensor2.min() == -3.0 }
|
assertTrue { tensor2.min() == -3.0 }
|
||||||
assertTrue { tensor2.min(0, true) eq fromArray(
|
assertTrue {
|
||||||
|
tensor2.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 {
|
||||||
|
tensor2.min(1, false) eq fromArray(
|
||||||
intArrayOf(2),
|
intArrayOf(2),
|
||||||
doubleArrayOf(1.0, -3.0)
|
doubleArrayOf(1.0, -3.0)
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMax() = DoubleTensorAlgebra {
|
fun testMax() = DoubleTensorAlgebra {
|
||||||
assertTrue { tensor2.max() == 4.0 }
|
assertTrue { tensor2.max() == 4.0 }
|
||||||
assertTrue { tensor2.max(0, true) eq fromArray(
|
assertTrue {
|
||||||
|
tensor2.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 {
|
||||||
|
tensor2.max(1, false) eq fromArray(
|
||||||
intArrayOf(2),
|
intArrayOf(2),
|
||||||
doubleArrayOf(2.0, 4.0)
|
doubleArrayOf(2.0, 4.0)
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSum() = DoubleTensorAlgebra {
|
fun testSum() = DoubleTensorAlgebra {
|
||||||
assertTrue { tensor2.sum() == 4.0 }
|
assertTrue { tensor2.sum() == 4.0 }
|
||||||
assertTrue { tensor2.sum(0, true) eq fromArray(
|
assertTrue {
|
||||||
|
tensor2.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 {
|
||||||
|
tensor2.sum(1, false) eq fromArray(
|
||||||
intArrayOf(2),
|
intArrayOf(2),
|
||||||
doubleArrayOf(3.0, 1.0)
|
doubleArrayOf(3.0, 1.0)
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMean() = DoubleTensorAlgebra {
|
fun testMean() = DoubleTensorAlgebra {
|
||||||
assertTrue { tensor2.mean() == 1.0 }
|
assertTrue { tensor2.mean() == 1.0 }
|
||||||
assertTrue { tensor2.mean(0, true) eq fromArray(
|
assertTrue {
|
||||||
|
tensor2.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 {
|
||||||
|
tensor2.mean(1, false) eq fromArray(
|
||||||
intArrayOf(2),
|
intArrayOf(2),
|
||||||
doubleArrayOf(1.5, 0.5)
|
doubleArrayOf(1.5, 0.5)
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
package space.kscience.kmath.tensors.core
|
package space.kscience.kmath.tensors.core
|
||||||
|
|
||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
import space.kscience.kmath.tensors.core.internal.array
|
|
||||||
import space.kscience.kmath.tensors.core.internal.svd1d
|
import space.kscience.kmath.tensors.core.internal.svd1d
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -142,11 +141,11 @@ internal class TestDoubleLinearOpsTensorAlgebra {
|
|||||||
@Test
|
@Test
|
||||||
fun testCholesky() = DoubleTensorAlgebra {
|
fun testCholesky() = DoubleTensorAlgebra {
|
||||||
val tensor = randomNormal(intArrayOf(2, 5, 5), 0)
|
val tensor = randomNormal(intArrayOf(2, 5, 5), 0)
|
||||||
val sigma = (tensor matmul tensor.transpose()) + diagonalEmbedding(
|
val sigma = (tensor matmul tensor.transposed()) + diagonalEmbedding(
|
||||||
fromArray(intArrayOf(2, 5), DoubleArray(10) { 0.1 })
|
fromArray(intArrayOf(2, 5), DoubleArray(10) { 0.1 })
|
||||||
)
|
)
|
||||||
val low = sigma.cholesky()
|
val low = sigma.cholesky()
|
||||||
val sigmChol = low matmul low.transpose()
|
val sigmChol = low matmul low.transposed()
|
||||||
assertTrue(sigma.eq(sigmChol))
|
assertTrue(sigma.eq(sigmChol))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,8 +156,8 @@ internal class TestDoubleLinearOpsTensorAlgebra {
|
|||||||
val res = svd1d(tensor2)
|
val res = svd1d(tensor2)
|
||||||
|
|
||||||
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(res.source[0]) - 0.386) < 0.01 }
|
||||||
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart + 1]) - 0.922) < 0.01 }
|
assertTrue { abs(abs(res.source[1]) - 0.922) < 0.01 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -171,16 +170,16 @@ internal class TestDoubleLinearOpsTensorAlgebra {
|
|||||||
fun testBatchedSVD() = DoubleTensorAlgebra {
|
fun testBatchedSVD() = DoubleTensorAlgebra {
|
||||||
val tensor = randomNormal(intArrayOf(2, 5, 3), 0)
|
val tensor = randomNormal(intArrayOf(2, 5, 3), 0)
|
||||||
val (tensorU, tensorS, tensorV) = tensor.svd()
|
val (tensorU, tensorS, tensorV) = tensor.svd()
|
||||||
val tensorSVD = tensorU matmul (diagonalEmbedding(tensorS) matmul tensorV.transpose())
|
val tensorSVD = tensorU matmul (diagonalEmbedding(tensorS) matmul tensorV.transposed())
|
||||||
assertTrue(tensor.eq(tensorSVD))
|
assertTrue(tensor.eq(tensorSVD))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBatchedSymEig() = DoubleTensorAlgebra {
|
fun testBatchedSymEig() = DoubleTensorAlgebra {
|
||||||
val tensor = randomNormal(shape = intArrayOf(2, 3, 3), 0)
|
val tensor = randomNormal(shape = intArrayOf(2, 3, 3), 0)
|
||||||
val tensorSigma = tensor + tensor.transpose()
|
val tensorSigma = tensor + tensor.transposed()
|
||||||
val (tensorS, tensorV) = tensorSigma.symEig()
|
val (tensorS, tensorV) = tensorSigma.symEig()
|
||||||
val tensorSigmaCalc = tensorV matmul (diagonalEmbedding(tensorS) matmul tensorV.transpose())
|
val tensorSigmaCalc = tensorV matmul (diagonalEmbedding(tensorS) matmul tensorV.transposed())
|
||||||
assertTrue(tensorSigma.eq(tensorSigmaCalc))
|
assertTrue(tensorSigma.eq(tensorSigmaCalc))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +193,7 @@ private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor, epsilon: Double
|
|||||||
val tensorSVD = svd.first
|
val tensorSVD = svd.first
|
||||||
.dot(
|
.dot(
|
||||||
diagonalEmbedding(svd.second)
|
diagonalEmbedding(svd.second)
|
||||||
.dot(svd.third.transpose())
|
.dot(svd.third.transposed())
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(tensor.eq(tensorSVD, epsilon))
|
assertTrue(tensor.eq(tensorSVD, epsilon))
|
||||||
|
@ -13,10 +13,7 @@ import space.kscience.kmath.nd.as2D
|
|||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
import space.kscience.kmath.structures.DoubleBuffer
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
import space.kscience.kmath.structures.toDoubleArray
|
import space.kscience.kmath.structures.toDoubleArray
|
||||||
import space.kscience.kmath.tensors.core.internal.array
|
|
||||||
import space.kscience.kmath.tensors.core.internal.matrixSequence
|
import space.kscience.kmath.tensors.core.internal.matrixSequence
|
||||||
import space.kscience.kmath.tensors.core.internal.toBufferedTensor
|
|
||||||
import space.kscience.kmath.tensors.core.internal.toTensor
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -37,7 +34,7 @@ internal class TestDoubleTensor {
|
|||||||
assertEquals(tensor[intArrayOf(0, 1)], 5.8)
|
assertEquals(tensor[intArrayOf(0, 1)], 5.8)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
tensor.elements().map { it.second }.toList()
|
tensor.elements().map { it.second }.toList()
|
||||||
.toDoubleArray() contentEquals tensor.mutableBuffer.toDoubleArray()
|
.toDoubleArray() contentEquals tensor.source.toDoubleArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,9 +54,9 @@ internal class TestDoubleTensor {
|
|||||||
assertEquals(tensor[intArrayOf(0, 1, 0)], 109.56)
|
assertEquals(tensor[intArrayOf(0, 1, 0)], 109.56)
|
||||||
|
|
||||||
tensor.matrixSequence().forEach {
|
tensor.matrixSequence().forEach {
|
||||||
val a = it.toTensor()
|
val a = it.asDoubleTensor()
|
||||||
val secondRow = a.getTensor(1).as1D()
|
val secondRow = a.getTensor(1).as1D()
|
||||||
val secondColumn = a.transpose(0, 1).getTensor(1).as1D()
|
val secondColumn = a.transposed(0, 1).getTensor(1).as1D()
|
||||||
assertEquals(secondColumn[0], 77.89)
|
assertEquals(secondColumn[0], 77.89)
|
||||||
assertEquals(secondRow[1], secondColumn[1])
|
assertEquals(secondRow[1], secondColumn[1])
|
||||||
}
|
}
|
||||||
@ -72,16 +69,16 @@ internal class TestDoubleTensor {
|
|||||||
val doubleArray = DoubleBuffer(doubleArrayOf(1.0, 2.0, 3.0))
|
val doubleArray = DoubleBuffer(doubleArrayOf(1.0, 2.0, 3.0))
|
||||||
|
|
||||||
// create ND buffers, no data is copied
|
// create ND buffers, no data is copied
|
||||||
val ndArray = MutableBufferND(DefaultStrides(intArrayOf(3)), doubleArray)
|
val ndArray: MutableBufferND<Double> = MutableBufferND(DefaultStrides(intArrayOf(3)), doubleArray)
|
||||||
|
|
||||||
// map to tensors
|
// map to tensors
|
||||||
val bufferedTensorArray = ndArray.toBufferedTensor() // strides are flipped so data copied
|
val bufferedTensorArray = ndArray.asDoubleTensor() // strides are flipped so data copied
|
||||||
val tensorArray = bufferedTensorArray.toTensor() // data not contiguous so copied again
|
val tensorArray = bufferedTensorArray.asDoubleTensor() // data not contiguous so copied again
|
||||||
|
|
||||||
val tensorArrayPublic = ndArray.asDoubleTensor() // public API, data copied twice
|
val tensorArrayPublic = ndArray.asDoubleTensor() // public API, data copied twice
|
||||||
val sharedTensorArray = tensorArrayPublic.asDoubleTensor() // no data copied by matching type
|
val sharedTensorArray = tensorArrayPublic.asDoubleTensor() // no data copied by matching type
|
||||||
|
|
||||||
assertTrue(tensorArray.mutableBuffer.array() contentEquals sharedTensorArray.mutableBuffer.array())
|
assertTrue(tensorArray.source contentEquals sharedTensorArray.source)
|
||||||
|
|
||||||
tensorArray[intArrayOf(0)] = 55.9
|
tensorArray[intArrayOf(0)] = 55.9
|
||||||
assertEquals(tensorArrayPublic[intArrayOf(0)], 1.0)
|
assertEquals(tensorArrayPublic[intArrayOf(0)], 1.0)
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
package space.kscience.kmath.tensors.core
|
package space.kscience.kmath.tensors.core
|
||||||
|
|
||||||
|
|
||||||
|
import space.kscience.kmath.nd.get
|
||||||
import space.kscience.kmath.operations.invoke
|
import space.kscience.kmath.operations.invoke
|
||||||
import space.kscience.kmath.tensors.core.internal.array
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ -18,55 +19,55 @@ internal class TestDoubleTensorAlgebra {
|
|||||||
fun testDoublePlus() = DoubleTensorAlgebra {
|
fun testDoublePlus() = DoubleTensorAlgebra {
|
||||||
val tensor = fromArray(intArrayOf(2), doubleArrayOf(1.0, 2.0))
|
val tensor = fromArray(intArrayOf(2), doubleArrayOf(1.0, 2.0))
|
||||||
val res = 10.0 + tensor
|
val res = 10.0 + tensor
|
||||||
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(11.0, 12.0))
|
assertTrue(res.source contentEquals doubleArrayOf(11.0, 12.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDoubleDiv() = DoubleTensorAlgebra {
|
fun testDoubleDiv() = DoubleTensorAlgebra {
|
||||||
val tensor = fromArray(intArrayOf(2), doubleArrayOf(2.0, 4.0))
|
val tensor = fromArray(intArrayOf(2), doubleArrayOf(2.0, 4.0))
|
||||||
val res = 2.0 / tensor
|
val res = 2.0 / tensor
|
||||||
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 0.5))
|
assertTrue(res.source contentEquals doubleArrayOf(1.0, 0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDivDouble() = DoubleTensorAlgebra {
|
fun testDivDouble() = DoubleTensorAlgebra {
|
||||||
val tensor = fromArray(intArrayOf(2), doubleArrayOf(10.0, 5.0))
|
val tensor = fromArray(intArrayOf(2), doubleArrayOf(10.0, 5.0))
|
||||||
val res = tensor / 2.5
|
val res = tensor / 2.5
|
||||||
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(4.0, 2.0))
|
assertTrue(res.source contentEquals doubleArrayOf(4.0, 2.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTranspose1x1() = DoubleTensorAlgebra {
|
fun testTranspose1x1() = DoubleTensorAlgebra {
|
||||||
val tensor = fromArray(intArrayOf(1), doubleArrayOf(0.0))
|
val tensor = fromArray(intArrayOf(1), doubleArrayOf(0.0))
|
||||||
val res = tensor.transpose(0, 0)
|
val res = tensor.transposed(0, 0)
|
||||||
|
|
||||||
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(0.0))
|
assertTrue(res.source contentEquals doubleArrayOf(0.0))
|
||||||
assertTrue(res.shape contentEquals intArrayOf(1))
|
assertTrue(res.shape contentEquals intArrayOf(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTranspose3x2() = DoubleTensorAlgebra {
|
fun testTranspose3x2() = DoubleTensorAlgebra {
|
||||||
val tensor = fromArray(intArrayOf(3, 2), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
val tensor = fromArray(intArrayOf(3, 2), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
||||||
val res = tensor.transpose(1, 0)
|
val res = tensor.transposed(1, 0)
|
||||||
|
|
||||||
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 3.0, 5.0, 2.0, 4.0, 6.0))
|
assertTrue(res.source contentEquals doubleArrayOf(1.0, 3.0, 5.0, 2.0, 4.0, 6.0))
|
||||||
assertTrue(res.shape contentEquals intArrayOf(2, 3))
|
assertTrue(res.shape contentEquals intArrayOf(2, 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTranspose1x2x3() = DoubleTensorAlgebra {
|
fun testTranspose1x2x3() = DoubleTensorAlgebra {
|
||||||
val tensor = fromArray(intArrayOf(1, 2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
val tensor = fromArray(intArrayOf(1, 2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
||||||
val res01 = tensor.transpose(0, 1)
|
val res01 = tensor.transposed(0, 1)
|
||||||
val res02 = tensor.transpose(-3, 2)
|
val res02 = tensor.transposed(-3, 2)
|
||||||
val res12 = tensor.transpose()
|
val res12 = tensor.transposed()
|
||||||
|
|
||||||
assertTrue(res01.shape contentEquals intArrayOf(2, 1, 3))
|
assertTrue(res01.shape contentEquals intArrayOf(2, 1, 3))
|
||||||
assertTrue(res02.shape contentEquals intArrayOf(3, 2, 1))
|
assertTrue(res02.shape contentEquals intArrayOf(3, 2, 1))
|
||||||
assertTrue(res12.shape contentEquals intArrayOf(1, 3, 2))
|
assertTrue(res12.shape contentEquals intArrayOf(1, 3, 2))
|
||||||
|
|
||||||
assertTrue(res01.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
assertTrue(res01.source contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
||||||
assertTrue(res02.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 4.0, 2.0, 5.0, 3.0, 6.0))
|
assertTrue(res02.source contentEquals doubleArrayOf(1.0, 4.0, 2.0, 5.0, 3.0, 6.0))
|
||||||
assertTrue(res12.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 4.0, 2.0, 5.0, 3.0, 6.0))
|
assertTrue(res12.source contentEquals doubleArrayOf(1.0, 4.0, 2.0, 5.0, 3.0, 6.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -97,8 +98,8 @@ internal class TestDoubleTensorAlgebra {
|
|||||||
assignResult += tensorC
|
assignResult += tensorC
|
||||||
assignResult += -39.4
|
assignResult += -39.4
|
||||||
|
|
||||||
assertTrue(expected.mutableBuffer.array() contentEquals result.mutableBuffer.array())
|
assertTrue(expected.source contentEquals result.source)
|
||||||
assertTrue(expected.mutableBuffer.array() contentEquals assignResult.mutableBuffer.array())
|
assertTrue(expected.source contentEquals assignResult.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -111,26 +112,28 @@ internal class TestDoubleTensorAlgebra {
|
|||||||
val tensor5 = fromArray(intArrayOf(2, 3, 3), (1..18).map { 1 + it.toDouble() }.toDoubleArray())
|
val tensor5 = fromArray(intArrayOf(2, 3, 3), (1..18).map { 1 + it.toDouble() }.toDoubleArray())
|
||||||
|
|
||||||
val res12 = tensor1.dot(tensor2)
|
val res12 = tensor1.dot(tensor2)
|
||||||
assertTrue(res12.mutableBuffer.array() contentEquals doubleArrayOf(140.0, 320.0))
|
assertTrue(res12.source contentEquals doubleArrayOf(140.0, 320.0))
|
||||||
assertTrue(res12.shape contentEquals intArrayOf(2))
|
assertTrue(res12.shape contentEquals intArrayOf(2))
|
||||||
|
|
||||||
val res32 = tensor3.matmul(tensor2)
|
val res32 = tensor3.matmul(tensor2)
|
||||||
assertTrue(res32.mutableBuffer.array() contentEquals doubleArrayOf(-140.0))
|
assertTrue(res32.source contentEquals doubleArrayOf(-140.0))
|
||||||
assertTrue(res32.shape contentEquals intArrayOf(1, 1))
|
assertTrue(res32.shape contentEquals intArrayOf(1, 1))
|
||||||
|
|
||||||
val res22 = tensor2.dot(tensor2)
|
val res22 = tensor2.dot(tensor2)
|
||||||
assertTrue(res22.mutableBuffer.array() contentEquals doubleArrayOf(1400.0))
|
assertTrue(res22.source contentEquals doubleArrayOf(1400.0))
|
||||||
assertTrue(res22.shape contentEquals intArrayOf(1))
|
assertTrue(res22.shape contentEquals intArrayOf(1))
|
||||||
|
|
||||||
val res11 = tensor1.dot(tensor11)
|
val res11 = tensor1.dot(tensor11)
|
||||||
assertTrue(res11.mutableBuffer.array() contentEquals doubleArrayOf(22.0, 28.0, 49.0, 64.0))
|
assertTrue(res11.source contentEquals doubleArrayOf(22.0, 28.0, 49.0, 64.0))
|
||||||
assertTrue(res11.shape contentEquals intArrayOf(2, 2))
|
assertTrue(res11.shape contentEquals intArrayOf(2, 2))
|
||||||
|
|
||||||
val res45 = tensor4.matmul(tensor5)
|
val res45 = tensor4.matmul(tensor5)
|
||||||
assertTrue(res45.mutableBuffer.array() contentEquals doubleArrayOf(
|
assertTrue(
|
||||||
|
res45.source contentEquals doubleArrayOf(
|
||||||
36.0, 42.0, 48.0, 81.0, 96.0, 111.0, 126.0, 150.0, 174.0,
|
36.0, 42.0, 48.0, 81.0, 96.0, 111.0, 126.0, 150.0, 174.0,
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,31 +143,44 @@ internal class TestDoubleTensorAlgebra {
|
|||||||
val tensor2 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
val tensor2 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
|
||||||
val tensor3 = zeros(intArrayOf(2, 3, 4, 5))
|
val tensor3 = zeros(intArrayOf(2, 3, 4, 5))
|
||||||
|
|
||||||
assertTrue(diagonalEmbedding(tensor3, 0, 3, 4).shape contentEquals
|
assertTrue(
|
||||||
intArrayOf(2, 3, 4, 5, 5))
|
diagonalEmbedding(tensor3, 0, 3, 4).shape contentEquals
|
||||||
assertTrue(diagonalEmbedding(tensor3, 1, 3, 4).shape contentEquals
|
intArrayOf(2, 3, 4, 5, 5)
|
||||||
intArrayOf(2, 3, 4, 6, 6))
|
)
|
||||||
assertTrue(diagonalEmbedding(tensor3, 2, 0, 3).shape contentEquals
|
assertTrue(
|
||||||
intArrayOf(7, 2, 3, 7, 4))
|
diagonalEmbedding(tensor3, 1, 3, 4).shape contentEquals
|
||||||
|
intArrayOf(2, 3, 4, 6, 6)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
diagonalEmbedding(tensor3, 2, 0, 3).shape contentEquals
|
||||||
|
intArrayOf(7, 2, 3, 7, 4)
|
||||||
|
)
|
||||||
|
|
||||||
val diagonal1 = diagonalEmbedding(tensor1, 0, 1, 0)
|
val diagonal1 = diagonalEmbedding(tensor1, 0, 1, 0)
|
||||||
assertTrue(diagonal1.shape contentEquals intArrayOf(3, 3))
|
assertTrue(diagonal1.shape contentEquals intArrayOf(3, 3))
|
||||||
assertTrue(diagonal1.mutableBuffer.array() contentEquals
|
assertTrue(
|
||||||
doubleArrayOf(10.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 30.0))
|
diagonal1.source contentEquals
|
||||||
|
doubleArrayOf(10.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 30.0)
|
||||||
|
)
|
||||||
|
|
||||||
val diagonal1Offset = diagonalEmbedding(tensor1, 1, 1, 0)
|
val diagonal1Offset = diagonalEmbedding(tensor1, 1, 1, 0)
|
||||||
assertTrue(diagonal1Offset.shape contentEquals intArrayOf(4, 4))
|
assertTrue(diagonal1Offset.shape contentEquals intArrayOf(4, 4))
|
||||||
assertTrue(diagonal1Offset.mutableBuffer.array() contentEquals
|
assertTrue(
|
||||||
doubleArrayOf(0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 0.0, 30.0, 0.0))
|
diagonal1Offset.source contentEquals
|
||||||
|
doubleArrayOf(0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 0.0, 30.0, 0.0)
|
||||||
|
)
|
||||||
|
|
||||||
val diagonal2 = diagonalEmbedding(tensor2, 1, 0, 2)
|
val diagonal2 = diagonalEmbedding(tensor2, 1, 0, 2)
|
||||||
assertTrue(diagonal2.shape contentEquals intArrayOf(4, 2, 4))
|
assertTrue(diagonal2.shape contentEquals intArrayOf(4, 2, 4))
|
||||||
assertTrue(diagonal2.mutableBuffer.array() contentEquals
|
assertTrue(
|
||||||
|
diagonal2.source contentEquals
|
||||||
doubleArrayOf(
|
doubleArrayOf(
|
||||||
0.0, 1.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0,
|
0.0, 1.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0,
|
||||||
0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 5.0, 0.0,
|
0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 5.0, 0.0,
|
||||||
0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 6.0,
|
0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 6.0,
|
||||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))
|
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -178,4 +194,14 @@ internal class TestDoubleTensorAlgebra {
|
|||||||
assertFalse(tensor1.eq(tensor3))
|
assertFalse(tensor1.eq(tensor3))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMap() = DoubleTensorAlgebra {
|
||||||
|
val tensor = one(5, 5, 5)
|
||||||
|
val l = tensor.getTensor(0).map { it + 1.0 }
|
||||||
|
val r = tensor.getTensor(1).map { it - 1.0 }
|
||||||
|
val res = l + r
|
||||||
|
assertTrue { intArrayOf(5, 5) contentEquals res.shape }
|
||||||
|
assertEquals(1.0, res[4, 4])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2022 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.tensors.core
|
||||||
|
|
||||||
|
import space.kscience.kmath.structures.Buffer
|
||||||
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
|
import space.kscience.kmath.structures.indices
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified [DoubleBuffer] to array comparison
|
||||||
|
*/
|
||||||
|
public fun OffsetDoubleBuffer.contentEquals(vararg doubles: Double): Boolean = indices.all { get(it) == doubles[it] }
|
||||||
|
|
||||||
|
@JvmName("contentEqualsArray")
|
||||||
|
public infix fun OffsetDoubleBuffer.contentEquals(otherArray: DoubleArray): Boolean = contentEquals(*otherArray)
|
||||||
|
|
||||||
|
@JvmName("contentEqualsBuffer")
|
||||||
|
public infix fun OffsetDoubleBuffer.contentEquals(otherBuffer: Buffer<Double>): Boolean =
|
||||||
|
indices.all { get(it) == otherBuffer[it] }
|
20
test-utils/src/commonMain/kotlin/bufferEquality.kt
Normal file
20
test-utils/src/commonMain/kotlin/bufferEquality.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2022 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.testutils
|
||||||
|
|
||||||
|
import space.kscience.kmath.structures.DoubleBuffer
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified [DoubleBuffer] to array comparison
|
||||||
|
*/
|
||||||
|
public fun DoubleBuffer.contentEquals(vararg doubles: Double): Boolean = array.contentEquals(doubles)
|
||||||
|
|
||||||
|
@JvmName("contentEqualsArray")
|
||||||
|
public infix fun DoubleBuffer.contentEquals(otherArray: DoubleArray): Boolean = array.contentEquals(otherArray)
|
||||||
|
|
||||||
|
@JvmName("contentEqualsBuffer")
|
||||||
|
public infix fun DoubleBuffer.contentEquals(otherBuffer: DoubleBuffer): Boolean = array.contentEquals(otherBuffer.array)
|
Loading…
Reference in New Issue
Block a user