Global refactor of tensors

This commit is contained in:
Alexander Nozik 2022-09-11 15:27:38 +03:00
parent 3729faf49b
commit b5d04ba02c
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
40 changed files with 1183 additions and 994 deletions

View File

@ -7,6 +7,7 @@
- Algebra now has an obligatory `bufferFactory` (#477).
### Changed
- Major refactor of tensors (only minor API changes)
- Kotlin 1.7.20
- `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`.

View File

@ -1,9 +1,10 @@
import space.kscience.gradle.isInDevelopment
import space.kscience.gradle.useApache2Licence
import space.kscience.gradle.useSPCTeam
plugins {
id("space.kscience.gradle.project")
id("org.jetbrains.kotlinx.kover") version "0.5.0"
id("org.jetbrains.kotlinx.kover") version "0.6.0"
}
allprojects {
@ -14,7 +15,7 @@ allprojects {
}
group = "space.kscience"
version = "0.3.1-dev-3"
version = "0.3.1-dev-4"
}
subprojects {
@ -75,8 +76,14 @@ ksciencePublish {
useApache2Licence()
useSPCTeam()
}
github("kmath", "SciProgCentre", addToRelease = false)
space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/dev")
github("kmath", "SciProgCentre")
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()
}

View File

@ -52,7 +52,7 @@ fun main() {
// 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 alphaOLS = v dot sigma dot u.transpose() dot y
val alphaOLS = v dot sigma dot u.transposed() dot y
println("Estimated alpha:\n" +
"$alphaOLS")

View File

@ -27,7 +27,7 @@ fun main(): Unit = Double.tensorAlgebra.withBroadcast { // work in context with
println("y:\n$y")
// stack them into single dataset
val dataset = stack(listOf(x, y)).transpose()
val dataset = stack(listOf(x, y)).transposed()
// normalize both x and y
val xMean = x.mean()

View File

@ -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
// create it by placing ones on side diagonal
val revMat = u.zeroesLike()
val revMat = zeroesLike(u)
val n = revMat.shape[0]
for (i in 0 until n) {
revMat[intArrayOf(i, n - 1 - i)] = 1.0

View File

@ -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.
*/
@file:OptIn(PerformancePitfall::class)
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.tensors.core.BroadcastDoubleTensorAlgebra
import space.kscience.kmath.tensors.core.DoubleTensor
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
const val seed = 100500L
@ -82,9 +80,9 @@ class Dense(
}
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()
weights -= learningRate * gradW
@ -109,12 +107,11 @@ fun accuracy(yPred: DoubleTensor, yTrue: DoubleTensor): Double {
}
// neural network class
@OptIn(ExperimentalStdlibApi::class)
class NeuralNetwork(private val layers: List<Layer>) {
private fun softMaxLoss(yPred: DoubleTensor, yTrue: DoubleTensor): DoubleTensor = BroadcastDoubleTensorAlgebra {
val onesForAnswers = yPred.zeroesLike()
yTrue.copyArray().forEachIndexed { index, labelDouble ->
val onesForAnswers = zeroesLike(yPred)
yTrue.source.asIterable().forEachIndexed { index, labelDouble ->
val label = labelDouble.toInt()
onesForAnswers[intArrayOf(index, label)] = 1.0
}
@ -166,7 +163,7 @@ class NeuralNetwork(private val layers: List<Layer>) {
for ((xBatch, yBatch) in iterBatch(xTrain, yTrain)) {
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)
// process raw prediction via argMax
val predictionLabels = prediction.argMax(1, true).asDouble()
val predictionLabels = prediction.argMax(1, true).toDoubleTensor()
// find out accuracy
val acc = accuracy(yTest, predictionLabels)

View File

@ -238,18 +238,3 @@ public interface MutableStructureND<T> : StructureND<T> {
public operator fun <T> MutableStructureND<T>.set(vararg index: Int, value: T) {
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]) }
}

View File

@ -114,7 +114,6 @@ public interface Buffer<out T> {
*
* 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> =
auto(T::class, size, initializer)
}

View File

@ -47,11 +47,6 @@ public inline fun DoubleBuffer(size: Int, init: (Int) -> Double): DoubleBuffer =
*/
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].
*/

View File

@ -24,8 +24,7 @@ public value class IntBuffer(public val array: IntArray) : MutableBuffer<Int> {
override operator fun iterator(): IntIterator = array.iterator()
override fun copy(): MutableBuffer<Int> =
IntBuffer(array.copyOf())
override fun copy(): IntBuffer = IntBuffer(array.copyOf())
}
/**

View File

@ -74,7 +74,9 @@ class NumberNDFieldTest {
@Test
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> {

View File

@ -2,13 +2,13 @@ plugins {
id("space.kscience.gradle.mpp")
}
kscience{
kscience {
native()
}
kotlin.sourceSets.commonMain {
dependencies {
api(project(":kmath-core"))
api(projects.kmathCore)
}
dependencies("commonTest") {
implementation(projects.testUtils)
}
}
@ -24,21 +24,21 @@ readme {
feature(
id = "DoubleVector",
ref = "src/commonMain/kotlin/space/kscience/kmath/real/DoubleVector.kt"
){
) {
"Numpy-like operations for Buffers/Points"
}
feature(
id = "DoubleMatrix",
ref = "src/commonMain/kotlin/space/kscience/kmath/real/DoubleMatrix.kt"
){
) {
"Numpy-like operations for 2d real structures"
}
feature(
id = "grids",
ref = "src/commonMain/kotlin/space/kscience/kmath/structures/grids.kt"
){
) {
"Uniform grid generators"
}
}

View File

@ -11,7 +11,7 @@ import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureND
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.assertEquals
import kotlin.test.assertTrue

View File

@ -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)
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
@ -121,7 +135,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
if (this is MultikTensor) {
array.plusAssign(value)
} 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) {
array.plusAssign(arg.asMultik().array)
} 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) {
array.minusAssign(value)
} 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) {
array.minusAssign(arg.asMultik().array)
} 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) {
array.timesAssign(value)
} 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) {
array.timesAssign(arg.asMultik().array)
} 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>.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> {
require(shape.all { it > 0 })
@ -283,7 +297,7 @@ public abstract class MultikDivisionTensorAlgebra<T, A : Field<T>>(
if (this is MultikTensor) {
array.divAssign(value)
} 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) {
array.divAssign(arg.asMultik().array)
} else {
mapInPlace { index, t -> elementAlgebra.divide(t, arg[index]) }
mapIndexedInPlace { index, t -> elementAlgebra.divide(t, arg[index]) }
}
}
}

View File

@ -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 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>.min(dim: Int, keepDim: Boolean): Nd4jArrayStructure<T> =

View File

@ -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()))
}
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)))
}

View File

@ -25,6 +25,12 @@ kotlin.sourceSets {
api(project(":kmath-stat"))
}
}
commonTest{
dependencies{
implementation(projects.testUtils)
}
}
}
readme {

View File

@ -180,7 +180,7 @@ public interface TensorAlgebra<T, A : Ring<T>> : RingOpsND<T, A> {
* @param j the second dimension to be transposed
* @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.

View File

@ -7,8 +7,8 @@ package space.kscience.kmath.tensors.core
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.structures.DoubleBuffer
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.broadcastTo
@ -22,8 +22,8 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
val newThis = broadcast[0]
val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[i] + newOther.mutableBuffer.array()[i]
val resBuffer = DoubleBuffer(newThis.indices.linearSize) { i ->
newThis.source[i] + newOther.source[i]
}
return DoubleTensor(newThis.shape, resBuffer)
}
@ -31,8 +31,7 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
override fun Tensor<Double>.plusAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
for (i in 0 until asDoubleTensor().indices.linearSize) {
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] +=
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
asDoubleTensor().source[i] += newOther.source[i]
}
}
@ -40,17 +39,16 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
val newThis = broadcast[0]
val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[i] - newOther.mutableBuffer.array()[i]
val resBuffer = DoubleBuffer(newThis.indices.linearSize) { i ->
newThis.source[i] - newOther.source[i]
}
return DoubleTensor(newThis.shape, resBuffer)
}
override fun Tensor<Double>.minusAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
for (i in 0 until asDoubleTensor().indices.linearSize) {
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] -=
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
for (i in 0 until indices.linearSize) {
asDoubleTensor().source[i] -= newOther.source[i]
}
}
@ -58,18 +56,16 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
val newThis = broadcast[0]
val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[newThis.bufferStart + i] *
newOther.mutableBuffer.array()[newOther.bufferStart + i]
val resBuffer = DoubleBuffer(newThis.indices.linearSize) { i ->
newThis.source[i] * newOther.source[i]
}
return DoubleTensor(newThis.shape, resBuffer)
}
override fun Tensor<Double>.timesAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
for (i in 0 until asDoubleTensor().indices.linearSize) {
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] *=
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
for (i in 0 until indices.linearSize) {
asDoubleTensor().source[+i] *= newOther.source[i]
}
}
@ -77,18 +73,16 @@ public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() {
val broadcast = broadcastTensors(asDoubleTensor(), arg.asDoubleTensor())
val newThis = broadcast[0]
val newOther = broadcast[1]
val resBuffer = DoubleArray(newThis.indices.linearSize) { i ->
newThis.mutableBuffer.array()[newOther.bufferStart + i] /
newOther.mutableBuffer.array()[newOther.bufferStart + i]
val resBuffer = DoubleBuffer(newThis.indices.linearSize) { i ->
newThis.source[i] / newOther.source[i]
}
return DoubleTensor(newThis.shape, resBuffer)
}
override fun Tensor<Double>.divAssign(arg: StructureND<Double>) {
val newOther = broadcastTo(arg.asDoubleTensor(), asDoubleTensor().shape)
for (i in 0 until asDoubleTensor().indices.linearSize) {
asDoubleTensor().mutableBuffer.array()[asDoubleTensor().bufferStart + i] /=
newOther.mutableBuffer.array()[asDoubleTensor().bufferStart + i]
for (i in 0 until indices.linearSize) {
asDoubleTensor().source[i] /= newOther.source[i]
}
}
}

View File

@ -13,12 +13,12 @@ import space.kscience.kmath.tensors.api.Tensor
/**
* 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,
@PublishedApi internal val mutableBuffer: MutableBuffer<T>,
@PublishedApi internal val bufferStart: Int,
) : Tensor<T> {
public abstract val source: MutableBuffer<T>
/**
* Buffer strides based on [TensorLinearStructure] implementation
*/
@ -27,14 +27,8 @@ public open class BufferedTensor<T> internal constructor(
/**
* Number of elements in tensor
*/
public val numElements: Int
get() = indices.linearSize
public val linearSize: Int 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
override fun elements(): Sequence<Pair<IntArray, T>> = indices.asSequence().map {

View File

@ -5,16 +5,88 @@
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
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
*/
public class DoubleTensor @PublishedApi internal constructor(
public class DoubleTensor(
shape: IntArray,
buffer: DoubleArray,
offset: Int = 0
) : BufferedTensor<Double>(shape, DoubleBuffer(buffer), offset) {
override val source: OffsetDoubleBuffer,
) : BufferedTensor<Double>(shape) {
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()
}

View File

@ -5,17 +5,87 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.structures.IntBuffer
import space.kscience.kmath.tensors.core.internal.array
import space.kscience.kmath.structures.*
/**
* Default [BufferedTensor] implementation for [Int] values
*/
public class IntTensor @PublishedApi internal constructor(
shape: IntArray,
buffer: IntArray,
offset: Int = 0,
) : BufferedTensor<Int>(shape, IntBuffer(buffer), offset) {
public fun asDouble(): DoubleTensor =
DoubleTensor(shape, mutableBuffer.array().map { it.toDouble() }.toDoubleArray(), bufferStart)
public class OffsetIntBuffer(
private val source: IntBuffer,
private val offset: Int,
override val size: Int,
) : MutableBuffer<Int> {
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
}
}

View File

@ -11,7 +11,7 @@ package space.kscience.kmath.tensors.core
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.*
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.core.internal.*
import kotlin.math.*
@ -23,10 +23,6 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
public companion object : IntTensorAlgebra()
override fun StructureND<Int>.dot(other: StructureND<Int>): Tensor<Int> {
TODO("Not yet implemented")
}
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.
* @return the resulting tensor after applying the function.
*/
@PerformancePitfall
@Suppress("OVERRIDE_BY_INLINE")
final override inline fun StructureND<Int>.map(transform: IntRing.(Int) -> Int): IntTensor {
val tensor = this.asIntTensor()
//TODO remove additional copy
val sourceArray = tensor.copyArray()
val array = IntArray(tensor.numElements) { IntRing.transform(sourceArray[it]) }
val array = IntBuffer(tensor.source.size) { IntRing.transform(tensor.source[it]) }
return IntTensor(
tensor.shape,
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")
final override inline fun StructureND<Int>.mapIndexed(transform: IntRing.(index: IntArray, Int) -> Int): IntTensor {
val tensor = this.asIntTensor()
//TODO remove additional copy
val sourceArray = tensor.copyArray()
val array = IntArray(tensor.numElements) { IntRing.transform(tensor.indices.index(it), sourceArray[it]) }
return IntTensor(
tensor.shape,
array,
tensor.bufferStart
)
val buffer = IntBuffer(tensor.source.size) {
IntRing.transform(tensor.indices.index(it), tensor.source[it])
}
return IntTensor(tensor.shape, buffer)
}
@PerformancePitfall
override fun zip(
@Suppress("OVERRIDE_BY_INLINE")
final override inline fun zip(
left: StructureND<Int>,
right: StructureND<Int>,
transform: IntRing.(Int, Int) -> Int,
): IntTensor {
require(left.shape.contentEquals(right.shape)) {
"The shapes in zip are not equal: left - ${left.shape}, right - ${right.shape}"
}
checkShapesCompatible(left, right)
val leftTensor = left.asIntTensor()
val leftArray = leftTensor.copyArray()
val rightTensor = right.asIntTensor()
val rightArray = rightTensor.copyArray()
val array = IntArray(leftTensor.numElements) { IntRing.transform(leftArray[it], rightArray[it]) }
return IntTensor(
leftTensor.shape,
array
)
val buffer = IntBuffer(leftTensor.source.size) {
IntRing.transform(leftTensor.source[it], rightTensor.source[it])
}
return IntTensor(leftTensor.shape, buffer)
}
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()
?: 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.
*
* @param shape the desired shape for the tensor.
* @param buffer one-dimensional data array.
* @return tensor with the [shape] shape and [buffer] data.
* @param array one-dimensional data array.
* @return tensor with the [shape] shape and [array] data.
*/
public fun fromArray(shape: IntArray, buffer: IntArray): IntTensor {
checkEmptyShape(shape)
check(buffer.isNotEmpty()) { "Illegal empty buffer provided" }
check(buffer.size == shape.reduce(Int::times)) {
"Inconsistent shape ${shape.toList()} for buffer of size ${buffer.size} provided"
public fun fromArray(shape: IntArray, array: IntArray): IntTensor {
checkNotEmptyShape(shape)
check(array.isNotEmpty()) { "Illegal empty buffer provided" }
check(array.size == shape.reduce(Int::times)) {
"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 {
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 newStart = newShape.reduce(Int::times) * i + asIntTensor().bufferStart
return IntTensor(newShape, asIntTensor().mutableBuffer.array(), newStart)
return IntTensor(newShape, dt.source.view(newShape.reduce(Int::times) * i))
}
/**
@ -133,8 +137,8 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
* @return tensor with the [shape] shape and filled with [value].
*/
public fun full(value: Int, shape: IntArray): IntTensor {
checkEmptyShape(shape)
val buffer = IntArray(shape.reduce(Int::times)) { value }
checkNotEmptyShape(shape)
val buffer = IntBuffer(shape.reduce(Int::times)) { value }
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.
* @return tensor with the `input` tensor shape and filled with [value].
*/
public fun Tensor<Int>.fullLike(value: Int): IntTensor {
val shape = asIntTensor().shape
val buffer = IntArray(asIntTensor().numElements) { value }
public fun fullLike(structureND: StructureND<*>, value: Int): IntTensor {
val shape = structureND.shape
val buffer = IntBuffer(structureND.indices.linearSize) { value }
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.
*/
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].
@ -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.
*/
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.
@ -188,7 +192,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
*/
public fun eye(n: Int): IntTensor {
val shape = intArrayOf(n, n)
val buffer = IntArray(n * n) { 0 }
val buffer = IntBuffer(n * n) { 0 }
val res = IntTensor(shape, buffer)
for (i in 0 until n) {
res[intArrayOf(i, i)] = 1
@ -196,151 +200,92 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
return res
}
/**
* 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 = arg.map { this@plus + it }
override fun Int.plus(arg: StructureND<Int>): IntTensor {
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 = map { it + arg }
override fun StructureND<Int>.plus(arg: Int): IntTensor = arg + asIntTensor()
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 StructureND<Int>.plus(arg: StructureND<Int>): IntTensor = zip(this, arg) { l, r -> l + r }
override fun Tensor<Int>.plusAssign(value: Int) {
for (i in 0 until asIntTensor().numElements) {
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] += value
}
mapInPlace { it + value }
}
override fun Tensor<Int>.plusAssign(arg: StructureND<Int>) {
checkShapesCompatible(asIntTensor(), arg.asIntTensor())
for (i in 0 until asIntTensor().numElements) {
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] +=
arg.asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i]
mapIndexedInPlace { index, value ->
value + arg[index]
}
}
override fun Int.minus(arg: StructureND<Int>): IntTensor {
val resBuffer = IntArray(arg.asIntTensor().numElements) { i ->
this - arg.asIntTensor().mutableBuffer.array()[arg.asIntTensor().bufferStart + i]
}
return IntTensor(arg.shape, resBuffer)
}
override fun Int.minus(arg: StructureND<Int>): IntTensor = arg.map { this@minus - it }
override fun StructureND<Int>.minus(arg: Int): IntTensor {
val resBuffer = IntArray(asIntTensor().numElements) { i ->
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] - arg
}
return IntTensor(asIntTensor().shape, resBuffer)
}
override fun StructureND<Int>.minus(arg: Int): IntTensor = map { it - arg }
override fun StructureND<Int>.minus(arg: StructureND<Int>): IntTensor {
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 StructureND<Int>.minus(arg: StructureND<Int>): IntTensor = zip(this, arg) { l, r -> l + r }
override fun Tensor<Int>.minusAssign(value: Int) {
for (i in 0 until asIntTensor().numElements) {
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] -= value
}
mapInPlace { it - value }
}
override fun Tensor<Int>.minusAssign(arg: StructureND<Int>) {
checkShapesCompatible(asIntTensor(), arg)
for (i in 0 until asIntTensor().numElements) {
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] -=
arg.asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i]
}
checkShapesCompatible(this, arg)
mapIndexedInPlace { index, value -> value - arg[index] }
}
override fun Int.times(arg: StructureND<Int>): IntTensor {
val resBuffer = IntArray(arg.asIntTensor().numElements) { i ->
arg.asIntTensor().mutableBuffer.array()[arg.asIntTensor().bufferStart + i] * this
}
return IntTensor(arg.shape, resBuffer)
}
override fun Int.times(arg: StructureND<Int>): IntTensor = arg.map { this@times * it }
override fun StructureND<Int>.times(arg: Int): IntTensor = arg * asIntTensor()
override fun StructureND<Int>.times(arg: StructureND<Int>): IntTensor {
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 StructureND<Int>.times(arg: StructureND<Int>): IntTensor = zip(this, arg) { l, r -> l * r }
override fun Tensor<Int>.timesAssign(value: Int) {
for (i in 0 until asIntTensor().numElements) {
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] *= value
}
mapInPlace { it * value }
}
override fun Tensor<Int>.timesAssign(arg: StructureND<Int>) {
checkShapesCompatible(asIntTensor(), arg)
for (i in 0 until asIntTensor().numElements) {
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i] *=
arg.asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i]
}
checkShapesCompatible(this, arg)
mapIndexedInPlace { index, value -> value * arg[index] }
}
override fun StructureND<Int>.unaryMinus(): IntTensor {
val resBuffer = IntArray(asIntTensor().numElements) { i ->
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + i].unaryMinus()
}
return IntTensor(asIntTensor().shape, resBuffer)
}
override fun StructureND<Int>.unaryMinus(): IntTensor = map { -it }
override fun Tensor<Int>.transpose(i: Int, j: Int): IntTensor {
val ii = asIntTensor().minusIndex(i)
val jj = asIntTensor().minusIndex(j)
checkTranspose(asIntTensor().dimension, ii, jj)
val n = asIntTensor().numElements
override fun Tensor<Int>.transposed(i: Int, j: Int): IntTensor {
// TODO change strides instead of changing content
val dt = asIntTensor()
val ii = dt.minusIndex(i)
val jj = dt.minusIndex(j)
checkTranspose(dt.dimension, ii, jj)
val n = dt.linearSize
val resBuffer = IntArray(n)
val resShape = asIntTensor().shape.copyOf()
val resShape = dt.shape.copyOf()
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) {
val oldMultiIndex = asIntTensor().indices.index(offset)
val oldMultiIndex = dt.indices.index(offset)
val newMultiIndex = oldMultiIndex.copyOf()
newMultiIndex[ii] = newMultiIndex[jj].also { newMultiIndex[jj] = newMultiIndex[ii] }
val linearIndex = resTensor.indices.offset(newMultiIndex)
resTensor.mutableBuffer.array()[linearIndex] =
asIntTensor().mutableBuffer.array()[asIntTensor().bufferStart + offset]
resTensor.source[linearIndex] = dt.source[offset]
}
return resTensor
}
override fun Tensor<Int>.view(shape: IntArray): IntTensor {
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 =
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(
diagonalEntries: Tensor<Int>,
@ -374,7 +319,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
diagonalEntries.shape.slice(greaterDim - 1 until n - 1).toIntArray()
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)
var offset1 = 0
@ -394,16 +339,27 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
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>,
eqFunction: (Int, Int) -> Boolean,
): Boolean {
checkShapesCompatible(asIntTensor(), other)
val n = asIntTensor().numElements
if (n != other.asIntTensor().numElements) {
val n = asIntTensor().linearSize
if (n != other.asIntTensor().linearSize) {
return false
}
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
}
}
@ -421,10 +377,12 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
val shape = tensors[0].shape
check(tensors.all { it.shape contentEquals shape }) { "Tensors must have same shapes" }
val resShape = intArrayOf(tensors.size) + shape
val resBuffer = tensors.flatMap {
it.asIntTensor().mutableBuffer.array().drop(it.asIntTensor().bufferStart).take(it.asIntTensor().numElements)
}.toIntArray()
return IntTensor(resShape, resBuffer, 0)
// val resBuffer: List<Int> = tensors.flatMap {
// it.asIntTensor().source.array.drop(it.asIntTensor().bufferStart)
// .take(it.asIntTensor().linearSize)
// }
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) })
private inline fun StructureND<Int>.fold(foldFunction: (IntArray) -> Int): Int =
foldFunction(asIntTensor().copyArray())
private inline fun <reified R : Any> StructureND<Int>.foldDim(
private inline fun StructureND<Int>.foldDimToInt(
dim: Int,
keepDim: Boolean,
foldFunction: (IntArray) -> R,
): BufferedTensor<R> {
foldFunction: (IntArray) -> Int,
): IntTensor {
check(dim < dimension) { "Dimension $dim out of range $dimension" }
val resShape = if (keepDim) {
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 init = foldFunction(IntArray(1) { 0 })
val resTensor = BufferedTensor(
val resTensor = IntTensor(
resShape,
MutableBuffer.auto(resNumElements) { init }, 0
IntBuffer(resNumElements) { init }
)
for (index in resTensor.indices) {
val prefix = index.take(dim).toIntArray()
@ -465,31 +420,33 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
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 =
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 =
foldDim(dim, keepDim) { x -> x.minOrNull()!! }.asIntTensor()
foldDimToInt(dim, keepDim) { x -> x.minOrNull()!! }
override fun StructureND<Int>.argMin(dim: Int, keepDim: Boolean): IntTensor =
foldDim(dim, keepDim) { x ->
x.withIndex().minByOrNull { it.value }?.index!!
}.asIntTensor()
override fun StructureND<Int>.argMin(dim: Int, keepDim: Boolean): Tensor<Int> = foldDimToInt(dim, keepDim) { x ->
x.withIndex().minBy { it.value }.index
}
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 =
foldDim(dim, keepDim) { x -> x.maxOrNull()!! }.asIntTensor()
foldDimToInt(dim, keepDim) { x -> x.max() }
override fun StructureND<Int>.argMax(dim: Int, keepDim: Boolean): IntTensor =
foldDim(dim, keepDim) { x ->
x.withIndex().maxByOrNull { it.value }?.index!!
}.asIntTensor()
foldDimToInt(dim, keepDim) { x ->
x.withIndex().maxBy { it.value }.index
}
public fun StructureND<Int>.mean(): Double = sum().toDouble() / indices.linearSize
}
public val Int.Companion.tensorAlgebra: IntTensorAlgebra get() = IntTensorAlgebra

View File

@ -5,6 +5,7 @@
package space.kscience.kmath.tensors.core.internal
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.tensors.core.DoubleTensor
import kotlin.math.max
@ -24,8 +25,8 @@ internal fun multiIndexBroadCasting(tensor: DoubleTensor, resTensor: DoubleTenso
}
val curLinearIndex = tensor.indices.offset(curMultiIndex)
resTensor.mutableBuffer.array()[linearIndex] =
tensor.mutableBuffer.array()[tensor.bufferStart + curLinearIndex]
resTensor.source[linearIndex] =
tensor.source[curLinearIndex]
}
}
@ -63,7 +64,7 @@ internal fun broadcastTo(tensor: DoubleTensor, newShape: IntArray): DoubleTensor
}
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) {
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 }
return tensors.map { tensor ->
val resTensor = DoubleTensor(totalShape, DoubleArray(n))
val resTensor = DoubleTensor(totalShape, DoubleArray(n).asBuffer())
multiIndexBroadCasting(tensor, resTensor, n)
resTensor
}
@ -106,17 +107,17 @@ internal fun broadcastOuterTensors(vararg tensors: DoubleTensor): List<DoubleTen
for (tensor in tensors) {
val matrixShape = tensor.shape.sliceArray(tensor.shape.size - 2 until tensor.shape.size).copyOf()
val matrixSize = matrixShape[0] * matrixShape[1]
val matrix = DoubleTensor(matrixShape, DoubleArray(matrixSize))
val matrix = DoubleTensor(matrixShape, DoubleArray(matrixSize).asBuffer())
val outerTensor = DoubleTensor(totalShape, DoubleArray(n))
val resTensor = DoubleTensor(totalShape + matrixShape, DoubleArray(n * matrixSize))
val outerTensor = DoubleTensor(totalShape, DoubleArray(n).asBuffer())
val resTensor = DoubleTensor(totalShape + matrixShape, DoubleArray(n * matrixSize).asBuffer())
for (linearIndex in 0 until n) {
val totalMultiIndex = outerTensor.indices.index(linearIndex)
var curMultiIndex = tensor.shape.sliceArray(0..tensor.shape.size - 3).copyOf()
curMultiIndex = IntArray(totalMultiIndex.size - curMultiIndex.size) { 1 } + curMultiIndex
val newTensor = DoubleTensor(curMultiIndex + matrixShape, tensor.mutableBuffer.array())
val newTensor = DoubleTensor(curMultiIndex + matrixShape, tensor.source)
for (i in curMultiIndex.indices) {
if (curMultiIndex[i] != 1) {
@ -136,8 +137,8 @@ internal fun broadcastOuterTensors(vararg tensors: DoubleTensor): List<DoubleTen
matrix.indices.index(i)
)
resTensor.mutableBuffer.array()[resTensor.bufferStart + newLinearIndex] =
newTensor.mutableBuffer.array()[newTensor.bufferStart + curLinearIndex]
resTensor.source[newLinearIndex] =
newTensor.source[curLinearIndex]
}
}
add(resTensor)

View File

@ -9,9 +9,10 @@ import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.tensors.api.Tensor
import space.kscience.kmath.tensors.core.DoubleTensor
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()) {
"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"
}
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) {
"Incompatible shapes ${a.shape.toList()} and ${b.shape.toList()} "
}
@ -50,15 +52,14 @@ internal fun checkSquareMatrix(shape: IntArray) {
internal fun DoubleTensorAlgebra.checkSymmetric(
tensor: Tensor<Double>, epsilon: Double = 1e-6,
) =
check(tensor.eq(tensor.transpose(), epsilon)) {
) = check(tensor.eq(tensor.transposed(), epsilon)) {
"Tensor is not symmetric about the last 2 dimensions at precision $epsilon"
}
}
internal fun DoubleTensorAlgebra.checkPositiveDefinite(tensor: DoubleTensor, epsilon: Double = 1e-6) {
checkSymmetric(tensor, epsilon)
for (mat in tensor.matrixSequence())
check(mat.toTensor().detLU().value() > 0.0) {
"Tensor contains matrices which are not positive definite ${mat.toTensor().detLU().value()}"
check(mat.asDoubleTensor().detLU().value() > 0.0) {
"Tensor contains matrices which are not positive definite ${mat.asDoubleTensor().detLU().value()}"
}
}

View File

@ -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()

View File

@ -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()

View File

@ -5,75 +5,33 @@
package space.kscience.kmath.tensors.core.internal
import space.kscience.kmath.nd.MutableStructure1D
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.nd.*
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.VirtualBuffer
import space.kscience.kmath.tensors.core.BufferedTensor
import space.kscience.kmath.tensors.core.DoubleTensor
import space.kscience.kmath.tensors.core.DoubleTensorAlgebra
import space.kscience.kmath.tensors.core.IntTensor
import space.kscience.kmath.structures.IntBuffer
import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.indices
import space.kscience.kmath.tensors.core.*
import kotlin.math.abs
import kotlin.math.min
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(
a: BufferedTensor<Double>,
b: BufferedTensor<Double>,
res: BufferedTensor<Double>,
l: Int, m: Int, n: Int,
) {
val aStart = a.bufferStart
val bStart = b.bufferStart
val resStart = res.bufferStart
val aBuffer = a.mutableBuffer
val bBuffer = b.mutableBuffer
val resBuffer = res.mutableBuffer
val aBuffer = a.source
val bBuffer = b.source
val resBuffer = res.source
for (i in 0 until l) {
for (j in 0 until n) {
var curr = 0.0
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
}
internal fun <T> BufferedTensor<T>.setUpPivots(): IntTensor {
internal fun <T> StructureND<T>.setUpPivots(): IntTensor {
val n = this.shape.size
val m = this.shape.last()
val pivotsShape = IntArray(n - 1) { i -> this.shape[i] }
@ -137,17 +95,17 @@ internal fun <T> BufferedTensor<T>.setUpPivots(): IntTensor {
return IntTensor(
pivotsShape,
IntArray(pivotsShape.reduce(Int::times)) { 0 }
IntBuffer(pivotsShape.reduce(Int::times)) { 0 }
)
}
internal fun DoubleTensorAlgebra.computeLU(
tensor: DoubleTensor,
tensor: StructureND<Double>,
epsilon: Double,
): Pair<DoubleTensor, IntTensor>? {
checkSquareMatrix(tensor.shape)
val luTensor = tensor.copy()
val luTensor = tensor.copyToTensor()
val pivotsTensor = tensor.setUpPivots()
for ((lu, pivots) in luTensor.matrixSequence().zip(pivotsTensor.vectorSequence()))
@ -253,8 +211,8 @@ internal fun DoubleTensorAlgebra.qrHelper(
checkSquareMatrix(matrix.shape)
val n = matrix.shape[0]
val qM = q.as2D()
val matrixT = matrix.transpose(0, 1)
val qT = q.transpose(0, 1)
val matrixT = matrix.transposed(0, 1)
val qT = q.transposed(0, 1)
for (j in 0 until n) {
val v = matrixT.getTensor(j)
@ -280,10 +238,10 @@ internal fun DoubleTensorAlgebra.svd1d(a: DoubleTensor, epsilon: Double = 1e-10)
var v: DoubleTensor
val b: DoubleTensor
if (n > m) {
b = a.transpose(0, 1).dot(a)
b = a.transposed(0, 1).dot(a)
v = DoubleTensor(intArrayOf(m), getRandomUnitVector(m, 0))
} else {
b = a.dot(a.transpose(0, 1))
b = a.dot(a.transposed(0, 1))
v = DoubleTensor(intArrayOf(n), getRandomUnitVector(n, 0))
}
@ -308,7 +266,7 @@ internal fun DoubleTensorAlgebra.svdHelper(
val (matrixU, matrixS, matrixV) = USV
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)) {
val outerProduct = DoubleArray(u.shape[0] * v.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()
}
}
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 u: DoubleTensor
@ -328,7 +286,7 @@ internal fun DoubleTensorAlgebra.svdHelper(
u = u.times(1.0 / norm)
} else {
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() }
v = v.times(1.0 / norm)
}
@ -337,15 +295,15 @@ internal fun DoubleTensorAlgebra.svdHelper(
}
val s = res.map { it.first }.toDoubleArray()
val uBuffer = res.map { it.second }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
val vBuffer = res.map { it.third }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray()
val uBuffer = res.map { it.second.source }.concat()
val vBuffer = res.map { it.third.source }.concat()
for (i in uBuffer.indices) {
matrixU.mutableBuffer.array()[matrixU.bufferStart + i] = uBuffer[i]
matrixU.source[i] = uBuffer[i]
}
for (i in s.indices) {
matrixS.mutableBuffer.array()[matrixS.bufferStart + i] = s[i]
matrixS.source[i] = s[i]
}
for (i in vBuffer.indices) {
matrixV.mutableBuffer.array()[matrixV.bufferStart + i] = vBuffer[i]
matrixV.source[i] = vBuffer[i]
}
}

View File

@ -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()
}

View File

@ -6,41 +6,25 @@
package space.kscience.kmath.tensors.core.internal
import space.kscience.kmath.nd.as1D
import space.kscience.kmath.operations.DoubleBufferOps.Companion.map
import space.kscience.kmath.operations.toMutableList
import space.kscience.kmath.samplers.GaussianSampler
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.DoubleTensor
import kotlin.math.*
/**
* 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 {
internal fun getRandomNormals(n: Int, seed: Long): DoubleBuffer {
val distribution = GaussianSampler(0.0, 1.0)
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 {
val unnorm = getRandomNormals(n, seed)
val norm = sqrt(unnorm.sumOf { it * it })
return unnorm.map { it / norm }.toDoubleArray()
internal fun getRandomUnitVector(n: Int, seed: Long): DoubleBuffer {
val unnorm: DoubleBuffer = getRandomNormals(n, seed)
val norm = sqrt(unnorm.array.sumOf { it * it })
return unnorm.map { it / norm }
}
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(order)
}
else -> {
append('e')
append(order)
@ -116,7 +101,7 @@ internal fun DoubleTensor.toPrettyString(): String = buildString {
}
offset += vectorSize
if (this@toPrettyString.numElements == offset) {
if (this@toPrettyString.linearSize == offset) {
break
}

View File

@ -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]
}
}

View File

@ -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()
)
}

View File

@ -6,7 +6,10 @@
package space.kscience.kmath.tensors.core
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.assertTrue
@ -34,7 +37,7 @@ internal class TestBroadcasting {
val res = broadcastTo(tensor2, tensor1.shape)
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
@ -49,9 +52,9 @@ internal class TestBroadcasting {
assertTrue(res[1].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[1].mutableBuffer.array() 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[0].source contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
assertTrue(res[1].source contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0))
assertTrue(res[2].source contentEquals doubleArrayOf(500.0, 500.0, 500.0, 500.0, 500.0, 500.0))
}
@Test
@ -66,15 +69,15 @@ internal class TestBroadcasting {
assertTrue(res[1].shape contentEquals intArrayOf(1, 1, 3))
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[1].mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0))
assertTrue(res[2].mutableBuffer.array() contentEquals doubleArrayOf(500.0))
assertTrue(res[0].source contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
assertTrue(res[1].source contentEquals doubleArrayOf(10.0, 20.0, 30.0))
assertTrue(res[2].source contentEquals doubleArrayOf(500.0))
}
@Test
fun testBroadcastOuterTensorsShapes() = DoubleTensorAlgebra {
val tensor1 = fromArray(intArrayOf(2, 1, 3, 2, 3), DoubleArray(2 * 1 * 3 * 2 * 3) {0.0})
val tensor2 = fromArray(intArrayOf(4, 2, 5, 1, 3, 3), DoubleArray(4 * 2 * 5 * 1 * 3 * 3) {0.0})
val tensor1 = fromArray(intArrayOf(2, 1, 3, 2, 3), DoubleArray(2 * 1 * 3 * 2 * 3) { 0.0 })
val tensor2 = fromArray(intArrayOf(4, 2, 5, 1, 3, 3), DoubleArray(4 * 2 * 5 * 1 * 3 * 3) { 0.0 })
val tensor3 = fromArray(intArrayOf(1, 1), doubleArrayOf(500.0))
val res = broadcastOuterTensors(tensor1, tensor2, tensor3)
@ -95,16 +98,16 @@ internal class TestBroadcasting {
val tensor32 = tensor3 - tensor2
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.mutableBuffer.array()
tensor31.source
contentEquals doubleArrayOf(499.0, 498.0, 497.0, 496.0, 495.0, 494.0)
)
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))
}
}

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.asBuffer
import kotlin.math.*
import kotlin.test.Test
import kotlin.test.assertTrue
@ -20,14 +21,14 @@ internal class TestDoubleAnalyticTensorAlgebra {
3.23, 133.7, 25.3,
100.3, 11.0, 12.012
)
val tensor = DoubleTensor(shape, buffer)
val tensor = DoubleTensor(shape, buffer.asBuffer())
fun DoubleArray.fmap(transform: (Double) -> Double): DoubleArray {
return this.map(transform).toDoubleArray()
}
fun expectedTensor(transform: (Double) -> Double): DoubleTensor {
return DoubleTensor(shape, buffer.fmap(transform))
return DoubleTensor(shape, buffer.fmap(transform).asBuffer())
}
@Test
@ -106,58 +107,74 @@ internal class TestDoubleAnalyticTensorAlgebra {
1.0, 2.0,
-3.0, 4.0
)
val tensor2 = DoubleTensor(shape2, buffer2)
val tensor2 = DoubleTensor(shape2, buffer2.asBuffer())
@Test
fun testMin() = DoubleTensorAlgebra {
assertTrue { tensor2.min() == -3.0 }
assertTrue { tensor2.min(0, true) eq fromArray(
assertTrue {
tensor2.min(0, true) eq fromArray(
intArrayOf(1, 2),
doubleArrayOf(-3.0, 2.0)
)}
assertTrue { tensor2.min(1, false) eq fromArray(
)
}
assertTrue {
tensor2.min(1, false) eq fromArray(
intArrayOf(2),
doubleArrayOf(1.0, -3.0)
)}
)
}
}
@Test
fun testMax() = DoubleTensorAlgebra {
assertTrue { tensor2.max() == 4.0 }
assertTrue { tensor2.max(0, true) eq fromArray(
assertTrue {
tensor2.max(0, true) eq fromArray(
intArrayOf(1, 2),
doubleArrayOf(1.0, 4.0)
)}
assertTrue { tensor2.max(1, false) eq fromArray(
)
}
assertTrue {
tensor2.max(1, false) eq fromArray(
intArrayOf(2),
doubleArrayOf(2.0, 4.0)
)}
)
}
}
@Test
fun testSum() = DoubleTensorAlgebra {
assertTrue { tensor2.sum() == 4.0 }
assertTrue { tensor2.sum(0, true) eq fromArray(
assertTrue {
tensor2.sum(0, true) eq fromArray(
intArrayOf(1, 2),
doubleArrayOf(-2.0, 6.0)
)}
assertTrue { tensor2.sum(1, false) eq fromArray(
)
}
assertTrue {
tensor2.sum(1, false) eq fromArray(
intArrayOf(2),
doubleArrayOf(3.0, 1.0)
)}
)
}
}
@Test
fun testMean() = DoubleTensorAlgebra {
assertTrue { tensor2.mean() == 1.0 }
assertTrue { tensor2.mean(0, true) eq fromArray(
assertTrue {
tensor2.mean(0, true) eq fromArray(
intArrayOf(1, 2),
doubleArrayOf(-1.0, 3.0)
)}
assertTrue { tensor2.mean(1, false) eq fromArray(
)
}
assertTrue {
tensor2.mean(1, false) eq fromArray(
intArrayOf(2),
doubleArrayOf(1.5, 0.5)
)}
)
}
}
}

View File

@ -6,7 +6,6 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.tensors.core.internal.array
import space.kscience.kmath.tensors.core.internal.svd1d
import kotlin.math.abs
import kotlin.test.Test
@ -142,11 +141,11 @@ internal class TestDoubleLinearOpsTensorAlgebra {
@Test
fun testCholesky() = DoubleTensorAlgebra {
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 })
)
val low = sigma.cholesky()
val sigmChol = low matmul low.transpose()
val sigmChol = low matmul low.transposed()
assertTrue(sigma.eq(sigmChol))
}
@ -157,12 +156,12 @@ internal class TestDoubleLinearOpsTensorAlgebra {
val res = svd1d(tensor2)
assertTrue(res.shape contentEquals intArrayOf(2))
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart]) - 0.386) < 0.01 }
assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart + 1]) - 0.922) < 0.01 }
assertTrue { abs(abs(res.source[0]) - 0.386) < 0.01 }
assertTrue { abs(abs(res.source[1]) - 0.922) < 0.01 }
}
@Test
fun testSVD() = DoubleTensorAlgebra{
fun testSVD() = DoubleTensorAlgebra {
testSVDFor(fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)))
testSVDFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0)))
}
@ -171,16 +170,16 @@ internal class TestDoubleLinearOpsTensorAlgebra {
fun testBatchedSVD() = DoubleTensorAlgebra {
val tensor = randomNormal(intArrayOf(2, 5, 3), 0)
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))
}
@Test
fun testBatchedSymEig() = DoubleTensorAlgebra {
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 tensorSigmaCalc = tensorV matmul (diagonalEmbedding(tensorS) matmul tensorV.transpose())
val tensorSigmaCalc = tensorV matmul (diagonalEmbedding(tensorS) matmul tensorV.transposed())
assertTrue(tensorSigma.eq(tensorSigmaCalc))
}
@ -194,7 +193,7 @@ private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor, epsilon: Double
val tensorSVD = svd.first
.dot(
diagonalEmbedding(svd.second)
.dot(svd.third.transpose())
.dot(svd.third.transposed())
)
assertTrue(tensor.eq(tensorSVD, epsilon))

View File

@ -13,10 +13,7 @@ import space.kscience.kmath.nd.as2D
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.DoubleBuffer
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.toBufferedTensor
import space.kscience.kmath.tensors.core.internal.toTensor
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -37,7 +34,7 @@ internal class TestDoubleTensor {
assertEquals(tensor[intArrayOf(0, 1)], 5.8)
assertTrue(
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)
tensor.matrixSequence().forEach {
val a = it.toTensor()
val a = it.asDoubleTensor()
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(secondRow[1], secondColumn[1])
}
@ -72,16 +69,16 @@ internal class TestDoubleTensor {
val doubleArray = DoubleBuffer(doubleArrayOf(1.0, 2.0, 3.0))
// 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
val bufferedTensorArray = ndArray.toBufferedTensor() // strides are flipped so data copied
val tensorArray = bufferedTensorArray.toTensor() // data not contiguous so copied again
val bufferedTensorArray = ndArray.asDoubleTensor() // strides are flipped so data copied
val tensorArray = bufferedTensorArray.asDoubleTensor() // data not contiguous so copied again
val tensorArrayPublic = ndArray.asDoubleTensor() // public API, data copied twice
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
assertEquals(tensorArrayPublic[intArrayOf(0)], 1.0)

View File

@ -6,9 +6,10 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.nd.get
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.tensors.core.internal.array
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -18,55 +19,55 @@ internal class TestDoubleTensorAlgebra {
fun testDoublePlus() = DoubleTensorAlgebra {
val tensor = fromArray(intArrayOf(2), doubleArrayOf(1.0, 2.0))
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
fun testDoubleDiv() = DoubleTensorAlgebra {
val tensor = fromArray(intArrayOf(2), doubleArrayOf(2.0, 4.0))
val res = 2.0/tensor
assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 0.5))
val res = 2.0 / tensor
assertTrue(res.source contentEquals doubleArrayOf(1.0, 0.5))
}
@Test
fun testDivDouble() = DoubleTensorAlgebra {
val tensor = fromArray(intArrayOf(2), doubleArrayOf(10.0, 5.0))
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
fun testTranspose1x1() = DoubleTensorAlgebra {
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))
}
@Test
fun testTranspose3x2() = DoubleTensorAlgebra {
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))
}
@Test
fun testTranspose1x2x3() = DoubleTensorAlgebra {
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 res02 = tensor.transpose(-3, 2)
val res12 = tensor.transpose()
val res01 = tensor.transposed(0, 1)
val res02 = tensor.transposed(-3, 2)
val res12 = tensor.transposed()
assertTrue(res01.shape contentEquals intArrayOf(2, 1, 3))
assertTrue(res02.shape contentEquals intArrayOf(3, 2, 1))
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(res02.mutableBuffer.array() 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(res01.source contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))
assertTrue(res02.source 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
@ -97,8 +98,8 @@ internal class TestDoubleTensorAlgebra {
assignResult += tensorC
assignResult += -39.4
assertTrue(expected.mutableBuffer.array() contentEquals result.mutableBuffer.array())
assertTrue(expected.mutableBuffer.array() contentEquals assignResult.mutableBuffer.array())
assertTrue(expected.source contentEquals result.source)
assertTrue(expected.source contentEquals assignResult.source)
}
@Test
@ -111,26 +112,28 @@ internal class TestDoubleTensorAlgebra {
val tensor5 = fromArray(intArrayOf(2, 3, 3), (1..18).map { 1 + it.toDouble() }.toDoubleArray())
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))
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))
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))
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))
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,
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))
}
@ -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 tensor3 = zeros(intArrayOf(2, 3, 4, 5))
assertTrue(diagonalEmbedding(tensor3, 0, 3, 4).shape contentEquals
intArrayOf(2, 3, 4, 5, 5))
assertTrue(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))
assertTrue(
diagonalEmbedding(tensor3, 0, 3, 4).shape contentEquals
intArrayOf(2, 3, 4, 5, 5)
)
assertTrue(
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)
assertTrue(diagonal1.shape contentEquals intArrayOf(3, 3))
assertTrue(diagonal1.mutableBuffer.array() contentEquals
doubleArrayOf(10.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 30.0))
assertTrue(
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)
assertTrue(diagonal1Offset.shape contentEquals intArrayOf(4, 4))
assertTrue(diagonal1Offset.mutableBuffer.array() 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))
assertTrue(
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)
assertTrue(diagonal2.shape contentEquals intArrayOf(4, 2, 4))
assertTrue(diagonal2.mutableBuffer.array() contentEquals
assertTrue(
diagonal2.source contentEquals
doubleArrayOf(
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, 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
@ -178,4 +194,14 @@ internal class TestDoubleTensorAlgebra {
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])
}
}

View File

@ -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] }

View 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)