diff --git a/CHANGELOG.md b/CHANGELOG.md index 435d958d9..80b0c3cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - 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. ### Changed - Package changed from `scientifik` to `kscience.kmath`. 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