Merge to update docs and contributions #504

Merged
altavir merged 199 commits from dev into master 2022-10-03 20:58:00 +03:00
40 changed files with 1183 additions and 994 deletions
Showing only changes of commit b5d04ba02c - Show all commits

View File

@ -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`.

View File

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

View File

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

View File

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

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 // 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

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. * 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)

View File

@ -237,19 +237,4 @@ 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]) }
}

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. * 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)
} }

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) 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].
*/ */

View File

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

View File

@ -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> {

View File

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

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

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 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> =

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

View File

@ -25,6 +25,12 @@ kotlin.sourceSets {
api(project(":kmath-stat")) api(project(":kmath-stat"))
} }
} }
commonTest{
dependencies{
implementation(projects.testUtils)
}
}
} }
readme { 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 * @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.

View File

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

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] * 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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 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,15 +69,15 @@ 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
fun testBroadcastOuterTensorsShapes() = DoubleTensorAlgebra { fun testBroadcastOuterTensorsShapes() = DoubleTensorAlgebra {
val tensor1 = fromArray(intArrayOf(2, 1, 3, 2, 3), DoubleArray(2 * 1 * 3 * 2 * 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 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 tensor3 = fromArray(intArrayOf(1, 1), doubleArrayOf(500.0))
val res = broadcastOuterTensors(tensor1, tensor2, tensor3) val res = broadcastOuterTensors(tensor1, tensor2, tensor3)
@ -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))
} }
} }

View File

@ -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 {
intArrayOf(1, 2), tensor2.min(0, true) eq fromArray(
doubleArrayOf(-3.0, 2.0) intArrayOf(1, 2),
)} doubleArrayOf(-3.0, 2.0)
assertTrue { tensor2.min(1, false) eq fromArray( )
intArrayOf(2), }
doubleArrayOf(1.0, -3.0) assertTrue {
)} tensor2.min(1, false) eq fromArray(
intArrayOf(2),
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 {
intArrayOf(1, 2), tensor2.max(0, true) eq fromArray(
doubleArrayOf(1.0, 4.0) intArrayOf(1, 2),
)} doubleArrayOf(1.0, 4.0)
assertTrue { tensor2.max(1, false) eq fromArray( )
intArrayOf(2), }
doubleArrayOf(2.0, 4.0) assertTrue {
)} tensor2.max(1, false) eq fromArray(
intArrayOf(2),
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 {
intArrayOf(1, 2), tensor2.sum(0, true) eq fromArray(
doubleArrayOf(-2.0, 6.0) intArrayOf(1, 2),
)} doubleArrayOf(-2.0, 6.0)
assertTrue { tensor2.sum(1, false) eq fromArray( )
intArrayOf(2), }
doubleArrayOf(3.0, 1.0) assertTrue {
)} tensor2.sum(1, false) eq fromArray(
intArrayOf(2),
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 {
intArrayOf(1, 2), tensor2.mean(0, true) eq fromArray(
doubleArrayOf(-1.0, 3.0) intArrayOf(1, 2),
)} doubleArrayOf(-1.0, 3.0)
assertTrue { tensor2.mean(1, false) eq fromArray( )
intArrayOf(2), }
doubleArrayOf(1.5, 0.5) 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 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,12 +156,12 @@ 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
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, 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))) testSVDFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0)))
} }
@ -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))

View File

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

View File

@ -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(
36.0, 42.0, 48.0, 81.0, 96.0, 111.0, 126.0, 150.0, 174.0, res45.source contentEquals doubleArrayOf(
468.0, 501.0, 534.0, 594.0, 636.0, 678.0, 720.0, 771.0, 822.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
)
)
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(
doubleArrayOf( diagonal2.source contentEquals
0.0, 1.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, doubleArrayOf(
0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0,
0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 5.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, 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
)
)
} }
@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])
}
} }

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)