From 876a363e0ba1a7b653e96d6216d473a0d4e83a9f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jan 2019 19:10:08 +0300 Subject: [PATCH] Cleanup after nd optimization phase II --- .../kmath/structures/NDFieldBenchmark.kt | 2 +- .../kmath/structures/NDFieldBenchmark.kt | 96 ++++----- .../scientifik/kmath/operations/Algebra.kt | 4 +- .../kmath/operations/AlgebraElements.kt | 23 ++- .../kmath/operations/NumberAlgebra.kt | 6 +- .../{BufferNDField.kt => BoxingNDField.kt} | 31 +-- .../kmath/structures/BufferedNDAlgebra.kt | 45 +++++ .../kmath/structures/BufferedNDElement.kt | 88 +++++++++ .../scientifik/kmath/structures/Buffers.kt | 8 +- .../scientifik/kmath/structures/NDAlgebra.kt | 93 ++++++--- .../scientifik/kmath/structures/NDElement.kt | 73 +++---- .../kmath/structures/RealNDField.kt | 32 +-- .../kmath/structures/ShortNDRing.kt | 182 +++++++++--------- .../kmath/structures/StridedContext.kt | 97 ---------- .../kmath/structures/ComplexBufferSpec.kt | 2 +- .../kmath/structures/LazyNDField.kt | 10 +- 16 files changed, 433 insertions(+), 359 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/{BufferNDField.kt => BoxingNDField.kt} (63%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 421b5fb6c..ad180b727 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -17,7 +17,7 @@ open class NDFieldBenchmark { @Benchmark fun autoElementAdd() { - var res = bufferedField.run { one.toElement() } + var res = genericField.one repeat(n) { res += 1.0 } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index a0cbd66ca..f4ffccd14 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -16,54 +16,54 @@ fun main(args: Array) { //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) - - val autoTime = measureTimeMillis { - autoField.run { - var res = one - repeat(n) { - res += 1.0 - } - } - } - - println("Buffered addition completed in $autoTime millis") - - val elementTime = measureTimeMillis { - var res = genericField.one - repeat(n) { - res += 1.0 - } - } - - println("Element addition completed in $elementTime millis") - - val specializedTime = measureTimeMillis { - specializedField.run { - var res:NDBuffer = one - repeat(n) { - res += 1.0 - } - } - } - - println("Specialized addition completed in $specializedTime millis") - - - val lazyTime = measureTimeMillis { - lazyField.run { - val res = one.map { - var c = 0.0 - repeat(n) { - c += 1.0 - } - c - } - - res.elements().forEach { it.second } - } - } - - println("Lazy addition completed in $lazyTime millis") +// +// val autoTime = measureTimeMillis { +// autoField.run { +// var res = one +// repeat(n) { +// res += 1.0 +// } +// } +// } +// +// println("Buffered addition completed in $autoTime millis") +// +// val elementTime = measureTimeMillis { +// var res = genericField.one +// repeat(n) { +// res += 1.0 +// } +// } +// +// println("Element addition completed in $elementTime millis") +// +// val specializedTime = measureTimeMillis { +// specializedField.run { +// var res:NDBuffer = one +// repeat(n) { +// res += 1.0 +// } +// } +// } +// +// println("Specialized addition completed in $specializedTime millis") +// +// +// val lazyTime = measureTimeMillis { +// lazyField.run { +// val res = one.map { +// var c = 0.0 +// repeat(n) { +// c += 1.0 +// } +// c +// } +// +// res.elements().forEach { it.second } +// } +// } +// +// println("Lazy addition completed in $lazyTime millis") val genericTime = measureTimeMillis { //genericField.run(action) 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 771c86389..d77df4b7f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -33,9 +33,9 @@ interface Space { operator fun T.times(k: Number) = multiply(this, k.toDouble()) operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun Number.times(b: T) = b * this - fun Iterable.sum(): T = fold(zero) { left, right -> left + right } + fun Iterable.sum(): T = fold(zero) { left, right -> add(left,right) } - fun Sequence.sum(): T = fold(zero) { left, right -> left + right } + fun Sequence.sum(): T = fold(zero) { left, right -> add(left, right) } } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt index 4e5854453..093021ae3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt @@ -2,13 +2,20 @@ package scientifik.kmath.operations /** * The generic mathematics elements which is able to store its context - * @param S the type of mathematical context for this element + * @param T the type of space operation results + * @param I self type of the element. Needed for static type checking + * @param C the type of mathematical context for this element */ -interface MathElement { +interface MathElement { /** * The context this element belongs to */ - val context: S + val context: C +} + +interface MathWrapper { + fun unwrap(): T + fun T.wrap(): I } /** @@ -17,13 +24,7 @@ interface MathElement { * @param I self type of the element. Needed for static type checking * @param S the type of space */ -interface SpaceElement, S : Space> : MathElement { - /** - * Self value. Needed for static type checking. - */ - fun unwrap(): T - - fun T.wrap(): I +interface SpaceElement, S : Space> : MathElement, MathWrapper { operator fun plus(b: T) = context.add(unwrap(), b).wrap() operator fun minus(b: T) = context.add(unwrap(), context.multiply(b, -1.0)).wrap() @@ -35,7 +36,6 @@ interface SpaceElement, S : Space> : MathElement * Ring element */ interface RingElement, R : Ring> : SpaceElement { - override val context: R operator fun times(b: T) = context.multiply(unwrap(), b).wrap() } @@ -44,6 +44,5 @@ interface RingElement, R : Ring> : SpaceElement, F : Field> : RingElement { override val context: F - operator fun div(b: T) = context.divide(unwrap(), b).wrap() } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index fa89cd793..2dc6d3228 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -32,6 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") object RealField : ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b @@ -45,10 +46,13 @@ object RealField : ExtendedField, Norm { override fun power(arg: Double, pow: Double): Double = arg.pow(pow) override fun exp(arg: Double): Double = kotlin.math.exp(arg) - override fun ln(arg: Double): Double = kotlin.math.ln(arg) override fun norm(arg: Double): Double = kotlin.math.abs(arg) + + override fun Double.unaryMinus(): Double = -this + + override fun Double.minus(b: Double): Double = this - b } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt similarity index 63% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt index 752663712..e06119766 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt @@ -1,15 +1,19 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement -class BufferNDField>( - shape: IntArray, +class BoxingNDField>( + override val shape: IntArray, override val elementContext: F, val bufferFactory: BufferFactory -) : StridedNDField(shape), NDField> { +) : BufferedNDField { - override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) + override val strides: Strides = DefaultStrides(shape) + + override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = + bufferFactory(size, initializer) override fun check(vararg elements: NDBuffer) { if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") @@ -18,14 +22,14 @@ class BufferNDField>( override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - override fun produce(initializer: F.(IntArray) -> T): StridedNDFieldElement = - StridedNDFieldElement( + override fun produce(initializer: F.(IntArray) -> T) = + BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDFieldElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferedNDFieldElement { check(arg) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) } @@ -33,9 +37,9 @@ class BufferNDField>( override fun mapIndexed( arg: NDBuffer, transform: F.(index: IntArray, T) -> T - ): StridedNDFieldElement { + ): BufferedNDFieldElement { check(arg) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform( @@ -49,10 +53,13 @@ class BufferNDField>( a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T - ): StridedNDFieldElement { + ): BufferedNDFieldElement { check(a, b) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } + + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + BufferedNDFieldElement(this@BoxingNDField, buffer) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt new file mode 100644 index 000000000..03b5407f5 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt @@ -0,0 +1,45 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.* + +interface BufferedNDAlgebra: NDAlgebra>{ + val strides: Strides + + fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer + + override fun check(vararg elements: NDBuffer) { + if (!elements.all { it.strides == this.strides }) error("Strides mismatch") + } + + /** + * 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@BufferedNDAlgebra.strides) { + this + } else { + produce { index -> get(index) } + } + } + + /** + * Convert a buffer to element of this algebra + */ + fun NDBuffer.toElement(): MathElement> +} + + +interface BufferedNDSpace> : NDSpace>, BufferedNDAlgebra { + override fun NDBuffer.toElement(): SpaceElement, *, out BufferedNDSpace> +} + +interface BufferedNDRing> : NDRing>, BufferedNDSpace { + override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> +} + +interface BufferedNDField> : NDField>, BufferedNDRing { + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt new file mode 100644 index 000000000..04049368a --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt @@ -0,0 +1,88 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.* + +/** + * Base interface for an element with context, containing strides + */ +interface BufferedNDElement : NDBuffer, NDElement> { + override val context: BufferedNDAlgebra + + override val strides get() = context.strides + + override val shape: IntArray get() = context.shape +} + +class BufferedNDSpaceElement>( + override val context: BufferedNDSpace, + override val buffer: Buffer +) : BufferedNDElement, SpaceElement, BufferedNDSpaceElement, BufferedNDSpace> { + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferedNDSpaceElement { + context.check(this) + return BufferedNDSpaceElement(context, buffer) + } +} + +class BufferedNDRingElement>( + override val context: BufferedNDRing, + override val buffer: Buffer +) : BufferedNDElement, RingElement, BufferedNDRingElement, BufferedNDRing> { + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferedNDRingElement { + context.check(this) + return BufferedNDRingElement(context, buffer) + } +} + +class BufferedNDFieldElement>( + override val context: BufferedNDField, + override val buffer: Buffer +) : BufferedNDElement, FieldElement, BufferedNDFieldElement, BufferedNDField> { + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferedNDFieldElement { + context.check(this) + return BufferedNDFieldElement(context, buffer) + } +} + + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: BufferedNDElement) = + ndElement.context.run { map(ndElement) { invoke(it) }.toElement() } + +/* plus and minus */ + +/** + * Summation operation for [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.plus(arg: T) = + context.map(this) { it + arg }.wrap() + +/** + * Subtraction operation between [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.minus(arg: T) = + context.map(this) { it - arg }.wrap() + +/* prod and div */ + +/** + * Product operation for [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.times(arg: T) = + context.map(this) { it * arg }.wrap() + +/** + * Division operation between [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.div(arg: T) = + context.map(this) { it / arg }.wrap() \ 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 8c112bb87..f7c04bdbe 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -30,7 +30,7 @@ interface Buffer { * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") - inline fun auto(size: Int, initializer: (Int) -> T): Buffer { + inline fun auto(size: Int, crossinline 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 @@ -40,8 +40,10 @@ interface Buffer { } } - val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } - val ShortBufferFactory: BufferFactory = { size, initializer -> ShortBuffer(ShortArray(size, initializer)) } + 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)) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 4a560107f..d8da8c937 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -11,40 +11,79 @@ import scientifik.kmath.operations.Space class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() -interface NDSpace, N : NDStructure> : Space { +/** + * The base interface for all nd-algebra implementations + * @param T the type of nd-structure element + * @param C the type of the context + * @param N the type of the structure + */ +interface NDAlgebra> { val shape: IntArray - val elementContext: S + val elementContext: C /** * Produce a new [N] structure using given initializer function */ - fun produce(initializer: S.(IntArray) -> T): N + fun produce(initializer: C.(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 + /** + * Map elements from one structure to another one + */ + fun map(arg: N, transform: C.(T) -> T): N + /** + * Map indexed elements + */ + fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N + + /** + * Combine two structures into one + */ + fun combine(a: N, b: N, transform: C.(T, T) -> T): N + + /** + * Check if given elements are consistent with this context + */ + fun check(vararg elements: N) { + elements.forEach { + if (!shape.contentEquals(it.shape)) { + throw ShapeMismatchException(shape, it.shape) + } + } + } + + /** + * element-by-element invoke a function working on [T] on a [NDStructure] + */ + operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } +} + +/** + * An nd-space over element space + */ +interface NDSpace, N : NDStructure> : Space, NDAlgebra { /** * Element-by-element addition */ override fun add(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue + bValue } + combine(a, b) { aValue, bValue -> add(aValue, bValue) } /** * Multiply all elements by constant */ override fun multiply(a: N, k: Double): N = - map(a) { it * k } + map(a) { multiply(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 + operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) } + operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) } + operator fun T.plus(arg: N) = map(arg) { value -> add(this@plus, value) } + operator fun T.minus(arg: N) = map(arg) { value -> add(-this@minus, value) } } +/** + * An nd-ring over element ring + */ interface NDRing, N : NDStructure> : Ring, NDSpace { /** @@ -53,16 +92,16 @@ interface NDRing, N : NDStructure> : Ring, NDSpace 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 + operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) } + operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) } } /** - * Field for n-dimensional arrays. + * Field for n-dimensional structures. * @param shape - the list of dimensions of the array * @param elementField - operations field defined on individual array element * @param T - the type of the element contained in ND structure - * @param F - field over structure elements + * @param F - field of structure elements * @param R - actual nd-element type of this field */ interface NDField, N : NDStructure> : Field, NDRing { @@ -73,17 +112,9 @@ interface NDField, N : NDStructure> : Field, NDRing aValue / bValue } - operator fun N.div(arg: T) = map(this) { value -> elementContext.run { arg / value } } + operator fun N.div(arg: T) = map(this) { value -> 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 @@ -94,16 +125,16 @@ interface NDField, N : NDStructure> : Field, NDRing> buffered(shape: IntArray, field: F) = - BufferNDField(shape, field, Buffer.Companion::boxing) + BoxingNDField(shape, field, Buffer.Companion::boxing) /** * Create a most suitable implementation for nd-field using reified class. */ @Suppress("UNCHECKED_CAST") - inline fun > auto(shape: IntArray, field: F): StridedNDField = + inline fun > auto(shape: IntArray, field: F): BufferedNDField = when { - T::class == Double::class -> real(shape) as StridedNDField - else -> BufferNDField(shape, field, Buffer.Companion::auto) + T::class == Double::class -> real(shape) as BufferedNDField + else -> BoxingNDField(shape, field, Buffer.Companion::auto) } } } 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 1f4bddf7b..6a09f1cb1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -1,16 +1,21 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space -interface NDElement> : NDStructure { - val elementField: S +interface NDElement> : NDStructure { - fun mapIndexed(transform: S.(index: IntArray, T) -> T): NDElement - fun map(action: S.(T) -> T) = mapIndexed { _, value -> action(value) } + val context: NDAlgebra + + fun unwrap(): N + + fun N.wrap(): NDElement + + fun mapIndexed(transform: C.(index: IntArray, T) -> T) = context.mapIndexed(unwrap(), transform).wrap() + fun map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap() companion object { /** @@ -38,8 +43,8 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, initializer: F.(IntArray) -> T - ): StridedNDFieldElement { - val ndField = BufferNDField(shape, field, Buffer.Companion::boxing) + ): BufferedNDElement { + val ndField = BoxingNDField(shape, field, Buffer.Companion::boxing) return ndField.produce(initializer) } @@ -47,47 +52,46 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T - ): StridedNDFieldElement { + ): BufferedNDFieldElement { val ndField = NDField.auto(shape, field) - return StridedNDFieldElement(ndField, ndField.produce(initializer).buffer) + return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer) } } } - /** - * Element by element application of any operation on elements to the whole array. Just like in numpy + * Element by element application of any operation on elements to the whole [NDElement] */ -operator fun > Function1.invoke(ndElement: NDElement) = +operator fun Function1.invoke(ndElement: NDElement) = ndElement.map { value -> this@invoke(value) } /* plus and minus */ /** - * Summation operation for [NDElements] and single element + * Summation operation for [NDElement] and single element */ -operator fun > NDElement.plus(arg: T): NDElement = - this.map { value -> elementField.run { arg + value } } +operator fun > NDElement.plus(arg: T) = + map { value -> arg + value } /** - * Subtraction operation between [NDElements] and single element + * Subtraction operation between [NDElement] and single element */ -operator fun > NDElement.minus(arg: T): NDElement = - this.map { value -> elementField.run { arg - value } } +operator fun > NDElement.minus(arg: T) = + map { value -> arg - value } /* prod and div */ /** - * Product operation for [NDElements] and single element + * Product operation for [NDElement] and single element */ -operator fun > NDElement.times(arg: T): NDElement = - this.map { value -> elementField.run { arg * value } } +operator fun > NDElement.times(arg: T) = + map { value -> arg * value } /** - * Division operation between [NDElements] and single element + * Division operation between [NDElement] and single element */ -operator fun > NDElement.div(arg: T): NDElement = - this.map { value -> elementField.run { arg / value } } +operator fun > NDElement.div(arg: T) = + map { value -> arg / value } // /** @@ -117,24 +121,3 @@ operator fun > NDElement.div(arg: T): NDElement = // operator fun T.div(arg: NDStructure): NDElement = produce { index -> // field.run { this@div / arg[index] } // } - - -/** - * Read-only [NDStructure] coupled to the context. - */ -class GenericNDFieldElement>( - override val context: NDField>, - private val structure: NDStructure -) : - NDStructure by structure, - NDElement, - FieldElement, GenericNDFieldElement, NDField>> { - override val elementField: F get() = context.elementContext - - override fun unwrap(): NDStructure = structure - - override fun NDStructure.wrap() = GenericNDFieldElement(context, this) - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - ndStructure(context.shape) { index: IntArray -> context.elementContext.transform(index, get(index)) }.wrap() -} 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 934583002..62c2338c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -1,13 +1,16 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.RealField -typealias RealNDElement = StridedNDFieldElement +typealias RealNDElement = BufferedNDFieldElement -class RealNDField(shape: IntArray) : - StridedNDField(shape), +class RealNDField(override val shape: IntArray) : + BufferedNDField, ExtendedNDField> { + override val strides: Strides = DefaultStrides(shape) + override val elementContext: RealField get() = RealField override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } @@ -25,20 +28,20 @@ class RealNDField(shape: IntArray) : ): RealNDElement { check(arg) val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } - return StridedNDFieldElement(this, array) + return BufferedNDFieldElement(this, array) } override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } - return StridedNDFieldElement(this, array) + return BufferedNDFieldElement(this, array) } override fun mapIndexed( arg: NDBuffer, transform: RealField.(index: IntArray, Double) -> Double - ): StridedNDFieldElement { + ): RealNDElement { check(arg) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform( @@ -52,13 +55,16 @@ class RealNDField(shape: IntArray) : a: NDBuffer, b: NDBuffer, transform: RealField.(Double, Double) -> Double - ): StridedNDFieldElement { + ): RealNDElement { check(a, b) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + BufferedNDFieldElement(this@RealNDField, buffer) + override fun power(arg: NDBuffer, pow: Double) = map(arg) { power(it, pow) } override fun exp(arg: NDBuffer) = map(arg) { exp(it) } @@ -75,9 +81,9 @@ class RealNDField(shape: IntArray) : /** * Fast element production using function inlining */ -inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { +inline fun BufferedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } - return StridedNDFieldElement(this, DoubleBuffer(array)) + return BufferedNDFieldElement(this, DoubleBuffer(array)) } /** @@ -90,13 +96,13 @@ operator fun Function1.invoke(ndElement: RealNDElement) = /* plus and minus */ /** - * Summation operation for [StridedNDFieldElement] and single element + * Summation operation for [BufferedNDElement] and single element */ operator fun RealNDElement.plus(arg: Double) = context.produceInline { i -> buffer[i] + arg } /** - * Subtraction operation between [StridedNDFieldElement] and single element + * Subtraction operation between [BufferedNDElement] 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 index e167516c8..5c887f343 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -1,86 +1,96 @@ -//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 +package scientifik.kmath.structures + +import scientifik.kmath.operations.RingElement +import scientifik.kmath.operations.ShortRing + + +typealias ShortNDElement = BufferedNDRingElement + +class ShortNDRing(override val shape: IntArray) : + BufferedNDRing { + + override val strides: Strides = DefaultStrides(shape) + + override val elementContext: ShortRing get() = ShortRing + override val zero by lazy { produce { ShortRing.zero } } + override val one by lazy { produce { ShortRing.one } } + + override 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 BufferedNDRingElement(this, array) + } + + override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement { + val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } + return BufferedNDRingElement(this, array) + } + + override fun mapIndexed( + arg: NDBuffer, + transform: ShortRing.(index: IntArray, Short) -> Short + ): ShortNDElement { + check(arg) + return BufferedNDRingElement( + 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 + ): ShortNDElement { + check(a, b) + return BufferedNDRingElement( + this, + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) + } + + override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> = + BufferedNDRingElement(this@ShortNDRing, buffer) +} + + +/** + * Fast element production using function inlining + */ +inline fun BufferedNDRing.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { + val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) } + return BufferedNDRingElement(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).toShort() } + +/** + * Subtraction operation between [StridedNDFieldElement] and single element + */ +operator fun ShortNDElement.minus(arg: Short) = + context.produceInline { i -> (buffer[i] - arg).toShort() } \ 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 deleted file mode 100644 index 653f56013..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt +++ /dev/null @@ -1,97 +0,0 @@ -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-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index 516653817..635a90dbc 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -29,7 +29,7 @@ fun MutableBuffer.Companion.complex(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size) fun NDField.Companion.complex(shape: IntArray) = - BufferNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } + BoxingNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = NDField.complex(shape).produce(initializer) 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 7840586ef..924ddc653 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -8,8 +8,7 @@ class LazyNDField>( override val shape: IntArray, override val elementContext: F, val scope: CoroutineScope = GlobalScope -) : - NDField> { +) : NDField> { override val zero by lazy { produce { zero } } @@ -65,7 +64,8 @@ class LazyNDField>( class LazyNDStructure>( override val context: LazyNDField, val function: suspend (IntArray) -> T -) : FieldElement, LazyNDStructure, LazyNDField>, NDElement { +) : FieldElement, LazyNDStructure, LazyNDField>, + NDElement> { override fun unwrap(): NDStructure = this @@ -73,10 +73,6 @@ class LazyNDStructure>( override fun NDStructure.wrap(): LazyNDStructure = LazyNDStructure(context) { await(it) } override val shape: IntArray get() = context.shape - override val elementField: F get() = context.elementContext - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement = - context.run { mapIndexed(this@LazyNDStructure, transform) } private val cache = HashMap>()