Lazy structures, structure is no longer iterable, removed copy on read-only buffers.

This commit is contained in:
Alexander Nozik 2018-12-11 17:25:55 +03:00
parent bdd9dccd4f
commit cddea1869d
25 changed files with 465 additions and 68 deletions

View File

@ -58,11 +58,11 @@ repositories {
Then use regular dependency like Then use regular dependency like
```groovy ```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 or in kotlin
```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). Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath).

View File

@ -1,7 +1,11 @@
buildscript { 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 kotlinVersion: String by extra
val ioVersion: String by extra
val coroutinesVersion: String by extra
repositories { repositories {
jcenter() jcenter()
@ -15,6 +19,7 @@ buildscript {
plugins { plugins {
id("com.jfrog.artifactory") version "4.8.1" apply false id("com.jfrog.artifactory") version "4.8.1" apply false
// id("org.jetbrains.kotlin.multiplatform") apply false
} }
allprojects { allprojects {
@ -22,7 +27,7 @@ allprojects {
apply(plugin = "com.jfrog.artifactory") apply(plugin = "com.jfrog.artifactory")
group = "scientifik" group = "scientifik"
version = "0.0.1-SNAPSHOT" version = "0.0.2-dev-1"
} }
if(file("artifactory.gradle").exists()){ if(file("artifactory.gradle").exists()){

View File

@ -1,5 +1,5 @@
plugins { plugins {
id 'kotlin-multiplatform' id "org.jetbrains.kotlin.multiplatform"
} }
kotlin { kotlin {
@ -14,7 +14,7 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' api 'org.jetbrains.kotlin:kotlin-stdlib-common'
} }
} }
commonTest { commonTest {
@ -25,7 +25,7 @@ kotlin {
} }
jvmMain { jvmMain {
dependencies { dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
} }
} }
jvmTest { jvmTest {
@ -36,7 +36,7 @@ kotlin {
} }
jsMain { jsMain {
dependencies { dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' api 'org.jetbrains.kotlin:kotlin-stdlib-js'
} }
} }
jsTest { jsTest {

View File

@ -85,7 +85,7 @@ class FastHistogram(
values[index].increment() values[index].increment()
} }
override fun iterator(): Iterator<PhantomBin<Double>> = values.asSequence().map { (index, value) -> override fun iterator(): Iterator<PhantomBin<Double>> = values.elements().map { (index, value) ->
PhantomBin(getTemplate(index), value.sum()) PhantomBin(getTemplate(index), value.sum())
}.iterator() }.iterator()
@ -100,7 +100,7 @@ class FastHistogram(
* Create a phantom lightweight immutable copy of this histogram * Create a phantom lightweight immutable copy of this histogram
*/ */
fun asPhantomHistogram(): PhantomHistogram<Double> { fun asPhantomHistogram(): PhantomHistogram<Double> {
val binTemplates = values.associate { (index, _) -> getTemplate(index) to index } val binTemplates = values.elements().associate { (index, _) -> getTemplate(index) to index }
return PhantomHistogram(binTemplates, asNDStructure()) return PhantomHistogram(binTemplates, asNDStructure())
} }

View File

@ -98,8 +98,6 @@ class ArrayVector<T : Any, F : Field<T>> internal constructor(override val conte
override fun iterator(): Iterator<T> = (0 until size).map { element[it] }.iterator() override fun iterator(): Iterator<T> = (0 until size).map { element[it] }.iterator()
override fun copy(): ArrayVector<T, F> = ArrayVector(context, element)
override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
} }

View File

@ -38,4 +38,8 @@ data class Complex(val re: Double, val im: Double) : FieldElement<Complex, Compl
val abs: Double val abs: Double
get() = kotlin.math.sqrt(square) get() = kotlin.math.sqrt(square)
companion object {
}
} }

View File

@ -49,6 +49,9 @@ inline class Real(val value: Double) : FieldElement<Real, RealField> {
override val context override val context
get() = RealField get() = RealField
companion object {
}
} }
/** /**
@ -72,3 +75,15 @@ object DoubleField : ExtendedField<Double>, Norm<Double, Double> {
override fun norm(arg: Double): Double = kotlin.math.abs(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<Int>{
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
}

View File

@ -11,11 +11,6 @@ interface Buffer<T> {
operator fun get(index: Int): T operator fun get(index: Int): T
operator fun iterator(): Iterator<T> operator fun iterator(): Iterator<T>
/**
* A shallow copy of the buffer
*/
fun copy(): Buffer<T>
} }
fun <T> Buffer<T>.asSequence(): Sequence<T> = iterator().asSequence() fun <T> Buffer<T>.asSequence(): Sequence<T> = iterator().asSequence()
@ -26,7 +21,7 @@ interface MutableBuffer<T> : Buffer<T> {
/** /**
* A shallow copy of the buffer * A shallow copy of the buffer
*/ */
override fun copy(): MutableBuffer<T> fun copy(): MutableBuffer<T>
} }
@ -38,8 +33,6 @@ inline class ListBuffer<T>(private val list: List<T>) : Buffer<T> {
override fun get(index: Int): T = list[index] override fun get(index: Int): T = list[index]
override fun iterator(): Iterator<T> = list.iterator() override fun iterator(): Iterator<T> = list.iterator()
override fun copy(): ListBuffer<T> = ListBuffer(ArrayList(list))
} }
inline class MutableListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> { inline class MutableListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> {
@ -89,7 +82,7 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double
override fun copy(): MutableBuffer<Double> = DoubleBuffer(array.copyOf()) override fun copy(): MutableBuffer<Double> = DoubleBuffer(array.copyOf())
} }
inline class IntBuffer(private val array: IntArray): MutableBuffer<Int>{ inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> {
override val size: Int override val size: Int
get() = array.size get() = array.size
@ -104,10 +97,43 @@ inline class IntBuffer(private val array: IntArray): MutableBuffer<Int>{
override fun copy(): MutableBuffer<Int> = IntBuffer(array.copyOf()) override fun copy(): MutableBuffer<Int> = IntBuffer(array.copyOf())
} }
inline fun <reified T : Any> buffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> { inline class ReadOnlyBuffer<T>(private val buffer: MutableBuffer<T>) : Buffer<T> {
return ArrayBuffer(Array(size, initializer)) override val size: Int get() = buffer.size
override fun get(index: Int): T = buffer.get(index)
override fun iterator(): Iterator<T> = buffer.iterator()
} }
inline fun <reified T : Any> mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> { /**
return ArrayBuffer(Array(size, initializer)) * Convert this buffer to read-only buffer
*/
fun <T> Buffer<T>.asReadOnly(): Buffer<T> = 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 <reified T : Any> buffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> {
return when (T::class) {
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer<T>
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer<T>
else -> ArrayBuffer(Array(size, initializer))
}
}
/**
* Create most appropriate mutable buffer for given type avoiding boxing wherever possible
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> {
return when (T::class) {
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer<T>
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer<T>
else -> ArrayBuffer(Array(size, initializer))
}
} }

View File

@ -23,7 +23,7 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
* Create new instance of NDArray using field shape and given initializer * Create new instance of NDArray using field shape and given initializer
* The producer takes list of indices as argument and returns contained value * The producer takes list of indices as argument and returns contained value
*/ */
fun produce(initializer: F.(IntArray) -> T): NDElement<T, F> = NDElement(this, produceStructure(initializer)) fun produce(initializer: F.(IntArray) -> T): NDElement<T, F> = NDStructureElement(this, produceStructure(initializer))
override val zero: NDElement<T, F> by lazy { override val zero: NDElement<T, F> by lazy {
produce { zero } produce { zero }
@ -83,7 +83,7 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
// /** // /**
// * Reverse minus operation // * Reverse minus operation
// */ // */
// operator fun T.minus(arg: NDElement<T, F>): NDElement<T, F> = arg.transform { _, value -> // operator fun T.minus(arg: NDElement<T, F>): NDElement<T, F> = arg.transformIndexed { _, value ->
// with(arg.context.field) { // with(arg.context.field) {
// this@minus - value // this@minus - value
// } // }
@ -97,38 +97,41 @@ abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Fie
// /** // /**
// * Reverse division operation // * Reverse division operation
// */ // */
// operator fun T.div(arg: NDElement<T, F>): NDElement<T, F> = arg.transform { _, value -> // operator fun T.div(arg: NDElement<T, F>): NDElement<T, F> = arg.transformIndexed { _, value ->
// with(arg.context.field) { // with(arg.context.field) {
// this@div / value // this@div / value
// } // }
// } // }
} }
interface NDElement<T, F : Field<T>>: FieldElement<NDElement<T, F>, NDField<T, F>>, NDStructure<T>
inline fun <T, F : Field<T>> NDElement<T, F>.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement<T, F> = context.produce { action(it, get(*it)) }
inline fun <T, F : Field<T>> NDElement<T, F>.transform(crossinline action: F.(T) -> T): NDElement<T, F> = context.produce { action(get(*it)) }
/** /**
* Immutable [NDStructure] coupled to the context. Emulates Python ndarray * Read-only [NDStructure] coupled to the context.
*/ */
class NDElement<T, F : Field<T>>(override val context: NDField<T, F>, private val structure: NDStructure<T>) : FieldElement<NDElement<T, F>, NDField<T, F>>, NDStructure<T> by structure { class NDStructureElement<T, F : Field<T>>(override val context: NDField<T, F>, private val structure: NDStructure<T>) : NDElement<T,F>, NDStructure<T> by structure {
//TODO ensure structure is immutable //TODO ensure structure is immutable
override val self: NDElement<T, F> override val self: NDElement<T, F> get() = this
get() = this
inline fun transform(crossinline action: (IntArray, T) -> T): NDElement<T, F> = context.produce { action(it, get(*it)) }
inline fun transform(crossinline action: (T) -> T): NDElement<T, F> = context.produce { action(get(*it)) }
} }
/** /**
* 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 array. Just like in numpy
*/ */
operator fun <T, F : Field<T>> Function1<T, T>.invoke(ndElement: NDElement<T, F>): NDElement<T, F> = ndElement.transform { _, value -> this(value) } operator fun <T, F : Field<T>> Function1<T, T>.invoke(ndElement: NDElement<T, F>): NDElement<T, F> = ndElement.transform {value -> this@invoke(value) }
/* plus and minus */ /* plus and minus */
/** /**
* Summation operation for [NDElement] and single element * Summation operation for [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.plus(arg: T): NDElement<T, F> = transform { _, value -> operator fun <T, F : Field<T>> NDElement<T, F>.plus(arg: T): NDElement<T, F> = transform {value ->
with(context.field) { with(context.field) {
arg + value arg + value
} }
@ -137,7 +140,7 @@ operator fun <T, F : Field<T>> NDElement<T, F>.plus(arg: T): NDElement<T, F> = t
/** /**
* Subtraction operation between [NDElement] and single element * Subtraction operation between [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.minus(arg: T): NDElement<T, F> = transform { _, value -> operator fun <T, F : Field<T>> NDElement<T, F>.minus(arg: T): NDElement<T, F> = transform {value ->
with(context.field) { with(context.field) {
arg - value arg - value
} }
@ -148,7 +151,7 @@ operator fun <T, F : Field<T>> NDElement<T, F>.minus(arg: T): NDElement<T, F> =
/** /**
* Product operation for [NDElement] and single element * Product operation for [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.times(arg: T): NDElement<T, F> = transform { _, value -> operator fun <T, F : Field<T>> NDElement<T, F>.times(arg: T): NDElement<T, F> = transform { value ->
with(context.field) { with(context.field) {
arg * value arg * value
} }
@ -157,7 +160,7 @@ operator fun <T, F : Field<T>> NDElement<T, F>.times(arg: T): NDElement<T, F> =
/** /**
* Division operation between [NDElement] and single element * Division operation between [NDElement] and single element
*/ */
operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> = transform { _, value -> operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> = transform { value ->
with(context.field) { with(context.field) {
arg / value arg / value
} }

View File

@ -1,7 +1,7 @@
package scientifik.kmath.structures package scientifik.kmath.structures
interface NDStructure<T> : Iterable<Pair<IntArray, T>> { interface NDStructure<T> {
val shape: IntArray val shape: IntArray
@ -9,6 +9,8 @@ interface NDStructure<T> : Iterable<Pair<IntArray, T>> {
get() = shape.size get() = shape.size
operator fun get(index: IntArray): T operator fun get(index: IntArray): T
fun elements(): Sequence<Pair<IntArray, T>>
} }
operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index) operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index)
@ -18,7 +20,7 @@ interface MutableNDStructure<T> : NDStructure<T> {
} }
fun <T> MutableNDStructure<T>.transformInPlace(action: (IntArray, T) -> T) { fun <T> MutableNDStructure<T>.transformInPlace(action: (IntArray, T) -> T) {
for ((index, oldValue) in this) { elements().forEach { (index, oldValue) ->
this[index] = action(index, oldValue) this[index] = action(index, oldValue)
} }
} }
@ -87,7 +89,7 @@ class DefaultStrides(override val shape: IntArray) : Strides {
var current = offset var current = offset
var strideIndex = strides.size - 2 var strideIndex = strides.size - 2
while (strideIndex >= 0) { while (strideIndex >= 0) {
res[ strideIndex] = (current / strides[strideIndex]) res[strideIndex] = (current / strides[strideIndex])
current %= strides[strideIndex] current %= strides[strideIndex]
strideIndex-- strideIndex--
} }
@ -107,8 +109,8 @@ abstract class GenericNDStructure<T, B : Buffer<T>> : NDStructure<T> {
override val shape: IntArray override val shape: IntArray
get() = strides.shape get() = strides.shape
override fun iterator(): Iterator<Pair<IntArray, T>> = override fun elements()=
strides.indices().map { it to this[it] }.iterator() strides.indices().map { it to this[it] }
} }
/** /**
@ -126,10 +128,10 @@ class BufferNDStructure<T>(
} }
} }
inline fun <reified T: Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = inline fun <reified T : Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
BufferNDStructure<T>(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))}) BufferNDStructure<T>(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) })
inline fun <reified T: Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = inline fun <reified T : Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
ndStructure(DefaultStrides(shape), initializer) ndStructure(DefaultStrides(shape), initializer)
@ -153,22 +155,22 @@ class MutableBufferNDStructure<T>(
/** /**
* Create optimized mutable structure for given type * Create optimized mutable structure for given type
*/ */
inline fun <reified T: Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = inline fun <reified T : Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
inline fun <reified T: Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = inline fun <reified T : Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
mutableNdStructure(DefaultStrides(shape), initializer) mutableNdStructure(DefaultStrides(shape), initializer)
/** /**
* Create universal mutable structure * Create universal mutable structure
*/ */
fun <T> genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure<T>{ fun <T> genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure<T> {
val strides = DefaultStrides(shape) val strides = DefaultStrides(shape)
val sequence = sequence{ val sequence = sequence {
strides.indices().forEach{ strides.indices().forEach {
yield(initializer(it)) yield(initializer(it))
} }
} }
val buffer = MutableListBuffer<T>(sequence.toMutableList()) val buffer = MutableListBuffer(sequence.toMutableList())
return MutableBufferNDStructure(strides, buffer) return MutableBufferNDStructure(strides, buffer)
} }

View File

@ -0,0 +1,55 @@
package scientifik.kmath.structures
import java.nio.ByteBuffer
/**
* A specification for serialization and deserialization objects to buffer
*/
interface BufferSpec<T : Any> {
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<T : Any> : BufferSpec<T> {
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)
}
}

View File

@ -0,0 +1,25 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.Complex
import java.nio.ByteBuffer
object ComplexBufferSpec : FixedSizeBufferSpec<Complex> {
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)

View File

@ -0,0 +1,28 @@
package scientifik.kmath.structures
import java.nio.ByteBuffer
class ObjectBuffer<T : Any>(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec<T>) : MutableBuffer<T> {
override val size: Int
get() = buffer.limit() / spec.unitSize
override fun get(index: Int): T = with(spec) { buffer.readObject(index) }
override fun iterator(): Iterator<T> = (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<T> {
val dup = buffer.duplicate()
val copy = ByteBuffer.allocate(dup.capacity())
dup.rewind()
copy.put(dup)
copy.flip()
return ObjectBuffer(copy, spec)
}
companion object {
fun <T : Any> create(spec: FixedSizeBufferSpec<T>, size: Int) =
ObjectBuffer<T>(ByteBuffer.allocate(size * spec.unitSize), spec)
}
}

View File

@ -0,0 +1,23 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.Real
import java.nio.ByteBuffer
object RealBufferSpec : FixedSizeBufferSpec<Real> {
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<Double> {
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)

View File

@ -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])
}
}

View File

@ -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 {
// }
}
}

View File

@ -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 <R> runBlocking(context: CoroutineContext = EmptyCoroutineContext, function: suspend CoroutineScope.()->R): R
val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default

View File

@ -0,0 +1,79 @@
package scientifik.kmath.structures
import kotlinx.coroutines.*
import scientifik.kmath.operations.Field
class LazyNDField<T, F : Field<T>>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : NDField<T, F>(shape, field) {
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> = LazyNDStructure(this) { initializer(field, it) }
override fun add(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
return LazyNDStructure(this) { index ->
val aDeferred = a.deferred(index)
val bDeferred = b.deferred(index)
aDeferred.await() + bDeferred.await()
}
}
override fun multiply(a: NDElement<T, F>, k: Double): NDElement<T, F> {
return LazyNDStructure(this) { index -> a.await(index) * k }
}
override fun multiply(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
return LazyNDStructure(this) { index ->
val aDeferred = a.deferred(index)
val bDeferred = b.deferred(index)
aDeferred.await() * bDeferred.await()
}
}
override fun divide(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
return LazyNDStructure(this) { index ->
val aDeferred = a.deferred(index)
val bDeferred = b.deferred(index)
aDeferred.await() / bDeferred.await()
}
}
}
class LazyNDStructure<T, F : Field<T>>(override val context: LazyNDField<T, F>, val function: suspend F.(IntArray) -> T) : NDElement<T, F>, NDStructure<T> {
override val self: NDElement<T, F> get() = this
override val shape: IntArray get() = context.shape
private val cache = HashMap<IntArray, Deferred<T>>()
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<Pair<IntArray, T>> {
val strides = DefaultStrides(shape)
return strides.indices().map { index -> index to runBlocking { await(index) } }
}
}
fun <T> NDElement<T, *>.deferred(index: IntArray) = if (this is LazyNDStructure<T, *>) this.deferred(index) else CompletableDeferred(get(index))
suspend fun <T> NDElement<T, *>.await(index: IntArray) = if (this is LazyNDStructure<T, *>) this.await(index) else get(index)
fun <T, F : Field<T>> NDElement<T, F>.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure<T, F> {
return if (this is LazyNDStructure<T, F>) {
this
} else {
val context = LazyNDField(context.shape, context.field)
LazyNDStructure(context) { get(it) }
}
}
inline fun <T, F : Field<T>> LazyNDStructure<T, F>.transformIndexed(crossinline action: suspend F.(IntArray, T) -> T) = LazyNDStructure(context) { index ->
action.invoke(this, index, await(index))
}
inline fun <T, F : Field<T>> LazyNDStructure<T, F>.transform(crossinline action: suspend F.(T) -> T) = LazyNDStructure(context) { index ->
action.invoke(this, await(index))
}

View File

@ -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)
}
}

View File

@ -0,0 +1,6 @@
package scientifik.kmath.structures
import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
actual fun <R> runBlocking(context: CoroutineContext, function: suspend CoroutineScope.() -> R): R = kotlinx.coroutines.runBlocking(context, function)

View File

@ -1,5 +1,5 @@
plugins { plugins {
id 'kotlin-multiplatform' id "org.jetbrains.kotlin.multiplatform"
} }
kotlin { kotlin {
@ -16,6 +16,7 @@ kotlin {
dependencies { dependencies {
api project(":kmath-core") api project(":kmath-core")
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion"
} }
} }
commonTest { commonTest {
@ -27,6 +28,7 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion"
} }
} }
jvmTest { jvmTest {

View File

@ -5,6 +5,6 @@ plugins {
} }
dependencies { dependencies {
implementation project(':kmath-core') compile project(':kmath-core')
jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' //jmh project(':kmath-core')
} }

View File

@ -4,8 +4,7 @@ import org.openjdk.jmh.annotations.*
import java.nio.IntBuffer import java.nio.IntBuffer
@Fork(1) @Warmup(iterations = 1)
@Warmup(iterations = 2)
@Measurement(iterations = 5) @Measurement(iterations = 5)
@State(Scope.Benchmark) @State(Scope.Benchmark)
open class ArrayBenchmark { open class ArrayBenchmark {
@ -30,7 +29,6 @@ open class ArrayBenchmark {
for (i in 1..10000) { for (i in 1..10000) {
res += array[10000 - i] res += array[10000 - i]
} }
print(res)
} }
@Benchmark @Benchmark
@ -39,7 +37,6 @@ open class ArrayBenchmark {
for (i in 1..10000) { for (i in 1..10000) {
res += arrayBuffer.get(10000 - i) res += arrayBuffer.get(10000 - i)
} }
print(res)
} }
@Benchmark @Benchmark
@ -48,6 +45,5 @@ open class ArrayBenchmark {
for (i in 1..10000) { for (i in 1..10000) {
res += nativeBuffer.get(10000 - i) res += nativeBuffer.get(10000 - i)
} }
print(res)
} }
} }

View File

@ -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
}
}

View File

@ -5,10 +5,12 @@ pluginManagement {
} }
} }
enableFeaturePreview("GRADLE_METADATA") //enableFeaturePreview("GRADLE_METADATA")
rootProject.name = "kmath" rootProject.name = "kmath"
include( include(
":kmath-core", ":kmath-core",
":kmath-io",
":kmath-coroutines",
":kmath-jmh" ":kmath-jmh"
) )