From cddea1869d0586f483caa90dff8429c2f3103354 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 11 Dec 2018 17:25:55 +0300 Subject: [PATCH] Lazy structures, structure is no longer iterable, removed copy on read-only buffers. --- README.md | 4 +- build.gradle.kts | 9 ++- kmath-core/build.gradle | 8 +- .../kmath/histogram/FastHistogram.kt | 4 +- .../kotlin/scientifik/kmath/linear/Vector.kt | 2 - .../scientifik/kmath/operations/Complex.kt | 4 + .../scientifik/kmath/operations/Fields.kt | 15 ++++ .../scientifik/kmath/structures/Buffers.kt | 60 ++++++++++---- .../scientifik/kmath/structures/NDField.kt | 33 ++++---- .../kmath/structures/NDStructure.kt | 32 ++++---- .../scientifik/kmath/structures/BufferSpec.kt | 55 +++++++++++++ .../kmath/structures/ComplexBufferSpec.kt | 25 ++++++ .../kmath/structures/ObjectBuffer.kt | 28 +++++++ .../kmath/structures/RealBufferSpec.kt | 23 ++++++ .../kmath/structures/ComplexBufferSpecTest.kt | 17 ++++ kmath-coroutines/build.gradle | 42 ++++++++++ .../kmath/structures/CoroutinesExtra.kt | 11 +++ .../kmath/structures/LazyNDField.kt | 79 +++++++++++++++++++ .../kmath/structures/LazyNDFieldTest.kt | 20 +++++ .../kmath/structures/_CoroutinesExtra.kt | 6 ++ kmath-io/build.gradle | 4 +- kmath-jmh/build.gradle | 4 +- .../kmath/structures/ArrayBenchmark.kt | 6 +- .../kmath/structures/BufferBenchmark.kt | 38 +++++++++ settings.gradle.kts | 4 +- 25 files changed, 465 insertions(+), 68 deletions(-) create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt create mode 100644 kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt create mode 100644 kmath-coroutines/build.gradle create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt create mode 100644 kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt create mode 100644 kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt create mode 100644 kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt diff --git a/README.md b/README.md index 607cda1bf..8e07b546a 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,11 @@ repositories { Then use regular dependency like ```groovy -compile(group: 'scientifik', name: 'kmath-core-jvm', version: '0.0.1-SNAPSHOT') +compile(group: 'scientifik', name: 'kmath-core', version: '0.0.1-SNAPSHOT') ``` or in kotlin ```kotlin -compile(group = "scientifik", name = "kmath-core-jvm", version = "0.0.1-SNAPSHOT") +compile(group = "scientifik", name = "kmath-core", version = "0.0.1-SNAPSHOT") ``` Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). diff --git a/build.gradle.kts b/build.gradle.kts index 8f8bcaa34..5b95be71a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,11 @@ buildscript { - extra["kotlinVersion"] = "1.3.10" + extra["kotlinVersion"] = "1.3.11" + extra["ioVersion"] = "0.1.2-dev-2" + extra["coroutinesVersion"] = "1.0.1" val kotlinVersion: String by extra + val ioVersion: String by extra + val coroutinesVersion: String by extra repositories { jcenter() @@ -15,6 +19,7 @@ buildscript { plugins { id("com.jfrog.artifactory") version "4.8.1" apply false +// id("org.jetbrains.kotlin.multiplatform") apply false } allprojects { @@ -22,7 +27,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.1-SNAPSHOT" + version = "0.0.2-dev-1" } if(file("artifactory.gradle").exists()){ diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index c63e518fc..d51ccdb65 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'kotlin-multiplatform' + id "org.jetbrains.kotlin.multiplatform" } kotlin { @@ -14,7 +14,7 @@ kotlin { sourceSets { commonMain { dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + api 'org.jetbrains.kotlin:kotlin-stdlib-common' } } commonTest { @@ -25,7 +25,7 @@ kotlin { } jvmMain { dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' } } jvmTest { @@ -36,7 +36,7 @@ kotlin { } jsMain { dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' + api 'org.jetbrains.kotlin:kotlin-stdlib-js' } } jsTest { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt index 8b30e7ebf..e48979430 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -85,7 +85,7 @@ class FastHistogram( values[index].increment() } - override fun iterator(): Iterator> = values.asSequence().map { (index, value) -> + override fun iterator(): Iterator> = values.elements().map { (index, value) -> PhantomBin(getTemplate(index), value.sum()) }.iterator() @@ -100,7 +100,7 @@ class FastHistogram( * Create a phantom lightweight immutable copy of this histogram */ fun asPhantomHistogram(): PhantomHistogram { - val binTemplates = values.associate { (index, _) -> getTemplate(index) to index } + val binTemplates = values.elements().associate { (index, _) -> getTemplate(index) to index } return PhantomHistogram(binTemplates, asNDStructure()) } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 755d58d9b..5ab9907de 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -98,8 +98,6 @@ class ArrayVector> internal constructor(override val conte override fun iterator(): Iterator = (0 until size).map { element[it] }.iterator() - override fun copy(): ArrayVector = ArrayVector(context, element) - override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index dd3be256e..8d3c01d28 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -38,4 +38,8 @@ data class Complex(val re: Double, val im: Double) : FieldElement { override val context get() = RealField + companion object { + + } } /** @@ -71,4 +74,16 @@ object DoubleField : ExtendedField, Norm { override fun ln(arg: Double): Double = kotlin.math.ln(arg) override fun norm(arg: Double): Double = kotlin.math.abs(arg) +} + +/** + * A field for double without boxing. Does not produce appropriate field element + */ +object IntField : Field{ + 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 divide(a: Int, b: Int): Int = a / b } \ 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 a4bb76060..69e8163c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -11,11 +11,6 @@ interface Buffer { operator fun get(index: Int): T operator fun iterator(): Iterator - - /** - * A shallow copy of the buffer - */ - fun copy(): Buffer } fun Buffer.asSequence(): Sequence = iterator().asSequence() @@ -26,7 +21,7 @@ interface MutableBuffer : Buffer { /** * A shallow copy of the buffer */ - override fun copy(): MutableBuffer + fun copy(): MutableBuffer } @@ -37,9 +32,7 @@ inline class ListBuffer(private val list: List) : Buffer { override fun get(index: Int): T = list[index] - override fun iterator(): Iterator = list.iterator() - - override fun copy(): ListBuffer = ListBuffer(ArrayList(list)) + override fun iterator(): Iterator = list.iterator() } inline class MutableListBuffer(private val list: MutableList) : MutableBuffer { @@ -53,7 +46,7 @@ inline class MutableListBuffer(private val list: MutableList) : MutableBuf list[index] = value } - override fun iterator(): Iterator = list.iterator() + override fun iterator(): Iterator = list.iterator() override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) } @@ -69,7 +62,7 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) } @@ -84,12 +77,12 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer = array.iterator() + override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) } -inline class IntBuffer(private val array: IntArray): MutableBuffer{ +inline class IntBuffer(private val array: IntArray) : MutableBuffer { override val size: Int get() = array.size @@ -99,15 +92,48 @@ inline class IntBuffer(private val array: IntArray): MutableBuffer{ array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) } -inline fun buffer(size: Int, noinline initializer: (Int) -> T): Buffer { - return ArrayBuffer(Array(size, initializer)) +inline class ReadOnlyBuffer(private val buffer: MutableBuffer) : Buffer { + override val size: Int get() = buffer.size + + override fun get(index: Int): T = buffer.get(index) + + override fun iterator(): Iterator = buffer.iterator() } +/** + * Convert this buffer to read-only buffer + */ +fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { + ReadOnlyBuffer(this) +} else { + this +} + +/** + * Create most appropriate immutable buffer for given type avoiding boxing wherever possible + */ +@Suppress("UNCHECKED_CAST") +inline fun buffer(size: Int, noinline 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 + else -> ArrayBuffer(Array(size, initializer)) + } +} + +/** + * Create most appropriate mutable buffer for given type avoiding boxing wherever possible + */ +@Suppress("UNCHECKED_CAST") inline fun mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { - return ArrayBuffer(Array(size, initializer)) + 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 + else -> ArrayBuffer(Array(size, initializer)) + } } \ No newline at end of file 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 d8e9cc31b..cddac5801 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -23,7 +23,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie * Create new instance of NDArray using field shape and given initializer * The producer takes list of indices as argument and returns contained value */ - fun produce(initializer: F.(IntArray) -> T): NDElement = NDElement(this, produceStructure(initializer)) + fun produce(initializer: F.(IntArray) -> T): NDElement = NDStructureElement(this, produceStructure(initializer)) override val zero: NDElement by lazy { produce { zero } @@ -83,7 +83,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie // /** // * Reverse minus operation // */ -// operator fun T.minus(arg: NDElement): NDElement = arg.transform { _, value -> +// operator fun T.minus(arg: NDElement): NDElement = arg.transformIndexed { _, value -> // with(arg.context.field) { // this@minus - value // } @@ -97,38 +97,41 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie // /** // * Reverse division operation // */ -// operator fun T.div(arg: NDElement): NDElement = arg.transform { _, value -> +// operator fun T.div(arg: NDElement): NDElement = arg.transformIndexed { _, value -> // with(arg.context.field) { // this@div / value // } // } } + +interface NDElement>: FieldElement, NDField>, NDStructure + +inline fun > NDElement.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement = context.produce { action(it, get(*it)) } +inline fun > NDElement.transform(crossinline action: F.(T) -> T): NDElement = context.produce { action(get(*it)) } + + /** - * Immutable [NDStructure] coupled to the context. Emulates Python ndarray + * Read-only [NDStructure] coupled to the context. */ -class NDElement>(override val context: NDField, private val structure: NDStructure) : FieldElement, NDField>, NDStructure by structure { +class NDStructureElement>(override val context: NDField, private val structure: NDStructure) : NDElement, NDStructure by structure { //TODO ensure structure is immutable - override val self: NDElement - get() = this - - inline fun transform(crossinline action: (IntArray, T) -> T): NDElement = context.produce { action(it, get(*it)) } - inline fun transform(crossinline action: (T) -> T): NDElement = context.produce { action(get(*it)) } + override val self: NDElement get() = this } /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ -operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.transform { _, value -> this(value) } +operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.transform {value -> this@invoke(value) } /* plus and minus */ /** * Summation operation for [NDElement] and single element */ -operator fun > NDElement.plus(arg: T): NDElement = transform { _, value -> +operator fun > NDElement.plus(arg: T): NDElement = transform {value -> with(context.field) { arg + value } @@ -137,7 +140,7 @@ operator fun > NDElement.plus(arg: T): NDElement = t /** * Subtraction operation between [NDElement] and single element */ -operator fun > NDElement.minus(arg: T): NDElement = transform { _, value -> +operator fun > NDElement.minus(arg: T): NDElement = transform {value -> with(context.field) { arg - value } @@ -148,7 +151,7 @@ operator fun > NDElement.minus(arg: T): NDElement = /** * Product operation for [NDElement] and single element */ -operator fun > NDElement.times(arg: T): NDElement = transform { _, value -> +operator fun > NDElement.times(arg: T): NDElement = transform { value -> with(context.field) { arg * value } @@ -157,7 +160,7 @@ operator fun > NDElement.times(arg: T): NDElement = /** * Division operation between [NDElement] and single element */ -operator fun > NDElement.div(arg: T): NDElement = transform { _, value -> +operator fun > NDElement.div(arg: T): NDElement = transform { value -> with(context.field) { arg / value } 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 ec985eb8b..f9521cf93 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -1,7 +1,7 @@ package scientifik.kmath.structures -interface NDStructure : Iterable> { +interface NDStructure { val shape: IntArray @@ -9,6 +9,8 @@ interface NDStructure : Iterable> { get() = shape.size operator fun get(index: IntArray): T + + fun elements(): Sequence> } operator fun NDStructure.get(vararg index: Int): T = get(index) @@ -18,7 +20,7 @@ interface MutableNDStructure : NDStructure { } fun MutableNDStructure.transformInPlace(action: (IntArray, T) -> T) { - for ((index, oldValue) in this) { + elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } } @@ -87,7 +89,7 @@ class DefaultStrides(override val shape: IntArray) : Strides { var current = offset var strideIndex = strides.size - 2 while (strideIndex >= 0) { - res[ strideIndex] = (current / strides[strideIndex]) + res[strideIndex] = (current / strides[strideIndex]) current %= strides[strideIndex] strideIndex-- } @@ -107,8 +109,8 @@ abstract class GenericNDStructure> : NDStructure { override val shape: IntArray get() = strides.shape - override fun iterator(): Iterator> = - strides.indices().map { it to this[it] }.iterator() + override fun elements()= + strides.indices().map { it to this[it] } } /** @@ -126,10 +128,10 @@ class BufferNDStructure( } } -inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))}) +inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = + BufferNDStructure(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) }) -inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = +inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = ndStructure(DefaultStrides(shape), initializer) @@ -153,22 +155,22 @@ class MutableBufferNDStructure( /** * Create optimized mutable structure for given type */ -inline fun mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) +inline fun mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = + MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) -inline fun mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = +inline fun mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = mutableNdStructure(DefaultStrides(shape), initializer) /** * Create universal mutable structure */ -fun genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure{ +fun genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure { val strides = DefaultStrides(shape) - val sequence = sequence{ - strides.indices().forEach{ + val sequence = sequence { + strides.indices().forEach { yield(initializer(it)) } } - val buffer = MutableListBuffer(sequence.toMutableList()) + val buffer = MutableListBuffer(sequence.toMutableList()) return MutableBufferNDStructure(strides, buffer) } diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt new file mode 100644 index 000000000..a5cc8c5de --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt @@ -0,0 +1,55 @@ +package scientifik.kmath.structures + +import java.nio.ByteBuffer + + +/** + * A specification for serialization and deserialization objects to buffer + */ +interface BufferSpec { + fun fromBuffer(buffer: ByteBuffer): T + fun toBuffer(value: T): ByteBuffer +} + +/** + * A [BufferSpec] with fixed unit size. Allows storage of any object without boxing. + */ +interface FixedSizeBufferSpec : BufferSpec { + val unitSize: Int + + /** + * Read an object from buffer in current position + */ + fun ByteBuffer.readObject(): T { + val buffer = ByteArray(unitSize) + get(buffer) + return fromBuffer(ByteBuffer.wrap(buffer)) + } + + /** + * Read an object from buffer in given index (not buffer position + */ + fun ByteBuffer.readObject(index: Int): T { + val dup = duplicate() + dup.position(index*unitSize) + return dup.readObject() + } + + /** + * Write object to [ByteBuffer] in current buffer position + */ + fun ByteBuffer.writeObject(obj: T) { + val buffer = toBuffer(obj).apply { rewind() } + assert(buffer.limit() == unitSize) + put(buffer) + } + + /** + * Put an object in given index + */ + fun ByteBuffer.writeObject(index: Int, obj: T) { + val dup = duplicate() + dup.position(index*unitSize) + dup.writeObject(obj) + } +} \ 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 new file mode 100644 index 000000000..68e989e81 --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -0,0 +1,25 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import java.nio.ByteBuffer + +object ComplexBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 16 + + override fun fromBuffer(buffer: ByteBuffer): Complex { + val re = buffer.getDouble(0) + val im = buffer.getDouble(8) + return Complex(re, im) + } + + override fun toBuffer(value: Complex): ByteBuffer = ByteBuffer.allocate(16).apply { + putDouble(value.re) + putDouble(value.im) + } +} + +/** + * Create a mutable buffer which ignores boxing + */ +fun Complex.Companion.createBuffer(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size) + diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt new file mode 100644 index 000000000..b50ed9674 --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -0,0 +1,28 @@ +package scientifik.kmath.structures + +import java.nio.ByteBuffer + +class ObjectBuffer(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec) : MutableBuffer { + override val size: Int + get() = buffer.limit() / spec.unitSize + + override fun get(index: Int): T = with(spec) { buffer.readObject(index) } + + override fun iterator(): Iterator = (0 until size).asSequence().map { get(it) }.iterator() + + override fun set(index: Int, value: T) = with(spec) { buffer.writeObject(index, value) } + + override fun copy(): MutableBuffer { + val dup = buffer.duplicate() + val copy = ByteBuffer.allocate(dup.capacity()) + dup.rewind() + copy.put(dup) + copy.flip() + return ObjectBuffer(copy, spec) + } + + companion object { + fun create(spec: FixedSizeBufferSpec, size: Int) = + ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec) + } +} \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt new file mode 100644 index 000000000..761cfc2db --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt @@ -0,0 +1,23 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Real +import java.nio.ByteBuffer + +object RealBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 8 + + override fun fromBuffer(buffer: ByteBuffer): Real = Real(buffer.double) + + override fun toBuffer(value: Real): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value.value) } +} + +object DoubleBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 8 + + override fun fromBuffer(buffer: ByteBuffer): Double = buffer.double + + override fun toBuffer(value: Double): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value) } +} + +fun Double.Companion.createBuffer(size: Int) = ObjectBuffer.create(DoubleBufferSpec, size) +fun Real.Companion.createBuffer(size: Int) = ObjectBuffer.create(RealBufferSpec, size) \ No newline at end of file diff --git a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt new file mode 100644 index 000000000..350f06848 --- /dev/null +++ b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt @@ -0,0 +1,17 @@ +package scientifik.kmath.structures + +import org.junit.Test +import scientifik.kmath.operations.Complex +import kotlin.test.assertEquals + +class ComplexBufferSpecTest { + @Test + fun testComplexBuffer() { + val buffer = Complex.createBuffer(20) + (0 until 20).forEach { + buffer[it] = Complex(it.toDouble(), -it.toDouble()) + } + + assertEquals(Complex(5.0, -5.0), buffer[5]) + } +} \ No newline at end of file diff --git a/kmath-coroutines/build.gradle b/kmath-coroutines/build.gradle new file mode 100644 index 000000000..7149e8bb9 --- /dev/null +++ b/kmath-coroutines/build.gradle @@ -0,0 +1,42 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.mingwX64, 'mingw') + } + sourceSets { + commonMain { + dependencies { + api project(":kmath-core") + api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion" + } + } + commonTest { + dependencies { + api 'org.jetbrains.kotlin:kotlin-test-common' + api 'org.jetbrains.kotlin:kotlin-test-annotations-common' + } + } + jvmMain { + dependencies { + api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" + } + } + jvmTest { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-test' + implementation 'org.jetbrains.kotlin:kotlin-test-junit' + } + } +// mingwMain { +// } +// mingwTest { +// } + } +} diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt new file mode 100644 index 000000000..a837cde01 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt @@ -0,0 +1,11 @@ +package scientifik.kmath.structures + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +expect fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, function: suspend CoroutineScope.()->R): R + +val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default \ 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 new file mode 100644 index 000000000..823245685 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -0,0 +1,79 @@ +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) } + + + override fun add(a: NDElement, b: NDElement): NDElement { + return LazyNDStructure(this) { index -> + val aDeferred = a.deferred(index) + val bDeferred = b.deferred(index) + aDeferred.await() + bDeferred.await() + } + } + + override fun multiply(a: NDElement, k: Double): NDElement { + return LazyNDStructure(this) { index -> a.await(index) * k } + } + + override fun multiply(a: NDElement, b: NDElement): NDElement { + return LazyNDStructure(this) { index -> + val aDeferred = a.deferred(index) + val bDeferred = b.deferred(index) + aDeferred.await() * bDeferred.await() + } + } + + override fun divide(a: NDElement, b: NDElement): NDElement { + return LazyNDStructure(this) { index -> + val aDeferred = a.deferred(index) + val bDeferred = b.deferred(index) + aDeferred.await() / bDeferred.await() + } + } +} + +class LazyNDStructure>(override val context: LazyNDField, val function: suspend F.(IntArray) -> T) : NDElement, NDStructure { + override val self: NDElement get() = this + override val shape: IntArray get() = context.shape + + private val cache = HashMap>() + + fun deferred(index: IntArray) = cache.getOrPut(index) { context.scope.async(context = Dispatchers.Math) { function.invoke(context.field, index) } } + + suspend fun await(index: IntArray): T = deferred(index).await() + + override fun get(index: IntArray): T = runBlocking { + deferred(index).await() + } + + override fun elements(): Sequence> { + val strides = DefaultStrides(shape) + return strides.indices().map { index -> index to runBlocking { await(index) } } + } +} + +fun NDElement.deferred(index: IntArray) = if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) + +suspend fun NDElement.await(index: IntArray) = if (this is LazyNDStructure) this.await(index) else get(index) + +fun > NDElement.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { + return if (this is LazyNDStructure) { + this + } else { + val context = LazyNDField(context.shape, context.field) + LazyNDStructure(context) { get(it) } + } +} + +inline fun > LazyNDStructure.transformIndexed(crossinline action: suspend F.(IntArray, T) -> T) = LazyNDStructure(context) { index -> + action.invoke(this, index, await(index)) +} + +inline fun > LazyNDStructure.transform(crossinline action: suspend F.(T) -> T) = LazyNDStructure(context) { index -> + action.invoke(this, await(index)) +} \ No newline at end of file diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt new file mode 100644 index 000000000..403fe2d31 --- /dev/null +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -0,0 +1,20 @@ +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 = NDArrays.create(IntField, intArrayOf(2, 2, 2)) { it[0] + it[1] - it[2] } + val result = (regularStructure.lazy() + 2).transform { + counter++ + it * it + } + assertEquals(4, result[0,0,0]) + assertEquals(1, counter) + } +} \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt new file mode 100644 index 000000000..72a764729 --- /dev/null +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt @@ -0,0 +1,6 @@ +package scientifik.kmath.structures + +import kotlinx.coroutines.CoroutineScope +import kotlin.coroutines.CoroutineContext + +actual fun runBlocking(context: CoroutineContext, function: suspend CoroutineScope.() -> R): R = kotlinx.coroutines.runBlocking(context, function) \ No newline at end of file diff --git a/kmath-io/build.gradle b/kmath-io/build.gradle index d0d93262e..28fb7eee5 100644 --- a/kmath-io/build.gradle +++ b/kmath-io/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'kotlin-multiplatform' + id "org.jetbrains.kotlin.multiplatform" } kotlin { @@ -16,6 +16,7 @@ kotlin { dependencies { api project(":kmath-core") implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" } } commonTest { @@ -27,6 +28,7 @@ kotlin { jvmMain { dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" } } jvmTest { diff --git a/kmath-jmh/build.gradle b/kmath-jmh/build.gradle index 97337cb40..1876d86d7 100644 --- a/kmath-jmh/build.gradle +++ b/kmath-jmh/build.gradle @@ -5,6 +5,6 @@ plugins { } dependencies { - implementation project(':kmath-core') - jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compile project(':kmath-core') + //jmh project(':kmath-core') } \ No newline at end of file diff --git a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt index eb40195e0..fe10fbd75 100644 --- a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt +++ b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt @@ -4,8 +4,7 @@ import org.openjdk.jmh.annotations.* import java.nio.IntBuffer -@Fork(1) -@Warmup(iterations = 2) +@Warmup(iterations = 1) @Measurement(iterations = 5) @State(Scope.Benchmark) open class ArrayBenchmark { @@ -30,7 +29,6 @@ open class ArrayBenchmark { for (i in 1..10000) { res += array[10000 - i] } - print(res) } @Benchmark @@ -39,7 +37,6 @@ open class ArrayBenchmark { for (i in 1..10000) { res += arrayBuffer.get(10000 - i) } - print(res) } @Benchmark @@ -48,6 +45,5 @@ open class ArrayBenchmark { for (i in 1..10000) { res += nativeBuffer.get(10000 - i) } - print(res) } } \ No newline at end of file diff --git a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt new file mode 100644 index 000000000..fd27ae3e0 --- /dev/null +++ b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt @@ -0,0 +1,38 @@ +package scientifik.kmath.structures + +import org.openjdk.jmh.annotations.* +import scientifik.kmath.operations.Complex + +@Warmup(iterations = 1) +@Measurement(iterations = 5) +@State(Scope.Benchmark) +open class BufferBenchmark { + + @Benchmark + fun genericDoubleBufferReadWrite() { + val buffer = Double.createBuffer(size) + (0 until size).forEach { + buffer[it] = it.toDouble() + } + + (0 until size).forEach { + buffer[it] + } + } + + @Benchmark + fun complexBufferReadWrite() { + val buffer = Complex.createBuffer(size/2) + (0 until size/2).forEach { + buffer[it] = Complex(it.toDouble(), -it.toDouble()) + } + + (0 until size/2).forEach { + buffer[it] + } + } + + companion object { + const val size = 1000 + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f90d4c0b..0c91b8446 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,10 +5,12 @@ pluginManagement { } } -enableFeaturePreview("GRADLE_METADATA") +//enableFeaturePreview("GRADLE_METADATA") rootProject.name = "kmath" include( ":kmath-core", + ":kmath-io", + ":kmath-coroutines", ":kmath-jmh" )