diff --git a/CHANGELOG.md b/CHANGELOG.md index 435d958d9..259c56c34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ - Chi squared optimization for array-like data in CM - `Fitting` utility object in prob/stat - ND4J support module submitting `NDStructure` and `NDAlgebra` over `INDArray`. +- Coroutine-deterministic Monte-Carlo scope with a random number generator. +- Some minor utilities to `kmath-for-real`. ### Changed - Package changed from `scientifik` to `kscience.kmath`. @@ -24,12 +26,14 @@ - `kmath-ast` doesn't depend on heavy `kotlin-reflect` library. - Full autodiff refactoring based on `Symbol` - `kmath-prob` renamed to `kmath-stat` +- Grid generators moved to `kmath-for-real` ### Deprecated ### Removed - `kmath-koma` module because it doesn't support Kotlin 1.4. - Support of `legacy` JS backend (we will support only IR) +- `toGrid` method. ### Fixed - `symbol` method in `MstExtendedField` (https://github.com/mipt-npm/kmath/pull/140) diff --git a/build.gradle.kts b/build.gradle.kts index 3514c91e6..561a2212b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("ru.mipt.npm.project") } -internal val kmathVersion: String by extra("0.2.0-dev-3") +internal val kmathVersion: String by extra("0.2.0-dev-4") internal val bintrayRepo: String by extra("kscience") internal val githubProject: String by extra("kmath") diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt index 98940e767..63bbc9312 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/expressions/Expression.kt @@ -3,6 +3,7 @@ package kscience.kmath.expressions import kscience.kmath.operations.Algebra import kotlin.jvm.JvmName import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty /** * A marker interface for a symbol. A symbol mus have an identity @@ -12,6 +13,13 @@ public interface Symbol { * Identity object for the symbol. Two symbols with the same identity are considered to be the same symbol. */ public val identity: String + + public companion object : ReadOnlyProperty { + //TODO deprecate and replace by top level function after fix of https://youtrack.jetbrains.com/issue/KT-40121 + override fun getValue(thisRef: Any?, property: KProperty<*>): Symbol { + return StringSymbol(property.name) + } + } } /** @@ -95,9 +103,9 @@ public fun ExpressionAlgebra.bind(symbol: Symbol): E = /** * A delegate to create a symbol with a string identity in this scope */ -public val symbol: ReadOnlyProperty = ReadOnlyProperty { _, property -> - StringSymbol(property.name) -} +public val symbol: ReadOnlyProperty get() = Symbol +//TODO does not work directly on native due to https://youtrack.jetbrains.com/issue/KT-40121 + /** * Bind a symbol by name inside the [ExpressionAlgebra] diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt index 2a03e2dd3..769c445d6 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/RealBuffer.kt @@ -5,19 +5,19 @@ package kscience.kmath.structures * * @property array the underlying array. */ +@Suppress("OVERRIDE_BY_INLINE") public inline class RealBuffer(public val array: DoubleArray) : MutableBuffer { override val size: Int get() = array.size - override operator fun get(index: Int): Double = array[index] + override inline operator fun get(index: Int): Double = array[index] - override operator fun set(index: Int, value: Double) { + override inline operator fun set(index: Int, value: Double) { array[index] = value } override operator fun iterator(): DoubleIterator = array.iterator() - override fun copy(): MutableBuffer = - RealBuffer(array.copyOf()) + override fun copy(): RealBuffer = RealBuffer(array.copyOf()) } /** @@ -34,6 +34,11 @@ public inline fun RealBuffer(size: Int, init: (Int) -> Double): RealBuffer = Rea */ public fun RealBuffer(vararg doubles: Double): RealBuffer = RealBuffer(doubles) +/** + * Simplified [RealBuffer] to array comparison + */ +public fun RealBuffer.contentEquals(vararg doubles: Double): Boolean = array.contentEquals(doubles) + /** * Returns a [DoubleArray] containing all of the elements of this [MutableBuffer]. */ diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realMatrix.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt similarity index 100% rename from kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realMatrix.kt rename to kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealMatrix.kt diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt index ba5f8444b..1ee33ee32 100644 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt +++ b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/RealVector.kt @@ -14,8 +14,9 @@ import kotlin.math.sqrt public typealias RealPoint = Point -public fun DoubleArray.asVector(): RealVector = RealVector(asBuffer()) -public fun List.asVector(): RealVector = RealVector(asBuffer()) +public fun RealPoint.asVector(): RealVector = RealVector(this) +public fun DoubleArray.asVector(): RealVector = asBuffer().asVector() +public fun List.asVector(): RealVector = asBuffer().asVector() public object VectorL2Norm : Norm, Double> { override fun norm(arg: Point): Double = sqrt(arg.asIterable().sumByDouble(Number::toDouble)) diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/Grids.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/grids.kt similarity index 72% rename from kmath-core/src/commonMain/kotlin/kscience/kmath/misc/Grids.kt rename to kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/grids.kt index 4d058c366..bd0e092e0 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/misc/Grids.kt +++ b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/grids.kt @@ -1,5 +1,7 @@ -package kscience.kmath.misc +package kscience.kmath.real +import kscience.kmath.linear.Point +import kscience.kmath.structures.asBuffer import kotlin.math.abs /** @@ -32,6 +34,9 @@ public fun ClosedFloatingPointRange.toSequenceWithStep(step: Double): Se } } +public infix fun ClosedFloatingPointRange.step(step: Double): Point = + toSequenceWithStep(step).toList().asBuffer() + /** * Convert double range to sequence with the fixed number of points */ @@ -39,12 +44,3 @@ public fun ClosedFloatingPointRange.toSequenceWithPoints(numPoints: Int) require(numPoints > 1) { "The number of points should be more than 2" } return toSequenceWithStep(abs(endInclusive - start) / (numPoints - 1)) } - -/** - * Convert double range to array of evenly spaced doubles, where the size of array equals [numPoints] - */ -@Deprecated("Replace by 'toSequenceWithPoints'") -public fun ClosedFloatingPointRange.toGrid(numPoints: Int): DoubleArray { - require(numPoints >= 2) { "Can't create generic grid with less than two points" } - return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i } -} diff --git a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realBuffer.kt b/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realBuffer.kt deleted file mode 100644 index 0a2119b0d..000000000 --- a/kmath-for-real/src/commonMain/kotlin/kscience/kmath/real/realBuffer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package kscience.kmath.real - -import kscience.kmath.structures.RealBuffer - -/** - * Simplified [RealBuffer] to array comparison - */ -public fun RealBuffer.contentEquals(vararg doubles: Double): Boolean = array.contentEquals(doubles) diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/GridTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/GridTest.kt new file mode 100644 index 000000000..5f19e94b7 --- /dev/null +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/GridTest.kt @@ -0,0 +1,13 @@ +package kaceince.kmath.real + +import kscience.kmath.real.step +import kotlin.test.Test +import kotlin.test.assertEquals + +class GridTest { + @Test + fun testStepGrid(){ + val grid = 0.0..1.0 step 0.2 + assertEquals(6, grid.size) + } +} \ No newline at end of file diff --git a/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt similarity index 98% rename from kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt rename to kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt index 859938481..5c33b76a9 100644 --- a/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealMatrixTest.kt @@ -1,9 +1,10 @@ -package scientific.kmath.real +package kaceince.kmath.real import kscience.kmath.linear.VirtualMatrix import kscience.kmath.linear.build import kscience.kmath.real.* import kscience.kmath.structures.Matrix +import kscience.kmath.structures.contentEquals import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/kmath-for-real/src/commonTest/kotlin/kscience/kmath/linear/VectorTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt similarity index 83% rename from kmath-for-real/src/commonTest/kotlin/kscience/kmath/linear/VectorTest.kt rename to kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt index 17ff4ef20..8a9f7a443 100644 --- a/kmath-for-real/src/commonTest/kotlin/kscience/kmath/linear/VectorTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/RealVectorTest.kt @@ -1,11 +1,14 @@ -package kscience.kmath.linear +package kaceince.kmath.real +import kscience.kmath.linear.MatrixContext +import kscience.kmath.linear.asMatrix +import kscience.kmath.linear.transpose import kscience.kmath.operations.invoke import kscience.kmath.real.RealVector import kotlin.test.Test import kotlin.test.assertEquals -internal class VectorTest { +internal class RealVectorTest { @Test fun testSum() { val vector1 = RealVector(5) { it.toDouble() } diff --git a/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/MCScope.kt b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/MCScope.kt new file mode 100644 index 000000000..5dc567db8 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/kscience/kmath/stat/MCScope.kt @@ -0,0 +1,58 @@ +package kscience.kmath.stat + +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.coroutines.coroutineContext + +/** + * A scope for a Monte-Carlo computations or multi-coroutine random number generation. + * The scope preserves the order of random generator calls as long as all concurrency calls is done via [launch] and [async] + * functions. + */ +public class MCScope( + public val coroutineContext: CoroutineContext, + public val random: RandomGenerator, +) + +/** + * Launches a supervised Monte-Carlo scope + */ +public suspend inline fun mcScope(generator: RandomGenerator, block: MCScope.() -> T): T = + MCScope(coroutineContext, generator).block() + +/** + * Launch mc scope with a given seed + */ +public suspend inline fun mcScope(seed: Long, block: MCScope.() -> T): T = + mcScope(RandomGenerator.default(seed), block) + +/** + * Specialized launch for [MCScope]. Behaves the same way as regular [CoroutineScope.launch], but also stores the generator fork. + * The method itself is not thread safe. + */ +public inline fun MCScope.launch( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + crossinline block: suspend MCScope.() -> Unit, +): Job { + val newRandom = random.fork() + return CoroutineScope(coroutineContext).launch(context, start) { + MCScope(coroutineContext, newRandom).block() + } +} + +/** + * Specialized async for [MCScope]. Behaves the same way as regular [CoroutineScope.async], but also stores the generator fork. + * The method itself is not thread safe. + */ +public inline fun MCScope.async( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + crossinline block: suspend MCScope.() -> T, +): Deferred { + val newRandom = random.fork() + return CoroutineScope(coroutineContext).async(context, start) { + MCScope(coroutineContext, newRandom).block() + } +} \ No newline at end of file diff --git a/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/MCScopeTest.kt b/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/MCScopeTest.kt new file mode 100644 index 000000000..c2304070f --- /dev/null +++ b/kmath-stat/src/jvmTest/kotlin/kscience/kmath/stat/MCScopeTest.kt @@ -0,0 +1,85 @@ +package kscience.kmath.stat + +import kotlinx.coroutines.* +import java.util.* +import kotlin.collections.HashSet +import kotlin.test.Test +import kotlin.test.assertEquals + +data class RandomResult(val branch: String, val order: Int, val value: Int) + +typealias ATest = suspend CoroutineScope.() -> Set + +class MCScopeTest { + val simpleTest: ATest = { + mcScope(1111) { + val res = Collections.synchronizedSet(HashSet()) + + launch { + //println(random) + repeat(10) { + delay(10) + res.add(RandomResult("first", it, random.nextInt())) + } + launch { + //empty fork + } + } + + launch { + //println(random) + repeat(10) { + delay(10) + res.add(RandomResult("second", it, random.nextInt())) + } + } + + + res + } + } + + val testWithJoin: ATest = { + mcScope(1111) { + val res = Collections.synchronizedSet(HashSet()) + + val job = launch { + repeat(10) { + delay(10) + res.add(RandomResult("first", it, random.nextInt())) + } + } + launch { + repeat(10) { + delay(10) + if (it == 4) job.join() + res.add(RandomResult("second", it, random.nextInt())) + } + } + + res + } + } + + + fun compareResult(test: ATest) { + val res1 = runBlocking(Dispatchers.Default) { test() } + val res2 = runBlocking(newSingleThreadContext("test")) { test() } + assertEquals( + res1.find { it.branch == "first" && it.order == 7 }?.value, + res2.find { it.branch == "first" && it.order == 7 }?.value + ) + assertEquals(res1, res2) + } + + @Test + fun testParallel() { + compareResult(simpleTest) + } + + + @Test + fun testConditionalJoin() { + compareResult(testWithJoin) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 97dfe1b96..10e4d9577 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,8 +8,8 @@ pluginManagement { maven("https://dl.bintray.com/kotlin/kotlinx") } - val toolsVersion = "0.6.4-dev-1.4.20-M2" - val kotlinVersion = "1.4.20-M2" + val toolsVersion = "0.7.0" + val kotlinVersion = "1.4.20" plugins { id("kotlinx.benchmark") version "0.2.0-dev-20" @@ -17,7 +17,7 @@ pluginManagement { id("ru.mipt.npm.mpp") version toolsVersion id("ru.mipt.npm.jvm") version toolsVersion id("ru.mipt.npm.publish") version toolsVersion - kotlin("jvm") version kotlinVersion + kotlin("jvm") version kotlinVersion kotlin("plugin.allopen") version kotlinVersion } }