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.algebra
import space.kscience.kmath.operations.bufferAlgebra import space.kscience.kmath.operations.bufferAlgebra
import space.kscience.kmath.operations.toList import space.kscience.kmath.operations.toList
import space.kscience.kmath.stat.KMComparisonResult
import space.kscience.kmath.stat.ksComparisonStatistic import space.kscience.kmath.stat.ksComparisonStatistic
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.slice 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 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 s3: Buffer<Double> = s1.zip(s2) { l, r -> l + r } //s1 + s2
val s4 = DoubleBufferOps.ln(s3) val s4 = DoubleBufferOps.ln(s3)
val kmTest = ksComparisonStatistic(s1, s2) val kmTest: KMComparisonResult<Double> = ksComparisonStatistic(s1, s2)
Plotly.page { Plotly.page {
h1 { +"This is my plot" } h1 { +"This is my plot" }

View File

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

View File

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

View File

@ -38,7 +38,7 @@ public inline fun <T, R : Any> StructureND<T>.mapToBuffer(
): BufferND<R> = if (this is BufferND<T>) ): BufferND<R> = if (this is BufferND<T>)
BufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) }) BufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) })
else { else {
val strides = DefaultStrides(shape) val strides = ColumnStrides(shape)
BufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) 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>) return if (this is MutableBufferND<T>)
MutableBufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) }) MutableBufferND(this.indices, factory.invoke(indices.linearSize) { transform(buffer[it]) })
else { else {
val strides = DefaultStrides(shape) val strides = ColumnStrides(shape)
MutableBufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) 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 interface WithShape {
public val shape: Shape 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) { internal fun requireIndexInShape(index: IntArray, shape: Shape) {

View File

@ -5,6 +5,7 @@
package space.kscience.kmath.nd package space.kscience.kmath.nd
import kotlin.math.max
import kotlin.native.concurrent.ThreadLocal import kotlin.native.concurrent.ThreadLocal
/** /**
@ -61,12 +62,16 @@ public abstract class Strides : ShapeIndexer {
* Iterate over ND indices in a natural order * Iterate over ND indices in a natural order
*/ */
public override fun asSequence(): Sequence<IntArray> = (0 until linearSize).asSequence().map(::index) 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] 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 { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is DefaultStrides) return false if (other !is ColumnStrides) return false
if (!shape.contentEquals(other.shape)) return false return shape.contentEquals(other.shape)
return true
} }
override fun hashCode(): Int = shape.contentHashCode() override fun hashCode(): Int = shape.contentHashCode()
public companion object { public companion object
/**
* Cached builder for default strides
*/
@Deprecated("Replace by Strides(shape)")
public operator fun invoke(shape: IntArray): Strides =
defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) }
} }
/**
* 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 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 @ThreadLocal
@ -124,4 +171,4 @@ private val defaultStridesCache = HashMap<IntArray, Strides>()
/** /**
* Cached builder for default 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.misc.PerformancePitfall
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.structures.MutableListBuffer import space.kscience.kmath.structures.MutableListBuffer
import space.kscience.kmath.structures.VirtualBuffer import space.kscience.kmath.structures.VirtualBuffer
import kotlin.jvm.JvmInline 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. * The buffer of rows of this structure. It gets elements from the structure dynamically.
*/ */
@PerformancePitfall @PerformancePitfall
override val rows: List<MutableStructure1D<T>> override val rows: List<MutableBuffer<T>>
get() = List(rowNum) { i -> MutableBuffer1DWrapper(MutableListBuffer(colNum) { j -> get(i, j) }) } get() = List(rowNum) { i -> MutableListBuffer(colNum) { j -> get(i, j) } }
/** /**
* The buffer of columns of this structure. It gets elements from the structure dynamically. * The buffer of columns of this structure. It gets elements from the structure dynamically.
*/ */
@PerformancePitfall @PerformancePitfall
override val columns: List<MutableStructure1D<T>> override val columns: List<MutableBuffer<T>>
get() = List(colNum) { j -> MutableBuffer1DWrapper(MutableListBuffer(rowNum) { i -> get(i, j) }) } 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, shape: IntArray,
bufferFactory: BufferFactory<T> = BufferFactory.boxing(), bufferFactory: BufferFactory<T> = BufferFactory.boxing(),
initializer: (IntArray) -> T, initializer: (IntArray) -> T,
): BufferND<T> = buffered(DefaultStrides(shape), bufferFactory, initializer) ): BufferND<T> = buffered(ColumnStrides(shape), bufferFactory, initializer)
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
shape: IntArray, shape: IntArray,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferND<T> = auto(DefaultStrides(shape), initializer) ): BufferND<T> = auto(ColumnStrides(shape), initializer)
@JvmName("autoVarArg") @JvmName("autoVarArg")
public inline fun <reified T : Any> auto( public inline fun <reified T : Any> auto(
vararg shape: Int, vararg shape: Int,
crossinline initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferND<T> = ): BufferND<T> =
auto(DefaultStrides(shape), initializer) auto(ColumnStrides(shape), initializer)
public inline fun <T : Any> auto( public inline fun <T : Any> auto(
type: KClass<T>, type: KClass<T>,
vararg shape: Int, vararg shape: Int,
crossinline initializer: (IntArray) -> T, 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 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.Structure2D
import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.nd.as2D import space.kscience.kmath.nd.as2D
@ -31,7 +31,7 @@ internal class BufferAccessor2D<T>(
//TODO optimize wrapper //TODO optimize wrapper
fun MutableBuffer<T>.collect(): Structure2D<T> = StructureND.buffered( fun MutableBuffer<T>.collect(): Structure2D<T> = StructureND.buffered(
DefaultStrides(intArrayOf(rowNum, colNum)), ColumnStrides(intArrayOf(rowNum, colNum)),
factory factory
) { (i, j) -> ) { (i, j) ->
get(i, j) get(i, j)

View File

@ -15,6 +15,9 @@ public interface BufferView<T> : Buffer<T> {
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public fun originIndex(index: Int): Int 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>( public class BufferSlice<T>(
override val origin: Buffer<T>, override val origin: Buffer<T>,
public val offset: UInt = 0U, public val offset: Int = 0,
override val size: Int, override val size: Int,
) : BufferView<T> { ) : BufferView<T> {
init { init {
require(size > 0) { "Size must be positive" } require(size > 0) { "Size must be positive" }
require(offset + size.toUInt() <= origin.size.toUInt()) { require(offset + size <= origin.size) {
"End of buffer ${offset + size.toUInt()} is beyond the end of origin buffer 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) { override fun get(index: Int): T = if (index >= size) {
throw IndexOutOfBoundsException("$index is out of ${0 until size} rage") throw IndexOutOfBoundsException("$index is out of ${0 until size} rage")
} else { } else {
origin[index.toUInt() + offset] origin[index + offset]
} }
override fun iterator(): Iterator<T> = 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 @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. * 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>( public class BufferExpanded<T>(
override val origin: Buffer<T>, override val origin: Buffer<T>,
public val defaultValue: T, private val defaultValue: T,
public val offset: Int = 0, public val offset: Int = 0,
override val size: Int = origin.size, override val size: Int = origin.size,
) : BufferView<T> { ) : BufferView<T> {
@ -79,17 +82,17 @@ public class BufferExpanded<T>(
/** /**
* Zero-copy select a slice inside the original buffer * 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( BufferSlice(
origin, origin,
this.offset + range.first, this.offset + range.first,
(range.last - range.first).toInt() + 1 (range.last - range.first) + 1
) )
} else { } else {
BufferSlice( BufferSlice(
this, this,
range.first, 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) { ): BufferView<T> = if (range.first >= 0 && range.last < size) {
BufferSlice( BufferSlice(
this, this,
range.first.toUInt(), range.first,
(range.last - range.first) + 1 (range.last - range.first) + 1
) )
} else { } else {
@ -118,7 +121,7 @@ public fun <T> Buffer<T>.expand(
/** /**
* A [BufferView] that overrides indexing of the original buffer * A [BufferView] that overrides indexing of the original buffer
*/ */
public class PermutatedBuffer<T>( public class PermutedBuffer<T>(
override val origin: Buffer<T>, override val origin: Buffer<T>,
private val permutations: IntArray, private val permutations: IntArray,
) : BufferView<T> { ) : BufferView<T> {
@ -145,4 +148,4 @@ public class PermutatedBuffer<T>(
/** /**
* Created a permuted view of given buffer using provided [indices] * 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

@ -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 @Test
fun shrink(){ fun shrink(){
val view = buffer.slice(20U..30U) val view = buffer.slice(20..30)
assertEquals(20, view[0]) assertEquals(20, view[0])
assertEquals(30, view[10]) assertEquals(30, view[10])
assertFails { view[11] } assertFails { view[11] }

View File

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

View File

@ -7,7 +7,7 @@ package space.kscience.kmath.histogram
import space.kscience.kmath.domains.Domain import space.kscience.kmath.domains.Domain
import space.kscience.kmath.linear.Point 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.FieldOpsND
import space.kscience.kmath.nd.Shape import space.kscience.kmath.nd.Shape
import space.kscience.kmath.nd.StructureND 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 dimension: Int get() = group.shape.size
override val bins: Iterable<DomainBin<T, D, V>> 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]) group.produceBin(it, values[it])
}.asIterable() }.asIterable()
} }

View File

@ -8,7 +8,7 @@
package space.kscience.kmath.histogram package space.kscience.kmath.histogram
import space.kscience.kmath.misc.UnstableKMathAPI 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.operations.invoke
import space.kscience.kmath.real.DoubleVector import space.kscience.kmath.real.DoubleVector
import kotlin.random.Random import kotlin.random.Random
@ -73,7 +73,7 @@ internal class MultivariateHistogramTest {
} }
val res = histogram1 - histogram2 val res = histogram1 - histogram2
assertTrue { assertTrue {
DefaultStrides(shape).asSequence().all { index -> ColumnStrides(shape).asSequence().all { index ->
res.values[index] <= histogram1.values[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() protected val multikStat: Statistics = multikEngine.getStatistics()
override fun structureND(shape: Shape, initializer: A.(IntArray) -> T): MultikTensor<T> { 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) val memoryView = initMemoryView<T>(strides.linearSize, type)
strides.asSequence().forEachIndexed { linearIndex, tensorIndex -> strides.asSequence().forEachIndexed { linearIndex, tensorIndex ->
memoryView[linearIndex] = elementAlgebra.initializer(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.factory.ops.NDBase
import org.nd4j.linalg.ops.transforms.Transforms import org.nd4j.linalg.ops.transforms.Transforms
import space.kscience.kmath.misc.PerformancePitfall 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.Shape
import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.DoubleField 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> { override fun structureND(shape: Shape, initializer: DoubleField.(IntArray) -> Double): Nd4jArrayStructure<Double> {
val array: INDArray = Nd4j.zeros(*shape) val array: INDArray = Nd4j.zeros(*shape)
val indices = DefaultStrides(shape) val indices = ColumnStrides(shape)
indices.asSequence().forEach { index -> indices.asSequence().forEach { index ->
array.putScalar(index, elementAlgebra.initializer(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 * 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 origin: Buffer<T>,
override val position: Int, override val position: Int,
override val size: Int = origin.size, 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] * Create an offset series with index starting point at [index]
*/ */
public fun Buffer<T>.moveTo(index: Int): Series<T> = if (this is Series) { public fun Buffer<T>.moveTo(index: Int): Series<T> = if (this is Series) {
OffsetBufer(origin, index, size) SeriesImpl(origin, index, size)
} else { } else {
OffsetBufer(this, index, size) SeriesImpl(this, index, size)
} }
public val Buffer<T>.offset: Int get() = if (this is Series) position else 0 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.expressions.Symbol
import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI 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.Shape
import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.DoubleField
@ -39,7 +39,7 @@ public class DoubleTensorFlowAlgebra internal constructor(
initializer: DoubleField.(IntArray) -> Double, initializer: DoubleField.(IntArray) -> Double,
): StructureND<Double> { ): StructureND<Double> {
val res = TFloat64.tensorOf(org.tensorflow.ndarray.Shape.of(*shape.toLongArray())) { array -> 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()) array.setDouble(elementAlgebra.initializer(index), *index.toLongArray())
} }
} }

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.tensors.core package space.kscience.kmath.tensors.core
import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.PerformancePitfall
import space.kscience.kmath.nd.RowStrides
import space.kscience.kmath.nd.Strides import space.kscience.kmath.nd.Strides
import space.kscience.kmath.structures.MutableBuffer import space.kscience.kmath.structures.MutableBuffer
import space.kscience.kmath.tensors.api.Tensor import space.kscience.kmath.tensors.api.Tensor
@ -20,9 +21,9 @@ public abstract class BufferedTensor<T>(
public abstract val source: MutableBuffer<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 * Number of elements in tensor

View File

@ -5,32 +5,38 @@
package space.kscience.kmath.tensors.core 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.structures.*
import space.kscience.kmath.tensors.core.internal.toPrettyString import space.kscience.kmath.tensors.core.internal.toPrettyString
import kotlin.jvm.JvmInline
public class OffsetDoubleBuffer( public class OffsetDoubleBuffer(
private val source: DoubleBuffer, override val origin: DoubleBuffer,
private val offset: Int, private val offset: Int,
override val size: Int, override val size: Int,
) : MutableBuffer<Double> { ) : MutableBuffer<Double>, BufferView<Double> {
init { init {
require(offset >= 0) { "Offset must be non-negative" } require(offset >= 0) { "Offset must be non-negative" }
require(size >= 0) { "Size 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) { override fun set(index: Int, value: Double) {
require(index in 0 until size) { "Index must be in [0, size)" } 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] * 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 { override fun iterator(): Iterator<Double> = iterator {
for (i in indices) { for (i in indices) {
@ -41,7 +47,14 @@ public class OffsetDoubleBuffer(
override fun toString(): String = Buffer.toString(this) override fun toString(): String = Buffer.toString(this)
public fun view(addOffset: Int, newSize: Int = size - addOffset): OffsetDoubleBuffer = 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) 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() 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.PerformancePitfall
import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.* import space.kscience.kmath.nd.*
import space.kscience.kmath.nd.Strides.Companion.linearSizeOf
import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.structures.* import space.kscience.kmath.structures.*
import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra 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( override fun structureND(shape: IntArray, initializer: DoubleField.(IntArray) -> Double): DoubleTensor = fromArray(
shape, 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 { 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) val newShape = if (lastShape.isNotEmpty()) lastShape else intArrayOf(1)
return DoubleTensor( return DoubleTensor(
newShape, 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( override fun structureND(shape: IntArray, initializer: IntRing.(IntArray) -> Int): IntTensor = fromArray(
shape, 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 { 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 package space.kscience.kmath.tensors.core.internal
import space.kscience.kmath.nd.MutableStructure2D import space.kscience.kmath.nd.*
import space.kscience.kmath.nd.Structure2D import space.kscience.kmath.nd.Strides.Companion.linearSizeOf
import space.kscience.kmath.nd.as2D
import space.kscience.kmath.nd.get
import space.kscience.kmath.operations.asSequence import space.kscience.kmath.operations.asSequence
import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.VirtualBuffer import space.kscience.kmath.structures.VirtualBuffer
import space.kscience.kmath.structures.asBuffer import space.kscience.kmath.structures.asBuffer
import space.kscience.kmath.structures.indices 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.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.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.sqrt import kotlin.math.sqrt
@ -177,7 +178,7 @@ internal val DoubleTensor.matrices: VirtualBuffer<DoubleTensor>
val matrixOffset = shape[n - 1] * shape[n - 2] val matrixOffset = shape[n - 1] * shape[n - 2]
val matrixShape = intArrayOf(shape[n - 2], shape[n - 1]) val matrixShape = intArrayOf(shape[n - 2], shape[n - 1])
val size = TensorLinearStructure.linearSizeOf(matrixShape) val size = linearSizeOf(matrixShape)
return VirtualBuffer(linearSize / matrixOffset) { index -> return VirtualBuffer(linearSize / matrixOffset) { index ->
val offset = index * matrixOffset val offset = index * matrixOffset

View File

@ -32,8 +32,7 @@ internal fun List<OffsetIntBuffer>.concat(): IntBuffer {
} }
internal val IntTensor.vectors: VirtualBuffer<IntTensor> internal fun IntTensor.vectors(): VirtualBuffer<IntTensor> {
get() {
val n = shape.size val n = shape.size
val vectorOffset = shape[n - 1] val vectorOffset = shape[n - 1]
val vectorShape = intArrayOf(shape.last()) val vectorShape = intArrayOf(shape.last())
@ -45,7 +44,7 @@ internal val IntTensor.vectors: VirtualBuffer<IntTensor>
} }
internal fun IntTensor.vectorSequence(): Sequence<IntTensor> = vectors.asSequence() internal fun IntTensor.vectorSequence(): Sequence<IntTensor> = vectors().asSequence()
internal val IntTensor.matrices: VirtualBuffer<IntTensor> internal val IntTensor.matrices: VirtualBuffer<IntTensor>

View File

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

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.tensors.core package space.kscience.kmath.tensors.core
import space.kscience.kmath.nd.DoubleBufferND import space.kscience.kmath.nd.DoubleBufferND
import space.kscience.kmath.nd.RowStrides
import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.asBuffer 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) { public fun StructureND<Double>.copyToTensor(): DoubleTensor = if (this is DoubleTensor) {
DoubleTensor(shape, source.copy()) DoubleTensor(shape, source.copy())
} else if (this is DoubleBufferND && indices is TensorLinearStructure) { } else if (this is DoubleBufferND && indices is RowStrides) {
DoubleTensor(shape, buffer.copy()) DoubleTensor(shape, buffer.copy())
} else { } else {
DoubleTensor( DoubleTensor(
shape, 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) { public fun StructureND<Double>.asDoubleTensor(): DoubleTensor = if (this is DoubleTensor) {
this this
} else if (this is DoubleBufferND && indices is TensorLinearStructure) { } else if (this is DoubleBufferND && indices is RowStrides) {
DoubleTensor(shape, buffer) DoubleTensor(shape, buffer)
} else { } else {
copyToTensor() copyToTensor()
@ -59,6 +60,6 @@ public fun StructureND<Int>.asIntTensor(): IntTensor = when (this) {
is IntTensor -> this is IntTensor -> this
else -> IntTensor( else -> IntTensor(
this.shape, 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) val doubleArray = DoubleBuffer(1.0, 2.0, 3.0)
// create ND buffers, no data is copied // 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 // map to tensors
val tensorArray = ndArray.asDoubleTensor() // Data is copied because of strides change. 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 = override fun structureND(shape: IntArray, initializer: DoubleField.(IntArray) -> Double): ViktorStructureND =
F64Array(*shape).apply { F64Array(*shape).apply {
DefaultStrides(shape).asSequence().forEach { index -> ColumnStrides(shape).asSequence().forEach { index ->
set(value = DoubleField.initializer(index), indices = index) set(value = DoubleField.initializer(index), indices = index)
} }
}.asStructure() }.asStructure()
@ -43,7 +43,7 @@ public open class ViktorFieldOpsND :
@PerformancePitfall @PerformancePitfall
override fun StructureND<Double>.map(transform: DoubleField.(Double) -> Double): ViktorStructureND = override fun StructureND<Double>.map(transform: DoubleField.(Double) -> Double): ViktorStructureND =
F64Array(*shape).apply { F64Array(*shape).apply {
DefaultStrides(shape).asSequence().forEach { index -> ColumnStrides(shape).asSequence().forEach { index ->
set(value = DoubleField.transform(this@map[index]), indices = index) set(value = DoubleField.transform(this@map[index]), indices = index)
} }
}.asStructure() }.asStructure()
@ -52,7 +52,7 @@ public open class ViktorFieldOpsND :
override fun StructureND<Double>.mapIndexed( override fun StructureND<Double>.mapIndexed(
transform: DoubleField.(index: IntArray, Double) -> Double, transform: DoubleField.(index: IntArray, Double) -> Double,
): ViktorStructureND = F64Array(*shape).apply { ): ViktorStructureND = F64Array(*shape).apply {
DefaultStrides(shape).asSequence().forEach { index -> ColumnStrides(shape).asSequence().forEach { index ->
set(value = DoubleField.transform(index, this@mapIndexed[index]), indices = index) set(value = DoubleField.transform(index, this@mapIndexed[index]), indices = index)
} }
}.asStructure() }.asStructure()
@ -65,7 +65,7 @@ public open class ViktorFieldOpsND :
): ViktorStructureND { ): ViktorStructureND {
require(left.shape.contentEquals(right.shape)) require(left.shape.contentEquals(right.shape))
return F64Array(*left.shape).apply { 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) set(value = DoubleField.transform(left[index], right[index]), indices = index)
} }
}.asStructure() }.asStructure()

View File

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