Change the default strides and unify strides processing

This commit is contained in:
Alexander Nozik 2022-09-27 16:57:06 +03:00
parent d70389d2e6
commit b602066f48
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
32 changed files with 268 additions and 180 deletions

View File

@ -7,6 +7,7 @@ import space.kscience.kmath.operations.DoubleBufferOps
import space.kscience.kmath.operations.algebra
import space.kscience.kmath.operations.bufferAlgebra
import space.kscience.kmath.operations.toList
import space.kscience.kmath.stat.KMComparisonResult
import space.kscience.kmath.stat.ksComparisonStatistic
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.slice
@ -31,12 +32,12 @@ fun main() = with(Double.algebra.bufferAlgebra.seriesAlgebra()) {
val s1 = series(100) { sin(2 * PI * it / 100) + 1.0 }
val s2 = s1.slice(20U..50U).moveTo(40)
val s2 = s1.slice(20..50).moveTo(40)
val s3: Buffer<Double> = s1.zip(s2) { l, r -> l + r } //s1 + s2
val s4 = DoubleBufferOps.ln(s3)
val kmTest = ksComparisonStatistic(s1, s2)
val kmTest: KMComparisonResult<Double> = ksComparisonStatistic(s1, s2)
Plotly.page {
h1 { +"This is my plot" }

View File

@ -21,7 +21,7 @@ class StreamDoubleFieldND(override val shape: IntArray) : FieldND<Double, Double
NumbersAddOps<StructureND<Double>>,
ExtendedField<StructureND<Double>> {
private val strides = DefaultStrides(shape)
private val strides = ColumnStrides(shape)
override val elementAlgebra: DoubleField get() = DoubleField
override val zero: BufferND<Double> by lazy { structureND(shape) { zero } }
override val one: BufferND<Double> by lazy { structureND(shape) { one } }

View File

@ -6,7 +6,7 @@
package space.kscience.kmath.structures
import space.kscience.kmath.nd.BufferND
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import kotlin.system.measureTimeMillis
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
@ -14,7 +14,7 @@ fun main() {
val n = 6000
val array = DoubleArray(n * n) { 1.0 }
val buffer = DoubleBuffer(array)
val strides = DefaultStrides(intArrayOf(n, n))
val strides = ColumnStrides(intArrayOf(n, n))
val structure = BufferND(strides, buffer)
measureTimeMillis {

View File

@ -38,7 +38,7 @@ public inline fun <T, R : Any> StructureND<T>.mapToBuffer(
): BufferND<R> = if (this is BufferND<T>)
BufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) })
else {
val strides = DefaultStrides(shape)
val strides = ColumnStrides(shape)
BufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) })
}
@ -75,7 +75,7 @@ public inline fun <T, reified R : Any> MutableStructureND<T>.mapToMutableBuffer(
return if (this is MutableBufferND<T>)
MutableBufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) })
else {
val strides = DefaultStrides(shape)
val strides = ColumnStrides(shape)
MutableBufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) })
}
}

View File

@ -24,7 +24,7 @@ public fun Shape(shapeFirst: Int, vararg shapeRest: Int): Shape = intArrayOf(sha
public interface WithShape {
public val shape: Shape
public val indices: ShapeIndexer get() = DefaultStrides(shape)
public val indices: ShapeIndexer get() = ColumnStrides(shape)
}
internal fun requireIndexInShape(index: IntArray, shape: Shape) {

View File

@ -5,6 +5,7 @@
package space.kscience.kmath.nd
import kotlin.math.max
import kotlin.native.concurrent.ThreadLocal
/**
@ -61,12 +62,16 @@ public abstract class Strides : ShapeIndexer {
* Iterate over ND indices in a natural order
*/
public override fun asSequence(): Sequence<IntArray> = (0 until linearSize).asSequence().map(::index)
public companion object{
public fun linearSizeOf(shape: IntArray): Int = shape.reduce(Int::times)
}
}
/**
* Simple implementation of [Strides].
* Column-first [Strides]. Columns are represented as continuous arrays
*/
public class DefaultStrides(override val shape: IntArray) : Strides() {
public class ColumnStrides(override val shape: IntArray) : Strides() {
override val linearSize: Int get() = strides[shape.size]
/**
@ -100,22 +105,64 @@ public class DefaultStrides(override val shape: IntArray) : Strides() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DefaultStrides) return false
if (!shape.contentEquals(other.shape)) return false
return true
if (other !is ColumnStrides) return false
return shape.contentEquals(other.shape)
}
override fun hashCode(): Int = shape.contentHashCode()
public companion object {
/**
* Cached builder for default strides
public companion object
}
/**
* This [Strides] implementation follows the last dimension first convention
* For more information: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.strides.html
*
* @param shape the shape of the tensor.
*/
@Deprecated("Replace by Strides(shape)")
public operator fun invoke(shape: IntArray): Strides =
defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) }
public class RowStrides(override val shape: IntArray) : Strides() {
override val strides: IntArray by lazy {
val nDim = shape.size
val res = IntArray(nDim)
if (nDim == 0) return@lazy res
var current = nDim - 1
res[current] = 1
while (current > 0) {
res[current - 1] = max(1, shape[current]) * res[current]
current--
}
res
}
override fun index(offset: Int): IntArray {
val res = IntArray(shape.size)
var current = offset
var strideIndex = 0
while (strideIndex < shape.size) {
res[strideIndex] = (current / strides[strideIndex])
current %= strides[strideIndex]
strideIndex++
}
return res
}
override val linearSize: Int get() = linearSizeOf(shape)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is RowStrides) return false
return shape.contentEquals(other.shape)
}
override fun hashCode(): Int = shape.contentHashCode()
public companion object
}
@ThreadLocal
@ -124,4 +171,4 @@ private val defaultStridesCache = HashMap<IntArray, Strides>()
/**
* Cached builder for default strides
*/
public fun Strides(shape: IntArray): Strides = defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) }
public fun Strides(shape: IntArray): Strides = defaultStridesCache.getOrPut(shape) { RowStrides(shape) }

View File

@ -7,6 +7,7 @@ package space.kscience.kmath.nd
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.structures.MutableListBuffer
import space.kscience.kmath.structures.VirtualBuffer
import kotlin.jvm.JvmInline
@ -84,15 +85,15 @@ public interface MutableStructure2D<T> : Structure2D<T>, MutableStructureND<T> {
* The buffer of rows of this structure. It gets elements from the structure dynamically.
*/
@PerformancePitfall
override val rows: List<MutableStructure1D<T>>
get() = List(rowNum) { i -> MutableBuffer1DWrapper(MutableListBuffer(colNum) { j -> get(i, j) }) }
override val rows: List<MutableBuffer<T>>
get() = List(rowNum) { i -> MutableListBuffer(colNum) { j -> get(i, j) } }
/**
* The buffer of columns of this structure. It gets elements from the structure dynamically.
*/
@PerformancePitfall
override val columns: List<MutableStructure1D<T>>
get() = List(colNum) { j -> MutableBuffer1DWrapper(MutableListBuffer(rowNum) { i -> get(i, j) }) }
override val columns: List<MutableBuffer<T>>
get() = List(colNum) { j -> MutableListBuffer(rowNum) { i -> get(i, j) } }
}
/**

View File

@ -148,25 +148,25 @@ public interface StructureND<out T> : Featured<StructureFeature>, WithShape {
shape: IntArray,
bufferFactory: BufferFactory<T> = BufferFactory.boxing(),
initializer: (IntArray) -> T,
): BufferND<T> = buffered(DefaultStrides(shape), bufferFactory, initializer)
): BufferND<T> = buffered(ColumnStrides(shape), bufferFactory, initializer)
public inline fun <reified T : Any> auto(
shape: IntArray,
crossinline initializer: (IntArray) -> T,
): BufferND<T> = auto(DefaultStrides(shape), initializer)
): BufferND<T> = auto(ColumnStrides(shape), initializer)
@JvmName("autoVarArg")
public inline fun <reified T : Any> auto(
vararg shape: Int,
crossinline initializer: (IntArray) -> T,
): BufferND<T> =
auto(DefaultStrides(shape), initializer)
auto(ColumnStrides(shape), initializer)
public inline fun <T : Any> auto(
type: KClass<T>,
vararg shape: Int,
crossinline initializer: (IntArray) -> T,
): BufferND<T> = auto(type, DefaultStrides(shape), initializer)
): BufferND<T> = auto(type, ColumnStrides(shape), initializer)
}
}

View File

@ -5,7 +5,7 @@
package space.kscience.kmath.structures
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.Structure2D
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.nd.as2D
@ -31,7 +31,7 @@ internal class BufferAccessor2D<T>(
//TODO optimize wrapper
fun MutableBuffer<T>.collect(): Structure2D<T> = StructureND.buffered(
DefaultStrides(intArrayOf(rowNum, colNum)),
ColumnStrides(intArrayOf(rowNum, colNum)),
factory
) { (i, j) ->
get(i, j)

View File

@ -15,6 +15,9 @@ public interface BufferView<T> : Buffer<T> {
*/
@UnstableKMathAPI
public fun originIndex(index: Int): Int
@OptIn(UnstableKMathAPI::class)
override fun get(index: Int): T = origin[originIndex(index)]
}
/**
@ -22,40 +25,40 @@ public interface BufferView<T> : Buffer<T> {
*/
public class BufferSlice<T>(
override val origin: Buffer<T>,
public val offset: UInt = 0U,
public val offset: Int = 0,
override val size: Int,
) : BufferView<T> {
init {
require(size > 0) { "Size must be positive" }
require(offset + size.toUInt() <= origin.size.toUInt()) {
"End of buffer ${offset + size.toUInt()} is beyond the end of origin buffer size ${origin.size}"
require(offset + size <= origin.size) {
"End of buffer ${offset + size} is beyond the end of origin buffer size ${origin.size}"
}
}
override fun get(index: Int): T = if (index >= size) {
throw IndexOutOfBoundsException("$index is out of ${0 until size} rage")
} else {
origin[index.toUInt() + offset]
origin[index + offset]
}
override fun iterator(): Iterator<T> =
(offset until (offset + size.toUInt())).asSequence().map { origin[it] }.iterator()
(offset until (offset + size)).asSequence().map { origin[it] }.iterator()
@UnstableKMathAPI
override fun originIndex(index: Int): Int = if (index >= size) -1 else index - offset.toInt()
override fun originIndex(index: Int): Int = if (index >= size) -1 else index - offset
override fun toString(): String = "$origin[$offset..${offset + size.toUInt()}"
override fun toString(): String = "$origin[$offset..${offset + size}"
}
/**
* An expanded buffer that could include the whole initial buffer ot its part and fills all space beyond it borders with [defaultValue].
* An expanded buffer that could include the whole initial buffer or its part and fills all space beyond it borders with [defaultValue].
*
* The [offset] parameter shows the shift of expanded buffer start relative to origin start and could be both positive and negative.
*/
public class BufferExpanded<T>(
override val origin: Buffer<T>,
public val defaultValue: T,
private val defaultValue: T,
public val offset: Int = 0,
override val size: Int = origin.size,
) : BufferView<T> {
@ -79,17 +82,17 @@ public class BufferExpanded<T>(
/**
* Zero-copy select a slice inside the original buffer
*/
public fun <T> Buffer<T>.slice(range: UIntRange): BufferView<T> = if (this is BufferSlice) {
public fun <T> Buffer<T>.slice(range: IntRange): BufferView<T> = if (this is BufferSlice) {
BufferSlice(
origin,
this.offset + range.first,
(range.last - range.first).toInt() + 1
(range.last - range.first) + 1
)
} else {
BufferSlice(
this,
range.first,
(range.last - range.first).toInt() + 1
(range.last - range.first) + 1
)
}
@ -103,7 +106,7 @@ public fun <T> Buffer<T>.expand(
): BufferView<T> = if (range.first >= 0 && range.last < size) {
BufferSlice(
this,
range.first.toUInt(),
range.first,
(range.last - range.first) + 1
)
} else {
@ -118,7 +121,7 @@ public fun <T> Buffer<T>.expand(
/**
* A [BufferView] that overrides indexing of the original buffer
*/
public class PermutatedBuffer<T>(
public class PermutedBuffer<T>(
override val origin: Buffer<T>,
private val permutations: IntArray,
) : BufferView<T> {
@ -145,4 +148,4 @@ public class PermutatedBuffer<T>(
/**
* Created a permuted view of given buffer using provided [indices]
*/
public fun <T> Buffer<T>.permute(indices: IntArray): PermutatedBuffer<T> = PermutatedBuffer(this, indices)
public fun <T> Buffer<T>.permute(indices: IntArray): PermutedBuffer<T> = PermutedBuffer(this, indices)

View File

@ -9,7 +9,7 @@ import space.kscience.kmath.misc.UnstableKMathAPI
@UnstableKMathAPI
public fun Buffer<Double>.getDouble(index: Int): Double = if (this is BufferView) {
val originIndex = originIndex(index)
if( originIndex>=0) {
if (originIndex >= 0) {
origin.getDouble(originIndex)
} else {
get(index)
@ -26,7 +26,7 @@ public fun Buffer<Double>.getDouble(index: Int): Double = if (this is BufferView
@UnstableKMathAPI
public fun Buffer<Int>.getInt(index: Int): Int = if (this is BufferView) {
val originIndex = originIndex(index)
if( originIndex>=0) {
if (originIndex >= 0) {
origin.getInt(originIndex)
} else {
get(index)

View File

@ -0,0 +1,38 @@
/*
* 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.nd
import kotlin.test.Test
class StridesTest {
@Test
fun checkRowBasedStrides() {
val strides = RowStrides(intArrayOf(3, 3))
var counter = 0
for(i in 0..2){
for(j in 0..2){
// print(strides.offset(intArrayOf(i,j)).toString() + "\t")
require(strides.offset(intArrayOf(i,j)) == counter)
counter++
}
println()
}
}
@Test
fun checkColumnBasedStrides() {
val strides = ColumnStrides(intArrayOf(3, 3))
var counter = 0
for(i in 0..2){
for(j in 0..2){
// print(strides.offset(intArrayOf(i,j)).toString() + "\t")
require(strides.offset(intArrayOf(j,i)) == counter)
counter++
}
println()
}
}
}

View File

@ -9,7 +9,7 @@ internal class BufferExpandedTest {
@Test
fun shrink(){
val view = buffer.slice(20U..30U)
val view = buffer.slice(20..30)
assertEquals(20, view[0])
assertEquals(30, view[10])
assertFails { view[11] }

View File

@ -8,7 +8,7 @@ package space.kscience.kmath.structures
import kotlinx.coroutines.*
import space.kscience.kmath.coroutines.Math
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.StructureND
public class LazyStructureND<out T>(
@ -27,7 +27,7 @@ public class LazyStructureND<out T>(
@OptIn(PerformancePitfall::class)
override fun elements(): Sequence<Pair<IntArray, T>> {
val strides = DefaultStrides(shape)
val strides = ColumnStrides(shape)
val res = runBlocking { strides.asSequence().toList().map { index -> index to await(index) } }
return res.asSequence()
}

View File

@ -7,7 +7,7 @@ package space.kscience.kmath.histogram
import space.kscience.kmath.domains.Domain
import space.kscience.kmath.linear.Point
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.FieldOpsND
import space.kscience.kmath.nd.Shape
import space.kscience.kmath.nd.StructureND
@ -32,7 +32,7 @@ public class HistogramND<T : Comparable<T>, D : Domain<T>, V : Any>(
override val dimension: Int get() = group.shape.size
override val bins: Iterable<DomainBin<T, D, V>>
get() = DefaultStrides(group.shape).asSequence().map {
get() = ColumnStrides(group.shape).asSequence().map {
group.produceBin(it, values[it])
}.asIterable()
}

View File

@ -8,7 +8,7 @@
package space.kscience.kmath.histogram
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.real.DoubleVector
import kotlin.random.Random
@ -73,7 +73,7 @@ internal class MultivariateHistogramTest {
}
val res = histogram1 - histogram2
assertTrue {
DefaultStrides(shape).asSequence().all { index ->
ColumnStrides(shape).asSequence().all { index ->
res.values[index] <= histogram1.values[index]
}
}

View File

@ -31,7 +31,7 @@ public abstract class MultikTensorAlgebra<T, A : Ring<T>>(
protected val multikStat: Statistics = multikEngine.getStatistics()
override fun structureND(shape: Shape, initializer: A.(IntArray) -> T): MultikTensor<T> {
val strides = DefaultStrides(shape)
val strides = ColumnStrides(shape)
val memoryView = initMemoryView<T>(strides.linearSize, type)
strides.asSequence().forEachIndexed { linearIndex, tensorIndex ->
memoryView[linearIndex] = elementAlgebra.initializer(tensorIndex)

View File

@ -13,7 +13,7 @@ import org.nd4j.linalg.factory.Nd4j
import org.nd4j.linalg.factory.ops.NDBase
import org.nd4j.linalg.ops.transforms.Transforms
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.Shape
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.DoubleField
@ -178,7 +178,7 @@ public object DoubleNd4jTensorAlgebra : Nd4jTensorAlgebra<Double, DoubleField> {
override fun structureND(shape: Shape, initializer: DoubleField.(IntArray) -> Double): Nd4jArrayStructure<Double> {
val array: INDArray = Nd4j.zeros(*shape)
val indices = DefaultStrides(shape)
val indices = ColumnStrides(shape)
indices.asSequence().forEach { index ->
array.putScalar(index, elementAlgebra.initializer(index))
}

View File

@ -38,7 +38,7 @@ public val <T> Series<T>.absoluteIndices: IntRange get() = position until positi
/**
* A [BufferView] with index offset (both positive and negative) and possible size change
*/
private class OffsetBufer<T>(
private class SeriesImpl<T>(
override val origin: Buffer<T>,
override val position: Int,
override val size: Int = origin.size,
@ -86,9 +86,9 @@ public class SeriesAlgebra<T, out A : Ring<T>, out BA : BufferAlgebra<T, A>, L>(
* Create an offset series with index starting point at [index]
*/
public fun Buffer<T>.moveTo(index: Int): Series<T> = if (this is Series) {
OffsetBufer(origin, index, size)
SeriesImpl(origin, index, size)
} else {
OffsetBufer(this, index, size)
SeriesImpl(this, index, size)
}
public val Buffer<T>.offset: Int get() = if (this is Series) position else 0

View File

@ -13,7 +13,7 @@ import org.tensorflow.types.TFloat64
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.Shape
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.DoubleField
@ -39,7 +39,7 @@ public class DoubleTensorFlowAlgebra internal constructor(
initializer: DoubleField.(IntArray) -> Double,
): StructureND<Double> {
val res = TFloat64.tensorOf(org.tensorflow.ndarray.Shape.of(*shape.toLongArray())) { array ->
DefaultStrides(shape).forEach { index ->
ColumnStrides(shape).forEach { index ->
array.setDouble(elementAlgebra.initializer(index), *index.toLongArray())
}
}

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.RowStrides
import space.kscience.kmath.nd.Strides
import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.tensors.api.Tensor
@ -20,9 +21,9 @@ public abstract class BufferedTensor<T>(
public abstract val source: MutableBuffer<T>
/**
* Buffer strides based on [TensorLinearStructure] implementation
* Buffer strides based on [RowStrides] implementation
*/
override val indices: Strides get() = TensorLinearStructure(shape)
override val indices: Strides get() = RowStrides(shape)
/**
* Number of elements in tensor

View File

@ -5,32 +5,38 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.MutableStructure2D
import space.kscience.kmath.nd.MutableStructureND
import space.kscience.kmath.nd.Shape
import space.kscience.kmath.structures.*
import space.kscience.kmath.tensors.core.internal.toPrettyString
import kotlin.jvm.JvmInline
public class OffsetDoubleBuffer(
private val source: DoubleBuffer,
override val origin: DoubleBuffer,
private val offset: Int,
override val size: Int,
) : MutableBuffer<Double> {
) : MutableBuffer<Double>, BufferView<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" }
require(offset + size <= origin.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
origin[index + offset] = value
}
override fun get(index: Int): Double = source[index + offset]
override fun get(index: Int): Double = origin[index + offset]
/**
* Copy only a part of buffer that belongs to this [OffsetDoubleBuffer]
*/
override fun copy(): DoubleBuffer = source.array.copyOfRange(offset, offset + size).asBuffer()
override fun copy(): DoubleBuffer = origin.array.copyOfRange(offset, offset + size).asBuffer()
override fun iterator(): Iterator<Double> = iterator {
for (i in indices) {
@ -41,7 +47,14 @@ public class OffsetDoubleBuffer(
override fun toString(): String = Buffer.toString(this)
public fun view(addOffset: Int, newSize: Int = size - addOffset): OffsetDoubleBuffer =
OffsetDoubleBuffer(source, offset + addOffset, newSize)
OffsetDoubleBuffer(origin, offset + addOffset, newSize)
@UnstableKMathAPI
override fun originIndex(index: Int): Int = if (index in 0 until size) {
index + offset
} else {
-1
}
}
public fun OffsetDoubleBuffer.slice(range: IntRange): OffsetDoubleBuffer = view(range.first, range.last - range.first)
@ -90,3 +103,59 @@ public class DoubleTensor(
override fun toString(): String = toPrettyString()
}
@JvmInline
public value class DoubleTensor2D(public val tensor: DoubleTensor) : MutableStructureND<Double> by tensor,
MutableStructure2D<Double> {
init {
require(tensor.shape.size == 2) { "Only 2D tensors could be cast to 2D" }
}
override val rowNum: Int get() = shape[0]
override val colNum: Int get() = shape[1]
override fun get(i: Int, j: Int): Double = tensor.source[i * colNum + j]
override fun set(i: Int, j: Int, value: Double) {
tensor.source[i * colNum + j] = value
}
@OptIn(PerformancePitfall::class)
override val rows: List<OffsetDoubleBuffer>
get() = List(rowNum) { i ->
tensor.source.view(i * colNum, colNum)
}
// @OptIn(PerformancePitfall::class)
// override val columns: List<MutableBuffer<Double>> get() = List(colNum) { j ->
// object : MutableBuffer<Double>{
//
// override fun get(index: Int): Double {
// tensor.source.get()
// }
//
// override fun set(index: Int, value: Double) {
// TODO("Not yet implemented")
// }
//
// override fun copy(): MutableBuffer<Double> {
// TODO("Not yet implemented")
// }
//
// override val size: Int
// get() = TODO("Not yet implemented")
//
// override fun toString(): String {
// TODO("Not yet implemented")
// }
//
// }
// }
@PerformancePitfall
override fun elements(): Sequence<Pair<IntArray, Double>> = tensor.elements()
override fun get(index: IntArray): Double = tensor[index]
override val shape: Shape get() = tensor.shape
}

View File

@ -11,6 +11,7 @@ package space.kscience.kmath.tensors.core
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.*
import space.kscience.kmath.nd.Strides.Companion.linearSizeOf
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.structures.*
import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra
@ -121,7 +122,7 @@ public open class DoubleTensorAlgebra :
*/
override fun structureND(shape: IntArray, initializer: DoubleField.(IntArray) -> Double): DoubleTensor = fromArray(
shape,
TensorLinearStructure(shape).asSequence().map { DoubleField.initializer(it) }.toMutableList().toDoubleArray()
RowStrides(shape).asSequence().map { DoubleField.initializer(it) }.toMutableList().toDoubleArray()
)
override fun Tensor<Double>.getTensor(i: Int): DoubleTensor {
@ -130,7 +131,7 @@ public open class DoubleTensorAlgebra :
val newShape = if (lastShape.isNotEmpty()) lastShape else intArrayOf(1)
return DoubleTensor(
newShape,
dt.source.view(newShape.reduce(Int::times) * i, TensorLinearStructure.linearSizeOf(newShape))
dt.source.view(newShape.reduce(Int::times) * i, linearSizeOf(newShape))
)
}

View File

@ -119,7 +119,7 @@ public open class IntTensorAlgebra : TensorAlgebra<Int, IntRing> {
*/
override fun structureND(shape: IntArray, initializer: IntRing.(IntArray) -> Int): IntTensor = fromArray(
shape,
TensorLinearStructure(shape).asSequence().map { IntRing.initializer(it) }.toMutableList().toIntArray()
RowStrides(shape).asSequence().map { IntRing.initializer(it) }.toMutableList().toIntArray()
)
override fun Tensor<Int>.getTensor(i: Int): IntTensor {

View File

@ -1,74 +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.Strides
import kotlin.math.max
/**
* This [Strides] implementation follows the last dimension first convention
* For more information: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.strides.html
*
* @param shape the shape of the tensor.
*/
public class TensorLinearStructure(override val shape: IntArray) : Strides() {
override val strides: IntArray get() = stridesFromShape(shape)
override fun index(offset: Int): IntArray =
indexFromOffset(offset, strides, shape.size)
override val linearSize: Int get() = linearSizeOf(shape)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as TensorLinearStructure
if (!shape.contentEquals(other.shape)) return false
return true
}
override fun hashCode(): Int {
return shape.contentHashCode()
}
public companion object {
public fun linearSizeOf(shape: IntArray): Int = shape.reduce(Int::times)
public fun stridesFromShape(shape: IntArray): IntArray {
val nDim = shape.size
val res = IntArray(nDim)
if (nDim == 0)
return res
var current = nDim - 1
res[current] = 1
while (current > 0) {
res[current - 1] = max(1, shape[current]) * res[current]
current--
}
return res
}
public fun indexFromOffset(offset: Int, strides: IntArray, nDim: Int): IntArray {
val res = IntArray(nDim)
var current = offset
var strideIndex = 0
while (strideIndex < nDim) {
res[strideIndex] = (current / strides[strideIndex])
current %= strides[strideIndex]
strideIndex++
}
return res
}
}
}

View File

@ -5,17 +5,18 @@
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.nd.*
import space.kscience.kmath.nd.Strides.Companion.linearSizeOf
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.*
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
@ -177,7 +178,7 @@ internal val DoubleTensor.matrices: VirtualBuffer<DoubleTensor>
val matrixOffset = shape[n - 1] * shape[n - 2]
val matrixShape = intArrayOf(shape[n - 2], shape[n - 1])
val size = TensorLinearStructure.linearSizeOf(matrixShape)
val size = linearSizeOf(matrixShape)
return VirtualBuffer(linearSize / matrixOffset) { index ->
val offset = index * matrixOffset

View File

@ -32,8 +32,7 @@ internal fun List<OffsetIntBuffer>.concat(): IntBuffer {
}
internal val IntTensor.vectors: VirtualBuffer<IntTensor>
get() {
internal fun IntTensor.vectors(): VirtualBuffer<IntTensor> {
val n = shape.size
val vectorOffset = shape[n - 1]
val vectorShape = intArrayOf(shape.last())
@ -42,10 +41,10 @@ internal val IntTensor.vectors: VirtualBuffer<IntTensor>
val offset = index * vectorOffset
IntTensor(vectorShape, source.view(offset, vectorShape.first()))
}
}
}
internal fun IntTensor.vectorSequence(): Sequence<IntTensor> = vectors.asSequence()
internal fun IntTensor.vectorSequence(): Sequence<IntTensor> = vectors().asSequence()
internal val IntTensor.matrices: VirtualBuffer<IntTensor>

View File

@ -5,9 +5,8 @@
package space.kscience.kmath.tensors.core.internal
import space.kscience.kmath.nd.as1D
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.operations.DoubleBufferOps.Companion.map
import space.kscience.kmath.operations.toMutableList
import space.kscience.kmath.random.RandomGenerator
import space.kscience.kmath.samplers.GaussianSampler
import space.kscience.kmath.structures.DoubleBuffer
@ -67,6 +66,7 @@ internal fun format(value: Double, digits: Int = 4): String = buildString {
repeat(fLength - res.length) { append(' ') }
}
@OptIn(PerformancePitfall::class)
internal fun DoubleTensor.toPrettyString(): String = buildString {
var offset = 0
val shape = this@toPrettyString.shape
@ -85,7 +85,7 @@ internal fun DoubleTensor.toPrettyString(): String = buildString {
charOffset += 1
}
val values = vector.as1D().toMutableList().map(::format)
val values = vector.elements().map { format(it.second) }
values.joinTo(this, separator = ", ")
@ -101,7 +101,7 @@ internal fun DoubleTensor.toPrettyString(): String = buildString {
}
offset += vectorSize
if (this@toPrettyString.linearSize == offset) {
if (this@toPrettyString.indices.linearSize == offset) {
break
}

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.tensors.core
import space.kscience.kmath.nd.DoubleBufferND
import space.kscience.kmath.nd.RowStrides
import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asBuffer
@ -17,12 +18,12 @@ import space.kscience.kmath.tensors.api.Tensor
*/
public fun StructureND<Double>.copyToTensor(): DoubleTensor = if (this is DoubleTensor) {
DoubleTensor(shape, source.copy())
} else if (this is DoubleBufferND && indices is TensorLinearStructure) {
} else if (this is DoubleBufferND && indices is RowStrides) {
DoubleTensor(shape, buffer.copy())
} else {
DoubleTensor(
shape,
TensorLinearStructure(this.shape).map(this::get).toDoubleArray().asBuffer(),
RowStrides(this.shape).map(this::get).toDoubleArray().asBuffer(),
)
}
@ -46,7 +47,7 @@ public fun StructureND<Int>.toDoubleTensor(): DoubleTensor {
*/
public fun StructureND<Double>.asDoubleTensor(): DoubleTensor = if (this is DoubleTensor) {
this
} else if (this is DoubleBufferND && indices is TensorLinearStructure) {
} else if (this is DoubleBufferND && indices is RowStrides) {
DoubleTensor(shape, buffer)
} else {
copyToTensor()
@ -59,6 +60,6 @@ public fun StructureND<Int>.asIntTensor(): IntTensor = when (this) {
is IntTensor -> this
else -> IntTensor(
this.shape,
TensorLinearStructure(this.shape).map(this::get).toIntArray().asBuffer()
RowStrides(this.shape).map(this::get).toIntArray().asBuffer()
)
}

View File

@ -66,7 +66,7 @@ internal class TestDoubleTensor {
val doubleArray = DoubleBuffer(1.0, 2.0, 3.0)
// create ND buffers, no data is copied
val ndArray: MutableBufferND<Double> = DoubleBufferND(DefaultStrides(intArrayOf(3)), doubleArray)
val ndArray: MutableBufferND<Double> = DoubleBufferND(ColumnStrides(intArrayOf(3)), doubleArray)
// map to tensors
val tensorArray = ndArray.asDoubleTensor() // Data is copied because of strides change.

View File

@ -33,7 +33,7 @@ public open class ViktorFieldOpsND :
override fun structureND(shape: IntArray, initializer: DoubleField.(IntArray) -> Double): ViktorStructureND =
F64Array(*shape).apply {
DefaultStrides(shape).asSequence().forEach { index ->
ColumnStrides(shape).asSequence().forEach { index ->
set(value = DoubleField.initializer(index), indices = index)
}
}.asStructure()
@ -43,7 +43,7 @@ public open class ViktorFieldOpsND :
@PerformancePitfall
override fun StructureND<Double>.map(transform: DoubleField.(Double) -> Double): ViktorStructureND =
F64Array(*shape).apply {
DefaultStrides(shape).asSequence().forEach { index ->
ColumnStrides(shape).asSequence().forEach { index ->
set(value = DoubleField.transform(this@map[index]), indices = index)
}
}.asStructure()
@ -52,7 +52,7 @@ public open class ViktorFieldOpsND :
override fun StructureND<Double>.mapIndexed(
transform: DoubleField.(index: IntArray, Double) -> Double,
): ViktorStructureND = F64Array(*shape).apply {
DefaultStrides(shape).asSequence().forEach { index ->
ColumnStrides(shape).asSequence().forEach { index ->
set(value = DoubleField.transform(index, this@mapIndexed[index]), indices = index)
}
}.asStructure()
@ -65,7 +65,7 @@ public open class ViktorFieldOpsND :
): ViktorStructureND {
require(left.shape.contentEquals(right.shape))
return F64Array(*left.shape).apply {
DefaultStrides(left.shape).asSequence().forEach { index ->
ColumnStrides(left.shape).asSequence().forEach { index ->
set(value = DoubleField.transform(left[index], right[index]), indices = index)
}
}.asStructure()

View File

@ -7,7 +7,7 @@ package space.kscience.kmath.viktor
import org.jetbrains.bio.viktor.F64Array
import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.DefaultStrides
import space.kscience.kmath.nd.ColumnStrides
import space.kscience.kmath.nd.MutableStructureND
@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
@ -22,7 +22,7 @@ public class ViktorStructureND(public val f64Buffer: F64Array) : MutableStructur
@PerformancePitfall
override fun elements(): Sequence<Pair<IntArray, Double>> =
DefaultStrides(shape).asSequence().map { it to get(it) }
ColumnStrides(shape).asSequence().map { it to get(it) }
}
public fun F64Array.asStructure(): ViktorStructureND = ViktorStructureND(this)