From 05d15d5b34595b1c70bc575717f4ed3de158a5a8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jan 2019 10:35:00 +0300 Subject: [PATCH] Cleanup after nd optimization phase I --- .../kmath/structures/NDFieldBenchmark.kt | 2 +- .../scientifik/kmath/operations/Algebra.kt | 24 ---- .../{Fields.kt => NumberAlgebra.kt} | 54 ++++--- .../kmath/structures/BufferNDField.kt | 122 ++-------------- .../scientifik/kmath/structures/Buffers.kt | 17 +++ .../kmath/structures/ExtendedNDField.kt | 62 ++++---- .../structures/{NDField.kt => NDAlgebra.kt} | 134 +++++++++--------- .../scientifik/kmath/structures/NDElement.kt | 25 ++-- .../kmath/structures/NDStructure.kt | 17 +-- .../kmath/structures/RealNDField.kt | 43 +++--- .../kmath/structures/ShortNDRing.kt | 86 +++++++++++ .../kmath/structures/StridedContext.kt | 97 +++++++++++++ .../kmath/structures/LazyNDField.kt | 20 +-- .../kmath/structures/LazyNDFieldTest.kt | 26 ++-- 14 files changed, 403 insertions(+), 326 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/{Fields.kt => NumberAlgebra.kt} (67%) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/{NDField.kt => NDAlgebra.kt} (68%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index bc5db1e2f..a0cbd66ca 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -39,7 +39,7 @@ fun main(args: Array) { val specializedTime = measureTimeMillis { specializedField.run { - var res = one + var res:NDBuffer = one repeat(n) { res += 1.0 } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index ff647504a..771c86389 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -38,21 +38,6 @@ interface Space { fun Sequence.sum(): T = fold(zero) { left, right -> left + right } } -abstract class AbstractSpace : Space { - //TODO move to external extensions when they are available - final override operator fun T.unaryMinus(): T = multiply(this, -1.0) - - final override operator fun T.plus(b: T): T = add(this, b) - final override operator fun T.minus(b: T): T = add(this, -b) - final override operator fun T.times(k: Number) = multiply(this, k.toDouble()) - final override operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) - final override operator fun Number.times(b: T) = b * this - - final override fun Iterable.sum(): T = fold(zero) { left, right -> left + right } - - final override fun Sequence.sum(): T = fold(zero) { left, right -> left + right } -} - /** * The same as {@link Space} but with additional multiplication operation */ @@ -76,10 +61,6 @@ interface Ring : Space { // operator fun Number.minus(b: T) = -b + this } -abstract class AbstractRing : AbstractSpace(), Ring { - final override operator fun T.times(b: T): T = multiply(this, b) -} - /** * Four operations algebra */ @@ -89,8 +70,3 @@ interface Field : Ring { operator fun T.div(b: T): T = divide(this, b) operator fun Number.div(b: T) = this * divide(one, b) } - -abstract class AbstractField : AbstractRing(), Field { - final override operator fun T.div(b: T): T = divide(this, b) - final override operator fun Number.div(b: T) = this * divide(one, b) -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt similarity index 67% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index ef0d91aab..fa89cd793 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -32,7 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ -object RealField : AbstractField(), ExtendedField, Norm { +object RealField : ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b @@ -54,41 +54,51 @@ object RealField : AbstractField(), ExtendedField, Norm { +object IntRing : Ring, Norm { override val zero: Int = 0 override fun add(a: Int, b: Int): Int = a + b override fun multiply(a: Int, b: Int): Int = a * b override fun multiply(a: Int, k: Double): Int = (k * a).toInt() override val one: Int = 1 + + override fun norm(arg: Int): Int = arg } /** * A field for [Short] without boxing. Does not produce appropriate field element */ -object ShortRing : Ring { +object ShortRing : Ring, Norm{ override val zero: Short = 0 override fun add(a: Short, b: Short): Short = (a + b).toShort() override fun multiply(a: Short, b: Short): Short = (a * b).toShort() override fun multiply(a: Short, k: Double): Short = (a * k).toShort() override val one: Short = 1 + + override fun norm(arg: Short): Short = arg } -//interface FieldAdapter : Field { -// -// val field: Field -// -// abstract fun T.evolve(): R -// abstract fun R.devolve(): T -// -// override val zero get() = field.zero.evolve() -// override val one get() = field.zero.evolve() -// -// override fun add(a: R, b: R): R = field.add(a.devolve(), b.devolve()).evolve() -// -// override fun multiply(a: R, k: Double): R = field.multiply(a.devolve(), k).evolve() -// -// -// override fun multiply(a: R, b: R): R = field.multiply(a.devolve(), b.devolve()).evolve() -// -// override fun divide(a: R, b: R): R = field.divide(a.devolve(), b.devolve()).evolve() -//} \ No newline at end of file +/** + * A field for [Byte] values + */ +object ByteRing : Ring, Norm { + override val zero: Byte = 0 + override fun add(a: Byte, b: Byte): Byte = (a + b).toByte() + override fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte() + override fun multiply(a: Byte, k: Double): Byte = (a * k).toByte() + override val one: Byte = 1 + + override fun norm(arg: Byte): Byte = arg +} + +/** + * A field for [Long] values + */ +object LongRing : Ring, Norm { + override val zero: Long = 0 + override fun add(a: Long, b: Long): Long = (a + b) + override fun multiply(a: Long, b: Long): Long = (a * b) + override fun multiply(a: Long, k: Double): Long = (a * k).toLong() + override val one: Long = 1 + + override fun norm(arg: Long): Long = arg +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index 018675be3..752663712 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -1,38 +1,13 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -import scientifik.kmath.operations.FieldElement - -abstract class StridedNDField>(shape: IntArray, elementField: F) : - AbstractNDField>(shape, elementField) { - val strides = DefaultStrides(shape) - - abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer - - /** - * Convert any [NDStructure] to buffered structure using strides from this context. - * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes - * - * If the argument is [NDBuffer] with different strides structure, the new element will be produced. - */ - fun NDStructure.toBuffer(): NDBuffer { - return if (this is NDBuffer && this.strides == this@StridedNDField.strides) { - this - } else { - produce { index -> get(index) } - } - } - - fun NDBuffer.toElement(): StridedNDElement = - StridedNDElement(this@StridedNDField, buffer) -} class BufferNDField>( shape: IntArray, - elementField: F, + override val elementContext: F, val bufferFactory: BufferFactory -) : StridedNDField(shape, elementField) { +) : StridedNDField(shape), NDField> { override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) @@ -43,27 +18,27 @@ class BufferNDField>( override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - override fun produce(initializer: F.(IntArray) -> T): StridedNDElement = - StridedNDElement( + override fun produce(initializer: F.(IntArray) -> T): StridedNDFieldElement = + StridedNDFieldElement( this, - buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) + buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDFieldElement { check(arg) - return StridedNDElement( + return StridedNDFieldElement( this, - buildBuffer(arg.strides.linearSize) { offset -> elementField.transform(arg.buffer[offset]) }) + buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) } override fun mapIndexed( arg: NDBuffer, transform: F.(index: IntArray, T) -> T - ): StridedNDElement { + ): StridedNDFieldElement { check(arg) - return StridedNDElement( + return StridedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> - elementField.transform( + elementContext.transform( arg.strides.index(offset), arg.buffer[offset] ) @@ -74,77 +49,10 @@ class BufferNDField>( a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T - ): StridedNDElement { + ): StridedNDFieldElement { check(a, b) - return StridedNDElement( + return StridedNDFieldElement( this, - buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } -} - -class StridedNDElement>(override val context: StridedNDField, override val buffer: Buffer) : - NDBuffer, - FieldElement, StridedNDElement, StridedNDField>, - NDElement { - - override val elementField: F - get() = context.elementField - - override fun unwrap(): NDBuffer = - this - - override fun NDBuffer.wrap(): StridedNDElement = - StridedNDElement(context, this.buffer) - - override val strides - get() = context.strides - - override val shape: IntArray - get() = context.shape - - override fun get(index: IntArray): T = - buffer[strides.offset(index)] - - override fun elements(): Sequence> = - strides.indices().map { it to get(it) } - - override fun map(action: F.(T) -> T) = - context.run { map(this@StridedNDElement, action) }.wrap() - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - context.run { mapIndexed(this@StridedNDElement, transform) }.wrap() -} - -/** - * Element by element application of any operation on elements to the whole array. Just like in numpy - */ -operator fun > Function1.invoke(ndElement: StridedNDElement) = - ndElement.context.run { ndElement.map { invoke(it) } } - -/* plus and minus */ - -/** - * Summation operation for [StridedNDElement] and single element - */ -operator fun > StridedNDElement.plus(arg: T) = - context.run { map { it + arg } } - -/** - * Subtraction operation between [StridedNDElement] and single element - */ -operator fun > StridedNDElement.minus(arg: T) = - context.run { map { it - arg } } - -/* prod and div */ - -/** - * Product operation for [StridedNDElement] and single element - */ -operator fun > StridedNDElement.times(arg: T) = - context.run { map { it * arg } } - -/** - * Division operation between [StridedNDElement] and single element - */ -operator fun > StridedNDElement.div(arg: T) = - context.run { map { it / arg } } \ No newline at end of file +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index d93a0d46e..8c112bb87 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -33,6 +33,7 @@ interface Buffer { inline fun auto(size: Int, initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer + Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer else -> boxing(size, initializer) @@ -40,6 +41,7 @@ interface Buffer { } val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } + val ShortBufferFactory: BufferFactory = { size, initializer -> ShortBuffer(ShortArray(size, initializer)) } val IntBufferFactory: BufferFactory = { size, initializer -> IntBuffer(IntArray(size, initializer)) } val LongBufferFactory: BufferFactory = { size, initializer -> LongBuffer(LongArray(size, initializer)) } } @@ -71,6 +73,7 @@ interface MutableBuffer : Buffer { inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer + Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer else -> boxing(size, initializer) @@ -136,6 +139,20 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer = DoubleBuffer(array.copyOf()) } +inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { + override val size: Int get() = array.size + + override fun get(index: Int): Short = array[index] + + override fun set(index: Int, value: Short) { + array[index] = value + } + + override fun iterator(): Iterator = array.iterator() + + override fun copy(): MutableBuffer = ShortBuffer(array.copyOf()) +} + inline class IntBuffer(private val array: IntArray) : MutableBuffer { override val size: Int get() = array.size diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 7648efef8..0a8f1de88 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -13,36 +13,36 @@ interface ExtendedNDField, N : NDStructure> : ExponentialOperations -/** - * NDField that supports [ExtendedField] operations on its elements - */ -class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : - ExtendedNDField, NDField by ndField { - - override val shape: IntArray get() = ndField.shape - override val elementField: F get() = ndField.elementField - - override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer) - - override fun power(arg: N, pow: Double): N { - return produce { with(elementField) { power(arg[it], pow) } } - } - - override fun exp(arg: N): N { - return produce { with(elementField) { exp(arg[it]) } } - } - - override fun ln(arg: N): N { - return produce { with(elementField) { ln(arg[it]) } } - } - - override fun sin(arg: N): N { - return produce { with(elementField) { sin(arg[it]) } } - } - - override fun cos(arg: N): N { - return produce { with(elementField) { cos(arg[it]) } } - } -} +///** +// * NDField that supports [ExtendedField] operations on its elements +// */ +//class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : +// ExtendedNDField, NDField by ndField { +// +// override val shape: IntArray get() = ndField.shape +// override val elementContext: F get() = ndField.elementContext +// +// override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer) +// +// override fun power(arg: N, pow: Double): N { +// return produce { with(elementContext) { power(arg[it], pow) } } +// } +// +// override fun exp(arg: N): N { +// return produce { with(elementContext) { exp(arg[it]) } } +// } +// +// override fun ln(arg: N): N { +// return produce { with(elementContext) { ln(arg[it]) } } +// } +// +// override fun sin(arg: N): N { +// return produce { with(elementContext) { sin(arg[it]) } } +// } +// +// override fun cos(arg: N): N { +// return produce { with(elementContext) { cos(arg[it]) } } +// } +//} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt similarity index 68% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 00f19ec7e..4a560107f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -1,14 +1,62 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.AbstractField import scientifik.kmath.operations.Field -import scientifik.kmath.structures.Buffer.Companion.boxing +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space + /** * An exception is thrown when the expected ans actual shape of NDArray differs */ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() + +interface NDSpace, N : NDStructure> : Space { + val shape: IntArray + val elementContext: S + + /** + * Produce a new [N] structure using given initializer function + */ + fun produce(initializer: S.(IntArray) -> T): N + + fun map(arg: N, transform: S.(T) -> T): N + fun mapIndexed(arg: N, transform: S.(index: IntArray, T) -> T): N + fun combine(a: N, b: N, transform: S.(T, T) -> T): N + + /** + * Element-by-element addition + */ + override fun add(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue + bValue } + + /** + * Multiply all elements by constant + */ + override fun multiply(a: N, k: Double): N = + map(a) { it * k } + + operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } + operator fun N.plus(arg: T) = map(this) { value -> elementContext.run { arg + value } } + operator fun N.minus(arg: T) = map(this) { value -> elementContext.run { arg - value } } + + operator fun T.plus(arg: N) = arg + this + operator fun T.minus(arg: N) = arg - this + +} + +interface NDRing, N : NDStructure> : Ring, NDSpace { + + /** + * Element-by-element multiplication + */ + override fun multiply(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue * bValue } + + operator fun N.times(arg: T) = map(this) { value -> elementContext.run { arg * value } } + operator fun T.times(arg: N) = arg * this +} + /** * Field for n-dimensional arrays. * @param shape - the list of dimensions of the array @@ -17,21 +65,31 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run * @param F - field over structure elements * @param R - actual nd-element type of this field */ -interface NDField, N : NDStructure> : Field { +interface NDField, N : NDStructure> : Field, NDRing { - val shape: IntArray - val elementField: F + /** + * Element-by-element division + */ + override fun divide(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue / bValue } - fun produce(initializer: F.(IntArray) -> T): N - fun map(arg: N, transform: F.(T) -> T): N - fun mapIndexed(arg: N, transform: F.(index: IntArray, T) -> T): N - fun combine(a: N, b: N, transform: F.(T, T) -> T): N + operator fun N.div(arg: T) = map(this) { value -> elementContext.run { arg / value } } + operator fun T.div(arg: N) = arg / this + + fun check(vararg elements: N) { + elements.forEach { + if (!shape.contentEquals(it.shape)) { + throw ShapeMismatchException(shape, it.shape) + } + } + } companion object { /** * Create a nd-field for [Double] values */ fun real(shape: IntArray) = RealNDField(shape) + /** * Create a nd-field with boxing generic buffer */ @@ -49,61 +107,3 @@ interface NDField, N : NDStructure> : Field { } } } - - -abstract class AbstractNDField, N : NDStructure>( - override val shape: IntArray, - override val elementField: F -) : AbstractField(), NDField { - override val zero: N by lazy { produce { zero } } - - override val one: N by lazy { produce { one } } - - operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } - operator fun N.plus(arg: T) = map(this) { value -> elementField.run { arg + value } } - operator fun N.minus(arg: T) = map(this) { value -> elementField.run { arg - value } } - operator fun N.times(arg: T) = map(this) { value -> elementField.run { arg * value } } - operator fun N.div(arg: T) = map(this) { value -> elementField.run { arg / value } } - - operator fun T.plus(arg: N) = arg + this - operator fun T.minus(arg: N) = arg - this - operator fun T.times(arg: N) = arg * this - operator fun T.div(arg: N) = arg / this - - - /** - * Element-by-element addition - */ - override fun add(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue + bValue } - - /** - * Multiply all elements by cinstant - */ - override fun multiply(a: N, k: Double): N = - map(a) { it * k } - - - /** - * Element-by-element multiplication - */ - override fun multiply(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue * bValue } - - /** - * Element-by-element division - */ - override fun divide(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue / bValue } - - /** - * Check if given objects are compatible with this context. Throw exception if they are not - */ - open fun check(vararg elements: N) { - elements.forEach { - if (!shape.contentEquals(it.shape)) { - throw ShapeMismatchException(shape, it.shape) - } - } - } -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 18ada3449..1f4bddf7b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -3,13 +3,14 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Space -interface NDElement> : NDStructure { - val elementField: F +interface NDElement> : NDStructure { + val elementField: S - fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement - fun map(action: F.(T) -> T) = mapIndexed { _, value -> action(value) } + fun mapIndexed(transform: S.(index: IntArray, T) -> T): NDElement + fun map(action: S.(T) -> T) = mapIndexed { _, value -> action(value) } companion object { /** @@ -37,7 +38,7 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, initializer: F.(IntArray) -> T - ): StridedNDElement { + ): StridedNDFieldElement { val ndField = BufferNDField(shape, field, Buffer.Companion::boxing) return ndField.produce(initializer) } @@ -46,9 +47,9 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T - ): StridedNDElement { + ): StridedNDFieldElement { val ndField = NDField.auto(shape, field) - return StridedNDElement(ndField, ndField.produce(initializer).buffer) + return StridedNDFieldElement(ndField, ndField.produce(initializer).buffer) } } } @@ -121,19 +122,19 @@ operator fun > NDElement.div(arg: T): NDElement = /** * Read-only [NDStructure] coupled to the context. */ -class GenericNDElement>( +class GenericNDFieldElement>( override val context: NDField>, private val structure: NDStructure ) : NDStructure by structure, NDElement, - FieldElement, GenericNDElement, NDField>> { - override val elementField: F get() = context.elementField + FieldElement, GenericNDFieldElement, NDField>> { + override val elementField: F get() = context.elementContext override fun unwrap(): NDStructure = structure - override fun NDStructure.wrap() = GenericNDElement(context, this) + override fun NDStructure.wrap() = GenericNDFieldElement(context, this) override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - ndStructure(context.shape) { index: IntArray -> context.elementField.transform(index, get(index)) }.wrap() + ndStructure(context.shape) { index: IntArray -> context.elementContext.transform(index, get(index)) }.wrap() } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 74e3a59a2..3c422797c 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -240,19 +240,4 @@ inline fun NDStructure.combine( ): NDStructure { if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination") return inlineNdStructure(shape) { block(this[it], struct[it]) } -} - - -///** -// * Create universal mutable structure -// */ -//fun genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure { -// val strides = DefaultStrides(shape) -// val sequence = sequence { -// strides.indices().forEach { -// yield(initializer(it)) -// } -// } -// val buffer = MutableListBuffer(sequence.toMutableList()) -// return MutableBufferNDStructure(strides, buffer) -//} +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 9b3e306d5..934583002 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -2,15 +2,19 @@ package scientifik.kmath.structures import scientifik.kmath.operations.RealField -typealias RealNDElement = StridedNDElement +typealias RealNDElement = StridedNDFieldElement class RealNDField(shape: IntArray) : - StridedNDField(shape, RealField), + StridedNDField(shape), ExtendedNDField> { + override val elementContext: RealField get() = RealField + override val zero by lazy { produce { zero } } + override val one by lazy { produce { one } } + @Suppress("OVERRIDE_BY_INLINE") override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = - DoubleBuffer(DoubleArray(size){initializer(it)}) + DoubleBuffer(DoubleArray(size) { initializer(it) }) /** * Inline transform an NDStructure to @@ -21,23 +25,23 @@ class RealNDField(shape: IntArray) : ): RealNDElement { check(arg) val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } - return StridedNDElement(this, array) + return StridedNDFieldElement(this, array) } override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { - val array = buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } - return StridedNDElement(this, array) + val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } + return StridedNDFieldElement(this, array) } override fun mapIndexed( arg: NDBuffer, transform: RealField.(index: IntArray, Double) -> Double - ): StridedNDElement { + ): StridedNDFieldElement { check(arg) - return StridedNDElement( + return StridedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> - elementField.transform( + elementContext.transform( arg.strides.index(offset), arg.buffer[offset] ) @@ -48,11 +52,11 @@ class RealNDField(shape: IntArray) : a: NDBuffer, b: NDBuffer, transform: RealField.(Double, Double) -> Double - ): StridedNDElement { + ): StridedNDFieldElement { check(a, b) - return StridedNDElement( + return StridedNDFieldElement( this, - buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } override fun power(arg: NDBuffer, pow: Double) = map(arg) { power(it, pow) } @@ -64,14 +68,7 @@ class RealNDField(shape: IntArray) : override fun sin(arg: NDBuffer) = map(arg) { sin(it) } override fun cos(arg: NDBuffer) = map(arg) { cos(it) } -// -// override fun NDBuffer.times(k: Number) = mapInline { value -> value * k.toDouble() } -// -// override fun NDBuffer.div(k: Number) = mapInline { value -> value / k.toDouble() } -// -// override fun Number.times(b: NDBuffer) = b * this -// -// override fun Number.div(b: NDBuffer) = b * (1.0 / this.toDouble()) + } @@ -80,7 +77,7 @@ class RealNDField(shape: IntArray) : */ inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } - return StridedNDElement(this, DoubleBuffer(array)) + return StridedNDFieldElement(this, DoubleBuffer(array)) } /** @@ -93,13 +90,13 @@ operator fun Function1.invoke(ndElement: RealNDElement) = /* plus and minus */ /** - * Summation operation for [StridedNDElement] and single element + * Summation operation for [StridedNDFieldElement] and single element */ operator fun RealNDElement.plus(arg: Double) = context.produceInline { i -> buffer[i] + arg } /** - * Subtraction operation between [StridedNDElement] and single element + * Subtraction operation between [StridedNDFieldElement] and single element */ operator fun RealNDElement.minus(arg: Double) = context.produceInline { i -> buffer[i] - arg } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt new file mode 100644 index 000000000..e167516c8 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -0,0 +1,86 @@ +//package scientifik.kmath.structures +// +//import scientifik.kmath.operations.ShortRing +// +// +////typealias ShortNDElement = StridedNDFieldElement +// +//class ShortNDRing(shape: IntArray) : +// NDRing> { +// +// inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = +// ShortBuffer(ShortArray(size) { initializer(it) }) +// +// /** +// * Inline transform an NDStructure to +// */ +// override fun map( +// arg: NDBuffer, +// transform: ShortRing.(Short) -> Short +// ): ShortNDElement { +// check(arg) +// val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) } +// return StridedNDFieldElement(this, array) +// } +// +// override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement { +// val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } +// return StridedNDFieldElement(this, array) +// } +// +// override fun mapIndexed( +// arg: NDBuffer, +// transform: ShortRing.(index: IntArray, Short) -> Short +// ): StridedNDFieldElement { +// check(arg) +// return StridedNDFieldElement( +// this, +// buildBuffer(arg.strides.linearSize) { offset -> +// elementContext.transform( +// arg.strides.index(offset), +// arg.buffer[offset] +// ) +// }) +// } +// +// override fun combine( +// a: NDBuffer, +// b: NDBuffer, +// transform: ShortRing.(Short, Short) -> Short +// ): StridedNDFieldElement { +// check(a, b) +// return StridedNDFieldElement( +// this, +// buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) +// } +//} +// +// +///** +// * Fast element production using function inlining +// */ +//inline fun StridedNDField.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { +// val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) } +// return StridedNDFieldElement(this, ShortBuffer(array)) +//} +// +///** +// * Element by element application of any operation on elements to the whole array. Just like in numpy +// */ +//operator fun Function1.invoke(ndElement: ShortNDElement) = +// ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } +// +// +///* plus and minus */ +// +///** +// * Summation operation for [StridedNDFieldElement] and single element +// */ +//operator fun ShortNDElement.plus(arg: Short) = +// context.produceInline { i -> buffer[i] + arg } +// +///** +// * Subtraction operation between [StridedNDFieldElement] and single element +// */ +//operator fun ShortNDElement.minus(arg: Short) = +// context.produceInline { i -> buffer[i] - arg } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt new file mode 100644 index 000000000..653f56013 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt @@ -0,0 +1,97 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement + +abstract class StridedNDField>(final override val shape: IntArray) : NDField> { + val strides = DefaultStrides(shape) + + abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer + + /** + * Convert any [NDStructure] to buffered structure using strides from this context. + * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes + * + * If the argument is [NDBuffer] with different strides structure, the new element will be produced. + */ + fun NDStructure.toBuffer(): NDBuffer { + return if (this is NDBuffer && this.strides == this@StridedNDField.strides) { + this + } else { + produce { index -> get(index) } + } + } + + fun NDBuffer.toElement(): StridedNDFieldElement = + StridedNDFieldElement(this@StridedNDField, buffer) +} + +class StridedNDFieldElement>( + override val context: StridedNDField, + override val buffer: Buffer +) : + NDBuffer, + FieldElement, StridedNDFieldElement, StridedNDField>, + NDElement { + + override val elementField: F + get() = context.elementContext + + override fun unwrap(): NDBuffer = + this + + override fun NDBuffer.wrap(): StridedNDFieldElement = + StridedNDFieldElement(context, this.buffer) + + override val strides + get() = context.strides + + override val shape: IntArray + get() = context.shape + + override fun get(index: IntArray): T = + buffer[strides.offset(index)] + + override fun elements(): Sequence> = + strides.indices().map { it to get(it) } + + override fun map(action: F.(T) -> T) = + context.run { map(this@StridedNDFieldElement, action) }.wrap() + + override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = + context.run { mapIndexed(this@StridedNDFieldElement, transform) }.wrap() +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: StridedNDFieldElement) = + ndElement.context.run { ndElement.map { invoke(it) } } + +/* plus and minus */ + +/** + * Summation operation for [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.plus(arg: T) = + context.run { map { it + arg } } + +/** + * Subtraction operation between [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.minus(arg: T) = + context.run { map { it - arg } } + +/* prod and div */ + +/** + * Product operation for [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.times(arg: T) = + context.run { map { it * arg } } + +/** + * Division operation between [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.div(arg: T) = + context.run { map { it / arg } } \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index 451ef9fdd..7840586ef 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -4,15 +4,19 @@ import kotlinx.coroutines.* import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement -class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : - AbstractNDField>(shape, field) { +class LazyNDField>( + override val shape: IntArray, + override val elementContext: F, + val scope: CoroutineScope = GlobalScope +) : + NDField> { override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } override fun produce(initializer: F.(IntArray) -> T) = - LazyNDStructure(this) { elementField.initializer(it) } + LazyNDStructure(this) { elementContext.initializer(it) } override fun mapIndexed( arg: NDStructure, @@ -22,10 +26,10 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti return if (arg is LazyNDStructure) { LazyNDStructure(this) { index -> //FIXME if value of arg is already calculated, it should be used - elementField.transform(index, arg.function(index)) + elementContext.transform(index, arg.function(index)) } } else { - LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } + LazyNDStructure(this) { elementContext.transform(it, arg.await(it)) } } // return LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } } @@ -37,13 +41,13 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti check(a, b) return if (a is LazyNDStructure && b is LazyNDStructure) { LazyNDStructure(this@LazyNDField) { index -> - elementField.transform( + elementContext.transform( a.function(index), b.function(index) ) } } else { - LazyNDStructure(this@LazyNDField) { elementField.transform(a.await(it), b.await(it)) } + LazyNDStructure(this@LazyNDField) { elementContext.transform(a.await(it), b.await(it)) } } // return LazyNDStructure(this) { elementField.transform(a.await(it), b.await(it)) } } @@ -69,7 +73,7 @@ class LazyNDStructure>( override fun NDStructure.wrap(): LazyNDStructure = LazyNDStructure(context) { await(it) } override val shape: IntArray get() = context.shape - override val elementField: F get() = context.elementField + override val elementField: F get() = context.elementContext override fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement = context.run { mapIndexed(this@LazyNDStructure, transform) } diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index d5f43d3eb..ea3cb9494 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -1,20 +1,16 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.IntField -import kotlin.test.Test -import kotlin.test.assertEquals - class LazyNDFieldTest { - @Test - fun testLazyStructure() { - var counter = 0 - val regularStructure = NDField.auto(intArrayOf(2, 2, 2), IntField).produce { it[0] + it[1] - it[2] } - val result = (regularStructure.lazy(IntField) + 2).map { - counter++ - it * it - } - assertEquals(4, result[0, 0, 0]) - assertEquals(1, counter) - } +// @Test +// fun testLazyStructure() { +// var counter = 0 +// val regularStructure = NDField.auto(intArrayOf(2, 2, 2), IntRing).produce { it[0] + it[1] - it[2] } +// val result = (regularStructure.lazy(IntRing) + 2).map { +// counter++ +// it * it +// } +// assertEquals(4, result[0, 0, 0]) +// assertEquals(1, counter) +// } } \ No newline at end of file