From 4060c70b175cc7a17a7078bfe7fa57051cd89956 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 9 Dec 2019 19:52:00 +0300 Subject: [PATCH] Examples for type-safe dimensions --- examples/build.gradle.kts | 11 ++--- .../kmath/structures/ViktorBenchmark.kt} | 47 +++++++++---------- .../kotlin/scientifik/kmath/utils/utils.kt | 8 ++++ .../kmath/structures/typeSafeDimensions.kt | 34 ++++++++++++++ kmath-dimensions/build.gradle.kts | 2 +- .../scientifik/kmath/dimensions/Dimensions.kt | 9 +++- .../scientifik/kmath/dimensions/Wrappers.kt | 26 ++++++++-- 7 files changed, 96 insertions(+), 41 deletions(-) rename examples/src/{main/kotlin/scientifik/kmath/structures/VictorTest.kt => benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt} (58%) create mode 100644 examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt create mode 100644 examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index dfd530618..47acaa5ba 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -5,7 +5,7 @@ plugins { java kotlin("jvm") kotlin("plugin.allopen") version "1.3.61" - id("kotlinx.benchmark") version "0.2.0-dev-5" + id("kotlinx.benchmark") version "0.2.0-dev-7" } configure { @@ -13,10 +13,7 @@ configure { } repositories { - maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("http://dl.bintray.com/kyonifer/maven") - maven ("https://dl.bintray.com/orangy/maven") - mavenCentral() } @@ -30,12 +27,10 @@ dependencies { implementation(project(":kmath-commons")) implementation(project(":kmath-koma")) implementation(project(":kmath-viktor")) + implementation(project(":kmath-dimensions")) implementation("com.kyonifer:koma-core-ejml:0.12") implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:${Scientifik.ioVersion}") - - implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-2") - - + implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-7") "benchmarksCompile"(sourceSets.main.get().compileClasspath) } diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt similarity index 58% rename from examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt rename to examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt index 19469ca72..be4115d81 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt @@ -1,28 +1,26 @@ package scientifik.kmath.structures import org.jetbrains.bio.viktor.F64Array +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State import scientifik.kmath.operations.RealField import scientifik.kmath.viktor.ViktorNDField -fun main() { - val dim = 1000 - val n = 400 +@State(Scope.Benchmark) +class ViktorBenchmark { + final val dim = 1000 + final val n = 100 // automatically build context most suited for given type. - val autoField = NDField.auto(RealField, dim, dim) - val realField = NDField.real(dim,dim) + final val autoField = NDField.auto(RealField, dim, dim) + final val realField = NDField.real(dim, dim) - val viktorField = ViktorNDField(intArrayOf(dim, dim)) + final val viktorField = ViktorNDField(intArrayOf(dim, dim)) - autoField.run { - var res = one - repeat(n/2) { - res += 1.0 - } - } - - measureAndPrint("Automatic field addition") { + @Benchmark + fun `Automatic field addition`() { autoField.run { var res = one repeat(n) { @@ -31,14 +29,8 @@ fun main() { } } - viktorField.run { - var res = one - repeat(n/2) { - res += one - } - } - - measureAndPrint("Viktor field addition") { + @Benchmark + fun `Viktor field addition`() { viktorField.run { var res = one repeat(n) { @@ -47,7 +39,8 @@ fun main() { } } - measureAndPrint("Raw Viktor") { + @Benchmark + fun `Raw Viktor`() { val one = F64Array.full(init = 1.0, shape = *intArrayOf(dim, dim)) var res = one repeat(n) { @@ -55,9 +48,10 @@ fun main() { } } - measureAndPrint("Automatic field log") { + @Benchmark + fun `Real field log`() { realField.run { - val fortyTwo = produce { 42.0 } + val fortyTwo = produce { 42.0 } var res = one repeat(n) { @@ -66,7 +60,8 @@ fun main() { } } - measureAndPrint("Raw Viktor log") { + @Benchmark + fun `Raw Viktor log`() { val fortyTwo = F64Array.full(dim, dim, init = 42.0) var res: F64Array repeat(n) { diff --git a/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt new file mode 100644 index 000000000..6ec9e9c17 --- /dev/null +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt @@ -0,0 +1,8 @@ +package scientifik.kmath.utils + +import kotlin.system.measureTimeMillis + +internal inline fun measureAndPrint(title: String, block: () -> Unit) { + val time = measureTimeMillis(block) + println("$title completed in $time millis") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt new file mode 100644 index 000000000..8693308ca --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt @@ -0,0 +1,34 @@ +package scientifik.kmath.structures + +import scientifik.kmath.dimensions.D2 +import scientifik.kmath.dimensions.D3 +import scientifik.kmath.dimensions.DMatrixContext +import scientifik.kmath.dimensions.Dimension +import scientifik.kmath.operations.RealField + +fun DMatrixContext.simple() { + val m1 = produce { i, j -> (i + j).toDouble() } + val m2 = produce { i, j -> (i + j).toDouble() } + + //Dimension-safe addition + m1.transpose() + m2 +} + +object D5: Dimension{ + override val dim: UInt = 5u +} + +fun DMatrixContext.custom() { + val m1 = produce { i, j -> (i+j).toDouble() } + val m2 = produce { i, j -> (i-j).toDouble() } + val m3 = produce { i, j -> (i-j).toDouble() } + + (m1 dot m2) + m3 +} + +fun main() { + DMatrixContext.real.run { + simple() + custom() + } +} \ No newline at end of file diff --git a/kmath-dimensions/build.gradle.kts b/kmath-dimensions/build.gradle.kts index 59a4c0fc3..50fb41391 100644 --- a/kmath-dimensions/build.gradle.kts +++ b/kmath-dimensions/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } description = "A proof of concept module for adding typ-safe dimensions to structures" diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt index 421ccb853..87511b885 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt @@ -1,9 +1,14 @@ package scientifik.kmath.dimensions +/** + * An interface which is not used in runtime. Designates a size of some structure. + * All descendants should be singleton objects. + */ interface Dimension { val dim: UInt companion object { + @Suppress("NOTHING_TO_INLINE") inline fun of(dim: UInt): Dimension { return when (dim) { 1u -> D1 @@ -15,8 +20,8 @@ interface Dimension { } } - inline fun dim(): UInt{ - return D::class.objectInstance!!.dim + inline fun dim(): UInt { + return D::class.objectInstance?.dim ?: error("Dimension object must be a singleton") } } } diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt index 3138558b8..9985888b9 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt @@ -8,14 +8,21 @@ import scientifik.kmath.operations.Ring import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.Structure2D +/** + * A matrix with compile-time controlled dimension + */ interface DMatrix : Structure2D { companion object { /** * Coerces a regular matrix to a matrix with type-safe dimensions and throws a error if coercion failed */ inline fun coerce(structure: Structure2D): DMatrix { - if (structure.rowNum != Dimension.dim().toInt()) error("Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}") - if (structure.colNum != Dimension.dim().toInt()) error("Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}") + if (structure.rowNum != Dimension.dim().toInt()) { + error("Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}") + } + if (structure.colNum != Dimension.dim().toInt()) { + error("Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}") + } return DMatrixWrapper(structure) } @@ -28,6 +35,9 @@ interface DMatrix : Structure2D { } } +/** + * An inline wrapper for a Matrix + */ inline class DMatrixWrapper( val structure: Structure2D ) : DMatrix { @@ -35,10 +45,15 @@ inline class DMatrixWrapper( override fun get(i: Int, j: Int): T = structure[i, j] } +/** + * Dimension-safe point + */ interface DPoint : Point { companion object { inline fun coerce(point: Point): DPoint { - if (point.size != Dimension.dim().toInt()) error("Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}") + if (point.size != Dimension.dim().toInt()) { + error("Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}") + } return DPointWrapper(point) } @@ -48,6 +63,9 @@ interface DPoint : Point { } } +/** + * Dimension-safe point wrapper + */ inline class DPointWrapper(val point: Point) : DPoint { override val size: Int get() = point.size @@ -58,7 +76,7 @@ inline class DPointWrapper(val point: Point) : DPoint /** - * Basic operations on matrices. Operates on [Matrix] + * Basic operations on dimension-safe matrices. Operates on [Matrix] */ inline class DMatrixContext>(val context: GenericMatrixContext) {