From 8437dd1cc1f3f663f8c190e1fdcbfa48957c4375 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 11 Feb 2023 20:24:35 +0300 Subject: [PATCH] [WIP] wasm prototype --- .../kotlin/space/kscience/kmath/fit/qowFit.kt | 2 - gradle.properties | 2 +- kmath-core/build.gradle.kts | 1 + kmath-memory/build.gradle.kts | 6 ++ .../space/kscience/kmath/memory/WasmMemory.kt | 100 ++++++++++++++++++ .../kscience/kmath/optimization/XYFit.kt | 26 +++++ test-utils/build.gradle.kts | 1 + 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 kmath-memory/src/wasmMain/kotlin/space/kscience/kmath/memory/WasmMemory.kt diff --git a/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt b/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt index fe7f48b72..32deba6a3 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt @@ -10,7 +10,6 @@ import kotlinx.html.h3 import space.kscience.kmath.data.XYErrorColumnarData import space.kscience.kmath.distributions.NormalDistribution import space.kscience.kmath.expressions.Symbol -import space.kscience.kmath.expressions.autodiff import space.kscience.kmath.expressions.binding import space.kscience.kmath.expressions.symbol import space.kscience.kmath.operations.asIterable @@ -62,7 +61,6 @@ suspend fun main() { val result = XYErrorColumnarData.of(x, y, yErr).fitWith( QowOptimizer, - Double.autodiff, mapOf(a to 0.9, b to 1.2, c to 2.0, e to 1.0, d to 1.0, e to 0.0), OptimizationParameters(a, b, c, d) ) { arg -> diff --git a/gradle.properties b/gradle.properties index 16cdd3551..fe7937dda 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ kotlin.native.ignoreDisabledTargets=true org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.14.0-kotlin-1.8.10 +toolsVersion=0.14.1-kotlin-1.8.20-Beta org.gradle.parallel=true diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 0e4646bed..7d8db2939 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -6,6 +6,7 @@ kscience{ jvm() js() native() + wasm() dependencies { api(projects.kmathMemory) diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index 4f0f996b9..d152fb376 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -8,6 +8,12 @@ kscience { native() } +kotlin { + wasm { + browser() + } +} + readme { maturity = space.kscience.gradle.Maturity.DEVELOPMENT description = """ diff --git a/kmath-memory/src/wasmMain/kotlin/space/kscience/kmath/memory/WasmMemory.kt b/kmath-memory/src/wasmMain/kotlin/space/kscience/kmath/memory/WasmMemory.kt new file mode 100644 index 000000000..84c1636f5 --- /dev/null +++ b/kmath-memory/src/wasmMain/kotlin/space/kscience/kmath/memory/WasmMemory.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.memory + +import kotlin.wasm.* + +@PublishedApi +internal class WasmMemory( + val array: ByteArray, + val startOffset: Int = 0, + override val size: Int = array.size, +) : Memory { + @Suppress("NOTHING_TO_INLINE") + private inline fun position(o: Int): Int = startOffset + o + + override fun view(offset: Int, length: Int): Memory { + require(offset >= 0) { "offset shouldn't be negative: $offset" } + require(length >= 0) { "length shouldn't be negative: $length" } + require(offset + length <= size) { "Can't view memory outside the parent region." } + return WasmMemory(array, position(offset), length) + } + + override fun copy(): Memory { + val copy = array.copyOfRange(startOffset, startOffset + size) + return WasmMemory(copy) + } + + private val reader: MemoryReader = object : MemoryReader { + override val memory: Memory get() = this@WasmMemory + + override fun readDouble(offset: Int) = array.getDoubleAt(position(offset)) + + override fun readFloat(offset: Int) = array.getFloatAt(position(offset)) + + override fun readByte(offset: Int) = array[position(offset)] + + override fun readShort(offset: Int) = array.getShortAt(position(offset)) + + override fun readInt(offset: Int) = array.getIntAt(position(offset)) + + override fun readLong(offset: Int) = array.getLongAt(position(offset)) + + override fun release() { + // does nothing on JVM + } + } + + override fun reader(): MemoryReader = reader + + private val writer: MemoryWriter = object : MemoryWriter { + override val memory: Memory get() = this@WasmMemory + + override fun writeDouble(offset: Int, value: Double) { + array.setDoubleAt(position(offset), value) + } + + override fun writeFloat(offset: Int, value: Float) { + array.setFloatAt(position(offset), value) + } + + override fun writeByte(offset: Int, value: Byte) { + array[position(offset)] = value + } + + override fun writeShort(offset: Int, value: Short) { + array.setShortAt(position(offset), value) + } + + override fun writeInt(offset: Int, value: Int) { + array.setIntAt(position(offset), value) + } + + override fun writeLong(offset: Int, value: Long) { + array.setLongAt(position(offset), value) + } + + override fun release() { + // does nothing on JVM + } + } + + override fun writer(): MemoryWriter = writer +} + +/** + * Wraps a [Memory] around existing [ByteArray]. This operation is unsafe since the array is not copied + * and could be mutated independently of the resulting [Memory]. + */ +public actual fun Memory.Companion.wrap(array: ByteArray): Memory = WasmMemory(array) + +/** + * Allocates the most effective platform-specific memory. + */ +public actual fun Memory.Companion.allocate(length: Int): Memory { + val array = ByteArray(length) + return WasmMemory(array) +} \ No newline at end of file diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index 5c28826ee..3970cdca6 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -12,6 +12,7 @@ import space.kscience.kmath.expressions.* import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.Loggable import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.bindSymbol import kotlin.math.pow @@ -147,6 +148,31 @@ public suspend fun XYColumnarData.fitWith( ) } +public suspend fun XYColumnarData.fitWith( + optimizer: Optimizer, + startingPoint: Map, + vararg features: OptimizationFeature = emptyArray(), + xSymbol: Symbol = Symbol.x, + pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, + pointWeight: PointWeight = PointWeight.byYSigma, + model: DSField.(DS) -> DS, +): XYFit { + val modelExpression: DifferentiableExpression = Double.autodiff.differentiate { + val x = bindSymbol(xSymbol) + model(x) + } + + return fitWith( + optimizer = optimizer, + modelExpression = modelExpression, + startingPoint = startingPoint, + features = features, + xSymbol = xSymbol, + pointToCurveDistance = pointToCurveDistance, + pointWeight = pointWeight + ) +} + /** * Compute chi squared value for completed fit. Return null for incomplete fit */ diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index b860a62ec..b03059eaf 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -6,6 +6,7 @@ kscience{ jvm() js() native() + wasm() } kotlin.sourceSets {