From b47cdd12c237c7fdb004dcf1f674cdeb042b230a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 31 Dec 2018 13:48:27 +0300 Subject: [PATCH] Adjustments to RealNDField --- .../structures/BufferNDStructureBenchmark.kt | 42 +++++++++++ .../kmath/structures/BufferNDField.kt | 64 +++++++++++++++- .../scientifik/kmath/structures/Buffers.kt | 8 +- .../kmath/structures/ExtendedNDField.kt | 12 ++- .../scientifik/kmath/structures/NDField.kt | 9 +-- .../kmath/structures/RealNDField.kt | 73 +++++++++++++++++++ .../kmath/structures/LazyNDField.kt | 5 +- 7 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt new file mode 100644 index 000000000..de2e6844e --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt @@ -0,0 +1,42 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.DoubleField +import kotlin.system.measureTimeMillis + +fun main(args: Array) { + val dim = 1000 + val n = 1000 + + val genericField = NDField.generic(intArrayOf(dim, dim), DoubleField) + val doubleField = NDField.inline(intArrayOf(dim, dim), DoubleField) + val specializedField = NDField.real(intArrayOf(dim, dim)) + + + val doubleTime = measureTimeMillis { + var res = doubleField.produce { one } + repeat(n) { + res += 1.0 + } + } + + println("Inlined addition completed in $doubleTime millis") + + val specializedTime = measureTimeMillis { + var res = specializedField.produce { one } + repeat(n) { + res += 1.0 + } + } + + println("Specialized addition completed in $specializedTime millis") + + + val genericTime = measureTimeMillis { + var res = genericField.produce { one } + repeat(n) { + res += 1.0 + } + } + + println("Generic addition completed in $genericTime millis") +} \ 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 edf34c7b4..ebe2a67cf 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -2,15 +2,36 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -class BufferNDField>(override val shape: IntArray, override val field: F, val bufferFactory: BufferFactory) : NDField { +open class BufferNDField>(final override val shape: IntArray, final override val field: F, val bufferFactory: BufferFactory) : NDField { val strides = DefaultStrides(shape) - override inline fun produce(crossinline initializer: F.(IntArray) -> T): NDElement { + override fun produce(initializer: F.(IntArray) -> T): BufferNDElement { return BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) } + + open fun produceBuffered(initializer: F.(Int) -> T) = + BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(offset) }) + +// override fun add(a: NDStructure, b: NDStructure): NDElement { +// checkShape(a, b) +// return if (a is BufferNDElement && b is BufferNDElement) { +// BufferNDElement(this,bufferFactory(strides.linearSize){i-> field.run { a.buffer[i] + b.buffer[i]}}) +// } else { +// produce { field.run { a[it] + b[it] } } +// } +// } +// +// override fun NDStructure.plus(b: Number): NDElement { +// checkShape(this) +// return if (this is BufferNDElement) { +// BufferNDElement(this@BufferNDField,bufferFactory(strides.linearSize){i-> field.run { this@plus.buffer[i] + b}}) +// } else { +// produce {index -> field.run { this@plus[index] + b } } +// } +// } } -class BufferNDElement>(override val context: BufferNDField, private val buffer: Buffer) : NDElement { +class BufferNDElement>(override val context: BufferNDField, val buffer: Buffer) : NDElement { override val self: NDStructure get() = this override val shape: IntArray get() = context.shape @@ -19,4 +40,39 @@ class BufferNDElement>(override val context: BufferNDField override fun elements(): Sequence> = context.strides.indices().map { it to get(it) } -} \ No newline at end of file +} + + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: BufferNDElement) = + ndElement.context.produceBuffered { i -> invoke(ndElement.buffer[i]) } + +/* plus and minus */ + +/** + * Summation operation for [BufferNDElement] and single element + */ +operator fun > BufferNDElement.plus(arg: T) = + context.produceBuffered { i -> buffer[i] + arg } + +/** + * Subtraction operation between [BufferNDElement] and single element + */ +operator fun > BufferNDElement.minus(arg: T) = + context.produceBuffered { i -> buffer[i] - arg } + +/* prod and div */ + +/** + * Product operation for [BufferNDElement] and single element + */ +operator fun > BufferNDElement.times(arg: T) = + context.produceBuffered { i -> buffer[i] * arg } + +/** + * Division operation between [BufferNDElement] and single element + */ +operator fun > BufferNDElement.div(arg: T) = + context.produceBuffered { i -> buffer[i] / arg } \ 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 442047dca..cffc2bea0 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -133,13 +133,13 @@ fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { /** * Create a boxing buffer of given type */ -fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) +inline fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) /** * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineBuffer(size: Int, noinline initializer: (Int) -> T): Buffer { +inline fun inlineBuffer(size: Int, initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer @@ -151,13 +151,13 @@ inline fun inlineBuffer(size: Int, noinline initializer: (Int) /** * Create a boxing mutable buffer of given type */ -fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) +inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) /** * Create most appropriate mutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineMutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { +inline fun inlineMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer 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 5fb9cc8c6..d274f92bd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -6,13 +6,17 @@ import scientifik.kmath.operations.PowerOperations import scientifik.kmath.operations.TrigonometricOperations +interface ExtendedNDField> : + NDField, + TrigonometricOperations>, + PowerOperations>, + ExponentialOperations> + + /** * NDField that supports [ExtendedField] operations on its elements */ -inline class ExtendedNDField>(private val ndField: NDField) : NDField, - TrigonometricOperations>, - PowerOperations>, - ExponentialOperations> { +inline class ExtendedNDFieldWrapper>(private val ndField: NDField) : ExtendedNDField { override val shape: IntArray get() = ndField.shape override val field: F get() = ndField.field diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index dedb0e6d3..4fd6c3ee5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -73,12 +73,11 @@ interface NDField> : Field> { return produce { field.run { a[it] / b[it] } } } - companion object { /** * Create a nd-field for [Double] values */ - fun real(shape: IntArray) = ExtendedNDField(BufferNDField(shape, DoubleField, DoubleBufferFactory)) + fun real(shape: IntArray) = RealNDField(shape) /** * Create a nd-field with boxing generic buffer @@ -123,11 +122,11 @@ interface NDElement> : FieldElement, NDField> generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): NDElement { - return NDField.generic(shape,field).produce(initializer) + return NDField.generic(shape, field).produce(initializer) } - inline fun > inline(shape: IntArray, field: F, crossinline initializer: F.(IntArray) -> T): NDElement { - return NDField.inline(shape,field).produce(initializer) + inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): NDElement { + return NDField.inline(shape, field).produce(initializer) } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt new file mode 100644 index 000000000..7705c710e --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -0,0 +1,73 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.DoubleField + +typealias RealNDElement = BufferNDElement + +class RealNDField(shape: IntArray) : BufferNDField(shape, DoubleField, DoubleBufferFactory), ExtendedNDField { + + /** + * Inline map an NDStructure to + */ + private inline fun NDStructure.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement { + return if (this is BufferNDElement) { + val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } + BufferNDElement(this@RealNDField, DoubleBuffer(array)) + } else { + produce { index -> DoubleField.operation(get(index)) } + } + } + + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun produce(initializer: DoubleField.(IntArray) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> field.initializer(strides.index(offset)) } + return BufferNDElement(this, DoubleBuffer(array)) + } + + override fun power(arg: NDStructure, pow: Double) = arg.mapInline { power(it, pow) } + + override fun exp(arg: NDStructure) = arg.mapInline { exp(it) } + + override fun ln(arg: NDStructure) = arg.mapInline { ln(it) } + + override fun sin(arg: NDStructure) = arg.mapInline { sin(it) } + + override fun cos(arg: NDStructure) = arg.mapInline { cos(it) } + + override fun NDStructure.times(k: Number) = mapInline { value -> value * k.toDouble() } + + override fun NDStructure.div(k: Number) = mapInline { value -> value / k.toDouble() } + + override fun Number.times(b: NDStructure) = b * this + + override fun Number.div(b: NDStructure) = b * (1.0 / this.toDouble()) +} + +/** + * Fast element production using function inlining + */ +inline fun BufferNDField.produceInline(crossinline initializer: DoubleField.(Int) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> field.initializer(offset) } + return BufferNDElement(this, DoubleBuffer(array)) +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun Function1.invoke(ndElement: RealNDElement) = + ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + +/* plus and minus */ + +/** + * Summation operation for [BufferNDElement] and single element + */ +operator fun RealNDElement.plus(arg: Double) = + context.produceInline { i -> buffer[i] + arg } + +/** + * Subtraction operation between [BufferNDElement] and single element + */ +operator fun RealNDElement.minus(arg: Double) = + context.produceInline { i -> buffer[i] - 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 adc2b4ed5..bf823d64b 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -3,10 +3,7 @@ package scientifik.kmath.structures import kotlinx.coroutines.* import scientifik.kmath.operations.Field -class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : NDField(shape, field) { - - override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = LazyNDStructure(this) { initializer(field, it) } - +class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : BufferNDField(shape,field, ::boxingBuffer) { override fun add(a: NDStructure, b: NDStructure): NDElement { return LazyNDStructure(this) { index ->