From 4065fda99aff286ec5059bef2749b10f8a199149 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 7 Apr 2019 09:18:06 +0300 Subject: [PATCH 01/34] Updated build script --- .gitignore | 5 +- .../kmath/linear/LinearAlgebraBenchmark.kt | 2 +- build.gradle.kts | 225 +++++++++++++----- settings.gradle.kts | 3 + 4 files changed, 175 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index d07c3c850..a9294eff9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .gradle -**/build/ -/.idea/ +build/ +out/ +.idea/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index d57631eba..45a6a774e 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -12,7 +12,7 @@ fun main() { val l = Matrix.real(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 } val matrix = l dot u - val n = 500 // iterations + val n = 5000 // iterations val solver = LUSolver.real diff --git a/build.gradle.kts b/build.gradle.kts index 58a5a5bf2..5ad331c48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,9 @@ +import com.moowork.gradle.node.NodeExtension +import com.moowork.gradle.node.npm.NpmTask +import com.moowork.gradle.node.task.NodeTask import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { val kotlinVersion: String by rootProject.extra("1.3.21") @@ -6,10 +11,11 @@ buildscript { val coroutinesVersion: String by rootProject.extra("1.1.1") val atomicfuVersion: String by rootProject.extra("0.12.1") val dokkaVersion: String by rootProject.extra("0.9.17") + val serializationVersion: String by rootProject.extra("0.10.0") repositories { - //maven("https://dl.bintray.com/kotlin/kotlin-eap") jcenter() + maven("https://dl.bintray.com/kotlin/kotlin-eap") } dependencies { @@ -17,94 +23,199 @@ buildscript { classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+") classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") classpath("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") + //classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") + //classpath("org.openjfx:javafx-plugin:0.0.7") } } plugins { id("com.jfrog.artifactory") version "4.9.1" apply false + id("com.moowork.node") version "1.3.1" apply false } -val kmathVersion by extra("0.1.0") +val kmathVersion by extra("0.1.2-dev-1") allprojects { - group = "scientifik" - version = kmathVersion - - repositories { - //maven("https://dl.bintray.com/kotlin/kotlin-eap") - jcenter() - } - apply(plugin = "maven") apply(plugin = "maven-publish") + apply(plugin = "com.jfrog.artifactory") - // apply bintray configuration - apply(from = "${rootProject.rootDir}/gradle/bintray.gradle") - - //apply artifactory configuration - apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle") + repositories { + jcenter() + maven("https://kotlin.bintray.com/kotlinx") + } + group = "scientifik" + version = kmathVersion } subprojects { - if (!name.startsWith("kmath")) return@subprojects + if(name.startsWith("kmath")) { + // apply bintray configuration + apply(from = "${rootProject.rootDir}/gradle/bintray.gradle") + //apply artifactory configuration + apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle") + } + // dokka { +// outputFormat = "html" +// outputDirectory = javadoc.destinationDir +// } +// +// task dokkaJar (type: Jar, dependsOn: dokka) { +// from javadoc . destinationDir +// classifier = "javadoc" +// } - extensions.findByType()?.apply { - jvm { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - } - } - } - targets.all { - sourceSets.all { - languageSettings.progressiveMode = true - } + // Create empty jar for sources classifier to satisfy maven requirements + val stubSources by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + //from(sourceSets.main.get().allSource) + } + + // Create empty jar for javadoc classifier to satisfy maven requirements + val stubJavadoc by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + } + + tasks.withType { + kotlinOptions { + jvmTarget = "1.8" } + } - extensions.findByType()?.apply { - publications.filterIsInstance().forEach { publication -> - if (publication.name == "kotlinMultiplatform") { - // for our root metadata publication, set artifactId with a package and project name - publication.artifactId = project.name - } else { - // for targets, set artifactId with a package, project name and target name (e.g. iosX64) - publication.artifactId = "${project.name}-${publication.name}" + afterEvaluate { + extensions.findByType()?.apply { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } } } - // Create empty jar for sources classifier to satisfy maven requirements - val stubSources by tasks.registering(Jar::class) { - archiveClassifier.set("sources") - //from(sourceSets.main.get().allSource) + js { + compilations.all { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + metaInfo = true + sourceMap = true + sourceMapEmbedSources = "always" + moduleKind = "commonjs" + } + } + } + + configure(listOf(compilations["main"])) { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + main = "call" + } + } + } + + + val runJsTests by ext(false) + + if(runJsTests) { + apply(plugin = "com.moowork.node") + configure { + nodeModulesDir = file("$buildDir/node_modules") + } + + val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) + val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) + + val populateNodeModules by tasks.registering(Copy::class) { + dependsOn(compileKotlinJs) + from(compileKotlinJs.destinationDir) + + compilations["test"].runtimeDependencyFiles.forEach { + if (it.exists() && !it.isDirectory) { + from(zipTree(it.absolutePath).matching { include("*.js") }) + } + } + + into("$buildDir/node_modules") + } + + val installMocha by tasks.registering(NpmTask::class) { + setWorkingDir(buildDir) + setArgs(listOf("install", "mocha")) + } + + val runMocha by tasks.registering(NodeTask::class) { + dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) + setScript(file("$buildDir/node_modules/mocha/bin/mocha")) + setArgs(listOf(compileTestKotlinJs.outputFile)) + } + + tasks["jsTest"].dependsOn(runMocha) + } } - // Create empty jar for javadoc classifier to satisfy maven requirements - val stubJavadoc by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") + sourceSets { + + val commonMain by getting { + dependencies { + api(kotlin("stdlib")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + api(kotlin("stdlib-jdk8")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + val jsMain by getting { + dependencies { + api(kotlin("stdlib-js")) + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } } - extensions.findByType()?.apply { + targets.all { + sourceSets.all { + languageSettings.progressiveMode = true + } + } - targets.forEach { target -> - val publication = publications.findByName(target.name) as MavenPublication + configure { + + publications.filterIsInstance().forEach { publication -> + if (publication.name == "kotlinMultiplatform") { + // for our root metadata publication, set artifactId with a package and project name + publication.artifactId = project.name + } else { + // for targets, set artifactId with a package, project name and target name (e.g. iosX64) + publication.artifactId = "${project.name}-${publication.name}" + } + } + + targets.all { + val publication = publications.findByName(name) as MavenPublication // Patch publications with fake javadoc - publication.artifact(stubJavadoc) - - // Remove gradle metadata publishing from all targets which are not native -// if (target.platformType.name != "native") { -// publication.gradleModuleMetadataFile = null -// tasks.matching { it.name == "generateMetadataFileFor${name.capitalize()}Publication" }.all { -// onlyIf { false } -// } -// } + publication.artifact(stubJavadoc.get()) } } } } -} - +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 8684de3ee..69b1f90fe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,9 @@ pluginManagement { "kotlinx-atomicfu" -> { useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") } + "kotlin-multiplatform" ->{ + useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") + } } } } From 14f05eb1e1d57e7e555a3a7e0e624785249e307c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 7 Apr 2019 12:27:49 +0300 Subject: [PATCH 02/34] Matrix refactoring --- .../kmath/linear/LinearAlgebraBenchmark.kt | 3 +- .../kmath/linear/MultiplicationBenchmark.kt | 1 + build.gradle.kts | 1 + .../scientifik/kmath/linear/CMMatrix.kt | 39 ++++----- kmath-core/build.gradle.kts | 28 ------- .../scientifik/kmath/linear/BufferMatrix.kt | 3 +- .../linear/{Matrix.kt => FeaturedMatrix.kt} | 79 ++++++++----------- .../kmath/linear/LUPDecomposition.kt | 18 +++-- .../scientifik/kmath/linear/LinearAlgrebra.kt | 17 ++-- .../scientifik/kmath/linear/MatrixFeatures.kt | 8 +- .../scientifik/kmath/linear/VirtualMatrix.kt | 11 ++- .../kmath/structures/NDStructure.kt | 3 +- ...pecializedStructures.kt => Structure1D.kt} | 60 ++++---------- .../kmath/structures/Structure2D.kt | 79 +++++++++++++++++++ .../scientifik/kmath/linear/MatrixTest.kt | 2 +- .../kmath/linear/RealLUSolverTest.kt | 4 +- .../scientifik.kmath.linear/KomaMatrix.kt | 14 ++-- kmath-memory/build.gradle.kts | 36 +-------- 18 files changed, 196 insertions(+), 210 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/{Matrix.kt => FeaturedMatrix.kt} (75%) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/{SpecializedStructures.kt => Structure1D.kt} (59%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index 45a6a774e..fda31b01e 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -1,6 +1,7 @@ package scientifik.kmath.linear import koma.matrix.ejml.EJMLMatrixFactory +import scientifik.kmath.structures.Matrix import kotlin.random.Random import kotlin.system.measureTimeMillis @@ -30,7 +31,7 @@ fun main() { //commons-math - val cmContext = CMMatrixContext + val cmContext = CMLUPSolver val commonsTime = measureTimeMillis { cmContext.run { diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt index 0b3b7c275..7b1bd9e7e 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt @@ -1,6 +1,7 @@ package scientifik.kmath.linear import koma.matrix.ejml.EJMLMatrixFactory +import scientifik.kmath.structures.Matrix import kotlin.random.Random import kotlin.system.measureTimeMillis diff --git a/build.gradle.kts b/build.gradle.kts index 5ad331c48..964eb4998 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -193,6 +193,7 @@ subprojects { targets.all { sourceSets.all { languageSettings.progressiveMode = true + languageSettings.enableLanguageFeature("InlineClasses") } } diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt index 808b2768b..513dfe517 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt @@ -3,13 +3,14 @@ package scientifik.kmath.linear import org.apache.commons.math3.linear.* import org.apache.commons.math3.linear.RealMatrix import org.apache.commons.math3.linear.RealVector +import scientifik.kmath.structures.Matrix -class CMMatrix(val origin: RealMatrix, features: Set? = null) : Matrix { +class CMMatrix(val origin: RealMatrix, features: Set? = null) : FeaturedMatrix { override val rowNum: Int get() = origin.rowDimension override val colNum: Int get() = origin.columnDimension override val features: Set = features ?: sequence { - if(origin is DiagonalMatrix) yield(DiagonalFeature) + if (origin is DiagonalMatrix) yield(DiagonalFeature) }.toSet() override fun suggestFeature(vararg features: MatrixFeature) = @@ -45,28 +46,13 @@ fun Point.toCM(): CMVector = if (this is CMVector) { fun RealVector.toPoint() = CMVector(this) -object CMMatrixContext : MatrixContext, LinearSolver { +object CMMatrixContext : MatrixContext { override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix { val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } } return CMMatrix(Array2DRowRealMatrix(array)) } - override fun solve(a: Matrix, b: Matrix): CMMatrix { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.solve(b.toCM().origin).toMatrix() - } - - override fun solve(a: Matrix, b: Point): CMVector { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.solve(b.toCM().origin).toPoint() - } - - override fun inverse(a: Matrix): CMMatrix { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.inverse.toMatrix() - } - override fun Matrix.dot(other: Matrix) = CMMatrix(this.toCM().origin.multiply(other.toCM().origin)) @@ -87,6 +73,23 @@ object CMMatrixContext : MatrixContext, LinearSolver { CMMatrix(this.toCM().origin.scalarMultiply(value.toDouble())) } +object CMLUPSolver: LinearSolver{ + override fun solve(a: Matrix, b: Matrix): CMMatrix { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.solve(b.toCM().origin).toMatrix() + } + + override fun solve(a: Matrix, b: Point): CMVector { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.solve(b.toCM().origin).toPoint() + } + + override fun inverse(a: Matrix): CMMatrix { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.inverse.toMatrix() + } +} + operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.subtract(other.origin)) diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index bd125b373..ab6ae7822 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -13,34 +13,6 @@ kotlin { val commonMain by getting { dependencies { api(project(":kmath-memory")) - api(kotlin("stdlib")) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - val jvmMain by getting { - dependencies { - api(kotlin("stdlib-jdk8")) - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - val jsMain by getting { - dependencies { - api(kotlin("stdlib-js")) - } - } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) } } // mingwMain { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index dbffea8f3..5d116358d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -2,7 +2,6 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* -import kotlin.jvm.JvmSynthetic /** * Basic implementation of Matrix space based on [NDStructure] @@ -25,7 +24,7 @@ class BufferMatrix( override val colNum: Int, val buffer: Buffer, override val features: Set = emptySet() -) : Matrix { +) : FeaturedMatrix { init { if (buffer.size != rowNum * colNum) { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt similarity index 75% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt index 329933019..62c360f26 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt @@ -8,6 +8,9 @@ import scientifik.kmath.structures.Buffer.Companion.boxing import kotlin.math.sqrt +/** + * Basic operations on matrices. Operates on [Matrix] + */ interface MatrixContext { /** * Produce a matrix with this context and given dimensions @@ -101,18 +104,18 @@ interface GenericMatrixContext> : MatrixContext { operator fun Matrix.times(number: Number): Matrix = produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } } - operator fun Number.times(matrix: Matrix): Matrix = matrix * this + operator fun Number.times(matrix: FeaturedMatrix): Matrix = matrix * this override fun Matrix.times(value: T): Matrix = produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } } } /** - * Specialized 2-d structure + * A 2d structure plus optional matrix-specific features */ -interface Matrix : Structure2D { - val rowNum: Int - val colNum: Int +interface FeaturedMatrix : Matrix { + + override val shape: IntArray get() = intArrayOf(rowNum, colNum) val features: Set @@ -122,70 +125,54 @@ interface Matrix : Structure2D { * The implementation does not guarantee to check that matrix actually have the feature, so one should be careful to * add only those features that are valid. */ - fun suggestFeature(vararg features: MatrixFeature): Matrix - - override fun get(index: IntArray): T = get(index[0], index[1]) - - override val shape: IntArray get() = intArrayOf(rowNum, colNum) - - val rows: Point> - get() = VirtualBuffer(rowNum) { i -> - VirtualBuffer(colNum) { j -> get(i, j) } - } - - val columns: Point> - get() = VirtualBuffer(colNum) { j -> - VirtualBuffer(rowNum) { i -> get(i, j) } - } - - override fun elements(): Sequence> = sequence { - for (i in (0 until rowNum)) { - for (j in (0 until colNum)) { - yield(intArrayOf(i, j) to get(i, j)) - } - } - } + fun suggestFeature(vararg features: MatrixFeature): FeaturedMatrix companion object { - fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = - MatrixContext.real.produce(rows, columns, initializer) - /** - * Build a square matrix from given elements. - */ - fun square(vararg elements: T): Matrix { - val size: Int = sqrt(elements.size.toDouble()).toInt() - if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") - val buffer = elements.asBuffer() - return BufferMatrix(size, size, buffer) - } - - fun build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) } } +fun Structure2D.Companion.real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = + MatrixContext.real.produce(rows, columns, initializer) + +/** + * Build a square matrix from given elements. + */ +fun Structure2D.Companion.square(vararg elements: T): FeaturedMatrix { + val size: Int = sqrt(elements.size.toDouble()).toInt() + if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") + val buffer = elements.asBuffer() + return BufferMatrix(size, size, buffer) +} + +fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) + class MatrixBuilder(val rows: Int, val columns: Int) { - operator fun invoke(vararg elements: T): Matrix { + operator fun invoke(vararg elements: T): FeaturedMatrix { if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns") val buffer = elements.asBuffer() return BufferMatrix(rows, columns, buffer) } } +val Matrix<*>.features get() = (this as? FeaturedMatrix)?.features?: emptySet() + /** * Check if matrix has the given feature class */ -inline fun Matrix<*>.hasFeature(): Boolean = features.find { it is T } != null +inline fun Matrix<*>.hasFeature(): Boolean = + features.find { it is T } != null /** * Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria */ -inline fun Matrix<*>.getFeature(): T? = features.filterIsInstance().firstOrNull() +inline fun Matrix<*>.getFeature(): T? = + features.filterIsInstance().firstOrNull() /** * Diagonal matrix of ones. The matrix is virtual no actual matrix is created */ -fun > GenericMatrixContext.one(rows: Int, columns: Int): Matrix = +fun > GenericMatrixContext.one(rows: Int, columns: Int): FeaturedMatrix = VirtualMatrix(rows, columns) { i, j -> if (i == j) elementContext.one else elementContext.zero } @@ -194,7 +181,7 @@ fun > GenericMatrixContext.one(rows: Int, columns: In /** * A virtual matrix of zeroes */ -fun > GenericMatrixContext.zero(rows: Int, columns: Int): Matrix = +fun > GenericMatrixContext.zero(rows: Int, columns: Int): FeaturedMatrix = VirtualMatrix(rows, columns) { _, _ -> elementContext.zero } class TransposedFeature(val original: Matrix) : MatrixFeature diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 85ddb8786..94ce8ed97 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -2,12 +2,12 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.Ring -import scientifik.kmath.structures.MutableBuffer +import scientifik.kmath.structures.* import scientifik.kmath.structures.MutableBuffer.Companion.boxing -import scientifik.kmath.structures.MutableBufferFactory -import scientifik.kmath.structures.NDStructure -import scientifik.kmath.structures.get +/** + * Common implementation of [LUPDecompositionFeature] + */ class LUPDecomposition>( private val elementContext: Ring, internal val lu: NDStructure, @@ -20,7 +20,7 @@ class LUPDecomposition>( * * L is a lower-triangular matrix with [Ring.one] in diagonal */ - override val l: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(LFeature)) { i, j -> + override val l: FeaturedMatrix = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(LFeature)) { i, j -> when { j < i -> lu[i, j] j == i -> elementContext.one @@ -34,7 +34,7 @@ class LUPDecomposition>( * * U is an upper-triangular matrix including the diagonal */ - override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(UFeature)) { i, j -> + override val u: FeaturedMatrix = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(UFeature)) { i, j -> if (j >= i) lu[i, j] else elementContext.zero } @@ -45,7 +45,7 @@ class LUPDecomposition>( * P is a sparse matrix with exactly one element set to [Ring.one] in * each row and each column, all other elements being set to [Ring.zero]. */ - override val p: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + override val p: FeaturedMatrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> if (j == pivot[i]) elementContext.one else elementContext.zero } @@ -62,7 +62,9 @@ class LUPDecomposition>( } - +/** + * Common implementation of LUP [LinearSolver] based on commons-math code + */ class LUSolver, F : Field>( val context: GenericMatrixContext, val bufferFactory: MutableBufferFactory = ::boxing, diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt index 51f61e030..4254fd7ea 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -3,6 +3,7 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.VirtualBuffer import scientifik.kmath.structures.asSequence @@ -10,9 +11,9 @@ import scientifik.kmath.structures.asSequence /** * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors */ -interface LinearSolver { +interface LinearSolver { fun solve(a: Matrix, b: Matrix): Matrix - fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).toVector() + fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).asPoint() fun inverse(a: Matrix): Matrix } @@ -32,14 +33,14 @@ object VectorL2Norm : Norm, Double> { typealias RealVector = Vector typealias RealMatrix = Matrix - - /** * Convert matrix to vector if it is possible */ -fun Matrix.toVector(): Point = +fun Matrix.asPoint(): Point = if (this.colNum == 1) { - VirtualBuffer(rowNum){ get(it, 0) } - } else error("Can't convert matrix with more than one column to vector") + VirtualBuffer(rowNum) { get(it, 0) } + } else { + error("Can't convert matrix with more than one column to vector") + } -fun Point.toMatrix(): Matrix = VirtualMatrix(size, 1) { i, _ -> get(i) } \ No newline at end of file +fun Point.toMatrix() = VirtualMatrix(size, 1) { i, _ -> get(i) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt index 6b45a14b1..de315071f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt @@ -25,7 +25,7 @@ object UnitFeature : MatrixFeature * Inverted matrix feature */ interface InverseMatrixFeature : MatrixFeature { - val inverse: Matrix + val inverse: FeaturedMatrix } /** @@ -54,9 +54,9 @@ object UFeature: MatrixFeature * TODO add documentation */ interface LUPDecompositionFeature : MatrixFeature { - val l: Matrix - val u: Matrix - val p: Matrix + val l: FeaturedMatrix + val u: FeaturedMatrix + val p: FeaturedMatrix } //TODO add sparse matrix feature \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt index 1bab52902..951471a07 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -1,11 +1,16 @@ package scientifik.kmath.linear +import scientifik.kmath.structures.Matrix + class VirtualMatrix( override val rowNum: Int, override val colNum: Int, override val features: Set = emptySet(), val generator: (i: Int, j: Int) -> T -) : Matrix { +) : FeaturedMatrix { + + override val shape: IntArray get() = intArrayOf(rowNum, colNum) + override fun get(i: Int, j: Int): T = generator(i, j) override fun suggestFeature(vararg features: MatrixFeature) = @@ -13,7 +18,7 @@ class VirtualMatrix( override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is Matrix<*>) return false + if (other !is FeaturedMatrix<*>) return false if (rowNum != other.rowNum) return false if (colNum != other.colNum) return false @@ -34,7 +39,7 @@ class VirtualMatrix( /** * Wrap a matrix adding additional features to it */ - fun wrap(matrix: Matrix, vararg features: MatrixFeature): Matrix { + fun wrap(matrix: Matrix, vararg features: MatrixFeature): FeaturedMatrix { return if (matrix is VirtualMatrix) { VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features, matrix.generator) } else { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 2cf9af6fb..5895251e8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -5,8 +5,7 @@ interface NDStructure { val shape: IntArray - val dimension - get() = shape.size + val dimension get() = shape.size operator fun get(index: IntArray): T diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt similarity index 59% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt index 734aba5ac..df56017a3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure1D.kt @@ -27,20 +27,6 @@ private inline class Structure1DWrapper(val structure: NDStructure) : Stru override fun elements(): Sequence> = structure.elements() } -/** - * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch - */ -fun NDStructure.as1D(): Structure1D = if (shape.size == 1) { - Structure1DWrapper(this) -} else { - error("Can't create 1d-structure from ${shape.size}d-structure") -} - -fun NDBuffer.as1D(): Structure1D = if (shape.size == 1) { - Buffer1DWrapper(this.buffer) -} else { - error("Can't create 1d-structure from ${shape.size}d-structure") -} /** * A structure wrapper for buffer @@ -56,39 +42,21 @@ private inline class Buffer1DWrapper(val buffer: Buffer) : Structure1D override fun get(index: Int): T = buffer.get(index) } -/** - * Represent this buffer as 1D structure - */ -fun Buffer.asND(): Structure1D = Buffer1DWrapper(this) - -/** - * A structure that is guaranteed to be two-dimensional - */ -interface Structure2D : NDStructure { - operator fun get(i: Int, j: Int): T - - override fun get(index: IntArray): T { - if (index.size != 2) error("Index dimension mismatch. Expected 2 but found ${index.size}") - return get(index[0], index[1]) - } -} - -/** - * A 2D wrapper for nd-structure - */ -private inline class Structure2DWrapper(val structure: NDStructure) : Structure2D { - override fun get(i: Int, j: Int): T = structure[i, j] - - override val shape: IntArray get() = structure.shape - - override fun elements(): Sequence> = structure.elements() -} - /** * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch */ -fun NDStructure.as2D(): Structure2D = if (shape.size == 2) { - Structure2DWrapper(this) +fun NDStructure.as1D(): Structure1D = if (shape.size == 1) { + if( this is NDBuffer){ + Buffer1DWrapper(this.buffer) + } else { + Structure1DWrapper(this) + } } else { - error("Can't create 2d-structure from ${shape.size}d-structure") -} \ No newline at end of file + error("Can't create 1d-structure from ${shape.size}d-structure") +} + + +/** + * Represent this buffer as 1D structure + */ +fun Buffer.asND(): Structure1D = Buffer1DWrapper(this) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt new file mode 100644 index 000000000..e736f84a0 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Structure2D.kt @@ -0,0 +1,79 @@ +package scientifik.kmath.structures + +/** + * A structure that is guaranteed to be two-dimensional + */ +interface Structure2D : NDStructure { + val rowNum: Int get() = shape[0] + val colNum: Int get() = shape[1] + + operator fun get(i: Int, j: Int): T + + override fun get(index: IntArray): T { + if (index.size != 2) error("Index dimension mismatch. Expected 2 but found ${index.size}") + return get(index[0], index[1]) + } + + + val rows: Buffer> + get() = VirtualBuffer(rowNum) { i -> + VirtualBuffer(colNum) { j -> get(i, j) } + } + + val columns: Buffer> + get() = VirtualBuffer(colNum) { j -> + VirtualBuffer(rowNum) { i -> get(i, j) } + } + + override fun elements(): Sequence> = sequence { + for (i in (0 until rowNum)) { + for (j in (0 until colNum)) { + yield(intArrayOf(i, j) to get(i, j)) + } + } + } + + companion object { + + } +} + +/** + * A 2D wrapper for nd-structure + */ +private inline class Structure2DWrapper(val structure: NDStructure) : Structure2D { + override fun get(i: Int, j: Int): T = structure[i, j] + + override val shape: IntArray get() = structure.shape + + override fun elements(): Sequence> = structure.elements() +} + +/** + * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch + */ +fun NDStructure.as2D(): Structure2D = if (shape.size == 2) { + Structure2DWrapper(this) +} else { + error("Can't create 2d-structure from ${shape.size}d-structure") +} + +/** + * Represent this 2D structure as 1D if it has exactly one column. Throw error otherwise. + */ +fun Structure2D.as1D() = if (colNum == 1) { + object : Structure1D { + override fun get(index: Int): T = get(index, 0) + + override val shape: IntArray get() = intArrayOf(rowNum) + + override fun elements(): Sequence> = elements() + + override val size: Int get() = rowNum + } +} else { + error("Can't convert matrix with more than one column to vector") +} + + +typealias Matrix = Structure2D \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 61aa506c4..8d09b6d7b 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -44,7 +44,7 @@ class MatrixTest { @Test fun testBuilder() { - val matrix = Matrix.build(2, 3)( + val matrix = FeaturedMatrix.build(2, 3)( 1.0, 0.0, 0.0, 0.0, 1.0, 2.0 ) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index bfa720369..8887d3a32 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -13,7 +13,7 @@ class RealLUSolverTest { @Test fun testInvert() { - val matrix = Matrix.square( + val matrix = FeaturedMatrix.square( 3.0, 1.0, 1.0, 3.0 ) @@ -31,7 +31,7 @@ class RealLUSolverTest { val inverted = LUSolver.real.inverse(decomposed) - val expected = Matrix.square( + val expected = FeaturedMatrix.square( 0.375, -0.125, -0.125, 0.375 ) diff --git a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt index 343d5b8b9..b95f04887 100644 --- a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt +++ b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt @@ -2,6 +2,7 @@ package scientifik.kmath.linear import koma.extensions.fill import koma.matrix.MatrixFactory +import scientifik.kmath.structures.Matrix class KomaMatrixContext(val factory: MatrixFactory>) : MatrixContext, LinearSolver { @@ -48,24 +49,25 @@ class KomaMatrixContext(val factory: MatrixFactory(val origin: koma.matrix.Matrix, features: Set? = null) : - Matrix { +class KomaMatrix(val origin: koma.matrix.Matrix, features: Set? = null) : FeaturedMatrix { override val rowNum: Int get() = origin.numRows() override val colNum: Int get() = origin.numCols() + override val shape: IntArray get() = intArrayOf(origin.numRows(),origin.numCols()) + override val features: Set = features ?: setOf( object : DeterminantFeature { override val determinant: T get() = origin.det() }, object : LUPDecompositionFeature { private val lup by lazy { origin.LU() } - override val l: Matrix get() = KomaMatrix(lup.second) - override val u: Matrix get() = KomaMatrix(lup.third) - override val p: Matrix get() = KomaMatrix(lup.first) + override val l: FeaturedMatrix get() = KomaMatrix(lup.second) + override val u: FeaturedMatrix get() = KomaMatrix(lup.third) + override val p: FeaturedMatrix get() = KomaMatrix(lup.first) } ) - override fun suggestFeature(vararg features: MatrixFeature): Matrix = + override fun suggestFeature(vararg features: MatrixFeature): FeaturedMatrix = KomaMatrix(this.origin, this.features + features) override fun get(i: Int, j: Int): T = origin.getGeneric(i, j) diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index f6d10875c..03a05c9fe 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -8,43 +8,9 @@ val ioVersion: String by rootProject.extra kotlin { jvm() js() - - sourceSets { - val commonMain by getting { - dependencies { - api(kotlin("stdlib")) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - val jvmMain by getting { - dependencies { - api(kotlin("stdlib-jdk8")) - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - val jsMain by getting { - dependencies { - api(kotlin("stdlib-js")) - } - } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) - } - } // mingwMain { // } // mingwTest { // } - } + } \ No newline at end of file From 271e762a95eecf5ba5b24c3441e56c1b799b9b6a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 9 Apr 2019 19:48:53 +0300 Subject: [PATCH 03/34] Optimizing inversion performance --- .../kmath/linear/LinearAlgebraBenchmark.kt | 21 +- .../scientifik/kmath/linear/BufferMatrix.kt | 5 + .../kmath/linear/LUPDecomposition.kt | 331 +++++++++++------- .../kmath/linear/Mutable2DStructure.kt | 40 --- .../scientifik/kmath/structures/Buffers.kt | 33 +- .../kmath/structures/NDStructure.kt | 15 +- 6 files changed, 257 insertions(+), 188 deletions(-) delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index fda31b01e..8816ba5cf 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -15,19 +15,20 @@ fun main() { val n = 5000 // iterations - val solver = LUSolver.real + MatrixContext.real.run { - repeat(50) { - val res = solver.inverse(matrix) - } - - val inverseTime = measureTimeMillis { - repeat(n) { - val res = solver.inverse(matrix) + repeat(50) { + val res = inverse(matrix) } - } - println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis") + val inverseTime = measureTimeMillis { + repeat(n) { + val res = inverse(matrix) + } + } + + println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis") + } //commons-math diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index 5d116358d..fc16c1f8e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -1,5 +1,6 @@ package scientifik.kmath.linear +import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* @@ -17,6 +18,10 @@ class BufferMatrixContext>( } override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) + + companion object { + + } } class BufferMatrix( diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 94ce8ed97..87b17cd76 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -1,16 +1,17 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field +import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* -import scientifik.kmath.structures.MutableBuffer.Companion.boxing +import kotlin.reflect.KClass /** * Common implementation of [LUPDecompositionFeature] */ -class LUPDecomposition>( +class LUPDecomposition( private val elementContext: Ring, - internal val lu: NDStructure, + val lu: Structure2D, val pivot: IntArray, private val even: Boolean ) : LUPDecompositionFeature, DeterminantFeature { @@ -62,146 +63,222 @@ class LUPDecomposition>( } +open class BufferAccessor(val type: KClass, val field: Field, val rowNum: Int, val colNum: Int) { + open operator fun MutableBuffer.get(i: Int, j: Int) = get(i + colNum * j) + open operator fun MutableBuffer.set(i: Int, j: Int, value: T) { + set(i + colNum * j, value) + } + + fun create(init: (i: Int, j: Int) -> T) = + MutableBuffer.auto(type, rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } + + fun create(mat: Structure2D) = create { i, j -> mat[i, j] } + + //TODO optimize wrapper + fun MutableBuffer.collect(): Structure2D = + NDStructure.auto(type, rowNum, colNum) { (i, j) -> get(i, j) }.as2D() + + open fun MutableBuffer.innerProduct(row: Int, col: Int, max: Int): T { + var sum = field.zero + field.run { + for (i in 0 until max) { + sum += get(row, i) * get(i, col) + } + } + return sum + } + + open fun MutableBuffer.divideInPlace(i: Int, j: Int, factor: T) { + field.run { set(i, j, get(i, j) / factor) } + } + + open fun MutableBuffer.subtractInPlace(i: Int, j: Int, lu: MutableBuffer, col: Int) { + field.run { + set(i, j, get(i, j) - get(col, j) * lu[i, col]) + } + } +} + /** - * Common implementation of LUP [LinearSolver] based on commons-math code + * Specialized LU operations for Doubles */ -class LUSolver, F : Field>( - val context: GenericMatrixContext, - val bufferFactory: MutableBufferFactory = ::boxing, - val singularityCheck: (T) -> Boolean -) : LinearSolver { - - - private fun abs(value: T) = - if (value > context.elementContext.zero) value else with(context.elementContext) { -value } - - fun buildDecomposition(matrix: Matrix): LUPDecomposition { - if (matrix.rowNum != matrix.colNum) { - error("LU decomposition supports only square matrices") - } - - val m = matrix.colNum - val pivot = IntArray(matrix.rowNum) - - val lu = Mutable2DStructure.create(matrix.rowNum, matrix.colNum, bufferFactory) { i, j -> - matrix[i, j] - } - - - with(context.elementContext) { - // Initialize permutation array and parity - for (row in 0 until m) { - pivot[row] = row - } - var even = true - - // Loop over columns - for (col in 0 until m) { - - // upper - for (row in 0 until col) { - var sum = lu[row, col] - for (i in 0 until row) { - sum -= lu[row, i] * lu[i, col] - } - lu[row, col] = sum - } - - // lower - val max = (col until m).maxBy { row -> - var sum = lu[row, col] - for (i in 0 until col) { - sum -= lu[row, i] * lu[i, col] - } - lu[row, col] = sum - - abs(sum) - } ?: col - - // Singularity check - if (singularityCheck(lu[max, col])) { - error("Singular matrix") - } - - // Pivot if necessary - if (max != col) { - for (i in 0 until m) { - lu[max, i] = lu[col, i] - lu[col, i] = lu[max, i] - } - val temp = pivot[max] - pivot[max] = pivot[col] - pivot[col] = temp - even = !even - } - - // Divide the lower elements by the "winning" diagonal elt. - val luDiag = lu[col, col] - for (row in col + 1 until m) { - lu[row, col] = lu[row, col] / luDiag - } - } - return LUPDecomposition(context.elementContext, lu, pivot, even) - } +class RealBufferAccessor(rowNum: Int, colNum: Int) : BufferAccessor(Double::class, RealField, rowNum, colNum) { + override inline fun MutableBuffer.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j] + override inline fun MutableBuffer.set(i: Int, j: Int, value: Double) { + (this as DoubleBuffer).array[i + colNum * j] = value } - /** - * Produce a matrix with added decomposition feature - */ - fun decompose(matrix: Matrix): Matrix { - if (matrix.hasFeature>()) { - return matrix - } else { - val decomposition = buildDecomposition(matrix) - return VirtualMatrix.wrap(matrix, decomposition) + override fun MutableBuffer.innerProduct(row: Int, col: Int, max: Int): Double { + var sum = 0.0 + for (i in 0 until max) { + sum += get(row, i) * get(i, col) } + return sum + } + + override fun MutableBuffer.divideInPlace(i: Int, j: Int, factor: Double) { + set(i, j, get(i, j) / factor) + } + + override fun MutableBuffer.subtractInPlace(i: Int, j: Int, lu: MutableBuffer, col: Int) { + set(i, j, get(i, j) - get(col, j) * lu[i, col]) + } +} + +fun , F : Field> GenericMatrixContext.buildAccessor( + type:KClass, + rowNum: Int, + colNum: Int +): BufferAccessor { + return if (elementContext == RealField) { + @Suppress("UNCHECKED_CAST") + RealBufferAccessor(rowNum, colNum) as BufferAccessor + } else { + BufferAccessor(type, elementContext, rowNum, colNum) + } +} + +fun , F : Field> GenericMatrixContext.abs(value: T) = + if (value > elementContext.zero) value else with(elementContext) { -value } + + +fun , F : Field> GenericMatrixContext.lupDecompose( + type: KClass, + matrix: Matrix, + checkSingular: (T) -> Boolean +): LUPDecomposition { + if (matrix.rowNum != matrix.colNum) { + error("LU decomposition supports only square matrices") } - override fun solve(a: Matrix, b: Matrix): Matrix { - if (b.rowNum != a.colNum) { - error("Matrix dimension mismatch expected ${a.rowNum}, but got ${b.colNum}") + val m = matrix.colNum + val pivot = IntArray(matrix.rowNum) + + buildAccessor(type, matrix.rowNum, matrix.colNum).run { + + val lu = create(matrix) + + // Initialize permutation array and parity + for (row in 0 until m) { + pivot[row] = row } + var even = true - // Use existing decomposition if it is provided by matrix - val decomposition = a.getFeature() ?: buildDecomposition(a) + // Loop over columns + for (col in 0 until m) { - with(decomposition) { - with(context.elementContext) { - // Apply permutations to b - val bp = Mutable2DStructure.create(a.rowNum, a.colNum, bufferFactory) { i, j -> - b[pivot[i], j] + // upper + for (row in 0 until col) { +// var sum = lu[row, col] +// for (i in 0 until row) { +// sum -= lu[row, i] * lu[i, col] +// } + val sum = lu.innerProduct(row, col, row) + lu[row, col] = field.run { lu[row, col] - sum } + } + + // lower + val max = (col until m).maxBy { row -> + // var sum = lu[row, col] +// for (i in 0 until col) { +// sum -= lu[row, i] * lu[i, col] +// } +// lu[row, col] = sum + val sum = lu.innerProduct(row, col, col) + lu[row, col] = field.run { lu[row, col] - sum } + abs(sum) + } ?: col + + // Singularity check + if (checkSingular(lu[max, col])) { + error("Singular matrix") + } + + // Pivot if necessary + if (max != col) { + for (i in 0 until m) { + lu[max, i] = lu[col, i] + lu[col, i] = lu[max, i] } + val temp = pivot[max] + pivot[max] = pivot[col] + pivot[col] = temp + even = !even + } - // Solve LY = b - for (col in 0 until a.rowNum) { - for (i in col + 1 until a.rowNum) { - for (j in 0 until b.colNum) { - bp[i, j] -= bp[col, j] * lu[i, col] - } - } - } - - // Solve UX = Y - for (col in a.rowNum - 1 downTo 0) { - for (j in 0 until b.colNum) { - bp[col, j] /= lu[col, col] - } - for (i in 0 until col) { - for (j in 0 until b.colNum) { - bp[i, j] -= bp[col, j] * lu[i, col] - } - } - } - - return context.produce(a.rowNum, a.colNum) { i, j -> bp[i, j] } + // Divide the lower elements by the "winning" diagonal elt. + val luDiag = lu[col, col] + for (row in col + 1 until m) { + lu.divideInPlace(row, col, luDiag) + //lu[row, col] = lu[row, col] / luDiag } } + return scientifik.kmath.linear.LUPDecomposition(elementContext, lu.collect(), pivot, even) + + } +} + +/** + * Solve a linear equation **a*x = b** + */ +fun , F : Field> GenericMatrixContext.solve( + type: KClass, + a: Matrix, + b: Matrix, + checkSingular: (T) -> Boolean +): Matrix { + if (b.rowNum != a.colNum) { + error("Matrix dimension mismatch. Expected ${a.rowNum}, but got ${b.colNum}") } - override fun inverse(a: Matrix): Matrix = solve(a, context.one(a.rowNum, a.colNum)) + // Use existing decomposition if it is provided by matrix + val decomposition = a.getFeature() ?: lupDecompose(type, a, checkSingular) - companion object { - val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } + buildAccessor(type, a.rowNum, a.colNum).run { + + val lu = create(decomposition.lu) + + // Apply permutations to b + val bp = create { i, j -> + b[decomposition.pivot[i], j] + } + + // Solve LY = b + for (col in 0 until a.rowNum) { + for (i in col + 1 until a.rowNum) { + for (j in 0 until b.colNum) { + bp.subtractInPlace(i, j, lu, col) + //bp[i, j] -= bp[col, j] * lu[i, col] + } + } + } + + // Solve UX = Y + for (col in a.rowNum - 1 downTo 0) { + val luDiag = lu[col, col] + for (j in 0 until b.colNum) { + bp.divideInPlace(col, j, luDiag) + //bp[col, j] /= lu[col, col] + } + for (i in 0 until col) { + for (j in 0 until b.colNum) { + bp.subtractInPlace(i, j, lu, col) + //bp[i, j] -= bp[col, j] * lu[i, col] + } + } + } + + return produce(a.rowNum, a.colNum) { i, j -> bp[i, j] } } -} \ No newline at end of file + +} + +inline fun , F : Field> GenericMatrixContext.inverse( + matrix: Matrix, + noinline checkSingular: (T) -> Boolean +) = + solve(T::class, matrix, one(matrix.rowNum, matrix.colNum), checkSingular) + +fun GenericMatrixContext.inverse(matrix: Matrix) = + inverse(matrix) { it < 1e-11 } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt deleted file mode 100644 index 0d35c57e6..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt +++ /dev/null @@ -1,40 +0,0 @@ -package scientifik.kmath.linear - -import scientifik.kmath.structures.MutableBuffer -import scientifik.kmath.structures.MutableBufferFactory -import scientifik.kmath.structures.MutableNDStructure - -class Mutable2DStructure(val rowNum: Int, val colNum: Int, val buffer: MutableBuffer) : MutableNDStructure { - override val shape: IntArray - get() = intArrayOf(rowNum, colNum) - - operator fun get(i: Int, j: Int): T = buffer[i * colNum + j] - - override fun get(index: IntArray): T = get(index[0], index[1]) - - override fun elements(): Sequence> = sequence { - for (i in 0 until rowNum) { - for (j in 0 until colNum) { - yield(intArrayOf(i, j) to get(i, j)) - } - } - } - - operator fun set(i: Int, j: Int, value: T) { - buffer[i * colNum + j] = value - } - - override fun set(index: IntArray, value: T) = set(index[0], index[1], value) - - companion object { - fun create( - rowNum: Int, - colNum: Int, - bufferFactory: MutableBufferFactory, - init: (i: Int, j: Int) -> T - ): Mutable2DStructure { - val buffer = bufferFactory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } - return Mutable2DStructure(rowNum, colNum, buffer) - } - } -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 66c4820e5..51970ebae 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -2,6 +2,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Complex import scientifik.kmath.operations.complex +import kotlin.reflect.KClass typealias BufferFactory = (Int, (Int) -> T) -> Buffer @@ -41,13 +42,10 @@ interface Buffer { */ inline fun boxing(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) - /** - * Create most appropriate immutable buffer for given type avoiding boxing wherever possible - */ @Suppress("UNCHECKED_CAST") - inline fun auto(size: Int, crossinline initializer: (Int) -> T): Buffer { + inline fun auto(type: KClass, size: Int, crossinline initializer: (Int) -> T): Buffer { //TODO add resolution based on Annotation or companion resolution - return when (T::class) { + return when (type) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer @@ -56,6 +54,13 @@ interface Buffer { else -> boxing(size, initializer) } } + + /** + * Create most appropriate immutable buffer for given type avoiding boxing wherever possible + */ + @Suppress("UNCHECKED_CAST") + inline fun auto(size: Int, crossinline initializer: (Int) -> T): Buffer = + auto(T::class, size, initializer) } } @@ -78,12 +83,9 @@ interface MutableBuffer : Buffer { inline fun boxing(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) - /** - * Create most appropriate mutable buffer for given type avoiding boxing wherever possible - */ @Suppress("UNCHECKED_CAST") - inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer { - return when (T::class) { + inline fun auto(type: KClass, size: Int, initializer: (Int) -> T): MutableBuffer { + return when (type) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer @@ -91,6 +93,17 @@ interface MutableBuffer : Buffer { else -> boxing(size, initializer) } } + + /** + * Create most appropriate mutable buffer for given type avoiding boxing wherever possible + */ + @Suppress("UNCHECKED_CAST") + inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer = + auto(T::class, size, initializer) + + val real: MutableBufferFactory = { size: Int, initializer: (Int) -> Double -> + DoubleBuffer(DoubleArray(size) { initializer(it) }) + } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 5895251e8..808f970c5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -1,5 +1,8 @@ package scientifik.kmath.structures +import kotlin.jvm.JvmName +import kotlin.reflect.KClass + interface NDStructure { @@ -40,6 +43,9 @@ interface NDStructure { inline fun auto(strides: Strides, crossinline initializer: (IntArray) -> T) = BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) + inline fun auto(type: KClass, strides: Strides, crossinline initializer: (IntArray) -> T) = + BufferNDStructure(strides, Buffer.auto(type, strides.linearSize) { i -> initializer(strides.index(i)) }) + fun build( shape: IntArray, bufferFactory: BufferFactory = Buffer.Companion::boxing, @@ -48,6 +54,13 @@ interface NDStructure { inline fun auto(shape: IntArray, crossinline initializer: (IntArray) -> T) = auto(DefaultStrides(shape), initializer) + + @JvmName("autoVarArg") + inline fun auto(vararg shape: Int, crossinline initializer: (IntArray) -> T) = + auto(DefaultStrides(shape), initializer) + + inline fun auto(type: KClass, vararg shape: Int, crossinline initializer: (IntArray) -> T) = + auto(type, DefaultStrides(shape), initializer) } } @@ -57,7 +70,7 @@ interface MutableNDStructure : NDStructure { operator fun set(index: IntArray, value: T) } -fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T) { +inline fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T) { elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } From 98bb72a6a0c6f38039f24b6d67491c6ed4cbaef0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 9 Apr 2019 20:19:16 +0300 Subject: [PATCH 04/34] removed unnecessary inlines --- .../kotlin/scientifik/kmath/linear/LUPDecomposition.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 87b17cd76..dc46216e8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -103,8 +103,8 @@ open class BufferAccessor(val type: KClass, val field: Field, val * Specialized LU operations for Doubles */ class RealBufferAccessor(rowNum: Int, colNum: Int) : BufferAccessor(Double::class, RealField, rowNum, colNum) { - override inline fun MutableBuffer.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j] - override inline fun MutableBuffer.set(i: Int, j: Int, value: Double) { + override fun MutableBuffer.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j] + override fun MutableBuffer.set(i: Int, j: Int, value: Double) { (this as DoubleBuffer).array[i + colNum * j] = value } From f1b1010c4d7a2ab630401b1cfffa6b7c12a923a0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 15 Apr 2019 20:50:50 +0300 Subject: [PATCH 05/34] Deprecated Vectors. Working on LUP optimization (not working yet) --- .../kmath/linear/LinearAlgebraBenchmark.kt | 11 +- .../kmath/linear/MultiplicationBenchmark.kt | 3 +- .../kmath/structures/ComplexNDBenchmark.kt | 2 +- build.gradle.kts | 5 +- doc/linear.md | 20 +++- .../scientifik/kmath/linear/CMMatrix.kt | 29 +---- .../scientifik/kmath/linear/CMSolver.kt | 39 +++++++ .../scientifik/kmath/linear/BufferMatrix.kt | 1 - .../scientifik/kmath/linear/FeaturedMatrix.kt | 103 ----------------- .../kmath/linear/LUPDecomposition.kt | 67 ++++++----- .../scientifik/kmath/linear/LinearAlgrebra.kt | 4 +- .../scientifik/kmath/linear/MatrixContext.kt | 106 ++++++++++++++++++ .../kotlin/scientifik/kmath/linear/Vector.kt | 70 ++---------- .../scientifik/kmath/linear/VectorSpace.kt | 75 +++++++++++++ .../scientifik/kmath/structures/NDAlgebra.kt | 3 + .../scientifik/kmath/linear/MatrixTest.kt | 9 +- .../kmath/linear/RealLUSolverTest.kt | 15 ++- .../scientifik.kmath.linear/KomaMatrix.kt | 32 ++++-- 18 files changed, 347 insertions(+), 247 deletions(-) create mode 100644 kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMSolver.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index 8816ba5cf..c2d74379e 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -1,10 +1,13 @@ package scientifik.kmath.linear import koma.matrix.ejml.EJMLMatrixFactory +import scientifik.kmath.operations.RealField import scientifik.kmath.structures.Matrix +import kotlin.contracts.ExperimentalContracts import kotlin.random.Random import kotlin.system.measureTimeMillis +@ExperimentalContracts fun main() { val random = Random(12224) val dim = 100 @@ -32,10 +35,8 @@ fun main() { //commons-math - val cmContext = CMLUPSolver - val commonsTime = measureTimeMillis { - cmContext.run { + CMMatrixContext.run { val cm = matrix.toCM() //avoid overhead on conversion repeat(n) { val res = inverse(cm) @@ -48,10 +49,8 @@ fun main() { //koma-ejml - val komaContext = KomaMatrixContext(EJMLMatrixFactory()) - val komaTime = measureTimeMillis { - komaContext.run { + KomaMatrixContext(EJMLMatrixFactory(), RealField).run { val km = matrix.toKoma() //avoid overhead on conversion repeat(n) { val res = inverse(km) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt index 7b1bd9e7e..31a890867 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt @@ -1,6 +1,7 @@ package scientifik.kmath.linear import koma.matrix.ejml.EJMLMatrixFactory +import scientifik.kmath.operations.RealField import scientifik.kmath.structures.Matrix import kotlin.random.Random import kotlin.system.measureTimeMillis @@ -27,7 +28,7 @@ fun main() { } - KomaMatrixContext(EJMLMatrixFactory()).run { + KomaMatrixContext(EJMLMatrixFactory(), RealField).run { val komaMatrix1 = matrix1.toKoma() val komaMatrix2 = matrix2.toKoma() diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt index 46b1b56dd..111b76ff0 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt @@ -23,7 +23,7 @@ fun main() { val complexTime = measureTimeMillis { complexField.run { - var res: ComplexNDElement = one + var res = one repeat(n) { res += 1.0 } diff --git a/build.gradle.kts b/build.gradle.kts index 964eb4998..3f25a622c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,6 @@ import com.moowork.gradle.node.NodeExtension import com.moowork.gradle.node.npm.NpmTask import com.moowork.gradle.node.task.NodeTask -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { val kotlinVersion: String by rootProject.extra("1.3.21") @@ -194,6 +191,8 @@ subprojects { sourceSets.all { languageSettings.progressiveMode = true languageSettings.enableLanguageFeature("InlineClasses") + languageSettings.useExperimentalAnnotation("ExperimentalContracts") + //languageSettings.enableLanguageFeature("Contracts") } } diff --git a/doc/linear.md b/doc/linear.md index 6208deaff..bbcc435ba 100644 --- a/doc/linear.md +++ b/doc/linear.md @@ -1 +1,19 @@ -**TODO** \ No newline at end of file +## Basic linear algebra layout + +Kmath support for linear algebra organized in a context-oriented way. Meaning that operations are in most cases declared +in context classes, and are not the members of classes that store data. This allows more flexible approach to maintain multiple +back-ends. The new operations added as extensions to contexts instead of being member functions of data structures. + +Two major contexts used for linear algebra and hyper-geometry: + +* `VectorSpace` forms a mathematical space on top of array-like structure (`Buffer` and its typealias `Point` used for geometry). + +* `MatrixContext` forms a space-like context for 2d-structures. It does not store matrix size and therefore does not implement +`Space` interface (it is not possible to create zero element without knowing the matrix size). + +## Vector spaces + + +## Matrix operations + +## Back-end overview \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt index 513dfe517..9103f0fb2 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt @@ -27,7 +27,7 @@ fun Matrix.toCM(): CMMatrix = if (this is CMMatrix) { CMMatrix(Array2DRowRealMatrix(array)) } -fun RealMatrix.toMatrix() = CMMatrix(this) +fun RealMatrix.asMatrix() = CMMatrix(this) class CMVector(val origin: RealVector) : Point { override val size: Int get() = origin.dimension @@ -47,7 +47,6 @@ fun Point.toCM(): CMVector = if (this is CMVector) { fun RealVector.toPoint() = CMVector(this) object CMMatrixContext : MatrixContext { - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix { val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } } return CMMatrix(Array2DRowRealMatrix(array)) @@ -59,35 +58,19 @@ object CMMatrixContext : MatrixContext { override fun Matrix.dot(vector: Point): CMVector = CMVector(this.toCM().origin.preMultiply(vector.toCM().origin)) - override fun Matrix.unaryMinus(): CMMatrix = produce(rowNum, colNum) { i, j -> -get(i, j) } - override fun Matrix.plus(b: Matrix) = - CMMatrix(this.toCM().origin.multiply(b.toCM().origin)) + override fun add(a: Matrix, b: Matrix) = + CMMatrix(a.toCM().origin.multiply(b.toCM().origin)) override fun Matrix.minus(b: Matrix) = CMMatrix(this.toCM().origin.subtract(b.toCM().origin)) - override fun Matrix.times(value: Double) = - CMMatrix(this.toCM().origin.scalarMultiply(value.toDouble())) -} + override fun multiply(a: Matrix, k: Number) = + CMMatrix(a.toCM().origin.scalarMultiply(k.toDouble())) -object CMLUPSolver: LinearSolver{ - override fun solve(a: Matrix, b: Matrix): CMMatrix { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.solve(b.toCM().origin).toMatrix() - } - - override fun solve(a: Matrix, b: Point): CMVector { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.solve(b.toCM().origin).toPoint() - } - - override fun inverse(a: Matrix): CMMatrix { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.inverse.toMatrix() - } + override fun Matrix.times(value: Double): Matrix = produce(rowNum,colNum){i,j-> get(i,j)*value} } operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMSolver.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMSolver.kt new file mode 100644 index 000000000..a56e8c93e --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMSolver.kt @@ -0,0 +1,39 @@ +package scientifik.kmath.linear + +import org.apache.commons.math3.linear.* +import scientifik.kmath.structures.Matrix + +enum class CMDecomposition { + LUP, + QR, + RRQR, + EIGEN, + CHOLESKY +} + + +fun CMMatrixContext.solver(a: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP) = + when (decomposition) { + CMDecomposition.LUP -> LUDecomposition(a.toCM().origin).solver + CMDecomposition.RRQR -> RRQRDecomposition(a.toCM().origin).solver + CMDecomposition.QR -> QRDecomposition(a.toCM().origin).solver + CMDecomposition.EIGEN -> EigenDecomposition(a.toCM().origin).solver + CMDecomposition.CHOLESKY -> CholeskyDecomposition(a.toCM().origin).solver + } + +fun CMMatrixContext.solve( + a: Matrix, + b: Matrix, + decomposition: CMDecomposition = CMDecomposition.LUP +) = solver(a, decomposition).solve(b.toCM().origin).asMatrix() + +fun CMMatrixContext.solve( + a: Matrix, + b: Point, + decomposition: CMDecomposition = CMDecomposition.LUP +) = solver(a, decomposition).solve(b.toCM().origin).toPoint() + +fun CMMatrixContext.inverse( + a: Matrix, + decomposition: CMDecomposition = CMDecomposition.LUP +) = solver(a, decomposition).inverse.asMatrix() diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index fc16c1f8e..739c170d4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -1,6 +1,5 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt index 62c360f26..8ac18ee4a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt @@ -7,109 +7,6 @@ import scientifik.kmath.structures.* import scientifik.kmath.structures.Buffer.Companion.boxing import kotlin.math.sqrt - -/** - * Basic operations on matrices. Operates on [Matrix] - */ -interface MatrixContext { - /** - * Produce a matrix with this context and given dimensions - */ - fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix - - infix fun Matrix.dot(other: Matrix): Matrix - - infix fun Matrix.dot(vector: Point): Point - - operator fun Matrix.unaryMinus(): Matrix - - operator fun Matrix.plus(b: Matrix): Matrix - - operator fun Matrix.minus(b: Matrix): Matrix - - operator fun Matrix.times(value: T): Matrix - - operator fun T.times(m: Matrix): Matrix = m * this - - companion object { - /** - * Non-boxing double matrix - */ - val real = BufferMatrixContext(RealField, Buffer.Companion::auto) - - /** - * A structured matrix with custom buffer - */ - fun > buffered( - ring: R, - bufferFactory: BufferFactory = ::boxing - ): GenericMatrixContext = - BufferMatrixContext(ring, bufferFactory) - - /** - * Automatic buffered matrix, unboxed if it is possible - */ - inline fun > auto(ring: R): GenericMatrixContext = - buffered(ring, Buffer.Companion::auto) - } -} - -interface GenericMatrixContext> : MatrixContext { - /** - * The ring context for matrix elements - */ - val elementContext: R - - /** - * Produce a point compatible with matrix space - */ - fun point(size: Int, initializer: (Int) -> T): Point - - override infix fun Matrix.dot(other: Matrix): Matrix { - //TODO add typed error - if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") - return produce(rowNum, other.colNum) { i, j -> - val row = rows[i] - val column = other.columns[j] - with(elementContext) { - sum(row.asSequence().zip(column.asSequence(), ::multiply)) - } - } - } - - override infix fun Matrix.dot(vector: Point): Point { - //TODO add typed error - if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})") - return point(rowNum) { i -> - val row = rows[i] - with(elementContext) { - sum(row.asSequence().zip(vector.asSequence(), ::multiply)) - } - } - } - - override operator fun Matrix.unaryMinus() = - produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } } - - override operator fun Matrix.plus(b: Matrix): Matrix { - if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] + [${b.rowNum},${b.colNum}]") - return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } - } - - override operator fun Matrix.minus(b: Matrix): Matrix { - if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]") - return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } - } - - operator fun Matrix.times(number: Number): Matrix = - produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } } - - operator fun Number.times(matrix: FeaturedMatrix): Matrix = matrix * this - - override fun Matrix.times(value: T): Matrix = - produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } } -} - /** * A 2d structure plus optional matrix-specific features */ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index dc46216e8..4bc463333 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -4,6 +4,9 @@ import scientifik.kmath.operations.Field import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.reflect.KClass /** @@ -63,7 +66,12 @@ class LUPDecomposition( } -open class BufferAccessor(val type: KClass, val field: Field, val rowNum: Int, val colNum: Int) { +internal open class BufferAccessor( + val type: KClass, + val field: Field, + val rowNum: Int, + val colNum: Int +) { open operator fun MutableBuffer.get(i: Int, j: Int) = get(i + colNum * j) open operator fun MutableBuffer.set(i: Int, j: Int, value: T) { set(i + colNum * j, value) @@ -102,7 +110,8 @@ open class BufferAccessor(val type: KClass, val field: Field, val /** * Specialized LU operations for Doubles */ -class RealBufferAccessor(rowNum: Int, colNum: Int) : BufferAccessor(Double::class, RealField, rowNum, colNum) { +private class RealBufferAccessor(rowNum: Int, colNum: Int) : + BufferAccessor(Double::class, RealField, rowNum, colNum) { override fun MutableBuffer.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j] override fun MutableBuffer.set(i: Int, j: Int, value: Double) { (this as DoubleBuffer).array[i + colNum * j] = value @@ -125,24 +134,33 @@ class RealBufferAccessor(rowNum: Int, colNum: Int) : BufferAccessor(Doub } } -fun , F : Field> GenericMatrixContext.buildAccessor( - type:KClass, +@ExperimentalContracts +private inline fun , F : Field> GenericMatrixContext.withAccessor( + type: KClass, rowNum: Int, - colNum: Int -): BufferAccessor { - return if (elementContext == RealField) { + colNum: Int, + block: BufferAccessor.() -> Unit +) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + if (elementContext == RealField) { @Suppress("UNCHECKED_CAST") RealBufferAccessor(rowNum, colNum) as BufferAccessor } else { BufferAccessor(type, elementContext, rowNum, colNum) - } + }.run(block) } -fun , F : Field> GenericMatrixContext.abs(value: T) = +private fun , F : Field> GenericMatrixContext.abs(value: T) = if (value > elementContext.zero) value else with(elementContext) { -value } -fun , F : Field> GenericMatrixContext.lupDecompose( +/** + * Create a lup decomposition of generic matrix + */ +@ExperimentalContracts +fun , F : Field> GenericMatrixContext.lup( type: KClass, matrix: Matrix, checkSingular: (T) -> Boolean @@ -155,7 +173,7 @@ fun , F : Field> GenericMatrixContext.lupDecompose( val m = matrix.colNum val pivot = IntArray(matrix.rowNum) - buildAccessor(type, matrix.rowNum, matrix.colNum).run { + withAccessor(type, matrix.rowNum, matrix.colNum) { val lu = create(matrix) @@ -170,21 +188,12 @@ fun , F : Field> GenericMatrixContext.lupDecompose( // upper for (row in 0 until col) { -// var sum = lu[row, col] -// for (i in 0 until row) { -// sum -= lu[row, i] * lu[i, col] -// } val sum = lu.innerProduct(row, col, row) lu[row, col] = field.run { lu[row, col] - sum } } // lower val max = (col until m).maxBy { row -> - // var sum = lu[row, col] -// for (i in 0 until col) { -// sum -= lu[row, i] * lu[i, col] -// } -// lu[row, col] = sum val sum = lu.innerProduct(row, col, col) lu[row, col] = field.run { lu[row, col] - sum } abs(sum) @@ -214,14 +223,17 @@ fun , F : Field> GenericMatrixContext.lupDecompose( //lu[row, col] = lu[row, col] / luDiag } } - return scientifik.kmath.linear.LUPDecomposition(elementContext, lu.collect(), pivot, even) - + return LUPDecomposition(elementContext, lu.collect(), pivot, even) } } +@ExperimentalContracts +fun GenericMatrixContext.lup(matrix: Matrix) = lup(Double::class, matrix) { it < 1e-11 } + /** * Solve a linear equation **a*x = b** */ +@ExperimentalContracts fun , F : Field> GenericMatrixContext.solve( type: KClass, a: Matrix, @@ -233,9 +245,9 @@ fun , F : Field> GenericMatrixContext.solve( } // Use existing decomposition if it is provided by matrix - val decomposition = a.getFeature() ?: lupDecompose(type, a, checkSingular) + val decomposition = a.getFeature() ?: lup(type, a, checkSingular) - buildAccessor(type, a.rowNum, a.colNum).run { + withAccessor(type, a.rowNum, a.colNum) { val lu = create(decomposition.lu) @@ -271,14 +283,19 @@ fun , F : Field> GenericMatrixContext.solve( return produce(a.rowNum, a.colNum) { i, j -> bp[i, j] } } - } +@ExperimentalContracts +fun GenericMatrixContext.solve(a: Matrix, b: Matrix) = + solve(Double::class, a, b) { it < 1e-11 } + +@ExperimentalContracts inline fun , F : Field> GenericMatrixContext.inverse( matrix: Matrix, noinline checkSingular: (T) -> Boolean ) = solve(T::class, matrix, one(matrix.rowNum, matrix.colNum), checkSingular) +@ExperimentalContracts fun GenericMatrixContext.inverse(matrix: Matrix) = inverse(matrix) { it < 1e-11 } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt index 4254fd7ea..7e631f5fc 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -13,7 +13,7 @@ import scientifik.kmath.structures.asSequence */ interface LinearSolver { fun solve(a: Matrix, b: Matrix): Matrix - fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).asPoint() + fun solve(a: Matrix, b: Point): Point = solve(a, b.asMatrix()).asPoint() fun inverse(a: Matrix): Matrix } @@ -43,4 +43,4 @@ fun Matrix.asPoint(): Point = error("Can't convert matrix with more than one column to vector") } -fun Point.toMatrix() = VirtualMatrix(size, 1) { i, _ -> get(i) } \ No newline at end of file +fun Point.asMatrix() = VirtualMatrix(size, 1) { i, _ -> get(i) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt new file mode 100644 index 000000000..f97c55cc4 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt @@ -0,0 +1,106 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.SpaceOperations +import scientifik.kmath.operations.sum +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.asSequence + +/** + * Basic operations on matrices. Operates on [Matrix] + */ +interface MatrixContext : SpaceOperations> { + /** + * Produce a matrix with this context and given dimensions + */ + fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix + + infix fun Matrix.dot(other: Matrix): Matrix + + infix fun Matrix.dot(vector: Point): Point + + operator fun Matrix.times(value: T): Matrix + + operator fun T.times(m: Matrix): Matrix = m * this + + companion object { + /** + * Non-boxing double matrix + */ + val real = BufferMatrixContext(RealField, Buffer.Companion::auto) + + /** + * A structured matrix with custom buffer + */ + fun > buffered( + ring: R, + bufferFactory: BufferFactory = Buffer.Companion::boxing + ): GenericMatrixContext = + BufferMatrixContext(ring, bufferFactory) + + /** + * Automatic buffered matrix, unboxed if it is possible + */ + inline fun > auto(ring: R): GenericMatrixContext = + buffered(ring, Buffer.Companion::auto) + } +} + +interface GenericMatrixContext> : MatrixContext { + /** + * The ring context for matrix elements + */ + val elementContext: R + + /** + * Produce a point compatible with matrix space + */ + fun point(size: Int, initializer: (Int) -> T): Point + + override infix fun Matrix.dot(other: Matrix): Matrix { + //TODO add typed error + if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") + return produce(rowNum, other.colNum) { i, j -> + val row = rows[i] + val column = other.columns[j] + with(elementContext) { + sum(row.asSequence().zip(column.asSequence(), ::multiply)) + } + } + } + + override infix fun Matrix.dot(vector: Point): Point { + //TODO add typed error + if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})") + return point(rowNum) { i -> + val row = rows[i] + with(elementContext) { + sum(row.asSequence().zip(vector.asSequence(), ::multiply)) + } + } + } + + override operator fun Matrix.unaryMinus() = + produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } } + + override fun add(a: Matrix, b: Matrix): Matrix { + if (a.rowNum != b.rowNum || a.colNum != b.colNum) error("Matrix operation dimension mismatch. [${a.rowNum},${a.colNum}] + [${b.rowNum},${b.colNum}]") + return produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a.get(i, j) + b[i, j] } } + } + + override operator fun Matrix.minus(b: Matrix): Matrix { + if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]") + return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } + } + + override fun multiply(a: Matrix, k: Number): Matrix = + produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a.get(i, j) * k } } + + operator fun Number.times(matrix: FeaturedMatrix): Matrix = matrix * this + + override fun Matrix.times(value: T): Matrix = + produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index d74d0ed4f..18a0021c3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -4,68 +4,23 @@ import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.BufferFactory import scientifik.kmath.structures.asSequence +import kotlin.jvm.JvmName typealias Point = Buffer -/** - * A linear space for vectors. - * Could be used on any point-like structure - */ -interface VectorSpace> : Space> { - val size: Int - - val space: S - - fun produce(initializer: (Int) -> T): Point - - /** - * Produce a space-element of this vector space for expressions - */ - fun produceElement(initializer: (Int) -> T): Vector - - override val zero: Point get() = produce { space.zero } - - override fun add(a: Point, b: Point): Point = produce { with(space) { a[it] + b[it] } } - - override fun multiply(a: Point, k: Number): Point = produce { with(space) { a[it] * k } } - - //TODO add basis - - companion object { - - private val realSpaceCache = HashMap>() - - /** - * Non-boxing double vector space - */ - fun real(size: Int): BufferVectorSpace { - return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, RealField, Buffer.Companion::auto) } - } - - /** - * A structured vector space with custom buffer - */ - fun > buffered( - size: Int, - space: S, - bufferFactory: BufferFactory = Buffer.Companion::boxing - ): VectorSpace = BufferVectorSpace(size, space, bufferFactory) - - /** - * Automatic buffered vector, unboxed if it is possible - */ - inline fun > smart(size: Int, space: S): VectorSpace = - buffered(size, space, Buffer.Companion::auto) - } -} +fun > BufferVectorSpace.produceElement(initializer: (Int) -> T): Vector = + BufferVector(this, produce(initializer)) +@JvmName("produceRealElement") +fun BufferVectorSpace.produceElement(initializer: (Int) -> Double): Vector = + BufferVector(this, produce(initializer)) /** * A point coupled to the linear space */ +@Deprecated("Use VectorContext instead") interface Vector> : SpaceElement, Vector, VectorSpace>, Point { override val size: Int get() = context.size @@ -90,16 +45,7 @@ interface Vector> : SpaceElement, Vector, V } } -data class BufferVectorSpace>( - override val size: Int, - override val space: S, - val bufferFactory: BufferFactory -) : VectorSpace { - override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer) - override fun produceElement(initializer: (Int) -> T): Vector = BufferVector(this, produce(initializer)) -} - - +@Deprecated("Use VectorContext instead") data class BufferVector>(override val context: VectorSpace, val buffer: Buffer) : Vector { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt new file mode 100644 index 000000000..8e14e2882 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VectorSpace.kt @@ -0,0 +1,75 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Space +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory + +/** + * A linear space for vectors. + * Could be used on any point-like structure + */ +interface VectorSpace> : Space> { + + val size: Int + + val space: S + + fun produce(initializer: (Int) -> T): Point + + /** + * Produce a space-element of this vector space for expressions + */ + //fun produceElement(initializer: (Int) -> T): Vector + + override val zero: Point get() = produce { space.zero } + + override fun add(a: Point, b: Point): Point = produce { with(space) { a[it] + b[it] } } + + override fun multiply(a: Point, k: Number): Point = produce { with(space) { a[it] * k } } + + //TODO add basis + + companion object { + + private val realSpaceCache = HashMap>() + + /** + * Non-boxing double vector space + */ + fun real(size: Int): BufferVectorSpace { + return realSpaceCache.getOrPut(size) { + BufferVectorSpace( + size, + RealField, + Buffer.Companion::auto + ) + } + } + + /** + * A structured vector space with custom buffer + */ + fun > buffered( + size: Int, + space: S, + bufferFactory: BufferFactory = Buffer.Companion::boxing + ) = BufferVectorSpace(size, space, bufferFactory) + + /** + * Automatic buffered vector, unboxed if it is possible + */ + inline fun > auto(size: Int, space: S): VectorSpace = + buffered(size, space, Buffer.Companion::auto) + } +} + + +class BufferVectorSpace>( + override val size: Int, + override val space: S, + val bufferFactory: BufferFactory +) : VectorSpace { + override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer) + //override fun produceElement(initializer: (Int) -> T): Vector = BufferVector(this, produce(initializer)) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 887a480bb..c6f0f7a48 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -73,6 +73,7 @@ interface NDSpace, N : NDStructure> : Space, NDAlgebra add(arg, value) } operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) } @@ -90,6 +91,7 @@ interface NDRing, N : NDStructure> : Ring, NDSpace */ override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } + //TODO move to extensions after KEEP-176 operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) } operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) } } @@ -109,6 +111,7 @@ interface NDField, N : NDStructure> : Field, NDRing divide(aValue, bValue) } + //TODO move to extensions after KEEP-176 operator fun N.div(arg: T) = map(this) { value -> divide(arg, value) } operator fun T.div(arg: N) = map(arg) { divide(it, this@div) } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 8d09b6d7b..f7d603dab 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -1,5 +1,6 @@ package scientifik.kmath.linear +import scientifik.kmath.structures.Matrix import kotlin.test.Test import kotlin.test.assertEquals @@ -16,7 +17,7 @@ class MatrixTest { @Test fun testVectorToMatrix() { val vector = Vector.real(5) { it.toDouble() } - val matrix = vector.toMatrix() + val matrix = vector.asMatrix() assertEquals(4.0, matrix[4, 0]) } @@ -33,8 +34,8 @@ class MatrixTest { val vector1 = Vector.real(5) { it.toDouble() } val vector2 = Vector.real(5) { 5 - it.toDouble() } - val matrix1 = vector1.toMatrix() - val matrix2 = vector2.toMatrix().transpose() + val matrix1 = vector1.asMatrix() + val matrix2 = vector2.asMatrix().transpose() val product = MatrixContext.real.run { matrix1 dot matrix2 } @@ -44,7 +45,7 @@ class MatrixTest { @Test fun testBuilder() { - val matrix = FeaturedMatrix.build(2, 3)( + val matrix = Matrix.build(2, 3)( 1.0, 0.0, 0.0, 0.0, 1.0, 2.0 ) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 8887d3a32..89f08ae5a 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -1,25 +1,28 @@ package scientifik.kmath.linear +import scientifik.kmath.structures.Matrix +import kotlin.contracts.ExperimentalContracts import kotlin.test.Test import kotlin.test.assertEquals +@ExperimentalContracts class RealLUSolverTest { + @Test fun testInvertOne() { val matrix = MatrixContext.real.one(2, 2) - val inverted = LUSolver.real.inverse(matrix) + val inverted = MatrixContext.real.inverse(matrix) assertEquals(matrix, inverted) } @Test fun testInvert() { - val matrix = FeaturedMatrix.square( + val matrix = Matrix.square( 3.0, 1.0, 1.0, 3.0 ) - val decomposed = LUSolver.real.decompose(matrix) - val decomposition = decomposed.getFeature>()!! + val decomposition = MatrixContext.real.lup(matrix) //Check determinant assertEquals(8.0, decomposition.determinant) @@ -29,9 +32,9 @@ class RealLUSolverTest { assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u) } - val inverted = LUSolver.real.inverse(decomposed) + val inverted = MatrixContext.real.inverse(matrix) - val expected = FeaturedMatrix.square( + val expected = Matrix.square( 0.375, -0.125, -0.125, 0.375 ) diff --git a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt index b95f04887..74681ac48 100644 --- a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt +++ b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt @@ -2,10 +2,14 @@ package scientifik.kmath.linear import koma.extensions.fill import koma.matrix.MatrixFactory +import scientifik.kmath.operations.Space import scientifik.kmath.structures.Matrix -class KomaMatrixContext(val factory: MatrixFactory>) : MatrixContext, - LinearSolver { +class KomaMatrixContext( + private val factory: MatrixFactory>, + private val space: Space +) : + MatrixContext { override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T) = KomaMatrix(factory.zeros(rows, columns).fill(initializer)) @@ -32,28 +36,38 @@ class KomaMatrixContext(val factory: MatrixFactory.unaryMinus() = KomaMatrix(this.toKoma().origin.unaryMinus()) - override fun Matrix.plus(b: Matrix) = - KomaMatrix(this.toKoma().origin + b.toKoma().origin) + override fun add(a: Matrix, b: Matrix) = + KomaMatrix(a.toKoma().origin + b.toKoma().origin) override fun Matrix.minus(b: Matrix) = KomaMatrix(this.toKoma().origin - b.toKoma().origin) + override fun multiply(a: Matrix, k: Number): Matrix = + produce(a.rowNum, a.colNum) { i, j -> space.run { a[i, j] * k } } + override fun Matrix.times(value: T) = KomaMatrix(this.toKoma().origin * value) + companion object { - override fun solve(a: Matrix, b: Matrix) = - KomaMatrix(a.toKoma().origin.solve(b.toKoma().origin)) + } - override fun inverse(a: Matrix) = - KomaMatrix(a.toKoma().origin.inv()) } +fun KomaMatrixContext.solve(a: Matrix, b: Matrix) = + KomaMatrix(a.toKoma().origin.solve(b.toKoma().origin)) + +fun KomaMatrixContext.solve(a: Matrix, b: Point) = + KomaVector(a.toKoma().origin.solve(b.toKoma().origin)) + +fun KomaMatrixContext.inverse(a: Matrix) = + KomaMatrix(a.toKoma().origin.inv()) + class KomaMatrix(val origin: koma.matrix.Matrix, features: Set? = null) : FeaturedMatrix { override val rowNum: Int get() = origin.numRows() override val colNum: Int get() = origin.numCols() - override val shape: IntArray get() = intArrayOf(origin.numRows(),origin.numCols()) + override val shape: IntArray get() = intArrayOf(origin.numRows(), origin.numCols()) override val features: Set = features ?: setOf( object : DeterminantFeature { From bbc012d8cd120b33b8a0b7ba54ae7f92a2ca67cd Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 20 Apr 2019 11:43:30 +0300 Subject: [PATCH 06/34] Optimizing decomposition performance --- .../kmath/linear/LinearAlgebraBenchmark.kt | 2 +- build.gradle.kts | 5 +- .../scientifik/kmath/linear/BufferMatrix.kt | 13 + .../kmath/linear/LUPDecomposition.kt | 335 ++++++++---------- .../scientifik/kmath/linear/MatrixContext.kt | 3 +- .../kmath/structures/BufferAccessor2D.kt | 45 +++ .../scientifik/kmath/structures/Buffers.kt | 2 +- 7 files changed, 205 insertions(+), 200 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index c2d74379e..c397e9bd0 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -9,7 +9,7 @@ import kotlin.system.measureTimeMillis @ExperimentalContracts fun main() { - val random = Random(12224) + val random = Random(1224) val dim = 100 //creating invertible matrix val u = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } diff --git a/build.gradle.kts b/build.gradle.kts index 3f25a622c..bff826b37 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,12 @@ import com.moowork.gradle.node.NodeExtension import com.moowork.gradle.node.npm.NpmTask import com.moowork.gradle.node.task.NodeTask +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { - val kotlinVersion: String by rootProject.extra("1.3.21") + val kotlinVersion: String by rootProject.extra("1.3.30") val ioVersion: String by rootProject.extra("0.1.5") val coroutinesVersion: String by rootProject.extra("1.1.1") val atomicfuVersion: String by rootProject.extra("0.12.1") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index 739c170d4..34d9a6883 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -1,5 +1,6 @@ package scientifik.kmath.linear +import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* @@ -23,6 +24,18 @@ class BufferMatrixContext>( } } +object RealMatrixContext : GenericMatrixContext { + + override val elementContext = RealField + + override inline fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): Matrix { + val buffer = DoubleBuffer(rows * columns) { offset -> initializer(offset / columns, offset % columns) } + return BufferMatrix(rows, columns, buffer) + } + + override inline fun point(size: Int, initializer: (Int) -> Double): Point = DoubleBuffer(size,initializer) +} + class BufferMatrix( override val rowNum: Int, override val colNum: Int, diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 4bc463333..9c4382e38 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -3,22 +3,22 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring -import scientifik.kmath.structures.* -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -import kotlin.reflect.KClass +import scientifik.kmath.structures.BufferAccessor2D +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.Structure2D /** * Common implementation of [LUPDecompositionFeature] */ class LUPDecomposition( - private val elementContext: Ring, + val context: GenericMatrixContext>, val lu: Structure2D, val pivot: IntArray, private val even: Boolean ) : LUPDecompositionFeature, DeterminantFeature { + val elementContext get() = context.elementContext + /** * Returns the matrix L of the decomposition. * @@ -66,102 +66,14 @@ class LUPDecomposition( } -internal open class BufferAccessor( - val type: KClass, - val field: Field, - val rowNum: Int, - val colNum: Int -) { - open operator fun MutableBuffer.get(i: Int, j: Int) = get(i + colNum * j) - open operator fun MutableBuffer.set(i: Int, j: Int, value: T) { - set(i + colNum * j, value) - } - - fun create(init: (i: Int, j: Int) -> T) = - MutableBuffer.auto(type, rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } - - fun create(mat: Structure2D) = create { i, j -> mat[i, j] } - - //TODO optimize wrapper - fun MutableBuffer.collect(): Structure2D = - NDStructure.auto(type, rowNum, colNum) { (i, j) -> get(i, j) }.as2D() - - open fun MutableBuffer.innerProduct(row: Int, col: Int, max: Int): T { - var sum = field.zero - field.run { - for (i in 0 until max) { - sum += get(row, i) * get(i, col) - } - } - return sum - } - - open fun MutableBuffer.divideInPlace(i: Int, j: Int, factor: T) { - field.run { set(i, j, get(i, j) / factor) } - } - - open fun MutableBuffer.subtractInPlace(i: Int, j: Int, lu: MutableBuffer, col: Int) { - field.run { - set(i, j, get(i, j) - get(col, j) * lu[i, col]) - } - } -} - -/** - * Specialized LU operations for Doubles - */ -private class RealBufferAccessor(rowNum: Int, colNum: Int) : - BufferAccessor(Double::class, RealField, rowNum, colNum) { - override fun MutableBuffer.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j] - override fun MutableBuffer.set(i: Int, j: Int, value: Double) { - (this as DoubleBuffer).array[i + colNum * j] = value - } - - override fun MutableBuffer.innerProduct(row: Int, col: Int, max: Int): Double { - var sum = 0.0 - for (i in 0 until max) { - sum += get(row, i) * get(i, col) - } - return sum - } - - override fun MutableBuffer.divideInPlace(i: Int, j: Int, factor: Double) { - set(i, j, get(i, j) / factor) - } - - override fun MutableBuffer.subtractInPlace(i: Int, j: Int, lu: MutableBuffer, col: Int) { - set(i, j, get(i, j) - get(col, j) * lu[i, col]) - } -} - -@ExperimentalContracts -private inline fun , F : Field> GenericMatrixContext.withAccessor( - type: KClass, - rowNum: Int, - colNum: Int, - block: BufferAccessor.() -> Unit -) { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - if (elementContext == RealField) { - @Suppress("UNCHECKED_CAST") - RealBufferAccessor(rowNum, colNum) as BufferAccessor - } else { - BufferAccessor(type, elementContext, rowNum, colNum) - }.run(block) -} - -private fun , F : Field> GenericMatrixContext.abs(value: T) = +fun , F : Field> GenericMatrixContext.abs(value: T) = if (value > elementContext.zero) value else with(elementContext) { -value } /** * Create a lup decomposition of generic matrix */ -@ExperimentalContracts -fun , F : Field> GenericMatrixContext.lup( - type: KClass, +inline fun , F : Field> GenericMatrixContext.lup( matrix: Matrix, checkSingular: (T) -> Boolean ): LUPDecomposition { @@ -169,133 +81,166 @@ fun , F : Field> GenericMatrixContext.lup( error("LU decomposition supports only square matrices") } - val m = matrix.colNum val pivot = IntArray(matrix.rowNum) - withAccessor(type, matrix.rowNum, matrix.colNum) { + //TODO just waits for KEEP-176 + BufferAccessor2D(T::class, matrix.rowNum, matrix.colNum).run { + elementContext.run { - val lu = create(matrix) + val lu = create(matrix) - // Initialize permutation array and parity - for (row in 0 until m) { - pivot[row] = row - } - var even = true - - // Loop over columns - for (col in 0 until m) { - - // upper - for (row in 0 until col) { - val sum = lu.innerProduct(row, col, row) - lu[row, col] = field.run { lu[row, col] - sum } + // Initialize permutation array and parity + for (row in 0 until m) { + pivot[row] = row } + var even = true - // lower - val max = (col until m).maxBy { row -> - val sum = lu.innerProduct(row, col, col) - lu[row, col] = field.run { lu[row, col] - sum } - abs(sum) - } ?: col - - // Singularity check - if (checkSingular(lu[max, col])) { - error("Singular matrix") + // Initialize permutation array and parity + for (row in 0 until m) { + pivot[row] = row } + var singular = false - // Pivot if necessary - if (max != col) { - for (i in 0 until m) { - lu[max, i] = lu[col, i] - lu[col, i] = lu[max, i] + // Loop over columns + for (col in 0 until m) { + + // upper + for (row in 0 until col) { + val luRow = lu.row(row) + var sum = luRow[col] + for (i in 0 until row) { + sum -= luRow[i] * lu[i, col] + } + luRow[col] = sum + } + + // lower + var max = col // permutation row + var largest = -one + for (row in col until m) { + val luRow = lu.row(row) + var sum = luRow[col] + for (i in 0 until col) { + sum -= luRow[i] * lu[i, col] + } + luRow[col] = sum + + // maintain best permutation choice + if (abs(sum) > largest) { + largest = abs(sum) + max = row + } + } + + // Singularity check + if (checkSingular(abs(lu[max, col]))) { + error("The matrix is singular") + } + + // Pivot if necessary + if (max != col) { + val luMax = lu.row(max) + val luCol = lu.row(col) + for (i in 0 until m) { + val tmp = luMax[i] + luMax[i] = luCol[i] + luCol[i] = tmp + } + val temp = pivot[max] + pivot[max] = pivot[col] + pivot[col] = temp + even = !even + } + + // Divide the lower elements by the "winning" diagonal elt. + val luDiag = lu[col, col] + for (row in col + 1 until m) { + lu[row, col] /= luDiag } - val temp = pivot[max] - pivot[max] = pivot[col] - pivot[col] = temp - even = !even } - // Divide the lower elements by the "winning" diagonal elt. - val luDiag = lu[col, col] - for (row in col + 1 until m) { - lu.divideInPlace(row, col, luDiag) - //lu[row, col] = lu[row, col] / luDiag - } + return LUPDecomposition(this@lup, lu.collect(), pivot, even) + } + } +} + +fun GenericMatrixContext.lup(matrix: Matrix) = lup(matrix) { it < 1e-11 } + +inline fun LUPDecomposition.solve(matrix: Matrix): Matrix { + + if (matrix.rowNum != pivot.size) { + error("Matrix dimension mismatch. Expected ${pivot.size}, but got ${matrix.colNum}") + } + + BufferAccessor2D(T::class, matrix.rowNum, matrix.colNum).run { + elementContext.run { + + val lu = create{i,j-> this@solve.lu[i,j]} + + // Apply permutations to b + val bp = create { i, j -> zero } + for (row in 0 until pivot.size) { + val bpRow = bp.row(row) + val pRow = pivot[row] + for (col in 0 until matrix.colNum) { + bpRow[col] = matrix[pRow, col] + } + } + + // Solve LY = b + for (col in 0 until pivot.size) { + val bpCol = bp.row(col) + for (i in col + 1 until pivot.size) { + val bpI = bp.row(i) + val luICol = lu[i, col] + for (j in 0 until matrix.colNum) { + bpI[j] -= bpCol[j] * luICol + } + } + } + + // Solve UX = Y + for (col in pivot.size - 1 downTo 0) { + val bpCol = bp.row(col) + val luDiag = lu[col, col] + for (j in 0 until matrix.colNum) { + bpCol[j] /= luDiag + } + for (i in 0 until col) { + val bpI = bp.row(i) + val luICol = lu[i, col] + for (j in 0 until matrix.colNum) { + bpI[j] -= bpCol[j] * luICol + } + } + } + return context.produce(pivot.size, matrix.colNum) { i, j -> bp[i, j] } } - return LUPDecomposition(elementContext, lu.collect(), pivot, even) } } -@ExperimentalContracts -fun GenericMatrixContext.lup(matrix: Matrix) = lup(Double::class, matrix) { it < 1e-11 } /** * Solve a linear equation **a*x = b** */ -@ExperimentalContracts -fun , F : Field> GenericMatrixContext.solve( - type: KClass, +inline fun , F : Field> GenericMatrixContext.solve( a: Matrix, b: Matrix, - checkSingular: (T) -> Boolean + crossinline checkSingular: (T) -> Boolean ): Matrix { - if (b.rowNum != a.colNum) { - error("Matrix dimension mismatch. Expected ${a.rowNum}, but got ${b.colNum}") - } - // Use existing decomposition if it is provided by matrix - val decomposition = a.getFeature() ?: lup(type, a, checkSingular) - - withAccessor(type, a.rowNum, a.colNum) { - - val lu = create(decomposition.lu) - - // Apply permutations to b - val bp = create { i, j -> - b[decomposition.pivot[i], j] - } - - // Solve LY = b - for (col in 0 until a.rowNum) { - for (i in col + 1 until a.rowNum) { - for (j in 0 until b.colNum) { - bp.subtractInPlace(i, j, lu, col) - //bp[i, j] -= bp[col, j] * lu[i, col] - } - } - } - - // Solve UX = Y - for (col in a.rowNum - 1 downTo 0) { - val luDiag = lu[col, col] - for (j in 0 until b.colNum) { - bp.divideInPlace(col, j, luDiag) - //bp[col, j] /= lu[col, col] - } - for (i in 0 until col) { - for (j in 0 until b.colNum) { - bp.subtractInPlace(i, j, lu, col) - //bp[i, j] -= bp[col, j] * lu[i, col] - } - } - } - - return produce(a.rowNum, a.colNum) { i, j -> bp[i, j] } - } + val decomposition = a.getFeature() ?: lup(a, checkSingular) + return decomposition.solve(b) } -@ExperimentalContracts -fun GenericMatrixContext.solve(a: Matrix, b: Matrix) = - solve(Double::class, a, b) { it < 1e-11 } +fun RealMatrixContext.solve(a: Matrix, b: Matrix) = + solve(a, b) { it < 1e-11 } -@ExperimentalContracts inline fun , F : Field> GenericMatrixContext.inverse( matrix: Matrix, noinline checkSingular: (T) -> Boolean -) = - solve(T::class, matrix, one(matrix.rowNum, matrix.colNum), checkSingular) +) = solve(matrix, one(matrix.rowNum, matrix.colNum), checkSingular) -@ExperimentalContracts -fun GenericMatrixContext.inverse(matrix: Matrix) = - inverse(matrix) { it < 1e-11 } \ No newline at end of file +fun RealMatrixContext.inverse(matrix: Matrix) = + solve(matrix, one(matrix.rowNum, matrix.colNum)) { it < 1e-11 } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt index f97c55cc4..7797fdadf 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixContext.kt @@ -1,6 +1,5 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.operations.SpaceOperations import scientifik.kmath.operations.sum @@ -30,7 +29,7 @@ interface MatrixContext : SpaceOperations> { /** * Non-boxing double matrix */ - val real = BufferMatrixContext(RealField, Buffer.Companion::auto) + val real = RealMatrixContext /** * A structured matrix with custom buffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt new file mode 100644 index 000000000..ac0727f7c --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt @@ -0,0 +1,45 @@ +package scientifik.kmath.structures + +import kotlin.reflect.KClass + +/** + * A context that allows to operate on a [MutableBuffer] as on 2d array + */ +class BufferAccessor2D(val type: KClass, val rowNum: Int, val colNum: Int) { + + inline operator fun Buffer.get(i: Int, j: Int) = get(i + colNum * j) + + inline operator fun MutableBuffer.set(i: Int, j: Int, value: T) { + set(i + colNum * j, value) + } + + inline fun create(init: (i: Int, j: Int) -> T) = + MutableBuffer.auto(type, rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } + + fun create(mat: Structure2D) = create { i, j -> mat[i, j] } + + //TODO optimize wrapper + fun MutableBuffer.collect(): Structure2D = + NDStructure.auto(type, rowNum, colNum) { (i, j) -> get(i, j) }.as2D() + + + inner class Row(val buffer: MutableBuffer, val rowIndex: Int) : MutableBuffer { + override val size: Int get() = colNum + + override fun get(index: Int): T = buffer[rowIndex, index] + + override fun set(index: Int, value: T) { + buffer[rowIndex, index] = value + } + + override fun copy(): MutableBuffer = MutableBuffer.auto(type, colNum) { get(it) } + + override fun iterator(): Iterator = (0 until colNum).map(::get).iterator() + + } + + /** + * Get row + */ + fun MutableBuffer.row(i: Int) = Row(this, i) +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 51970ebae..c8d091017 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -84,7 +84,7 @@ interface MutableBuffer : Buffer { MutableListBuffer(MutableList(size, initializer)) @Suppress("UNCHECKED_CAST") - inline fun auto(type: KClass, size: Int, initializer: (Int) -> T): MutableBuffer { + inline fun auto(type: KClass, size: Int, initializer: (Int) -> T): MutableBuffer { return when (type) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as MutableBuffer From a9ed3fb72e9cdf174b422daad6e39bd69389a7d8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 21 Apr 2019 16:17:30 +0300 Subject: [PATCH 07/34] LU decomposition and inversion working --- build.gradle.kts | 2 +- .../scientifik/kmath/linear/FeaturedMatrix.kt | 9 +- .../kmath/linear/LUPDecomposition.kt | 28 ++-- .../scientifik/kmath/linear/VirtualMatrix.kt | 7 + .../kmath/operations/NumberAlgebra.kt | 132 ++++++++++++++---- .../kmath/linear/RealLUSolverTest.kt | 21 ++- kmath-sequential/build.gradle.kts | 25 +--- 7 files changed, 150 insertions(+), 74 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bff826b37..f7b63a680 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { val kotlinVersion: String by rootProject.extra("1.3.30") val ioVersion: String by rootProject.extra("0.1.5") - val coroutinesVersion: String by rootProject.extra("1.1.1") + val coroutinesVersion: String by rootProject.extra("1.2.0") val atomicfuVersion: String by rootProject.extra("0.12.1") val dokkaVersion: String by rootProject.extra("0.9.17") val serializationVersion: String by rootProject.extra("0.10.0") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt index 8ac18ee4a..0886b3ce5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt @@ -1,10 +1,9 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring -import scientifik.kmath.operations.sum -import scientifik.kmath.structures.* -import scientifik.kmath.structures.Buffer.Companion.boxing +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.Structure2D +import scientifik.kmath.structures.asBuffer import kotlin.math.sqrt /** @@ -70,7 +69,7 @@ inline fun Matrix<*>.getFeature(): T? = * Diagonal matrix of ones. The matrix is virtual no actual matrix is created */ fun > GenericMatrixContext.one(rows: Int, columns: Int): FeaturedMatrix = - VirtualMatrix(rows, columns) { i, j -> + VirtualMatrix(rows, columns, DiagonalFeature) { i, j -> if (i == j) elementContext.one else elementContext.zero } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 9c4382e38..910ed4551 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -6,6 +6,7 @@ import scientifik.kmath.operations.Ring import scientifik.kmath.structures.BufferAccessor2D import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.Structure2D +import kotlin.reflect.KClass /** * Common implementation of [LUPDecompositionFeature] @@ -73,7 +74,8 @@ fun , F : Field> GenericMatrixContext.abs(value: T) = /** * Create a lup decomposition of generic matrix */ -inline fun , F : Field> GenericMatrixContext.lup( +fun , F : Field> GenericMatrixContext.lup( + type: KClass, matrix: Matrix, checkSingular: (T) -> Boolean ): LUPDecomposition { @@ -85,7 +87,7 @@ inline fun , F : Field> GenericMatrixContext. val pivot = IntArray(matrix.rowNum) //TODO just waits for KEEP-176 - BufferAccessor2D(T::class, matrix.rowNum, matrix.colNum).run { + BufferAccessor2D(type, matrix.rowNum, matrix.colNum).run { elementContext.run { val lu = create(matrix) @@ -100,7 +102,6 @@ inline fun , F : Field> GenericMatrixContext. for (row in 0 until m) { pivot[row] = row } - var singular = false // Loop over columns for (col in 0 until m) { @@ -165,21 +166,25 @@ inline fun , F : Field> GenericMatrixContext. } } -fun GenericMatrixContext.lup(matrix: Matrix) = lup(matrix) { it < 1e-11 } +inline fun , F : Field> GenericMatrixContext.lup( + matrix: Matrix, + noinline checkSingular: (T) -> Boolean +) = lup(T::class, matrix, checkSingular) -inline fun LUPDecomposition.solve(matrix: Matrix): Matrix { +fun GenericMatrixContext.lup(matrix: Matrix) = lup(Double::class, matrix) { it < 1e-11 } + +fun LUPDecomposition.solve(type: KClass, matrix: Matrix): Matrix { if (matrix.rowNum != pivot.size) { error("Matrix dimension mismatch. Expected ${pivot.size}, but got ${matrix.colNum}") } - BufferAccessor2D(T::class, matrix.rowNum, matrix.colNum).run { + BufferAccessor2D(type, matrix.rowNum, matrix.colNum).run { elementContext.run { - val lu = create{i,j-> this@solve.lu[i,j]} - // Apply permutations to b val bp = create { i, j -> zero } + for (row in 0 until pivot.size) { val bpRow = bp.row(row) val pRow = pivot[row] @@ -220,6 +225,7 @@ inline fun LUPDecomposition.solve(matrix: Matrix): Matri } } +inline fun LUPDecomposition.solve(matrix: Matrix) = solve(T::class, matrix) /** * Solve a linear equation **a*x = b** @@ -227,11 +233,11 @@ inline fun LUPDecomposition.solve(matrix: Matrix): Matri inline fun , F : Field> GenericMatrixContext.solve( a: Matrix, b: Matrix, - crossinline checkSingular: (T) -> Boolean + noinline checkSingular: (T) -> Boolean ): Matrix { // Use existing decomposition if it is provided by matrix - val decomposition = a.getFeature() ?: lup(a, checkSingular) - return decomposition.solve(b) + val decomposition = a.getFeature() ?: lup(T::class, a, checkSingular) + return decomposition.solve(T::class, b) } fun RealMatrixContext.solve(a: Matrix, b: Matrix) = diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt index 951471a07..0806cabea 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -9,6 +9,13 @@ class VirtualMatrix( val generator: (i: Int, j: Int) -> T ) : FeaturedMatrix { + constructor(rowNum: Int, colNum: Int, vararg features: MatrixFeature, generator: (i: Int, j: Int) -> T) : this( + rowNum, + colNum, + setOf(*features), + generator + ) + override val shape: IntArray get() = intArrayOf(rowNum, colNum) override fun get(i: Int, j: Int): T = generator(i, j) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index b80212375..83fe3a4d9 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -1,5 +1,6 @@ package scientifik.kmath.operations +import kotlin.math.abs import kotlin.math.pow /** @@ -31,79 +32,152 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") object RealField : Field, ExtendedFieldOperations, Norm { override val zero: Double = 0.0 - override fun add(a: Double, b: Double): Double = a + b - override fun multiply(a: Double, b: Double): Double = a * b - override fun multiply(a: Double, k: Number): Double = a * k.toDouble() + override inline fun add(a: Double, b: Double) = a + b + override inline fun multiply(a: Double, b: Double) = a * b + override inline fun multiply(a: Double, k: Number) = a * k.toDouble() override val one: Double = 1.0 - override fun divide(a: Double, b: Double): Double = a / b + override inline fun divide(a: Double, b: Double) = a / b - override fun sin(arg: Double): Double = kotlin.math.sin(arg) - override fun cos(arg: Double): Double = kotlin.math.cos(arg) + override inline fun sin(arg: Double) = kotlin.math.sin(arg) + override inline fun cos(arg: Double) = kotlin.math.cos(arg) - override fun power(arg: Double, pow: Number): Double = arg.pow(pow.toDouble()) + override inline fun power(arg: Double, pow: Number) = arg.pow(pow.toDouble()) - override fun exp(arg: Double): Double = kotlin.math.exp(arg) - override fun ln(arg: Double): Double = kotlin.math.ln(arg) + override inline fun exp(arg: Double) = kotlin.math.exp(arg) + override inline fun ln(arg: Double) = kotlin.math.ln(arg) - override fun norm(arg: Double): Double = kotlin.math.abs(arg) + override inline fun norm(arg: Double) = abs(arg) - override fun Double.unaryMinus(): Double = -this + override inline fun Double.unaryMinus() = -this - override fun Double.minus(b: Double): Double = this - b + override inline fun Double.plus(b: Double) = this + b + + override inline fun Double.minus(b: Double) = this - b + + override inline fun Double.times(b: Double) = this * b + + override inline fun Double.div(b: Double) = this / b +} + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") +object FloatField : Field, ExtendedFieldOperations, Norm { + override val zero: Float = 0f + override inline fun add(a: Float, b: Float) = a + b + override inline fun multiply(a: Float, b: Float) = a * b + override inline fun multiply(a: Float, k: Number) = a * k.toFloat() + + override val one: Float = 1f + override inline fun divide(a: Float, b: Float) = a / b + + override inline fun sin(arg: Float) = kotlin.math.sin(arg) + override inline fun cos(arg: Float) = kotlin.math.cos(arg) + + override inline fun power(arg: Float, pow: Number) = arg.pow(pow.toFloat()) + + override inline fun exp(arg: Float) = kotlin.math.exp(arg) + override inline fun ln(arg: Float) = kotlin.math.ln(arg) + + override inline fun norm(arg: Float) = abs(arg) + + override inline fun Float.unaryMinus() = -this + + override inline fun Float.plus(b: Float) = this + b + + override inline fun Float.minus(b: Float) = this - b + + override inline fun Float.times(b: Float) = this * b + + override inline fun Float.div(b: Float) = this / b } /** * A field for [Int] without boxing. Does not produce corresponding field element */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") object IntRing : Ring, Norm { 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: Number): Int = (k * a) + override inline fun add(a: Int, b: Int) = a + b + override inline fun multiply(a: Int, b: Int) = a * b + override inline fun multiply(a: Int, k: Number) = (k * a) override val one: Int = 1 - override fun norm(arg: Int): Int = arg + override inline fun norm(arg: Int) = abs(arg) + + override inline fun Int.unaryMinus() = -this + + override inline fun Int.plus(b: Int): Int = this + b + + override inline fun Int.minus(b: Int): Int = this - b + + override inline fun Int.times(b: Int): Int = this * b } /** * A field for [Short] without boxing. Does not produce appropriate field element */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") object ShortRing : Ring, Norm { override val zero: Short = 0 - override fun add(a: Short, b: Short): Short = (a + b).toShort() - override fun multiply(a: Short, b: Short): Short = (a * b).toShort() - override fun multiply(a: Short, k: Number): Short = (a * k) + override inline fun add(a: Short, b: Short) = (a + b).toShort() + override inline fun multiply(a: Short, b: Short) = (a * b).toShort() + override inline fun multiply(a: Short, k: Number) = (a * k) override val one: Short = 1 - override fun norm(arg: Short): Short = arg + override fun norm(arg: Short): Short = if (arg > 0) arg else (-arg).toShort() + + override inline fun Short.unaryMinus() = (-this).toShort() + + override inline fun Short.plus(b: Short) = (this + b).toShort() + + override inline fun Short.minus(b: Short) = (this - b).toShort() + + override inline fun Short.times(b: Short) = (this * b).toShort() } /** * A field for [Byte] values */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") object ByteRing : Ring, Norm { override val zero: Byte = 0 - override fun add(a: Byte, b: Byte): Byte = (a + b).toByte() - override fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte() - override fun multiply(a: Byte, k: Number): Byte = (a * k) + override inline fun add(a: Byte, b: Byte) = (a + b).toByte() + override inline fun multiply(a: Byte, b: Byte) = (a * b).toByte() + override inline fun multiply(a: Byte, k: Number) = (a * k) override val one: Byte = 1 - override fun norm(arg: Byte): Byte = arg + override fun norm(arg: Byte): Byte = if (arg > 0) arg else (-arg).toByte() + + override inline fun Byte.unaryMinus() = (-this).toByte() + + override inline fun Byte.plus(b: Byte) = (this + b).toByte() + + override inline fun Byte.minus(b: Byte) = (this - b).toByte() + + override inline fun Byte.times(b: Byte) = (this * b).toByte() } /** * A field for [Long] values */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") object LongRing : Ring, Norm { override val zero: Long = 0 - override fun add(a: Long, b: Long): Long = (a + b) - override fun multiply(a: Long, b: Long): Long = (a * b) - override fun multiply(a: Long, k: Number): Long = (a * k) + override inline fun add(a: Long, b: Long) = (a + b) + override inline fun multiply(a: Long, b: Long) = (a * b) + override inline fun multiply(a: Long, k: Number) = (a * k) override val one: Long = 1 - override fun norm(arg: Long): Long = arg + override fun norm(arg: Long): Long = abs(arg) + + override inline fun Long.unaryMinus() = (-this) + + override inline fun Long.plus(b: Long) = (this + b) + + override inline fun Long.minus(b: Long) = (this - b) + + override inline fun Long.times(b: Long) = (this * b) } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 89f08ae5a..56a0b7aad 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -16,21 +16,28 @@ class RealLUSolverTest { } @Test - fun testInvert() { + fun testDecomposition() { val matrix = Matrix.square( 3.0, 1.0, 1.0, 3.0 ) - val decomposition = MatrixContext.real.lup(matrix) + MatrixContext.real.run { + val lup = lup(matrix) - //Check determinant - assertEquals(8.0, decomposition.determinant) + //Check determinant + assertEquals(8.0, lup.determinant) - //Check decomposition - with(MatrixContext.real) { - assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u) + assertEquals(lup.p dot matrix, lup.l dot lup.u) } + } + + @Test + fun testInvert() { + val matrix = Matrix.square( + 3.0, 1.0, + 1.0, 3.0 + ) val inverted = MatrixContext.real.inverse(matrix) diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 391504f23..9ca7a35ad 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -1,13 +1,13 @@ plugins { kotlin("multiplatform") - id("kotlinx-atomicfu") version "0.12.1" + id("kotlinx-atomicfu") version "0.12.4" } val atomicfuVersion: String by rootProject.extra kotlin { jvm () - //js() + js() sourceSets { val commonMain by getting { @@ -17,33 +17,16 @@ kotlin { compileOnly("org.jetbrains.kotlinx:atomicfu-common:$atomicfuVersion") } } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } val jvmMain by getting { dependencies { compileOnly("org.jetbrains.kotlinx:atomicfu:$atomicfuVersion") } } - val jvmTest by getting { + val jsMain by getting { dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) + compileOnly("org.jetbrains.kotlinx:atomicfu-js:$atomicfuVersion") } } -// val jsMain by getting { -// dependencies { -// compileOnly("org.jetbrains.kotlinx:atomicfu-js:$atomicfuVersion") -// } -// } -// val jsTest by getting { -// dependencies { -// implementation(kotlin("test-js")) -// } -// } } } From e5000469328eed30231fcfc59feddc8b9fe71e72 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 21 Apr 2019 18:02:16 +0300 Subject: [PATCH 08/34] Added Comparable to Complex --- .../kmath/structures/BufferBenchmark.kt | 11 ++----- .../kmath/structures/NDFieldBenchmark.kt | 18 ++-------- .../scientifik/kmath/operations/Complex.kt | 33 ++++++++++++------- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt index 5ca05d451..a41ca83d3 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt @@ -4,16 +4,14 @@ import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.complex @State(Scope.Benchmark) open class BufferBenchmark { @Benchmark fun genericDoubleBufferReadWrite() { - val buffer = Double.createBuffer(size) - (0 until size).forEach { - buffer[it] = it.toDouble() - } + val buffer = DoubleBuffer(size){it.toDouble()} (0 until size).forEach { buffer[it] @@ -22,10 +20,7 @@ open class BufferBenchmark { @Benchmark fun complexBufferReadWrite() { - val buffer = MutableBuffer.complex(size / 2) - (0 until size / 2).forEach { - buffer[it] = Complex(it.toDouble(), -it.toDouble()) - } + val buffer = MutableBuffer.complex(size / 2){Complex(it.toDouble(), -it.toDouble())} (0 until size / 2).forEach { buffer[it] diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 0cef4bdb4..3e41323f1 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -34,19 +34,6 @@ open class NDFieldBenchmark { } - @Benchmark - fun lazyFieldAdd() { - lazyNDField.run { - var res = one - repeat(n) { - res += one - } - - res.elements().sumByDouble { it.second } - } - } - - @Benchmark fun boxingFieldAdd() { genericField.run { @@ -61,9 +48,8 @@ open class NDFieldBenchmark { val dim = 1000 val n = 100 - val bufferedField = NDField.auto(RealField, intArrayOf(dim, dim)) - val specializedField = NDField.real(intArrayOf(dim, dim)) + val bufferedField = NDField.auto(RealField, dim, dim) + val specializedField = NDField.real(dim, dim) val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) - val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 3092c9123..8f373b8de 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -55,23 +55,14 @@ object ComplexField : ExtendedFieldOperations, Field { /** * Complex number class */ -data class Complex(val re: Double, val im: Double) : FieldElement { +data class Complex(val re: Double, val im: Double) : FieldElement, Comparable { override fun unwrap(): Complex = this override fun Complex.wrap(): Complex = this override val context: ComplexField get() = ComplexField - /** - * A complex conjugate - */ - val conjugate: Complex get() = Complex(re, -im) - - val square: Double get() = re * re + im * im - - val abs: Double get() = sqrt(square) - - val theta: Double get() = atan(im / re) + override fun compareTo(other: Complex): Int = abs.compareTo(other.abs) companion object : MemorySpec { override val objectSize: Int = 16 @@ -86,6 +77,26 @@ data class Complex(val re: Double, val im: Double) : FieldElement Complex): Buffer { From b2d97651b1a1809f014d301e41aa6457aaf49d66 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 21 Apr 2019 22:34:35 +0300 Subject: [PATCH 09/34] Removed unnecessary inlines --- build.gradle.kts | 1 + .../kotlin/scientifik/kmath/structures/BufferAccessor2D.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f7b63a680..b4610930e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,6 +90,7 @@ subprojects { compilations.all { kotlinOptions { jvmTarget = "1.8" + //freeCompilerArgs = listOf("-Xno-call-assertions", "-Xno-param-assertions") } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt index ac0727f7c..b14da5d99 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferAccessor2D.kt @@ -7,9 +7,9 @@ import kotlin.reflect.KClass */ class BufferAccessor2D(val type: KClass, val rowNum: Int, val colNum: Int) { - inline operator fun Buffer.get(i: Int, j: Int) = get(i + colNum * j) + operator fun Buffer.get(i: Int, j: Int) = get(i + colNum * j) - inline operator fun MutableBuffer.set(i: Int, j: Int, value: T) { + operator fun MutableBuffer.set(i: Int, j: Int, value: T) { set(i + colNum * j, value) } From e30337e196cd603208e424692b512485d4b0b25b Mon Sep 17 00:00:00 2001 From: Andrey Mischenko Date: Wed, 17 Apr 2019 17:24:06 +0800 Subject: [PATCH 10/34] WIP move config to buildSrc --- .gitignore | 3 +- build.gradle.kts | 207 +----------------- buildSrc/build.gradle.kts | 18 ++ buildSrc/settings.gradle.kts | 0 buildSrc/src/main/kotlin/Dep.kt | 10 + .../main/kotlin/artifactory-config.gradle.kts | 38 ++++ .../src/main/kotlin/bintray-config.gradle.kts | 97 ++++++++ buildSrc/src/main/kotlin/js-test.gradle.kts | 50 +++++ .../kotlin/multiplatform-config.gradle.kts | 119 ++++++++++ gradle/artifactory.gradle | 31 --- gradle/bintray.gradle | 85 ------- gradle/wrapper/gradle-wrapper.properties | 2 +- kmath-commons/build.gradle.kts | 6 +- kmath-core/build.gradle.kts | 4 +- kmath-coroutines/build.gradle.kts | 22 +- kmath-histograms/build.gradle.kts | 13 +- kmath-io/build.gradle | 4 +- kmath-sequential/build.gradle.kts | 18 +- settings.gradle.kts | 7 +- 19 files changed, 379 insertions(+), 355 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/Dep.kt create mode 100644 buildSrc/src/main/kotlin/artifactory-config.gradle.kts create mode 100644 buildSrc/src/main/kotlin/bintray-config.gradle.kts create mode 100644 buildSrc/src/main/kotlin/js-test.gradle.kts create mode 100644 buildSrc/src/main/kotlin/multiplatform-config.gradle.kts delete mode 100644 gradle/artifactory.gradle delete mode 100644 gradle/bintray.gradle diff --git a/.gitignore b/.gitignore index a9294eff9..72fe16c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ build/ out/ .idea/ +*.iml # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar @@ -9,4 +10,4 @@ out/ # Cache of project .gradletasknamecache -gradle.properties \ No newline at end of file +gradle.properties diff --git a/build.gradle.kts b/build.gradle.kts index bff826b37..39498305e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,45 +1,6 @@ -import com.moowork.gradle.node.NodeExtension -import com.moowork.gradle.node.npm.NpmTask -import com.moowork.gradle.node.task.NodeTask -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -buildscript { - val kotlinVersion: String by rootProject.extra("1.3.30") - val ioVersion: String by rootProject.extra("0.1.5") - val coroutinesVersion: String by rootProject.extra("1.1.1") - val atomicfuVersion: String by rootProject.extra("0.12.1") - val dokkaVersion: String by rootProject.extra("0.9.17") - val serializationVersion: String by rootProject.extra("0.10.0") - - repositories { - jcenter() - maven("https://dl.bintray.com/kotlin/kotlin-eap") - } - - dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") - classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+") - classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") - classpath("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") - //classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") - //classpath("org.openjfx:javafx-plugin:0.0.7") - } -} - -plugins { - id("com.jfrog.artifactory") version "4.9.1" apply false - id("com.moowork.node") version "1.3.1" apply false -} - val kmathVersion by extra("0.1.2-dev-1") allprojects { - apply(plugin = "maven") - apply(plugin = "maven-publish") - apply(plugin = "com.jfrog.artifactory") - repositories { jcenter() maven("https://kotlin.bintray.com/kotlinx") @@ -49,12 +10,17 @@ allprojects { } subprojects { - if(name.startsWith("kmath")) { + if (name.startsWith("kmath")) { // apply bintray configuration - apply(from = "${rootProject.rootDir}/gradle/bintray.gradle") + apply(plugin = "bintray-config") //apply artifactory configuration - apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle") + apply(plugin = "artifactory-config") + + plugins.withId("org.jetbrains.kotlin.multiplatform") { + apply(plugin = "multiplatform-config") + } + } // dokka { // outputFormat = "html" @@ -66,159 +32,4 @@ subprojects { // classifier = "javadoc" // } - // Create empty jar for sources classifier to satisfy maven requirements - val stubSources by tasks.registering(Jar::class) { - archiveClassifier.set("sources") - //from(sourceSets.main.get().allSource) - } - - // Create empty jar for javadoc classifier to satisfy maven requirements - val stubJavadoc by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") - } - - tasks.withType { - kotlinOptions { - jvmTarget = "1.8" - } - } - - - afterEvaluate { - extensions.findByType()?.apply { - jvm { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - } - } - } - - js { - compilations.all { - tasks.getByName(compileKotlinTaskName) { - kotlinOptions { - metaInfo = true - sourceMap = true - sourceMapEmbedSources = "always" - moduleKind = "commonjs" - } - } - } - - configure(listOf(compilations["main"])) { - tasks.getByName(compileKotlinTaskName) { - kotlinOptions { - main = "call" - } - } - } - - - val runJsTests by ext(false) - - if(runJsTests) { - apply(plugin = "com.moowork.node") - configure { - nodeModulesDir = file("$buildDir/node_modules") - } - - val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) - val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) - - val populateNodeModules by tasks.registering(Copy::class) { - dependsOn(compileKotlinJs) - from(compileKotlinJs.destinationDir) - - compilations["test"].runtimeDependencyFiles.forEach { - if (it.exists() && !it.isDirectory) { - from(zipTree(it.absolutePath).matching { include("*.js") }) - } - } - - into("$buildDir/node_modules") - } - - val installMocha by tasks.registering(NpmTask::class) { - setWorkingDir(buildDir) - setArgs(listOf("install", "mocha")) - } - - val runMocha by tasks.registering(NodeTask::class) { - dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) - setScript(file("$buildDir/node_modules/mocha/bin/mocha")) - setArgs(listOf(compileTestKotlinJs.outputFile)) - } - - tasks["jsTest"].dependsOn(runMocha) - } - } - - sourceSets { - - val commonMain by getting { - dependencies { - api(kotlin("stdlib")) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - val jvmMain by getting { - dependencies { - api(kotlin("stdlib-jdk8")) - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - val jsMain by getting { - dependencies { - api(kotlin("stdlib-js")) - } - } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) - } - } - } - - targets.all { - sourceSets.all { - languageSettings.progressiveMode = true - languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.useExperimentalAnnotation("ExperimentalContracts") - //languageSettings.enableLanguageFeature("Contracts") - } - } - - configure { - - publications.filterIsInstance().forEach { publication -> - if (publication.name == "kotlinMultiplatform") { - // for our root metadata publication, set artifactId with a package and project name - publication.artifactId = project.name - } else { - // for targets, set artifactId with a package, project name and target name (e.g. iosX64) - publication.artifactId = "${project.name}-${publication.name}" - } - } - - targets.all { - val publication = publications.findByName(name) as MavenPublication - - // Patch publications with fake javadoc - publication.artifact(stubJavadoc.get()) - } - } - } - } - -} \ No newline at end of file +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..65827214c --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() + jcenter() +} + +val kotlinVersion = "1.3.30" + +// Add plugins used in buildSrc as dependencies, also we should specify version only here +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.9.5") + implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") + implementation("com.moowork.gradle:gradle-node-plugin:1.3.1") +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 000000000..e69de29bb diff --git a/buildSrc/src/main/kotlin/Dep.kt b/buildSrc/src/main/kotlin/Dep.kt new file mode 100644 index 000000000..ab7a243a7 --- /dev/null +++ b/buildSrc/src/main/kotlin/Dep.kt @@ -0,0 +1,10 @@ +// Instead of defining runtime properties and use them dynamically +// define version in buildSrc and have autocompletion and compile-time check +// Also dependencies itself can be moved here +object Ver { + val ioVersion = "0.1.5" + val coroutinesVersion = "1.1.1" + val atomicfuVersion = "0.12.1" + // This version is not used and IDEA shows this property as unused + val dokkaVersion = "0.9.17" +} diff --git a/buildSrc/src/main/kotlin/artifactory-config.gradle.kts b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts new file mode 100644 index 000000000..7445c8b49 --- /dev/null +++ b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts @@ -0,0 +1,38 @@ +import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig +import org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig +import groovy.lang.GroovyObject + +plugins { + id("com.jfrog.artifactory") +} + +artifactory { + val artifactoryUser: String? by project + val artifactoryPassword: String? by project + val artifactoryContextUrl = "http://npm.mipt.ru:8081/artifactory" + + setContextUrl(artifactoryContextUrl)//The base Artifactory URL if not overridden by the publisher/resolver + publish(delegateClosureOf { + repository(delegateClosureOf { + setProperty("repoKey", "gradle-dev-local") + setProperty("username", artifactoryUser) + setProperty("password", artifactoryPassword) + }) + + defaults(delegateClosureOf{ + invokeMethod("publications", arrayOf("jvm", "js", "kotlinMultiplatform", "metadata")) + //TODO: This property is not available for ArtifactoryTask + //setProperty("publishBuildInfo", false) + setProperty("publishArtifacts", true) + setProperty("publishPom", true) + setProperty("publishIvy", false) + }) + }) + resolve(delegateClosureOf { + repository(delegateClosureOf { + setProperty("repoKey", "gradle-dev") + setProperty("username", artifactoryUser) + setProperty("password", artifactoryPassword) + }) + }) +} diff --git a/buildSrc/src/main/kotlin/bintray-config.gradle.kts b/buildSrc/src/main/kotlin/bintray-config.gradle.kts new file mode 100644 index 000000000..29a81f617 --- /dev/null +++ b/buildSrc/src/main/kotlin/bintray-config.gradle.kts @@ -0,0 +1,97 @@ +@file:Suppress("UnstableApiUsage") + +import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig +import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig + +// Old bintray.gradle script converted to real Gradle plugin (precompiled script plugin) +// It now has own dependencies and support type safe accessors +// Syntax is pretty close to what we had in Groovy +// (excluding Property.set and bintray dynamic configs) + +plugins { + id("com.jfrog.bintray") + `maven-publish` +} + +val vcs = "https://github.com/mipt-npm/kmath" + +// Configure publishing +publishing { + repositories { + maven("https://bintray.com/mipt-npm/scientifik") + } + + // Process each publication we have in this project + publications.filterIsInstance().forEach { publication -> + + // use type safe pom config GSL insterad of old dynamic + publication.pom { + name.set(project.name) + description.set(project.description) + url.set(vcs) + + licenses { + license { + name.set("The Apache Software License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + distribution.set("repo") + } + } + developers { + developer { + id.set("MIPT-NPM") + name.set("MIPT nuclear physics methods laboratory") + organization.set("MIPT") + organizationUrl.set("http://npm.mipt.ru") + } + + } + scm { + url.set(vcs) + } + } + + } +} + +bintray { + // delegates for runtime properties + val bintrayUser: String? by project + val bintrayApiKey: String? by project + user = bintrayUser ?: System.getenv("BINTRAY_USER") + key = bintrayApiKey ?: System.getenv("BINTRAY_API_KEY") + publish = true + override = true // for multi-platform Kotlin/Native publishing + + // We have to use delegateClosureOf because bintray supports only dynamic groovy syntax + // this is a problem of this plugin + pkg(delegateClosureOf { + userOrg = "mipt-npm" + repo = "scientifik" + name = "scientifik.kmath" + issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" + setLicenses("Apache-2.0") + vcsUrl = vcs + version(delegateClosureOf { + name = project.version.toString() + vcsTag = project.version.toString() + released = java.util.Date().toString() + }) + }) + + tasks { + bintrayUpload { + dependsOn(publishToMavenLocal) + doFirst { + setPublications(project.publishing.publications + .filterIsInstance() + .filter { !it.name.contains("-test") && it.name != "kotlinMultiplatform" } + .map { + println("""Uploading artifact "${it.groupId}:${it.artifactId}:${it.version}" from publication "${it.name}""") + it.name //https://github.com/bintray/gradle-bintray-plugin/issues/256 + }) + } + } + + } +} diff --git a/buildSrc/src/main/kotlin/js-test.gradle.kts b/buildSrc/src/main/kotlin/js-test.gradle.kts new file mode 100644 index 000000000..dd00419f5 --- /dev/null +++ b/buildSrc/src/main/kotlin/js-test.gradle.kts @@ -0,0 +1,50 @@ +import com.moowork.gradle.node.npm.NpmTask +import com.moowork.gradle.node.task.NodeTask +import org.gradle.api.tasks.Copy +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.gradle.kotlin.dsl.* + +plugins { + id("com.moowork.node") + kotlin("multiplatform") +} + +node { + nodeModulesDir = file("$buildDir/node_modules") +} + +val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) +val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) + +val populateNodeModules by tasks.registering(Copy::class) { + dependsOn(compileKotlinJs) + from(compileKotlinJs.destinationDir) + + kotlin.js().compilations["test"].runtimeDependencyFiles.forEach { + if (it.exists() && !it.isDirectory) { + from(zipTree(it.absolutePath).matching { include("*.js") }) + } + } + + into("$buildDir/node_modules") +} + +val installMocha by tasks.registering { + setWorkingDir(buildDir) + setArgs(listOf("install", "mocha")) +} + + +inline fun TaskContainer.registering( + crossinline action: T.() -> Unit +): RegisteringDomainObjectDelegateProviderWithTypeAndAction = + RegisteringDomainObjectDelegateProviderWithTypeAndAction.of(this, T::class, { action() }) + + +val runMocha by tasks.registering(NodeTask::class) { + dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) + setScript(file("$buildDir/node_modules/mocha/bin/mocha")) + setArgs(listOf(compileTestKotlinJs.outputFile)) +} + +tasks["jsTest"].dependsOn(runMocha) diff --git a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts new file mode 100644 index 000000000..0b845aa76 --- /dev/null +++ b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts @@ -0,0 +1,119 @@ +import org.gradle.kotlin.dsl.* + +plugins { + kotlin("multiplatform") + `maven-publish` +} + + +kotlin { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + + js { + compilations.all { + kotlinOptions { + metaInfo = true + sourceMap = true + sourceMapEmbedSources = "always" + moduleKind = "commonjs" + } + } + + compilations.named("main") { + kotlinOptions { + main = "call" + } + } + } + + sourceSets.invoke { + commonMain { + dependencies { + api(kotlin("stdlib")) + } + } + commonTest { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + "jvmMain" { + dependencies { + api(kotlin("stdlib-jdk8")) + } + } + "jvmTest" { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + "jsMain" { + dependencies { + api(kotlin("stdlib-js")) + } + } + "jsTest" { + dependencies { + implementation(kotlin("test-js")) + } + } + } + + targets.all { + sourceSets.all { + languageSettings.progressiveMode = true + languageSettings.enableLanguageFeature("InlineClasses") + languageSettings.useExperimentalAnnotation("ExperimentalContracts") + //languageSettings.enableLanguageFeature("Contracts") + } + } + + // Create empty jar for sources classifier to satisfy maven requirements + tasks.register("stubSources") { + archiveClassifier.set("sources") + //from(sourceSets.main.get().allSource) + } + + // Create empty jar for javadoc classifier to satisfy maven requirements + val stubJavadoc by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + } + + + publishing { + + publications.filterIsInstance().forEach { publication -> + if (publication.name == "kotlinMultiplatform") { + // for our root metadata publication, set artifactId with a package and project name + publication.artifactId = project.name + } else { + // for targets, set artifactId with a package, project name and target name (e.g. iosX64) + publication.artifactId = "${project.name}-${publication.name}" + } + } + + targets.all { + val publication = publications.findByName(name) as MavenPublication + + // Patch publications with fake javadoc + publication.artifact(stubJavadoc.get()) + } + } + + // Apply JS test configuration + val runJsTests by ext(false) + + if (runJsTests) { + apply(plugin = "js-test") + } + +} + diff --git a/gradle/artifactory.gradle b/gradle/artifactory.gradle deleted file mode 100644 index 12e59642b..000000000 --- a/gradle/artifactory.gradle +++ /dev/null @@ -1,31 +0,0 @@ -apply plugin: "com.jfrog.artifactory" - -artifactory { - def artifactory_user = project.hasProperty('artifactoryUser') ? project.property('artifactoryUser') : "" - def artifactory_password = project.hasProperty('artifactoryPassword') ? project.property('artifactoryPassword') : "" - def artifactory_contextUrl = 'http://npm.mipt.ru:8081/artifactory' - - contextUrl = artifactory_contextUrl //The base Artifactory URL if not overridden by the publisher/resolver - publish { - repository { - repoKey = 'gradle-dev-local' - username = artifactory_user - password = artifactory_password - } - - defaults { - publications('jvm', 'js', 'kotlinMultiplatform', 'metadata') - publishBuildInfo = false - publishArtifacts = true - publishPom = true - publishIvy = false - } - } - resolve { - repository { - repoKey = 'gradle-dev' - username = artifactory_user - password = artifactory_password - } - } -} \ No newline at end of file diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle deleted file mode 100644 index 8da83c860..000000000 --- a/gradle/bintray.gradle +++ /dev/null @@ -1,85 +0,0 @@ -apply plugin: 'com.jfrog.bintray' - -def vcs = "https://github.com/mipt-npm/kmath" - -def pomConfig = { - licenses { - license { - name "The Apache Software License, Version 2.0" - url "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution "repo" - } - } - developers { - developer { - id "MIPT-NPM" - name "MIPT nuclear physics methods laboratory" - organization "MIPT" - organizationUrl "http://npm.mipt.ru" - } - } - scm { - url vcs - } -} - -project.ext.configureMavenCentralMetadata = { pom -> - def root = asNode() - root.appendNode('name', project.name) - root.appendNode('description', project.description) - root.appendNode('url', vcs) - root.children().last() + pomConfig -} - -project.ext.configurePom = pomConfig - - -// Configure publishing -publishing { - repositories { - maven { - url = "https://bintray.com/mipt-npm/scientifik" - } - } - - // Process each publication we have in this project - publications.all { publication -> - // apply changes to pom.xml files, see pom.gradle - pom.withXml(configureMavenCentralMetadata) - - - } -} - -bintray { - user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') - key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') - publish = true - override = true // for multi-platform Kotlin/Native publishing - - pkg { - userOrg = "mipt-npm" - repo = "scientifik" - name = "scientifik.kmath" - issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" - licenses = ['Apache-2.0'] - vcsUrl = vcs - version { - name = project.version - vcsTag = project.version - released = new Date() - } - } -} - -bintrayUpload.dependsOn publishToMavenLocal - -// This is for easier debugging of bintray uploading problems -bintrayUpload.doFirst { - publications = project.publishing.publications.findAll { - !it.name.contains('-test') && it.name != 'kotlinMultiplatform' - }.collect { - println("Uploading artifact '$it.groupId:$it.artifactId:$it.version' from publication '$it.name'") - it.name//https://github.com/bintray/gradle-bintray-plugin/issues/256 - } -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 75b8c7c8c..5f1b1201a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 0ea7b7eff..ad8b3912a 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -15,15 +15,15 @@ dependencies { val sourcesJar by tasks.registering(Jar::class) { - classifier = "sources" + archiveClassifier.set("sources") from(sourceSets.main.get().allSource) } publishing { publications { - register("jvm", MavenPublication::class) { + register("jvm") { from(components["java"]) artifact(sourcesJar.get()) } } -} \ No newline at end of file +} diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index ab6ae7822..108ee75fa 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -10,7 +10,7 @@ kotlin { js() sourceSets { - val commonMain by getting { + commonMain { dependencies { api(project(":kmath-memory")) } @@ -20,4 +20,4 @@ kotlin { // mingwTest { // } } -} \ No newline at end of file +} diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index c73d4e4dc..1da34e0d7 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -2,42 +2,40 @@ plugins { kotlin("multiplatform") } -val coroutinesVersion: String by rootProject.extra - kotlin { jvm() js() - sourceSets { - val commonMain by getting { + sourceSets.invoke { + commonMain { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Ver.coroutinesVersion}") } } - val commonTest by getting { + commonTest { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - val jvmMain by getting { + "jvmMain" { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Ver.coroutinesVersion}") } } - val jvmTest by getting { + "jvmTest" { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - val jsMain by getting { + "jsMain" { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Ver.coroutinesVersion}") } } - val jsTest by getting { + "jsTest" { dependencies { implementation(kotlin("test-js")) } diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 81b3fb83f..2e9210843 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -6,29 +6,28 @@ kotlin { jvm() js() - sourceSets { - - val commonMain by getting { + sourceSets.invoke { + commonMain { dependencies { api(project(":kmath-core")) } } - val commonTest by getting { + commonTest { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - val jvmTest by getting { + "jvmTest" { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - val jsTest by getting { + "jsTest" { dependencies { implementation(kotlin("test-js")) } } } -} \ No newline at end of file +} diff --git a/kmath-io/build.gradle b/kmath-io/build.gradle index 28fb7eee5..4b1470835 100644 --- a/kmath-io/build.gradle +++ b/kmath-io/build.gradle @@ -16,7 +16,7 @@ kotlin { dependencies { api project(":kmath-core") implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' - api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" + api "org.jetbrains.kotlinx:kotlinx-io:${Ver.ioVersion}" } } commonTest { @@ -28,7 +28,7 @@ kotlin { jvmMain { dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" + api "org.jetbrains.kotlinx:kotlinx-io-jvm:${Ver.ioVersion}" } } jvmTest { diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 391504f23..686089079 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("multiplatform") - id("kotlinx-atomicfu") version "0.12.1" + id("kotlinx-atomicfu") } val atomicfuVersion: String by rootProject.extra @@ -9,26 +9,26 @@ kotlin { jvm () //js() - sourceSets { - val commonMain by getting { + sourceSets.invoke { + commonMain { dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:$atomicfuVersion") + compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") } } - val commonTest by getting { + commonTest { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - val jvmMain by getting { + "jvmMain" { dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu:$atomicfuVersion") + compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") } } - val jvmTest by getting { + "jvmTest" { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) @@ -50,4 +50,4 @@ kotlin { atomicfu { variant = "VH" -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 69b1f90fe..b70b8d8be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,10 +8,9 @@ pluginManagement { eachPlugin { when (requested.id.id) { "kotlinx-atomicfu" -> { - useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") - } - "kotlin-multiplatform" ->{ - useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") + // Just hardcode version here, + // because anyway different submodules cannot use different versions + useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.12.1") } } } From eb2162987b9d89977247bde4c90eae11f7a64d4c Mon Sep 17 00:00:00 2001 From: Andrey Mischenko Date: Wed, 17 Apr 2019 17:24:06 +0800 Subject: [PATCH 11/34] WIP move config to buildSrc --- .gitignore | 2 +- build.gradle.kts | 209 +----------------- buildSrc/build.gradle.kts | 18 ++ buildSrc/settings.gradle.kts | 0 buildSrc/src/main/kotlin/Dep.kt | 10 + .../main/kotlin/artifactory-config.gradle.kts | 38 ++++ .../src/main/kotlin/bintray-config.gradle.kts | 97 ++++++++ buildSrc/src/main/kotlin/js-test.gradle.kts | 45 ++++ .../kotlin/multiplatform-config.gradle.kts | 124 +++++++++++ gradle.properties | 1 + gradle/artifactory.gradle | 31 --- gradle/bintray.gradle | 85 ------- gradle/wrapper/gradle-wrapper.properties | 2 +- kmath-commons/build.gradle.kts | 6 +- kmath-core/build.gradle.kts | 6 +- kmath-coroutines/build.gradle.kts | 24 +- kmath-histograms/build.gradle.kts | 15 +- kmath-io/build.gradle | 4 +- kmath-koma/build.gradle.kts | 19 +- kmath-memory/build.gradle.kts | 13 +- kmath-sequential/build.gradle.kts | 24 +- settings.gradle.kts | 3 - 22 files changed, 390 insertions(+), 386 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/Dep.kt create mode 100644 buildSrc/src/main/kotlin/artifactory-config.gradle.kts create mode 100644 buildSrc/src/main/kotlin/bintray-config.gradle.kts create mode 100644 buildSrc/src/main/kotlin/js-test.gradle.kts create mode 100644 buildSrc/src/main/kotlin/multiplatform-config.gradle.kts create mode 100644 gradle.properties delete mode 100644 gradle/artifactory.gradle delete mode 100644 gradle/bintray.gradle diff --git a/.gitignore b/.gitignore index a9294eff9..3a6e6e10a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ build/ out/ .idea/ +*.iml # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar @@ -9,4 +10,3 @@ out/ # Cache of project .gradletasknamecache -gradle.properties \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b4610930e..5e0a91447 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,45 +1,6 @@ -import com.moowork.gradle.node.NodeExtension -import com.moowork.gradle.node.npm.NpmTask -import com.moowork.gradle.node.task.NodeTask -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -buildscript { - val kotlinVersion: String by rootProject.extra("1.3.30") - val ioVersion: String by rootProject.extra("0.1.5") - val coroutinesVersion: String by rootProject.extra("1.2.0") - val atomicfuVersion: String by rootProject.extra("0.12.1") - val dokkaVersion: String by rootProject.extra("0.9.17") - val serializationVersion: String by rootProject.extra("0.10.0") - - repositories { - jcenter() - maven("https://dl.bintray.com/kotlin/kotlin-eap") - } - - dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") - classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+") - classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") - classpath("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") - //classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") - //classpath("org.openjfx:javafx-plugin:0.0.7") - } -} - -plugins { - id("com.jfrog.artifactory") version "4.9.1" apply false - id("com.moowork.node") version "1.3.1" apply false -} - val kmathVersion by extra("0.1.2-dev-1") allprojects { - apply(plugin = "maven") - apply(plugin = "maven-publish") - apply(plugin = "com.jfrog.artifactory") - repositories { jcenter() maven("https://kotlin.bintray.com/kotlinx") @@ -49,12 +10,11 @@ allprojects { } subprojects { - if(name.startsWith("kmath")) { - // apply bintray configuration - apply(from = "${rootProject.rootDir}/gradle/bintray.gradle") - - //apply artifactory configuration - apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle") + // Actually, probably we should apply it to plugins explicitly + // We also can merge them to single kmath-publish plugin + if (name.startsWith("kmath")) { + apply(plugin = "bintray-config") + apply(plugin = "artifactory-config") } // dokka { // outputFormat = "html" @@ -65,161 +25,4 @@ subprojects { // from javadoc . destinationDir // classifier = "javadoc" // } - - // Create empty jar for sources classifier to satisfy maven requirements - val stubSources by tasks.registering(Jar::class) { - archiveClassifier.set("sources") - //from(sourceSets.main.get().allSource) - } - - // Create empty jar for javadoc classifier to satisfy maven requirements - val stubJavadoc by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") - } - - tasks.withType { - kotlinOptions { - jvmTarget = "1.8" - } - } - - - afterEvaluate { - extensions.findByType()?.apply { - jvm { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - //freeCompilerArgs = listOf("-Xno-call-assertions", "-Xno-param-assertions") - } - } - } - - js { - compilations.all { - tasks.getByName(compileKotlinTaskName) { - kotlinOptions { - metaInfo = true - sourceMap = true - sourceMapEmbedSources = "always" - moduleKind = "commonjs" - } - } - } - - configure(listOf(compilations["main"])) { - tasks.getByName(compileKotlinTaskName) { - kotlinOptions { - main = "call" - } - } - } - - - val runJsTests by ext(false) - - if(runJsTests) { - apply(plugin = "com.moowork.node") - configure { - nodeModulesDir = file("$buildDir/node_modules") - } - - val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) - val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) - - val populateNodeModules by tasks.registering(Copy::class) { - dependsOn(compileKotlinJs) - from(compileKotlinJs.destinationDir) - - compilations["test"].runtimeDependencyFiles.forEach { - if (it.exists() && !it.isDirectory) { - from(zipTree(it.absolutePath).matching { include("*.js") }) - } - } - - into("$buildDir/node_modules") - } - - val installMocha by tasks.registering(NpmTask::class) { - setWorkingDir(buildDir) - setArgs(listOf("install", "mocha")) - } - - val runMocha by tasks.registering(NodeTask::class) { - dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) - setScript(file("$buildDir/node_modules/mocha/bin/mocha")) - setArgs(listOf(compileTestKotlinJs.outputFile)) - } - - tasks["jsTest"].dependsOn(runMocha) - } - } - - sourceSets { - - val commonMain by getting { - dependencies { - api(kotlin("stdlib")) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - val jvmMain by getting { - dependencies { - api(kotlin("stdlib-jdk8")) - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - val jsMain by getting { - dependencies { - api(kotlin("stdlib-js")) - } - } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) - } - } - } - - targets.all { - sourceSets.all { - languageSettings.progressiveMode = true - languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.useExperimentalAnnotation("ExperimentalContracts") - //languageSettings.enableLanguageFeature("Contracts") - } - } - - configure { - - publications.filterIsInstance().forEach { publication -> - if (publication.name == "kotlinMultiplatform") { - // for our root metadata publication, set artifactId with a package and project name - publication.artifactId = project.name - } else { - // for targets, set artifactId with a package, project name and target name (e.g. iosX64) - publication.artifactId = "${project.name}-${publication.name}" - } - } - - targets.all { - val publication = publications.findByName(name) as MavenPublication - - // Patch publications with fake javadoc - publication.artifact(stubJavadoc.get()) - } - } - } - } - -} \ No newline at end of file +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..65827214c --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() + jcenter() +} + +val kotlinVersion = "1.3.30" + +// Add plugins used in buildSrc as dependencies, also we should specify version only here +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.9.5") + implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") + implementation("com.moowork.gradle:gradle-node-plugin:1.3.1") +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 000000000..e69de29bb diff --git a/buildSrc/src/main/kotlin/Dep.kt b/buildSrc/src/main/kotlin/Dep.kt new file mode 100644 index 000000000..87f963643 --- /dev/null +++ b/buildSrc/src/main/kotlin/Dep.kt @@ -0,0 +1,10 @@ +// Instead of defining runtime properties and use them dynamically +// define version in buildSrc and have autocompletion and compile-time check +// Also dependencies itself can be moved here +object Ver { + val ioVersion = "0.1.5" + val coroutinesVersion = "1.1.1" + val atomicfuVersion = "0.12.4" + // This version is not used and IDEA shows this property as unused + val dokkaVersion = "0.9.17" +} diff --git a/buildSrc/src/main/kotlin/artifactory-config.gradle.kts b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts new file mode 100644 index 000000000..7445c8b49 --- /dev/null +++ b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts @@ -0,0 +1,38 @@ +import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig +import org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig +import groovy.lang.GroovyObject + +plugins { + id("com.jfrog.artifactory") +} + +artifactory { + val artifactoryUser: String? by project + val artifactoryPassword: String? by project + val artifactoryContextUrl = "http://npm.mipt.ru:8081/artifactory" + + setContextUrl(artifactoryContextUrl)//The base Artifactory URL if not overridden by the publisher/resolver + publish(delegateClosureOf { + repository(delegateClosureOf { + setProperty("repoKey", "gradle-dev-local") + setProperty("username", artifactoryUser) + setProperty("password", artifactoryPassword) + }) + + defaults(delegateClosureOf{ + invokeMethod("publications", arrayOf("jvm", "js", "kotlinMultiplatform", "metadata")) + //TODO: This property is not available for ArtifactoryTask + //setProperty("publishBuildInfo", false) + setProperty("publishArtifacts", true) + setProperty("publishPom", true) + setProperty("publishIvy", false) + }) + }) + resolve(delegateClosureOf { + repository(delegateClosureOf { + setProperty("repoKey", "gradle-dev") + setProperty("username", artifactoryUser) + setProperty("password", artifactoryPassword) + }) + }) +} diff --git a/buildSrc/src/main/kotlin/bintray-config.gradle.kts b/buildSrc/src/main/kotlin/bintray-config.gradle.kts new file mode 100644 index 000000000..29a81f617 --- /dev/null +++ b/buildSrc/src/main/kotlin/bintray-config.gradle.kts @@ -0,0 +1,97 @@ +@file:Suppress("UnstableApiUsage") + +import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig +import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig + +// Old bintray.gradle script converted to real Gradle plugin (precompiled script plugin) +// It now has own dependencies and support type safe accessors +// Syntax is pretty close to what we had in Groovy +// (excluding Property.set and bintray dynamic configs) + +plugins { + id("com.jfrog.bintray") + `maven-publish` +} + +val vcs = "https://github.com/mipt-npm/kmath" + +// Configure publishing +publishing { + repositories { + maven("https://bintray.com/mipt-npm/scientifik") + } + + // Process each publication we have in this project + publications.filterIsInstance().forEach { publication -> + + // use type safe pom config GSL insterad of old dynamic + publication.pom { + name.set(project.name) + description.set(project.description) + url.set(vcs) + + licenses { + license { + name.set("The Apache Software License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + distribution.set("repo") + } + } + developers { + developer { + id.set("MIPT-NPM") + name.set("MIPT nuclear physics methods laboratory") + organization.set("MIPT") + organizationUrl.set("http://npm.mipt.ru") + } + + } + scm { + url.set(vcs) + } + } + + } +} + +bintray { + // delegates for runtime properties + val bintrayUser: String? by project + val bintrayApiKey: String? by project + user = bintrayUser ?: System.getenv("BINTRAY_USER") + key = bintrayApiKey ?: System.getenv("BINTRAY_API_KEY") + publish = true + override = true // for multi-platform Kotlin/Native publishing + + // We have to use delegateClosureOf because bintray supports only dynamic groovy syntax + // this is a problem of this plugin + pkg(delegateClosureOf { + userOrg = "mipt-npm" + repo = "scientifik" + name = "scientifik.kmath" + issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" + setLicenses("Apache-2.0") + vcsUrl = vcs + version(delegateClosureOf { + name = project.version.toString() + vcsTag = project.version.toString() + released = java.util.Date().toString() + }) + }) + + tasks { + bintrayUpload { + dependsOn(publishToMavenLocal) + doFirst { + setPublications(project.publishing.publications + .filterIsInstance() + .filter { !it.name.contains("-test") && it.name != "kotlinMultiplatform" } + .map { + println("""Uploading artifact "${it.groupId}:${it.artifactId}:${it.version}" from publication "${it.name}""") + it.name //https://github.com/bintray/gradle-bintray-plugin/issues/256 + }) + } + } + + } +} diff --git a/buildSrc/src/main/kotlin/js-test.gradle.kts b/buildSrc/src/main/kotlin/js-test.gradle.kts new file mode 100644 index 000000000..2b79ec8f5 --- /dev/null +++ b/buildSrc/src/main/kotlin/js-test.gradle.kts @@ -0,0 +1,45 @@ +import com.moowork.gradle.node.npm.NpmTask +import com.moowork.gradle.node.task.NodeTask +import org.gradle.api.tasks.Copy +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.gradle.kotlin.dsl.* + +plugins { + id("com.moowork.node") + kotlin("multiplatform") +} + +node { + nodeModulesDir = file("$buildDir/node_modules") +} + +val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) +val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) + +val populateNodeModules by tasks.registering(Copy::class) { + dependsOn(compileKotlinJs) + from(compileKotlinJs.destinationDir) + + kotlin.js().compilations["test"].runtimeDependencyFiles.forEach { + if (it.exists() && !it.isDirectory) { + from(zipTree(it.absolutePath).matching { include("*.js") }) + } + } + + into("$buildDir/node_modules") +} + +val installMocha by tasks.registering(NpmTask::class) { + setWorkingDir(buildDir) + setArgs(listOf("install", "mocha")) +} + +val runMocha by tasks.registering(NodeTask::class) { + dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) + setScript(file("$buildDir/node_modules/mocha/bin/mocha")) + setArgs(listOf(compileTestKotlinJs.outputFile)) +} + +tasks["jsTest"].dependsOn(runMocha) + + diff --git a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts new file mode 100644 index 000000000..0008a72eb --- /dev/null +++ b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts @@ -0,0 +1,124 @@ +import com.moowork.gradle.node.NodeExtension +import com.moowork.gradle.node.npm.NpmTask +import com.moowork.gradle.node.task.NodeTask +import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("multiplatform") + `maven-publish` +} + + +kotlin { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + + js { + compilations.all { + kotlinOptions { + metaInfo = true + sourceMap = true + sourceMapEmbedSources = "always" + moduleKind = "commonjs" + } + } + + compilations.named("main") { + kotlinOptions { + main = "call" + } + } + } + + sourceSets.invoke { + commonMain { + dependencies { + api(kotlin("stdlib")) + } + } + commonTest { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + "jvmMain" { + dependencies { + api(kotlin("stdlib-jdk8")) + } + } + "jvmTest" { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + "jsMain" { + dependencies { + api(kotlin("stdlib-js")) + } + } + "jsTest" { + dependencies { + implementation(kotlin("test-js")) + } + } + } + + targets.all { + sourceSets.all { + languageSettings.progressiveMode = true + languageSettings.enableLanguageFeature("InlineClasses") + languageSettings.useExperimentalAnnotation("ExperimentalContracts") + //languageSettings.enableLanguageFeature("Contracts") + } + } + + // Create empty jar for sources classifier to satisfy maven requirements + tasks.register("stubSources") { + archiveClassifier.set("sources") + //from(sourceSets.main.get().allSource) + } + + // Create empty jar for javadoc classifier to satisfy maven requirements + val stubJavadoc by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + } + + + publishing { + + publications.filterIsInstance().forEach { publication -> + if (publication.name == "kotlinMultiplatform") { + // for our root metadata publication, set artifactId with a package and project name + publication.artifactId = project.name + } else { + // for targets, set artifactId with a package, project name and target name (e.g. iosX64) + publication.artifactId = "${project.name}-${publication.name}" + } + } + + targets.all { + val publication = publications.findByName(name) as MavenPublication + + // Patch publications with fake javadoc + publication.artifact(stubJavadoc.get()) + } + } + + // Apply JS test configuration + val runJsTests by ext(false) + + if (runJsTests) { + apply(plugin = "js-test") + } + +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..1e453aace --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +runJsTests=false diff --git a/gradle/artifactory.gradle b/gradle/artifactory.gradle deleted file mode 100644 index 12e59642b..000000000 --- a/gradle/artifactory.gradle +++ /dev/null @@ -1,31 +0,0 @@ -apply plugin: "com.jfrog.artifactory" - -artifactory { - def artifactory_user = project.hasProperty('artifactoryUser') ? project.property('artifactoryUser') : "" - def artifactory_password = project.hasProperty('artifactoryPassword') ? project.property('artifactoryPassword') : "" - def artifactory_contextUrl = 'http://npm.mipt.ru:8081/artifactory' - - contextUrl = artifactory_contextUrl //The base Artifactory URL if not overridden by the publisher/resolver - publish { - repository { - repoKey = 'gradle-dev-local' - username = artifactory_user - password = artifactory_password - } - - defaults { - publications('jvm', 'js', 'kotlinMultiplatform', 'metadata') - publishBuildInfo = false - publishArtifacts = true - publishPom = true - publishIvy = false - } - } - resolve { - repository { - repoKey = 'gradle-dev' - username = artifactory_user - password = artifactory_password - } - } -} \ No newline at end of file diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle deleted file mode 100644 index 8da83c860..000000000 --- a/gradle/bintray.gradle +++ /dev/null @@ -1,85 +0,0 @@ -apply plugin: 'com.jfrog.bintray' - -def vcs = "https://github.com/mipt-npm/kmath" - -def pomConfig = { - licenses { - license { - name "The Apache Software License, Version 2.0" - url "http://www.apache.org/licenses/LICENSE-2.0.txt" - distribution "repo" - } - } - developers { - developer { - id "MIPT-NPM" - name "MIPT nuclear physics methods laboratory" - organization "MIPT" - organizationUrl "http://npm.mipt.ru" - } - } - scm { - url vcs - } -} - -project.ext.configureMavenCentralMetadata = { pom -> - def root = asNode() - root.appendNode('name', project.name) - root.appendNode('description', project.description) - root.appendNode('url', vcs) - root.children().last() + pomConfig -} - -project.ext.configurePom = pomConfig - - -// Configure publishing -publishing { - repositories { - maven { - url = "https://bintray.com/mipt-npm/scientifik" - } - } - - // Process each publication we have in this project - publications.all { publication -> - // apply changes to pom.xml files, see pom.gradle - pom.withXml(configureMavenCentralMetadata) - - - } -} - -bintray { - user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') - key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') - publish = true - override = true // for multi-platform Kotlin/Native publishing - - pkg { - userOrg = "mipt-npm" - repo = "scientifik" - name = "scientifik.kmath" - issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" - licenses = ['Apache-2.0'] - vcsUrl = vcs - version { - name = project.version - vcsTag = project.version - released = new Date() - } - } -} - -bintrayUpload.dependsOn publishToMavenLocal - -// This is for easier debugging of bintray uploading problems -bintrayUpload.doFirst { - publications = project.publishing.publications.findAll { - !it.name.contains('-test') && it.name != 'kotlinMultiplatform' - }.collect { - println("Uploading artifact '$it.groupId:$it.artifactId:$it.version' from publication '$it.name'") - it.name//https://github.com/bintray/gradle-bintray-plugin/issues/256 - } -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 75b8c7c8c..5f1b1201a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 0ea7b7eff..ad8b3912a 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -15,15 +15,15 @@ dependencies { val sourcesJar by tasks.registering(Jar::class) { - classifier = "sources" + archiveClassifier.set("sources") from(sourceSets.main.get().allSource) } publishing { publications { - register("jvm", MavenPublication::class) { + register("jvm") { from(components["java"]) artifact(sourcesJar.get()) } } -} \ No newline at end of file +} diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index ab6ae7822..93f4a1af6 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") + id("multiplatform-config") } val ioVersion: String by rootProject.extra @@ -10,7 +10,7 @@ kotlin { js() sourceSets { - val commonMain by getting { + commonMain { dependencies { api(project(":kmath-memory")) } @@ -20,4 +20,4 @@ kotlin { // mingwTest { // } } -} \ No newline at end of file +} diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index c73d4e4dc..d249e1c48 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -1,43 +1,41 @@ plugins { - kotlin("multiplatform") + id("multiplatform-config") } -val coroutinesVersion: String by rootProject.extra - kotlin { jvm() js() - sourceSets { - val commonMain by getting { + sourceSets.invoke { + commonMain { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Ver.coroutinesVersion}") } } - val commonTest by getting { + commonTest { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - val jvmMain by getting { + "jvmMain" { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Ver.coroutinesVersion}") } } - val jvmTest by getting { + "jvmTest" { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - val jsMain by getting { + "jsMain" { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Ver.coroutinesVersion}") } } - val jsTest by getting { + "jsTest" { dependencies { implementation(kotlin("test-js")) } diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 81b3fb83f..069c11bc9 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -1,34 +1,33 @@ plugins { - kotlin("multiplatform") + id("multiplatform-config") } kotlin { jvm() js() - sourceSets { - - val commonMain by getting { + sourceSets.invoke { + commonMain { dependencies { api(project(":kmath-core")) } } - val commonTest by getting { + commonTest { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - val jvmTest by getting { + "jvmTest" { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - val jsTest by getting { + "jsTest" { dependencies { implementation(kotlin("test-js")) } } } -} \ No newline at end of file +} diff --git a/kmath-io/build.gradle b/kmath-io/build.gradle index 28fb7eee5..4b1470835 100644 --- a/kmath-io/build.gradle +++ b/kmath-io/build.gradle @@ -16,7 +16,7 @@ kotlin { dependencies { api project(":kmath-core") implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' - api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" + api "org.jetbrains.kotlinx:kotlinx-io:${Ver.ioVersion}" } } commonTest { @@ -28,7 +28,7 @@ kotlin { jvmMain { dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" + api "org.jetbrains.kotlinx:kotlinx-io-jvm:${Ver.ioVersion}" } } jvmTest { diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index b95aaf3c8..5d8e10cb1 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") + id("multiplatform-config") } repositories { @@ -10,48 +10,47 @@ kotlin { jvm { compilations.all { kotlinOptions { - jvmTarget = "1.8" freeCompilerArgs += "-progressive" } } } js() - sourceSets { + sourceSets.invoke { - val commonMain by getting { + commonMain { dependencies { api(project(":kmath-core")) api("com.kyonifer:koma-core-api-common:0.12") } } - val commonTest by getting { + commonTest { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - val jvmMain by getting { + "jvmMain" { dependencies { api("com.kyonifer:koma-core-api-jvm:0.12") } } - val jvmTest by getting { + "jvmTest" { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) implementation("com.kyonifer:koma-core-ejml:0.12") } } - val jsMain by getting { + "jsMain" { dependencies { api("com.kyonifer:koma-core-api-js:0.12") } } - val jsTest by getting { + "jsTest" { dependencies { implementation(kotlin("test-js")) } } } -} \ No newline at end of file +} diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index 03a05c9fe..12cce0bbb 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -1,16 +1,9 @@ plugins { - kotlin("multiplatform") + id("multiplatform-config") } -val ioVersion: String by rootProject.extra - - +// We actually don't need this, we define jvm and js targets in multiplatform-config kotlin { jvm() js() -// mingwMain { -// } -// mingwTest { -// } - -} \ No newline at end of file +} diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 9ca7a35ad..6309d97d0 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -1,30 +1,28 @@ plugins { - kotlin("multiplatform") - id("kotlinx-atomicfu") version "0.12.4" + id("multiplatform-config") + id("kotlinx-atomicfu") version Ver.atomicfuVersion } -val atomicfuVersion: String by rootProject.extra - kotlin { - jvm () + jvm() js() - sourceSets { - val commonMain by getting { + sourceSets.invoke { + commonMain { dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:$atomicfuVersion") + compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") } } - val jvmMain by getting { + "jvmMain" { dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu:$atomicfuVersion") + compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") } } - val jsMain by getting { + "jsMain" { dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu-js:$atomicfuVersion") + compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Ver.atomicfuVersion}") } } @@ -33,4 +31,4 @@ kotlin { atomicfu { variant = "VH" -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 69b1f90fe..8684de3ee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,9 +10,6 @@ pluginManagement { "kotlinx-atomicfu" -> { useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") } - "kotlin-multiplatform" ->{ - useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") - } } } } From f797d7b082ac594874914314f8ba75986d6b3163 Mon Sep 17 00:00:00 2001 From: Andrey Mischenko Date: Tue, 23 Apr 2019 17:47:57 +0800 Subject: [PATCH 12/34] Use type safe accessors for sourceSets that available now with multiplatform-config --- .../src/main/kotlin/multiplatform-config.gradle.kts | 6 ------ kmath-core/build.gradle.kts | 3 --- kmath-coroutines/build.gradle.kts | 13 +++++-------- kmath-histograms/build.gradle.kts | 9 +++------ kmath-koma/build.gradle.kts | 12 +++++------- kmath-memory/build.gradle.kts | 6 ------ kmath-sequential/build.gradle.kts | 9 +++------ 7 files changed, 16 insertions(+), 42 deletions(-) diff --git a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts index 0008a72eb..dddb51730 100644 --- a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts @@ -1,10 +1,4 @@ -import com.moowork.gradle.node.NodeExtension -import com.moowork.gradle.node.npm.NpmTask -import com.moowork.gradle.node.task.NodeTask import org.gradle.kotlin.dsl.* -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("multiplatform") diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 93f4a1af6..6faba5a1b 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -6,9 +6,6 @@ val ioVersion: String by rootProject.extra kotlin { - jvm() - js() - sourceSets { commonMain { dependencies { diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index d249e1c48..627b475d5 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -3,10 +3,7 @@ plugins { } kotlin { - jvm() - js() - - sourceSets.invoke { + sourceSets { commonMain { dependencies { api(project(":kmath-core")) @@ -19,23 +16,23 @@ kotlin { implementation(kotlin("test-annotations-common")) } } - "jvmMain" { + jvmMain { dependencies { api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Ver.coroutinesVersion}") } } - "jvmTest" { + jvmTest { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - "jsMain" { + jsMain { dependencies { api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Ver.coroutinesVersion}") } } - "jsTest" { + jsTest { dependencies { implementation(kotlin("test-js")) } diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 069c11bc9..61eb61a43 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -3,10 +3,7 @@ plugins { } kotlin { - jvm() - js() - - sourceSets.invoke { + sourceSets { commonMain { dependencies { api(project(":kmath-core")) @@ -18,13 +15,13 @@ kotlin { implementation(kotlin("test-annotations-common")) } } - "jvmTest" { + jvmTest { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - "jsTest" { + jsTest { dependencies { implementation(kotlin("test-js")) } diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index 5d8e10cb1..4270ab7d5 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -14,10 +14,8 @@ kotlin { } } } - js() - - sourceSets.invoke { + sourceSets { commonMain { dependencies { api(project(":kmath-core")) @@ -30,24 +28,24 @@ kotlin { implementation(kotlin("test-annotations-common")) } } - "jvmMain" { + jvmMain { dependencies { api("com.kyonifer:koma-core-api-jvm:0.12") } } - "jvmTest" { + jvmTest { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) implementation("com.kyonifer:koma-core-ejml:0.12") } } - "jsMain" { + jsMain { dependencies { api("com.kyonifer:koma-core-api-js:0.12") } } - "jsTest" { + jsTest { dependencies { implementation(kotlin("test-js")) } diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index 12cce0bbb..e23030fba 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -1,9 +1,3 @@ plugins { id("multiplatform-config") } - -// We actually don't need this, we define jvm and js targets in multiplatform-config -kotlin { - jvm() - js() -} diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 6309d97d0..8d24f8cc9 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -4,10 +4,7 @@ plugins { } kotlin { - jvm() - js() - - sourceSets.invoke { + sourceSets { commonMain { dependencies { api(project(":kmath-core")) @@ -15,12 +12,12 @@ kotlin { compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") } } - "jvmMain" { + jvmMain { dependencies { compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") } } - "jsMain" { + jsMain { dependencies { compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Ver.atomicfuVersion}") } From bae77b6979a92fbb67e0346a506def5260c8b5e0 Mon Sep 17 00:00:00 2001 From: Andrey Mischenko Date: Tue, 23 Apr 2019 18:07:36 +0800 Subject: [PATCH 13/34] Use type safe plugin accessors, collapse some nested DSLs, removed duplicate declarations --- benchmarks/build.gradle | 2 +- .../kotlin/multiplatform-config.gradle.kts | 2 + kmath-core/build.gradle.kts | 22 +++---- kmath-coroutines/build.gradle.kts | 47 +++++---------- kmath-histograms/build.gradle.kts | 30 ++-------- kmath-koma/build.gradle.kts | 57 ++++++------------- kmath-memory/build.gradle.kts | 2 +- kmath-sequential/build.gradle.kts | 38 ++++++------- 8 files changed, 66 insertions(+), 134 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 989590397..7c948e219 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -21,7 +21,7 @@ dependencies { //jmh project(':kmath-core') } -jmh{ +jmh { warmupIterations = 1 } diff --git a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts index dddb51730..1d40a1114 100644 --- a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts +++ b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts @@ -11,6 +11,8 @@ kotlin { compilations.all { kotlinOptions { jvmTarget = "1.8" + // This was used in kmath-koma, but probably if we need it better to apply it for all modules + //freeCompilerArgs += "-progressive" } } } diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 6faba5a1b..b24d41a87 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -1,20 +1,14 @@ plugins { - id("multiplatform-config") + `multiplatform-config` } -val ioVersion: String by rootProject.extra - - -kotlin { - sourceSets { - commonMain { - dependencies { - api(project(":kmath-memory")) - } +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-memory")) } -// mingwMain { -// } -// mingwTest { -// } } + //mingwMain {} + //mingwTest {} } + diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index 627b475d5..e72514803 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -1,41 +1,22 @@ plugins { - id("multiplatform-config") + `multiplatform-config` } -kotlin { - sourceSets { - commonMain { - dependencies { - api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Ver.coroutinesVersion}") - } +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Ver.coroutinesVersion}") } - commonTest { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } + } + jvmMain { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Ver.coroutinesVersion}") } - jvmMain { - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Ver.coroutinesVersion}") - } - } - jvmTest { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - jsMain { - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Ver.coroutinesVersion}") - } - } - jsTest { - dependencies { - implementation(kotlin("test-js")) - } + } + jsMain { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Ver.coroutinesVersion}") } } } diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 61eb61a43..a65115f90 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -1,30 +1,10 @@ plugins { - id("multiplatform-config") + `multiplatform-config` } -kotlin { - sourceSets { - commonMain { - dependencies { - api(project(":kmath-core")) - } - } - commonTest { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - jvmTest { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - jsTest { - dependencies { - implementation(kotlin("test-js")) - } - } +// Just an example how we can collapse nested DSL for simple declarations +kotlin.sourceSets.commonMain { + dependencies { + api(project(":kmath-core")) } } diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index 4270ab7d5..5942182d5 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -1,54 +1,31 @@ plugins { - id("multiplatform-config") + `multiplatform-config` } repositories { maven("http://dl.bintray.com/kyonifer/maven") } -kotlin { - jvm { - compilations.all { - kotlinOptions { - freeCompilerArgs += "-progressive" - } +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + api("com.kyonifer:koma-core-api-common:0.12") } } - - sourceSets { - commonMain { - dependencies { - api(project(":kmath-core")) - api("com.kyonifer:koma-core-api-common:0.12") - } + jvmMain { + dependencies { + api("com.kyonifer:koma-core-api-jvm:0.12") } - commonTest { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } + } + jvmTest { + dependencies { + implementation("com.kyonifer:koma-core-ejml:0.12") } - jvmMain { - dependencies { - api("com.kyonifer:koma-core-api-jvm:0.12") - } - } - jvmTest { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - implementation("com.kyonifer:koma-core-ejml:0.12") - } - } - jsMain { - dependencies { - api("com.kyonifer:koma-core-api-js:0.12") - } - } - jsTest { - dependencies { - implementation(kotlin("test-js")) - } + } + jsMain { + dependencies { + api("com.kyonifer:koma-core-api-js:0.12") } } } diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index e23030fba..c42d35c3a 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -1,3 +1,3 @@ plugins { - id("multiplatform-config") + `multiplatform-config` } diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 8d24f8cc9..26449f6d4 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -1,29 +1,27 @@ plugins { - id("multiplatform-config") + `multiplatform-config` id("kotlinx-atomicfu") version Ver.atomicfuVersion } -kotlin { - sourceSets { - commonMain { - dependencies { - api(project(":kmath-core")) - api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") - } +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + api(project(":kmath-coroutines")) + compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") } - jvmMain { - dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") - } - } - jsMain { - dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Ver.atomicfuVersion}") - } - } - } + jvmMain { + dependencies { + compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") + } + } + jsMain { + dependencies { + compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Ver.atomicfuVersion}") + } + } + } atomicfu { From 4cd316758aca9a63c5d49f9010f3aec16f6171dd Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 23 Apr 2019 13:10:38 +0300 Subject: [PATCH 14/34] Build refactoring --- build.gradle.kts | 25 ++++----- buildSrc/src/main/kotlin/js-test.gradle.kts | 56 +++++++++++---------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ea3b00d36..49592b0d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin + val kmathVersion by extra("0.1.2-dev-1") allprojects { @@ -16,20 +18,19 @@ subprojects { //apply artifactory configuration apply(plugin = "artifactory-config") - - plugins.withId("org.jetbrains.kotlin.multiplatform") { - apply(plugin = "multiplatform-config") - } - } - // dokka { -// outputFormat = "html" -// outputDirectory = javadoc.destinationDir -// } + + plugins.withType { + apply(plugin = "multiplatform-config") +// dokka { +// outputFormat = "html" +// outputDirectory = javadoc.destinationDir +// } // -// task dokkaJar (type: Jar, dependsOn: dokka) { -// from javadoc . destinationDir -// classifier = "javadoc" +// task dokkaJar (type: Jar, dependsOn: dokka) { +// from javadoc . destinationDir +// classifier = "javadoc" // } + } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/js-test.gradle.kts b/buildSrc/src/main/kotlin/js-test.gradle.kts index ce408e2ec..1f49b2c3b 100644 --- a/buildSrc/src/main/kotlin/js-test.gradle.kts +++ b/buildSrc/src/main/kotlin/js-test.gradle.kts @@ -1,6 +1,7 @@ +import com.moowork.gradle.node.NodeExtension import com.moowork.gradle.node.npm.NpmTask import com.moowork.gradle.node.task.NodeTask -import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile plugins { @@ -8,42 +9,45 @@ plugins { kotlin("multiplatform") } -node { +configure { nodeModulesDir = file("$buildDir/node_modules") } val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) -val populateNodeModules by tasks.registering(Copy::class) { - dependsOn(compileKotlinJs) - from(compileKotlinJs.destinationDir) - - kotlin.js().compilations["test"].runtimeDependencyFiles.forEach { - if (it.exists() && !it.isDirectory) { - from(zipTree(it.absolutePath).matching { include("*.js") }) - } - } - - into("$buildDir/node_modules") -} - -val installMocha by tasks.registering { - setWorkingDir(buildDir) - setArgs(listOf("install", "mocha")) -} - - inline fun TaskContainer.registering( crossinline action: T.() -> Unit ): RegisteringDomainObjectDelegateProviderWithTypeAndAction = RegisteringDomainObjectDelegateProviderWithTypeAndAction.of(this, T::class, { action() }) -val runMocha by tasks.registering(NodeTask::class) { - dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) - setScript(file("$buildDir/node_modules/mocha/bin/mocha")) - setArgs(listOf(compileTestKotlinJs.outputFile)) +configure { + + val populateNodeModules by tasks.registering(Copy::class) { + dependsOn(compileKotlinJs) + from(compileKotlinJs.destinationDir) + + js().compilations["test"].runtimeDependencyFiles.forEach { + if (it.exists() && !it.isDirectory) { + from(zipTree(it.absolutePath).matching { include("*.js") }) + } + } + + into("$buildDir/node_modules") + } + + val installMocha by tasks.registering { + setWorkingDir(buildDir) + setArgs(listOf("install", "mocha")) + } + + val runMocha by tasks.registering(NodeTask::class) { + dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) + setScript(file("$buildDir/node_modules/mocha/bin/mocha")) + setArgs(listOf(compileTestKotlinJs.outputFile)) + } + + tasks["jsTest"].dependsOn(runMocha) } -tasks["jsTest"].dependsOn(runMocha) From da4a756a4df73c9f09344d3678147580b8b422d4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 23 Apr 2019 21:36:04 +0300 Subject: [PATCH 15/34] sequential renamed to streaming --- kmath-commons/build.gradle.kts | 2 +- .../kmath/transform/Transformations.kt | 6 ++-- .../build.gradle.kts | 0 kmath-streaming/build.gradle.kts | 36 +++++++++++++++++++ .../kmath/streaming}/BufferStreaming.kt | 2 +- .../scientifik/kmath/streaming}/Chain.kt | 2 +- .../scientifik/kmath/streaming}/Cumulative.kt | 2 +- .../scientifik/kmath/streaming}/RingBuffer.kt | 2 +- .../scientifik/kmath/streaming}/Streaming.kt | 2 +- .../kmath/streaming}/CumulativeKtTest.kt | 4 +-- .../scientifik/kmath/streaming}/ChainExt.kt | 2 +- .../kotlin/streaming}/RingBufferTest.kt | 4 +-- 12 files changed, 48 insertions(+), 16 deletions(-) rename {kmath-sequential => kmath-prob}/build.gradle.kts (100%) create mode 100644 kmath-streaming/build.gradle.kts rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/BufferStreaming.kt (98%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/Chain.kt (99%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/Cumulative.kt (97%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/RingBuffer.kt (98%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/Streaming.kt (99%) rename {kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonTest/kotlin/scientifik/kmath/streaming}/CumulativeKtTest.kt (71%) rename {kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/jvmMain/kotlin/scientifik/kmath/streaming}/ChainExt.kt (96%) rename {kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential => kmath-streaming/src/jvmTest/kotlin/streaming}/RingBufferTest.kt (80%) diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index ad8b3912a..a340a1e78 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -7,7 +7,7 @@ description = "Commons math binding for kmath" dependencies { api(project(":kmath-core")) - api(project(":kmath-sequential")) + api(project(":kmath-streaming")) api("org.apache.commons:commons-math3:3.6.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt index 17907adbe..89ecf5f9f 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt @@ -2,9 +2,9 @@ package scientifik.kmath.transform import org.apache.commons.math3.transform.* import scientifik.kmath.operations.Complex -import scientifik.kmath.sequential.Processor -import scientifik.kmath.sequential.Producer -import scientifik.kmath.sequential.map +import scientifik.kmath.streaming.Processor +import scientifik.kmath.streaming.Producer +import scientifik.kmath.streaming.map import scientifik.kmath.structures.* diff --git a/kmath-sequential/build.gradle.kts b/kmath-prob/build.gradle.kts similarity index 100% rename from kmath-sequential/build.gradle.kts rename to kmath-prob/build.gradle.kts diff --git a/kmath-streaming/build.gradle.kts b/kmath-streaming/build.gradle.kts new file mode 100644 index 000000000..45979c89f --- /dev/null +++ b/kmath-streaming/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + kotlin("multiplatform") + id("kotlinx-atomicfu") +} + +val atomicfuVersion: String by rootProject.extra + +kotlin { + jvm () + js() + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":kmath-core")) + api(project(":kmath-coroutines")) + compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") + } + } + val jvmMain by getting { + dependencies { + compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") + } + } + val jsMain by getting { + dependencies { + compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Ver.atomicfuVersion}") + } + } + + } +} + +atomicfu { + variant = "VH" +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt similarity index 98% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt index b98f48186..023199452 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Chain.kt similarity index 99% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Chain.kt index 7633b2223..3f81e4365 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Chain.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineScope diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Cumulative.kt similarity index 97% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Cumulative.kt index b0e1e9ac5..942ed7b50 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Cumulative.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import scientifik.kmath.operations.Space import kotlin.jvm.JvmName diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt similarity index 98% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt index 108b7f828..ac64f921f 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt similarity index 99% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt index c0332d639..3cb3bcfdd 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope diff --git a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt b/kmath-streaming/src/commonTest/kotlin/scientifik/kmath/streaming/CumulativeKtTest.kt similarity index 71% rename from kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt rename to kmath-streaming/src/commonTest/kotlin/scientifik/kmath/streaming/CumulativeKtTest.kt index cafa0526f..75a08274d 100644 --- a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt +++ b/kmath-streaming/src/commonTest/kotlin/scientifik/kmath/streaming/CumulativeKtTest.kt @@ -1,8 +1,6 @@ package scientifik.kmath.misc -import scientifik.kmath.sequential.cumulativeSum -import kotlin.test.Test -import kotlin.test.assertEquals +import scientifik.kmath.streaming.cumulativeSum class CumulativeKtTest { @Test diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt b/kmath-streaming/src/jvmMain/kotlin/scientifik/kmath/streaming/ChainExt.kt similarity index 96% rename from kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt rename to kmath-streaming/src/jvmMain/kotlin/scientifik/kmath/streaming/ChainExt.kt index 74cb6bc6d..b273d9fa0 100644 --- a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt +++ b/kmath-streaming/src/jvmMain/kotlin/scientifik/kmath/streaming/ChainExt.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.runBlocking import kotlin.sequences.Sequence diff --git a/kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt b/kmath-streaming/src/jvmTest/kotlin/streaming/RingBufferTest.kt similarity index 80% rename from kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt rename to kmath-streaming/src/jvmTest/kotlin/streaming/RingBufferTest.kt index c8f84e7d8..48d12755b 100644 --- a/kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt +++ b/kmath-streaming/src/jvmTest/kotlin/streaming/RingBufferTest.kt @@ -1,9 +1,7 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.runBlocking import scientifik.kmath.structures.asSequence -import kotlin.test.Test -import kotlin.test.assertEquals class RingBufferTest { @Test From 7b899365dbec5bce2545e84f47aecbee9a807aa0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 23 Apr 2019 21:36:25 +0300 Subject: [PATCH 16/34] Build refactoring --- settings.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index b70b8d8be..f8a3797ae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ include( ":kmath-histograms", ":kmath-commons", ":kmath-koma", - ":kmath-sequential", + ":kmath-streaming", + ":kmath-prob", ":benchmarks" ) From 3de3ff0219be2d65a6a343378aaced80d2999017 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 23 Apr 2019 22:13:22 +0300 Subject: [PATCH 17/34] Build refactoring --- kmath-streaming/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kmath-streaming/build.gradle.kts b/kmath-streaming/build.gradle.kts index 562fe1c57..2fead4f39 100644 --- a/kmath-streaming/build.gradle.kts +++ b/kmath-streaming/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") + `multiplatform-config` id("kotlinx-atomicfu") version Ver.atomicfuVersion } From 9182a4279c3910765278d14c421df6a240f2baa7 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 23 Apr 2019 22:31:05 +0300 Subject: [PATCH 18/34] Revert to single build file --- .gitignore | 3 +- build.gradle.kts | 219 ++++++++++++++++-- buildSrc/build.gradle.kts | 18 -- buildSrc/settings.gradle.kts | 0 buildSrc/src/main/kotlin/Dep.kt | 10 - .../main/kotlin/artifactory-config.gradle.kts | 38 --- .../src/main/kotlin/bintray-config.gradle.kts | 97 -------- buildSrc/src/main/kotlin/js-test.gradle.kts | 53 ----- .../kotlin/multiplatform-config.gradle.kts | 119 ---------- gradle/artifactory.gradle | 31 +++ gradle/bintray.gradle | 85 +++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- kmath-commons/build.gradle.kts | 6 +- kmath-core/build.gradle.kts | 4 +- kmath-coroutines/build.gradle.kts | 22 +- kmath-histograms/build.gradle.kts | 13 +- kmath-io/build.gradle | 4 +- kmath-sequential/build.gradle.kts | 8 +- settings.gradle.kts | 7 +- 19 files changed, 356 insertions(+), 383 deletions(-) delete mode 100644 buildSrc/build.gradle.kts delete mode 100644 buildSrc/settings.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/Dep.kt delete mode 100644 buildSrc/src/main/kotlin/artifactory-config.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/bintray-config.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/js-test.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/multiplatform-config.gradle.kts create mode 100644 gradle/artifactory.gradle create mode 100644 gradle/bintray.gradle diff --git a/.gitignore b/.gitignore index 72fe16c2d..a9294eff9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ build/ out/ .idea/ -*.iml # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar @@ -10,4 +9,4 @@ out/ # Cache of project .gradletasknamecache -gradle.properties +gradle.properties \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 49592b0d7..b4610930e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,45 @@ -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin +import com.moowork.gradle.node.NodeExtension +import com.moowork.gradle.node.npm.NpmTask +import com.moowork.gradle.node.task.NodeTask +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +buildscript { + val kotlinVersion: String by rootProject.extra("1.3.30") + val ioVersion: String by rootProject.extra("0.1.5") + val coroutinesVersion: String by rootProject.extra("1.2.0") + val atomicfuVersion: String by rootProject.extra("0.12.1") + val dokkaVersion: String by rootProject.extra("0.9.17") + val serializationVersion: String by rootProject.extra("0.10.0") + + repositories { + jcenter() + maven("https://dl.bintray.com/kotlin/kotlin-eap") + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+") + classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") + //classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") + //classpath("org.openjfx:javafx-plugin:0.0.7") + } +} + +plugins { + id("com.jfrog.artifactory") version "4.9.1" apply false + id("com.moowork.node") version "1.3.1" apply false +} val kmathVersion by extra("0.1.2-dev-1") allprojects { + apply(plugin = "maven") + apply(plugin = "maven-publish") + apply(plugin = "com.jfrog.artifactory") + repositories { jcenter() maven("https://kotlin.bintray.com/kotlinx") @@ -12,25 +49,177 @@ allprojects { } subprojects { - if (name.startsWith("kmath")) { + if(name.startsWith("kmath")) { // apply bintray configuration - apply(plugin = "bintray-config") + apply(from = "${rootProject.rootDir}/gradle/bintray.gradle") //apply artifactory configuration - apply(plugin = "artifactory-config") + apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle") + } + // dokka { +// outputFormat = "html" +// outputDirectory = javadoc.destinationDir +// } +// +// task dokkaJar (type: Jar, dependsOn: dokka) { +// from javadoc . destinationDir +// classifier = "javadoc" +// } + + // Create empty jar for sources classifier to satisfy maven requirements + val stubSources by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + //from(sourceSets.main.get().allSource) } - plugins.withType { - apply(plugin = "multiplatform-config") -// dokka { -// outputFormat = "html" -// outputDirectory = javadoc.destinationDir -// } -// -// task dokkaJar (type: Jar, dependsOn: dokka) { -// from javadoc . destinationDir -// classifier = "javadoc" -// } + // Create empty jar for javadoc classifier to satisfy maven requirements + val stubJavadoc by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + } + + tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + } + } + + + afterEvaluate { + extensions.findByType()?.apply { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + //freeCompilerArgs = listOf("-Xno-call-assertions", "-Xno-param-assertions") + } + } + } + + js { + compilations.all { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + metaInfo = true + sourceMap = true + sourceMapEmbedSources = "always" + moduleKind = "commonjs" + } + } + } + + configure(listOf(compilations["main"])) { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + main = "call" + } + } + } + + + val runJsTests by ext(false) + + if(runJsTests) { + apply(plugin = "com.moowork.node") + configure { + nodeModulesDir = file("$buildDir/node_modules") + } + + val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) + val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) + + val populateNodeModules by tasks.registering(Copy::class) { + dependsOn(compileKotlinJs) + from(compileKotlinJs.destinationDir) + + compilations["test"].runtimeDependencyFiles.forEach { + if (it.exists() && !it.isDirectory) { + from(zipTree(it.absolutePath).matching { include("*.js") }) + } + } + + into("$buildDir/node_modules") + } + + val installMocha by tasks.registering(NpmTask::class) { + setWorkingDir(buildDir) + setArgs(listOf("install", "mocha")) + } + + val runMocha by tasks.registering(NodeTask::class) { + dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) + setScript(file("$buildDir/node_modules/mocha/bin/mocha")) + setArgs(listOf(compileTestKotlinJs.outputFile)) + } + + tasks["jsTest"].dependsOn(runMocha) + } + } + + sourceSets { + + val commonMain by getting { + dependencies { + api(kotlin("stdlib")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + api(kotlin("stdlib-jdk8")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + val jsMain by getting { + dependencies { + api(kotlin("stdlib-js")) + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } + } + + targets.all { + sourceSets.all { + languageSettings.progressiveMode = true + languageSettings.enableLanguageFeature("InlineClasses") + languageSettings.useExperimentalAnnotation("ExperimentalContracts") + //languageSettings.enableLanguageFeature("Contracts") + } + } + + configure { + + publications.filterIsInstance().forEach { publication -> + if (publication.name == "kotlinMultiplatform") { + // for our root metadata publication, set artifactId with a package and project name + publication.artifactId = project.name + } else { + // for targets, set artifactId with a package, project name and target name (e.g. iosX64) + publication.artifactId = "${project.name}-${publication.name}" + } + } + + targets.all { + val publication = publications.findByName(name) as MavenPublication + + // Patch publications with fake javadoc + publication.artifact(stubJavadoc.get()) + } + } + } } } \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 65827214c..000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - gradlePluginPortal() - jcenter() -} - -val kotlinVersion = "1.3.30" - -// Add plugins used in buildSrc as dependencies, also we should specify version only here -dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") - implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.9.5") - implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") - implementation("com.moowork.gradle:gradle-node-plugin:1.3.1") -} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts deleted file mode 100644 index e69de29bb..000000000 diff --git a/buildSrc/src/main/kotlin/Dep.kt b/buildSrc/src/main/kotlin/Dep.kt deleted file mode 100644 index ab7a243a7..000000000 --- a/buildSrc/src/main/kotlin/Dep.kt +++ /dev/null @@ -1,10 +0,0 @@ -// Instead of defining runtime properties and use them dynamically -// define version in buildSrc and have autocompletion and compile-time check -// Also dependencies itself can be moved here -object Ver { - val ioVersion = "0.1.5" - val coroutinesVersion = "1.1.1" - val atomicfuVersion = "0.12.1" - // This version is not used and IDEA shows this property as unused - val dokkaVersion = "0.9.17" -} diff --git a/buildSrc/src/main/kotlin/artifactory-config.gradle.kts b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts deleted file mode 100644 index 8bbdf1e54..000000000 --- a/buildSrc/src/main/kotlin/artifactory-config.gradle.kts +++ /dev/null @@ -1,38 +0,0 @@ -import groovy.lang.GroovyObject -import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig -import org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig - -plugins { - id("com.jfrog.artifactory") -} - -artifactory { - val artifactoryUser: String? by project - val artifactoryPassword: String? by project - val artifactoryContextUrl = "http://npm.mipt.ru:8081/artifactory" - - setContextUrl(artifactoryContextUrl)//The base Artifactory URL if not overridden by the publisher/resolver - publish(delegateClosureOf { - repository(delegateClosureOf { - setProperty("repoKey", "gradle-dev-local") - setProperty("username", artifactoryUser) - setProperty("password", artifactoryPassword) - }) - - defaults(delegateClosureOf{ - invokeMethod("publications", arrayOf("jvm", "js", "kotlinMultiplatform", "metadata")) - //TODO: This property is not available for ArtifactoryTask - //setProperty("publishBuildInfo", false) - setProperty("publishArtifacts", true) - setProperty("publishPom", true) - setProperty("publishIvy", false) - }) - }) - resolve(delegateClosureOf { - repository(delegateClosureOf { - setProperty("repoKey", "gradle-dev") - setProperty("username", artifactoryUser) - setProperty("password", artifactoryPassword) - }) - }) -} diff --git a/buildSrc/src/main/kotlin/bintray-config.gradle.kts b/buildSrc/src/main/kotlin/bintray-config.gradle.kts deleted file mode 100644 index 29a81f617..000000000 --- a/buildSrc/src/main/kotlin/bintray-config.gradle.kts +++ /dev/null @@ -1,97 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig -import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig - -// Old bintray.gradle script converted to real Gradle plugin (precompiled script plugin) -// It now has own dependencies and support type safe accessors -// Syntax is pretty close to what we had in Groovy -// (excluding Property.set and bintray dynamic configs) - -plugins { - id("com.jfrog.bintray") - `maven-publish` -} - -val vcs = "https://github.com/mipt-npm/kmath" - -// Configure publishing -publishing { - repositories { - maven("https://bintray.com/mipt-npm/scientifik") - } - - // Process each publication we have in this project - publications.filterIsInstance().forEach { publication -> - - // use type safe pom config GSL insterad of old dynamic - publication.pom { - name.set(project.name) - description.set(project.description) - url.set(vcs) - - licenses { - license { - name.set("The Apache Software License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - distribution.set("repo") - } - } - developers { - developer { - id.set("MIPT-NPM") - name.set("MIPT nuclear physics methods laboratory") - organization.set("MIPT") - organizationUrl.set("http://npm.mipt.ru") - } - - } - scm { - url.set(vcs) - } - } - - } -} - -bintray { - // delegates for runtime properties - val bintrayUser: String? by project - val bintrayApiKey: String? by project - user = bintrayUser ?: System.getenv("BINTRAY_USER") - key = bintrayApiKey ?: System.getenv("BINTRAY_API_KEY") - publish = true - override = true // for multi-platform Kotlin/Native publishing - - // We have to use delegateClosureOf because bintray supports only dynamic groovy syntax - // this is a problem of this plugin - pkg(delegateClosureOf { - userOrg = "mipt-npm" - repo = "scientifik" - name = "scientifik.kmath" - issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" - setLicenses("Apache-2.0") - vcsUrl = vcs - version(delegateClosureOf { - name = project.version.toString() - vcsTag = project.version.toString() - released = java.util.Date().toString() - }) - }) - - tasks { - bintrayUpload { - dependsOn(publishToMavenLocal) - doFirst { - setPublications(project.publishing.publications - .filterIsInstance() - .filter { !it.name.contains("-test") && it.name != "kotlinMultiplatform" } - .map { - println("""Uploading artifact "${it.groupId}:${it.artifactId}:${it.version}" from publication "${it.name}""") - it.name //https://github.com/bintray/gradle-bintray-plugin/issues/256 - }) - } - } - - } -} diff --git a/buildSrc/src/main/kotlin/js-test.gradle.kts b/buildSrc/src/main/kotlin/js-test.gradle.kts deleted file mode 100644 index 1f49b2c3b..000000000 --- a/buildSrc/src/main/kotlin/js-test.gradle.kts +++ /dev/null @@ -1,53 +0,0 @@ -import com.moowork.gradle.node.NodeExtension -import com.moowork.gradle.node.npm.NpmTask -import com.moowork.gradle.node.task.NodeTask -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile - -plugins { - id("com.moowork.node") - kotlin("multiplatform") -} - -configure { - nodeModulesDir = file("$buildDir/node_modules") -} - -val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class) -val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class) - -inline fun TaskContainer.registering( - crossinline action: T.() -> Unit -): RegisteringDomainObjectDelegateProviderWithTypeAndAction = - RegisteringDomainObjectDelegateProviderWithTypeAndAction.of(this, T::class, { action() }) - - -configure { - - val populateNodeModules by tasks.registering(Copy::class) { - dependsOn(compileKotlinJs) - from(compileKotlinJs.destinationDir) - - js().compilations["test"].runtimeDependencyFiles.forEach { - if (it.exists() && !it.isDirectory) { - from(zipTree(it.absolutePath).matching { include("*.js") }) - } - } - - into("$buildDir/node_modules") - } - - val installMocha by tasks.registering { - setWorkingDir(buildDir) - setArgs(listOf("install", "mocha")) - } - - val runMocha by tasks.registering(NodeTask::class) { - dependsOn(compileTestKotlinJs, populateNodeModules, installMocha) - setScript(file("$buildDir/node_modules/mocha/bin/mocha")) - setArgs(listOf(compileTestKotlinJs.outputFile)) - } - - tasks["jsTest"].dependsOn(runMocha) -} - diff --git a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts b/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts deleted file mode 100644 index 0b845aa76..000000000 --- a/buildSrc/src/main/kotlin/multiplatform-config.gradle.kts +++ /dev/null @@ -1,119 +0,0 @@ -import org.gradle.kotlin.dsl.* - -plugins { - kotlin("multiplatform") - `maven-publish` -} - - -kotlin { - jvm { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - } - } - } - - js { - compilations.all { - kotlinOptions { - metaInfo = true - sourceMap = true - sourceMapEmbedSources = "always" - moduleKind = "commonjs" - } - } - - compilations.named("main") { - kotlinOptions { - main = "call" - } - } - } - - sourceSets.invoke { - commonMain { - dependencies { - api(kotlin("stdlib")) - } - } - commonTest { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - "jvmMain" { - dependencies { - api(kotlin("stdlib-jdk8")) - } - } - "jvmTest" { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - } - } - "jsMain" { - dependencies { - api(kotlin("stdlib-js")) - } - } - "jsTest" { - dependencies { - implementation(kotlin("test-js")) - } - } - } - - targets.all { - sourceSets.all { - languageSettings.progressiveMode = true - languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.useExperimentalAnnotation("ExperimentalContracts") - //languageSettings.enableLanguageFeature("Contracts") - } - } - - // Create empty jar for sources classifier to satisfy maven requirements - tasks.register("stubSources") { - archiveClassifier.set("sources") - //from(sourceSets.main.get().allSource) - } - - // Create empty jar for javadoc classifier to satisfy maven requirements - val stubJavadoc by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") - } - - - publishing { - - publications.filterIsInstance().forEach { publication -> - if (publication.name == "kotlinMultiplatform") { - // for our root metadata publication, set artifactId with a package and project name - publication.artifactId = project.name - } else { - // for targets, set artifactId with a package, project name and target name (e.g. iosX64) - publication.artifactId = "${project.name}-${publication.name}" - } - } - - targets.all { - val publication = publications.findByName(name) as MavenPublication - - // Patch publications with fake javadoc - publication.artifact(stubJavadoc.get()) - } - } - - // Apply JS test configuration - val runJsTests by ext(false) - - if (runJsTests) { - apply(plugin = "js-test") - } - -} - diff --git a/gradle/artifactory.gradle b/gradle/artifactory.gradle new file mode 100644 index 000000000..12e59642b --- /dev/null +++ b/gradle/artifactory.gradle @@ -0,0 +1,31 @@ +apply plugin: "com.jfrog.artifactory" + +artifactory { + def artifactory_user = project.hasProperty('artifactoryUser') ? project.property('artifactoryUser') : "" + def artifactory_password = project.hasProperty('artifactoryPassword') ? project.property('artifactoryPassword') : "" + def artifactory_contextUrl = 'http://npm.mipt.ru:8081/artifactory' + + contextUrl = artifactory_contextUrl //The base Artifactory URL if not overridden by the publisher/resolver + publish { + repository { + repoKey = 'gradle-dev-local' + username = artifactory_user + password = artifactory_password + } + + defaults { + publications('jvm', 'js', 'kotlinMultiplatform', 'metadata') + publishBuildInfo = false + publishArtifacts = true + publishPom = true + publishIvy = false + } + } + resolve { + repository { + repoKey = 'gradle-dev' + username = artifactory_user + password = artifactory_password + } + } +} \ No newline at end of file diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle new file mode 100644 index 000000000..8da83c860 --- /dev/null +++ b/gradle/bintray.gradle @@ -0,0 +1,85 @@ +apply plugin: 'com.jfrog.bintray' + +def vcs = "https://github.com/mipt-npm/kmath" + +def pomConfig = { + licenses { + license { + name "The Apache Software License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0.txt" + distribution "repo" + } + } + developers { + developer { + id "MIPT-NPM" + name "MIPT nuclear physics methods laboratory" + organization "MIPT" + organizationUrl "http://npm.mipt.ru" + } + } + scm { + url vcs + } +} + +project.ext.configureMavenCentralMetadata = { pom -> + def root = asNode() + root.appendNode('name', project.name) + root.appendNode('description', project.description) + root.appendNode('url', vcs) + root.children().last() + pomConfig +} + +project.ext.configurePom = pomConfig + + +// Configure publishing +publishing { + repositories { + maven { + url = "https://bintray.com/mipt-npm/scientifik" + } + } + + // Process each publication we have in this project + publications.all { publication -> + // apply changes to pom.xml files, see pom.gradle + pom.withXml(configureMavenCentralMetadata) + + + } +} + +bintray { + user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') + key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') + publish = true + override = true // for multi-platform Kotlin/Native publishing + + pkg { + userOrg = "mipt-npm" + repo = "scientifik" + name = "scientifik.kmath" + issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" + licenses = ['Apache-2.0'] + vcsUrl = vcs + version { + name = project.version + vcsTag = project.version + released = new Date() + } + } +} + +bintrayUpload.dependsOn publishToMavenLocal + +// This is for easier debugging of bintray uploading problems +bintrayUpload.doFirst { + publications = project.publishing.publications.findAll { + !it.name.contains('-test') && it.name != 'kotlinMultiplatform' + }.collect { + println("Uploading artifact '$it.groupId:$it.artifactId:$it.version' from publication '$it.name'") + it.name//https://github.com/bintray/gradle-bintray-plugin/issues/256 + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5f1b1201a..75b8c7c8c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index ad8b3912a..0ea7b7eff 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -15,15 +15,15 @@ dependencies { val sourcesJar by tasks.registering(Jar::class) { - archiveClassifier.set("sources") + classifier = "sources" from(sourceSets.main.get().allSource) } publishing { publications { - register("jvm") { + register("jvm", MavenPublication::class) { from(components["java"]) artifact(sourcesJar.get()) } } -} +} \ No newline at end of file diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 108ee75fa..ab6ae7822 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -10,7 +10,7 @@ kotlin { js() sourceSets { - commonMain { + val commonMain by getting { dependencies { api(project(":kmath-memory")) } @@ -20,4 +20,4 @@ kotlin { // mingwTest { // } } -} +} \ No newline at end of file diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index 1da34e0d7..c73d4e4dc 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -2,40 +2,42 @@ plugins { kotlin("multiplatform") } +val coroutinesVersion: String by rootProject.extra + kotlin { jvm() js() - sourceSets.invoke { - commonMain { + sourceSets { + val commonMain by getting { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Ver.coroutinesVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") } } - commonTest { + val commonTest by getting { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - "jvmMain" { + val jvmMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Ver.coroutinesVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") } } - "jvmTest" { + val jvmTest by getting { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - "jsMain" { + val jsMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Ver.coroutinesVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") } } - "jsTest" { + val jsTest by getting { dependencies { implementation(kotlin("test-js")) } diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 2e9210843..81b3fb83f 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -6,28 +6,29 @@ kotlin { jvm() js() - sourceSets.invoke { - commonMain { + sourceSets { + + val commonMain by getting { dependencies { api(project(":kmath-core")) } } - commonTest { + val commonTest by getting { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } } - "jvmTest" { + val jvmTest by getting { dependencies { implementation(kotlin("test")) implementation(kotlin("test-junit")) } } - "jsTest" { + val jsTest by getting { dependencies { implementation(kotlin("test-js")) } } } -} +} \ No newline at end of file diff --git a/kmath-io/build.gradle b/kmath-io/build.gradle index 4b1470835..28fb7eee5 100644 --- a/kmath-io/build.gradle +++ b/kmath-io/build.gradle @@ -16,7 +16,7 @@ kotlin { dependencies { api project(":kmath-core") implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' - api "org.jetbrains.kotlinx:kotlinx-io:${Ver.ioVersion}" + api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" } } commonTest { @@ -28,7 +28,7 @@ kotlin { jvmMain { dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - api "org.jetbrains.kotlinx:kotlinx-io-jvm:${Ver.ioVersion}" + api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" } } jvmTest { diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 45979c89f..9ca7a35ad 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("multiplatform") - id("kotlinx-atomicfu") + id("kotlinx-atomicfu") version "0.12.4" } val atomicfuVersion: String by rootProject.extra @@ -14,17 +14,17 @@ kotlin { dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Ver.atomicfuVersion}") + compileOnly("org.jetbrains.kotlinx:atomicfu-common:$atomicfuVersion") } } val jvmMain by getting { dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu:${Ver.atomicfuVersion}") + compileOnly("org.jetbrains.kotlinx:atomicfu:$atomicfuVersion") } } val jsMain by getting { dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Ver.atomicfuVersion}") + compileOnly("org.jetbrains.kotlinx:atomicfu-js:$atomicfuVersion") } } diff --git a/settings.gradle.kts b/settings.gradle.kts index b70b8d8be..69b1f90fe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,9 +8,10 @@ pluginManagement { eachPlugin { when (requested.id.id) { "kotlinx-atomicfu" -> { - // Just hardcode version here, - // because anyway different submodules cannot use different versions - useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.12.1") + useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") + } + "kotlin-multiplatform" ->{ + useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") } } } From 8208cb772f51e53c8296406783b678e446202920 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 24 Apr 2019 09:36:43 +0300 Subject: [PATCH 19/34] Chains moved to coroutines. Cumulatives to core --- kmath-commons/build.gradle.kts | 2 +- .../scientifik/kmath/transform/Transformations.kt | 6 +++--- .../kotlin/scientifik/kmath/misc}/Cumulative.kt | 2 +- .../scientifik/kmath/misc}/CumulativeKtTest.kt | 1 - .../kotlin/sicentifik/kmath/chains}/Chain.kt | 15 ++++++--------- .../kotlin/scientifik/kmath/chains}/ChainExt.kt | 3 ++- .../build.gradle.kts | 0 .../kmath/streaming}/BufferStreaming.kt | 2 +- .../scientifik/kmath/streaming}/RingBuffer.kt | 2 +- .../scientifik/kmath/streaming}/Streaming.kt | 2 +- .../jvmTest/kotlin/streaming}/RingBufferTest.kt | 2 +- settings.gradle.kts | 2 +- 12 files changed, 18 insertions(+), 21 deletions(-) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-core/src/commonMain/kotlin/scientifik/kmath/misc}/Cumulative.kt (97%) rename {kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential => kmath-core/src/commonTest/kotlin/scientifik/kmath/misc}/CumulativeKtTest.kt (86%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains}/Chain.kt (89%) rename {kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential => kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains}/ChainExt.kt (93%) rename {kmath-sequential => kmath-streaming}/build.gradle.kts (100%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/BufferStreaming.kt (98%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/RingBuffer.kt (98%) rename {kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential => kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming}/Streaming.kt (99%) rename {kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential => kmath-streaming/src/jvmTest/kotlin/streaming}/RingBufferTest.kt (92%) diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 0ea7b7eff..6e3a62ef7 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -7,7 +7,7 @@ description = "Commons math binding for kmath" dependencies { api(project(":kmath-core")) - api(project(":kmath-sequential")) + api(project(":kmath-streaming")) api("org.apache.commons:commons-math3:3.6.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt index 17907adbe..89ecf5f9f 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt @@ -2,9 +2,9 @@ package scientifik.kmath.transform import org.apache.commons.math3.transform.* import scientifik.kmath.operations.Complex -import scientifik.kmath.sequential.Processor -import scientifik.kmath.sequential.Producer -import scientifik.kmath.sequential.map +import scientifik.kmath.streaming.Processor +import scientifik.kmath.streaming.Producer +import scientifik.kmath.streaming.map import scientifik.kmath.structures.* diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt similarity index 97% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt index b0e1e9ac5..314696262 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.misc import scientifik.kmath.operations.Space import kotlin.jvm.JvmName diff --git a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt similarity index 86% rename from kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt index cafa0526f..e7c99e7d0 100644 --- a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt @@ -1,6 +1,5 @@ package scientifik.kmath.misc -import scientifik.kmath.sequential.cumulativeSum import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt b/kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains/Chain.kt similarity index 89% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt rename to kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains/Chain.kt index 7633b2223..21c2c9eb3 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt +++ b/kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains/Chain.kt @@ -14,14 +14,10 @@ * limitations under the License. */ -package scientifik.kmath.sequential +package sicentifik.kmath.chains import kotlinx.atomicfu.atomic -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.produce -import kotlinx.coroutines.isActive +import kotlinx.coroutines.FlowPreview /** @@ -47,10 +43,11 @@ interface Chain { } /** - * Chain as a coroutine receive channel + * Chain as a coroutine flow. The flow emit affects chain state and vice versa */ -@ExperimentalCoroutinesApi -fun Chain.asChannel(scope: CoroutineScope): ReceiveChannel = scope.produce { while (isActive) send(next()) } +@FlowPreview +val Chain.flow + get() = kotlinx.coroutines.flow.flow { while (true) emit(next()) } fun Iterator.asChain(): Chain = SimpleChain { next() } fun Sequence.asChain(): Chain = iterator().asChain() diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt similarity index 93% rename from kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt rename to kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt index 74cb6bc6d..e23748bdb 100644 --- a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt @@ -1,6 +1,7 @@ -package scientifik.kmath.sequential +package scientifik.kmath.chains import kotlinx.coroutines.runBlocking +import sicentifik.kmath.chains.Chain import kotlin.sequences.Sequence /** diff --git a/kmath-sequential/build.gradle.kts b/kmath-streaming/build.gradle.kts similarity index 100% rename from kmath-sequential/build.gradle.kts rename to kmath-streaming/build.gradle.kts diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt similarity index 98% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt index b98f48186..023199452 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt similarity index 98% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt index 108b7f828..ac64f921f 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt similarity index 99% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt rename to kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt index c0332d639..3cb3bcfdd 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt +++ b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope diff --git a/kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt b/kmath-streaming/src/jvmTest/kotlin/streaming/RingBufferTest.kt similarity index 92% rename from kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt rename to kmath-streaming/src/jvmTest/kotlin/streaming/RingBufferTest.kt index c8f84e7d8..a2d798d6b 100644 --- a/kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt +++ b/kmath-streaming/src/jvmTest/kotlin/streaming/RingBufferTest.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.sequential +package scientifik.kmath.streaming import kotlinx.coroutines.runBlocking import scientifik.kmath.structures.asSequence diff --git a/settings.gradle.kts b/settings.gradle.kts index 69b1f90fe..b053776f3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,6 +29,6 @@ include( ":kmath-histograms", ":kmath-commons", ":kmath-koma", - ":kmath-sequential", + ":kmath-streaming", ":benchmarks" ) From 55bf621363ced5c47d73089b8d01366c9c6da254 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 24 Apr 2019 09:39:25 +0300 Subject: [PATCH 20/34] wrapper update --- gradle/wrapper/gradle-wrapper.jar | Bin 55741 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 18 +++++++++++++++++- gradlew.bat | 18 +++++++++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 457aad0d98108420a977756b7145c93c8910b076..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch delta 19567 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 PFlPAY0appj7IgMMQM_1| diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 75b8c7c8c..5f1b1201a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708ff2..b0d6d0ab5 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index 6d57edc70..9991c5032 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome From 13a246d85d10c7271c5ac601bca43635b772f30b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 24 Apr 2019 14:11:51 +0300 Subject: [PATCH 21/34] 0.1.2-dev-2 --- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/artifactory-config.gradle.kts | 2 +- buildSrc/src/main/kotlin/bintray-config.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0084793bd..119ed11b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -val kmathVersion by extra("0.1.2-dev-1") +val kmathVersion by extra("0.1.2-dev-2") allprojects { // apply(plugin = "maven") diff --git a/buildSrc/src/main/kotlin/artifactory-config.gradle.kts b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts index 0eec888f1..d792dffb7 100644 --- a/buildSrc/src/main/kotlin/artifactory-config.gradle.kts +++ b/buildSrc/src/main/kotlin/artifactory-config.gradle.kts @@ -3,7 +3,7 @@ import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig import org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig plugins { - id("com.jfrog.artifactory") version "4.9.5" + id("com.jfrog.artifactory") } artifactory { diff --git a/buildSrc/src/main/kotlin/bintray-config.gradle.kts b/buildSrc/src/main/kotlin/bintray-config.gradle.kts index 15c178953..b152d1639 100644 --- a/buildSrc/src/main/kotlin/bintray-config.gradle.kts +++ b/buildSrc/src/main/kotlin/bintray-config.gradle.kts @@ -9,7 +9,7 @@ import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig // (excluding Property.set and bintray dynamic configs) plugins { - id("com.jfrog.bintray") version "1.8.4" + id("com.jfrog.bintray") `maven-publish` } From 68ccc0b3fcb698a6c87c5fd26fd8e32033ff5da8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 26 Apr 2019 21:36:28 +0300 Subject: [PATCH 22/34] Migrating streaming to Flow --- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/Versions.kt | 2 +- kmath-commons/build.gradle.kts | 2 +- .../kmath/{structures => }/CoroutinesExtra.kt | 2 +- .../kmath/chains/Chain.kt | 4 +- .../scientifik/kmath/streaming/BufferFlow.kt | 95 ++++++ .../kmath/streaming/BufferStreaming.kt | 82 ++++++ .../scientifik/kmath/streaming/RingBuffer.kt | 89 ++++++ .../scientifik/kmath/streaming/Streaming.kt | 273 ++++++++++++++++++ .../scientifik/kmath/chains/ChainExt.kt | 1 - .../kmath/structures/LazyNDStructure.kt | 1 + .../kmath/streaming/BufferFlowTest.kt | 27 ++ .../kmath/streaming/RingBufferTest.kt | 0 settings.gradle.kts | 1 - 14 files changed, 573 insertions(+), 8 deletions(-) rename kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/{structures => }/CoroutinesExtra.kt (87%) rename kmath-coroutines/src/commonMain/kotlin/{sicentifik => scientifik}/kmath/chains/Chain.kt (97%) create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt create mode 100644 kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt rename {kmath-streaming => kmath-coroutines}/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 119ed11b9..f078c1de8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -val kmathVersion by extra("0.1.2-dev-2") +val kmathVersion by extra("0.1.2-dev-3") allprojects { // apply(plugin = "maven") diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 5133cba48..8279b081a 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -3,7 +3,7 @@ // Also dependencies itself can be moved here object Versions { val ioVersion = "0.1.8" - val coroutinesVersion = "1.2.0" + val coroutinesVersion = "1.2.1" val atomicfuVersion = "0.12.6" // This version is not used and IDEA shows this property as unused val dokkaVersion = "0.9.18" diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 6e3a62ef7..c610749be 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -7,7 +7,7 @@ description = "Commons math binding for kmath" dependencies { api(project(":kmath-core")) - api(project(":kmath-streaming")) + api(project(":kmath-coroutines")) api("org.apache.commons:commons-math3:3.6.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt similarity index 87% rename from kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt index f6f21af09..7580ef6ef 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.structures +package scientifik.kmath import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope diff --git a/kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains/Chain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt similarity index 97% rename from kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains/Chain.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt index 21c2c9eb3..4d2f604e1 100644 --- a/kmath-coroutines/src/commonMain/kotlin/sicentifik/kmath/chains/Chain.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package sicentifik.kmath.chains +package scientifik.kmath.chains import kotlinx.atomicfu.atomic import kotlinx.coroutines.FlowPreview @@ -26,7 +26,7 @@ import kotlinx.coroutines.FlowPreview */ interface Chain { /** - * Last value of the chain. Returns null if [next] was not called + * Last cached value of the chain. Returns null if [next] was not called */ val value: R? diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt new file mode 100644 index 000000000..f6c38e153 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt @@ -0,0 +1,95 @@ +package scientifik.kmath.streaming + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.flow.* +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.DoubleBuffer +import kotlin.coroutines.coroutineContext + +/** + * Create a [Flow] from buffer + */ +@FlowPreview +fun Buffer.asFlow() = iterator().asFlow() + +/** + * Flat map a [Flow] of [Buffer] into continuous [Flow] of elements + */ +@FlowPreview +fun Flow>.spread(): Flow = flatMapConcat { it.asFlow() } + +/** + * Collect incoming flow into fixed size chunks + */ +@FlowPreview +fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory) = flow { + require(bufferSize > 0) { "Resulting chunk size must be more than zero" } + val list = ArrayList(bufferSize) + var counter = 0 + + this@chunked.collect { element -> + list.add(element) + counter++ + if (counter == bufferSize) { + val buffer = bufferFactory(bufferSize) { list[it] } + emit(buffer) + list.clear() + counter = 0 + } + } +} + +/** + * Specialized flow chunker for real buffer + */ +@FlowPreview +fun Flow.chunked(bufferSize: Int) = flow { + require(bufferSize > 0) { "Resulting chunk size must be more than zero" } + val array = DoubleArray(bufferSize) + var counter = 0 + + this@chunked.collect { element -> + array[counter] = element + counter++ + if (counter == bufferSize) { + val buffer = DoubleBuffer(array) + emit(buffer) + } + } +} + +/** + * Perform parallel mapping of flow elements + */ +@InternalCoroutinesApi +@ExperimentalCoroutinesApi +@FlowPreview +fun Flow.mapParallel(dispatcher: CoroutineDispatcher = Dispatchers.Default, bufferSize: Int = 16, transform: suspend (T) -> R) : Flow{ + require(bufferSize >= 0) { + "Buffer size should be positive, but was $bufferSize" + } + return flow { + coroutineScope { + val channel: ReceiveChannel> = produce(capacity = bufferSize) { + collect { value -> + send(async(dispatcher) { transform(value) }) + } + } + + // TODO semantics doesn't play well here and we pay for that with additional object + (channel as Job).invokeOnCompletion { if (it is CancellationException && it.cause == null) cancel() } + for (element in channel) { + emit(element.await()) + } + + val producer = channel as Job + if (producer.isCancelled) { + producer.join() + throw producer.getCancellationException() + } + } + } +} \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt new file mode 100644 index 000000000..023199452 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt @@ -0,0 +1,82 @@ +package scientifik.kmath.streaming + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.isActive +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory + +/** + * A processor that collects incoming elements into fixed size buffers + */ +@ExperimentalCoroutinesApi +class JoinProcessor( + scope: CoroutineScope, + bufferSize: Int, + bufferFactory: BufferFactory = Buffer.Companion::boxing +) : AbstractProcessor>(scope) { + + private val input = Channel(bufferSize) + + private val output = produce(coroutineContext) { + val list = ArrayList(bufferSize) + while (isActive) { + list.clear() + repeat(bufferSize) { + list.add(input.receive()) + } + val buffer = bufferFactory(bufferSize) { list[it] } + send(buffer) + } + } + + override suspend fun receive(): Buffer = output.receive() + + override suspend fun send(value: T) { + input.send(value) + } +} + +/** + * A processor that splits incoming buffers into individual elements + */ +class SplitProcessor(scope: CoroutineScope) : AbstractProcessor, T>(scope) { + + private val input = Channel>() + + private val mutex = Mutex() + + private var currentBuffer: Buffer? = null + + private var pos = 0 + + + override suspend fun receive(): T { + mutex.withLock { + while (currentBuffer == null || pos == currentBuffer!!.size) { + currentBuffer = input.receive() + pos = 0 + } + return currentBuffer!![pos].also { pos++ } + } + } + + override suspend fun send(value: Buffer) { + input.send(value) + } +} + +@ExperimentalCoroutinesApi +fun Producer.chunked(chunkSize: Int, bufferFactory: BufferFactory) = + JoinProcessor(this, chunkSize, bufferFactory).also { connect(it) } + +@ExperimentalCoroutinesApi +inline fun Producer.chunked(chunkSize: Int) = + JoinProcessor(this, chunkSize, Buffer.Companion::auto).also { connect(it) } + + + diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt new file mode 100644 index 000000000..ac64f921f --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt @@ -0,0 +1,89 @@ +package scientifik.kmath.streaming + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.MutableBuffer +import scientifik.kmath.structures.VirtualBuffer + +/** + * Thread-safe ring buffer + */ +internal class RingBuffer( + private val buffer: MutableBuffer, + private var startIndex: Int = 0, + size: Int = 0 +) : Buffer { + + private val mutex = Mutex() + + override var size: Int = size + private set + + override fun get(index: Int): T { + require(index >= 0) { "Index must be positive" } + require(index < size) { "Index $index is out of circular buffer size $size" } + return buffer[startIndex.forward(index)] + } + + fun isFull() = size == buffer.size + + /** + * Iterator could provide wrong results if buffer is changed in initialization (iteration is safe) + */ + override fun iterator(): Iterator = object : AbstractIterator() { + private var count = size + private var index = startIndex + val copy = buffer.copy() + + override fun computeNext() { + if (count == 0) { + done() + } else { + setNext(copy[index]) + index = index.forward(1) + count-- + } + } + } + + /** + * A safe snapshot operation + */ + suspend fun snapshot(): Buffer { + mutex.withLock { + val copy = buffer.copy() + return VirtualBuffer(size) { i -> copy[startIndex.forward(i)] } + } + } + + suspend fun push(element: T) { + mutex.withLock { + buffer[startIndex.forward(size)] = element + if (isFull()) { + startIndex++ + } else { + size++ + } + } + } + + + @Suppress("NOTHING_TO_INLINE") + private inline fun Int.forward(n: Int): Int = (this + n) % (buffer.size) + + companion object { + inline fun build(size: Int, empty: T): RingBuffer { + val buffer = MutableBuffer.auto(size) { empty } + return RingBuffer(buffer) + } + + /** + * Slow yet universal buffer + */ + fun boxing(size: Int): RingBuffer { + val buffer: MutableBuffer = MutableBuffer.boxing(size) { null } + return RingBuffer(buffer) + } + } +} \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt new file mode 100644 index 000000000..0382b6a89 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt @@ -0,0 +1,273 @@ +package scientifik.kmath.streaming + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import scientifik.kmath.structures.Buffer +import kotlin.coroutines.CoroutineContext + +/** + * Initial chain block. Could produce an element sequence and be connected to single [Consumer] + * + * The general rule is that channel is created on first call. Also each element is responsible for its connection so + * while the connections are symmetric, the scope, used for making the connection is responsible for cancelation. + * + * Also connections are not reversible. Once connected block stays faithful until it finishes processing. + * Manually putting elements to connected block could lead to undetermined behavior and must be avoided. + */ +interface Producer : CoroutineScope { + fun connect(consumer: Consumer) + + suspend fun receive(): T + + val consumer: Consumer? + + val outputIsConnected: Boolean get() = consumer != null + + //fun close() +} + +/** + * Terminal chain block. Could consume an element sequence and be connected to signle [Producer] + */ +interface Consumer : CoroutineScope { + fun connect(producer: Producer) + + suspend fun send(value: T) + + val producer: Producer? + + val inputIsConnected: Boolean get() = producer != null + + //fun close() +} + +interface Processor : Consumer, Producer + +abstract class AbstractProducer(scope: CoroutineScope) : Producer { + override val coroutineContext: CoroutineContext = scope.coroutineContext + + override var consumer: Consumer? = null + protected set + + override fun connect(consumer: Consumer) { + //Ignore if already connected to specific consumer + if (consumer != this.consumer) { + if (outputIsConnected) error("The output slot of producer is occupied") + if (consumer.inputIsConnected) error("The input slot of consumer is occupied") + this.consumer = consumer + if (consumer.producer != null) { + //No need to save the job, it will be canceled on scope cancel + connectOutput(consumer) + // connect back, consumer is already set so no circular reference + consumer.connect(this) + } else error("Unreachable statement") + } + } + + protected open fun connectOutput(consumer: Consumer) { + launch { + while (this.isActive) { + consumer.send(receive()) + } + } + } +} + +abstract class AbstractConsumer(scope: CoroutineScope) : Consumer { + override val coroutineContext: CoroutineContext = scope.coroutineContext + + override var producer: Producer? = null + protected set + + override fun connect(producer: Producer) { + //Ignore if already connected to specific consumer + if (producer != this.producer) { + if (inputIsConnected) error("The input slot of consumer is occupied") + if (producer.outputIsConnected) error("The input slot of producer is occupied") + this.producer = producer + //No need to save the job, it will be canceled on scope cancel + if (producer.consumer != null) { + connectInput(producer) + // connect back + producer.connect(this) + } else error("Unreachable statement") + } + } + + protected open fun connectInput(producer: Producer) { + launch { + while (isActive) { + send(producer.receive()) + } + } + } +} + +abstract class AbstractProcessor(scope: CoroutineScope) : Processor, AbstractProducer(scope) { + + override var producer: Producer? = null + protected set + + override fun connect(producer: Producer) { + //Ignore if already connected to specific consumer + if (producer != this.producer) { + if (inputIsConnected) error("The input slot of consumer is occupied") + if (producer.outputIsConnected) error("The input slot of producer is occupied") + this.producer = producer + //No need to save the job, it will be canceled on scope cancel + if (producer.consumer != null) { + connectInput(producer) + // connect back + producer.connect(this) + } else error("Unreachable statement") + } + } + + protected open fun connectInput(producer: Producer) { + launch { + while (isActive) { + send(producer.receive()) + } + } + } +} + +/** + * A simple [produce]-based producer + */ +@ExperimentalCoroutinesApi +class GenericProducer( + scope: CoroutineScope, + capacity: Int = Channel.UNLIMITED, + block: suspend ProducerScope.() -> Unit +) : AbstractProducer(scope) { + + private val channel: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } + + override suspend fun receive(): T = channel.receive() +} + +/** + * A simple pipeline [Processor] block + */ +class PipeProcessor( + scope: CoroutineScope, + capacity: Int = Channel.RENDEZVOUS, + process: suspend (T) -> R +) : AbstractProcessor(scope) { + + private val input = Channel(capacity) + private val output: ReceiveChannel = input.map(coroutineContext, process) + + override suspend fun receive(): R = output.receive() + + override suspend fun send(value: T) { + input.send(value) + } +} + + +/** + * A moving window [Processor] with circular buffer + */ +class WindowedProcessor( + scope: CoroutineScope, + window: Int, + val process: suspend (Buffer) -> R +) : AbstractProcessor(scope) { + + private val ringBuffer = RingBuffer.boxing(window) + + private val channel = Channel(Channel.RENDEZVOUS) + + override suspend fun receive(): R { + return channel.receive() + } + + override suspend fun send(value: T) { + ringBuffer.push(value) + channel.send(process(ringBuffer.snapshot())) + } +} + +/** + * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure + * to incorporate them into state on-arrival. + * The current aggregated state could be accessed by [state]. The input channel is inactive unless requested + * @param T - the type of the input element + * @param S - the type of the aggregator + */ +class Reducer( + scope: CoroutineScope, + initialState: S, + val fold: suspend (S, T) -> S +) : AbstractConsumer(scope) { + + var state: S = initialState + private set + + private val mutex = Mutex() + + override suspend fun send(value: T) = mutex.withLock { + state = fold(state, value) + } +} + +/** + * Collector that accumulates all values in a list. List could be accessed from non-suspending environment via [list] value. + */ +class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { + + private val _list = ArrayList() + private val mutex = Mutex() + val list: List get() = _list + + override suspend fun send(value: T) { + mutex.withLock { + _list.add(value) + } + } +} + +/** + * Convert a sequence to [Producer] + */ +fun Sequence.produce(scope: CoroutineScope = GlobalScope) = + GenericProducer(scope) { forEach { send(it) } } + +/** + * Convert a [ReceiveChannel] to [Producer] + */ +fun ReceiveChannel.produce(scope: CoroutineScope = GlobalScope) = + GenericProducer(scope) { for (e in this@produce) send(e) } + + +fun > Producer.consumer(consumerFactory: () -> C): C = + consumerFactory().also { connect(it) } + +fun Producer.map(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = + PipeProcessor(this, capacity, process).also { connect(it) } + +/** + * Create a reducer and connect this producer to reducer + */ +fun Producer.reduce(initialState: S, fold: suspend (S, T) -> S) = + Reducer(this, initialState, fold).also { connect(it) } + +/** + * Create a [Collector] and attach it to this [Producer] + */ +fun Producer.collect() = + Collector(this).also { connect(it) } + +fun > Producer.process(processorBuilder: () -> P): P = + processorBuilder().also { connect(it) } + +fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = + PipeProcessor(this, capacity, process).also { connect(it) } + + +fun Producer.windowed(window: Int, process: suspend (Buffer) -> R) = + WindowedProcessor(this, window, process).also { connect(it) } \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt index e23748bdb..4f2c4cb8b 100644 --- a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt @@ -1,7 +1,6 @@ package scientifik.kmath.chains import kotlinx.coroutines.runBlocking -import sicentifik.kmath.chains.Chain import kotlin.sequences.Sequence /** diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt index b4832827d..fa81fc67b 100644 --- a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt @@ -1,6 +1,7 @@ package scientifik.kmath.structures import kotlinx.coroutines.* +import scientifik.kmath.Math class LazyNDStructure( val scope: CoroutineScope, diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt new file mode 100644 index 000000000..4dd0700d1 --- /dev/null +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -0,0 +1,27 @@ +package scientifik.kmath.streaming + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Test + +@InternalCoroutinesApi +class BufferFlowTest { + + @Test(timeout = 2000) + fun mapParallel() { + runBlocking { + (1..20).asFlow().mapParallel { + Thread.sleep(200) + it + }.collect { + println("Completed $it") + } + } + } + +} \ No newline at end of file diff --git a/kmath-streaming/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt similarity index 100% rename from kmath-streaming/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt rename to kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 32ac77e48..690d80b59 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,7 +26,6 @@ include( ":kmath-histograms", ":kmath-commons", ":kmath-koma", - ":kmath-streaming", ":kmath-prob", ":benchmarks" ) From 08e14b15c59f1bd7234dabb3be69af05c18a3754 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 27 Apr 2019 22:15:21 +0300 Subject: [PATCH 23/34] Migrating streaming to Flow --- .../scientifik/kmath/CoroutinesExtra.kt | 76 ++++- .../scientifik/kmath/streaming/BufferFlow.kt | 33 --- .../kmath/streaming/BufferFlowTest.kt | 20 +- kmath-streaming/build.gradle.kts | 36 --- .../kmath/streaming/BufferStreaming.kt | 82 ------ .../scientifik/kmath/streaming/RingBuffer.kt | 89 ------ .../scientifik/kmath/streaming/Streaming.kt | 275 ------------------ 7 files changed, 81 insertions(+), 530 deletions(-) delete mode 100644 kmath-streaming/build.gradle.kts delete mode 100644 kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt delete mode 100644 kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt delete mode 100644 kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt index 7580ef6ef..a0e11390d 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt @@ -1,9 +1,73 @@ package scientifik.kmath -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.flow.* -val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default \ No newline at end of file +val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default + +@FlowPreview +inline class AsyncFlow(val deferredFlow: Flow>) : Flow { + override suspend fun collect(collector: FlowCollector) { + deferredFlow.collect { + collector.emit((it.await())) + } + } +} + +@FlowPreview +fun Flow.async( + dispatcher: CoroutineDispatcher = Dispatchers.Default, + block: suspend (T) -> R +): AsyncFlow { + val flow = map { + coroutineScope { + async(dispatcher, start = CoroutineStart.LAZY) { block(it) } + } + } + return AsyncFlow(flow) +} + +@FlowPreview +fun AsyncFlow.map(action: (T) -> R) = deferredFlow.map { input -> + coroutineScope { + async(start = CoroutineStart.LAZY) { action(input.await()) } + } +} + +@ExperimentalCoroutinesApi +@FlowPreview +suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector){ + require(concurrency >= 0) { "Buffer size should be positive, but was $concurrency" } + coroutineScope { + //Starting up to N deferred coroutines ahead of time + val channel = produce(capacity = concurrency) { + deferredFlow.collect { value -> + value.start() + send(value) + } + } + + (channel as Job).invokeOnCompletion { + if (it is CancellationException && it.cause == null) cancel() + } + + for (element in channel) { + collector.emit(element.await()) + } + + val producer = channel as Job + if (producer.isCancelled) { + producer.join() + //throw producer.getCancellationException() + } + } +} + +@ExperimentalCoroutinesApi +@FlowPreview +suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T) -> Unit): Unit{ + collect(concurrency, object : FlowCollector { + override suspend fun emit(value: T) = action(value) + }) +} diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt index f6c38e153..67b74d7de 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt @@ -59,37 +59,4 @@ fun Flow.chunked(bufferSize: Int) = flow { emit(buffer) } } -} - -/** - * Perform parallel mapping of flow elements - */ -@InternalCoroutinesApi -@ExperimentalCoroutinesApi -@FlowPreview -fun Flow.mapParallel(dispatcher: CoroutineDispatcher = Dispatchers.Default, bufferSize: Int = 16, transform: suspend (T) -> R) : Flow{ - require(bufferSize >= 0) { - "Buffer size should be positive, but was $bufferSize" - } - return flow { - coroutineScope { - val channel: ReceiveChannel> = produce(capacity = bufferSize) { - collect { value -> - send(async(dispatcher) { transform(value) }) - } - } - - // TODO semantics doesn't play well here and we pay for that with additional object - (channel as Job).invokeOnCompletion { if (it is CancellationException && it.cause == null) cancel() } - for (element in channel) { - emit(element.await()) - } - - val producer = channel as Job - if (producer.isCancelled) { - producer.join() - throw producer.getCancellationException() - } - } - } } \ No newline at end of file diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index 4dd0700d1..e3a38ff43 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -1,24 +1,26 @@ package scientifik.kmath.streaming -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.Job +import kotlinx.coroutines.* import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.runBlocking import org.junit.Test +import scientifik.kmath.async +import scientifik.kmath.collect +@ExperimentalCoroutinesApi @InternalCoroutinesApi +@FlowPreview class BufferFlowTest { - @Test(timeout = 2000) + + @Test fun mapParallel() { runBlocking { - (1..20).asFlow().mapParallel { + (1..20).asFlow().async(Dispatchers.IO) { + println("Started $it") + @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) it - }.collect { + }.collect(4) { println("Completed $it") } } diff --git a/kmath-streaming/build.gradle.kts b/kmath-streaming/build.gradle.kts deleted file mode 100644 index f49e71116..000000000 --- a/kmath-streaming/build.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -plugins { - `multiplatform-config` - id("kotlinx-atomicfu") version Versions.atomicfuVersion -} - - - -kotlin { - jvm () - js() - - sourceSets { - val commonMain by getting { - dependencies { - api(project(":kmath-core")) - api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Versions.atomicfuVersion}") - } - } - val jvmMain by getting { - dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfuVersion}") - } - } - val jsMain by getting { - dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Versions.atomicfuVersion}") - } - } - - } -} - -atomicfu { - variant = "VH" -} \ No newline at end of file diff --git a/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt deleted file mode 100644 index 023199452..000000000 --- a/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt +++ /dev/null @@ -1,82 +0,0 @@ -package scientifik.kmath.streaming - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.produce -import kotlinx.coroutines.isActive -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.BufferFactory - -/** - * A processor that collects incoming elements into fixed size buffers - */ -@ExperimentalCoroutinesApi -class JoinProcessor( - scope: CoroutineScope, - bufferSize: Int, - bufferFactory: BufferFactory = Buffer.Companion::boxing -) : AbstractProcessor>(scope) { - - private val input = Channel(bufferSize) - - private val output = produce(coroutineContext) { - val list = ArrayList(bufferSize) - while (isActive) { - list.clear() - repeat(bufferSize) { - list.add(input.receive()) - } - val buffer = bufferFactory(bufferSize) { list[it] } - send(buffer) - } - } - - override suspend fun receive(): Buffer = output.receive() - - override suspend fun send(value: T) { - input.send(value) - } -} - -/** - * A processor that splits incoming buffers into individual elements - */ -class SplitProcessor(scope: CoroutineScope) : AbstractProcessor, T>(scope) { - - private val input = Channel>() - - private val mutex = Mutex() - - private var currentBuffer: Buffer? = null - - private var pos = 0 - - - override suspend fun receive(): T { - mutex.withLock { - while (currentBuffer == null || pos == currentBuffer!!.size) { - currentBuffer = input.receive() - pos = 0 - } - return currentBuffer!![pos].also { pos++ } - } - } - - override suspend fun send(value: Buffer) { - input.send(value) - } -} - -@ExperimentalCoroutinesApi -fun Producer.chunked(chunkSize: Int, bufferFactory: BufferFactory) = - JoinProcessor(this, chunkSize, bufferFactory).also { connect(it) } - -@ExperimentalCoroutinesApi -inline fun Producer.chunked(chunkSize: Int) = - JoinProcessor(this, chunkSize, Buffer.Companion::auto).also { connect(it) } - - - diff --git a/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt deleted file mode 100644 index ac64f921f..000000000 --- a/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt +++ /dev/null @@ -1,89 +0,0 @@ -package scientifik.kmath.streaming - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.MutableBuffer -import scientifik.kmath.structures.VirtualBuffer - -/** - * Thread-safe ring buffer - */ -internal class RingBuffer( - private val buffer: MutableBuffer, - private var startIndex: Int = 0, - size: Int = 0 -) : Buffer { - - private val mutex = Mutex() - - override var size: Int = size - private set - - override fun get(index: Int): T { - require(index >= 0) { "Index must be positive" } - require(index < size) { "Index $index is out of circular buffer size $size" } - return buffer[startIndex.forward(index)] - } - - fun isFull() = size == buffer.size - - /** - * Iterator could provide wrong results if buffer is changed in initialization (iteration is safe) - */ - override fun iterator(): Iterator = object : AbstractIterator() { - private var count = size - private var index = startIndex - val copy = buffer.copy() - - override fun computeNext() { - if (count == 0) { - done() - } else { - setNext(copy[index]) - index = index.forward(1) - count-- - } - } - } - - /** - * A safe snapshot operation - */ - suspend fun snapshot(): Buffer { - mutex.withLock { - val copy = buffer.copy() - return VirtualBuffer(size) { i -> copy[startIndex.forward(i)] } - } - } - - suspend fun push(element: T) { - mutex.withLock { - buffer[startIndex.forward(size)] = element - if (isFull()) { - startIndex++ - } else { - size++ - } - } - } - - - @Suppress("NOTHING_TO_INLINE") - private inline fun Int.forward(n: Int): Int = (this + n) % (buffer.size) - - companion object { - inline fun build(size: Int, empty: T): RingBuffer { - val buffer = MutableBuffer.auto(size) { empty } - return RingBuffer(buffer) - } - - /** - * Slow yet universal buffer - */ - fun boxing(size: Int): RingBuffer { - val buffer: MutableBuffer = MutableBuffer.boxing(size) { null } - return RingBuffer(buffer) - } - } -} \ No newline at end of file diff --git a/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt b/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt deleted file mode 100644 index 3cb3bcfdd..000000000 --- a/kmath-streaming/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt +++ /dev/null @@ -1,275 +0,0 @@ -package scientifik.kmath.streaming - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.* -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import scientifik.kmath.structures.Buffer -import kotlin.coroutines.CoroutineContext - -/** - * Initial chain block. Could produce an element sequence and be connected to single [Consumer] - * - * The general rule is that channel is created on first call. Also each element is responsible for its connection so - * while the connections are symmetric, the scope, used for making the connection is responsible for cancelation. - * - * Also connections are not reversible. Once connected block stays faithful until it finishes processing. - * Manually putting elements to connected block could lead to undetermined behavior and must be avoided. - */ -interface Producer : CoroutineScope { - fun connect(consumer: Consumer) - - suspend fun receive(): T - - val consumer: Consumer? - - val outputIsConnected: Boolean get() = consumer != null - - //fun close() -} - -/** - * Terminal chain block. Could consume an element sequence and be connected to signle [Producer] - */ -interface Consumer : CoroutineScope { - fun connect(producer: Producer) - - suspend fun send(value: T) - - val producer: Producer? - - val inputIsConnected: Boolean get() = producer != null - - //fun close() -} - -interface Processor : Consumer, Producer - -abstract class AbstractProducer(scope: CoroutineScope) : Producer { - override val coroutineContext: CoroutineContext = scope.coroutineContext - - override var consumer: Consumer? = null - protected set - - override fun connect(consumer: Consumer) { - //Ignore if already connected to specific consumer - if (consumer != this.consumer) { - if (outputIsConnected) error("The output slot of producer is occupied") - if (consumer.inputIsConnected) error("The input slot of consumer is occupied") - this.consumer = consumer - if (consumer.producer != null) { - //No need to save the job, it will be canceled on scope cancel - connectOutput(consumer) - // connect back, consumer is already set so no circular reference - consumer.connect(this) - } else error("Unreachable statement") - } - } - - protected open fun connectOutput(consumer: Consumer) { - launch { - while (this.isActive) { - consumer.send(receive()) - } - } - } -} - -abstract class AbstractConsumer(scope: CoroutineScope) : Consumer { - override val coroutineContext: CoroutineContext = scope.coroutineContext - - override var producer: Producer? = null - protected set - - override fun connect(producer: Producer) { - //Ignore if already connected to specific consumer - if (producer != this.producer) { - if (inputIsConnected) error("The input slot of consumer is occupied") - if (producer.outputIsConnected) error("The input slot of producer is occupied") - this.producer = producer - //No need to save the job, it will be canceled on scope cancel - if (producer.consumer != null) { - connectInput(producer) - // connect back - producer.connect(this) - } else error("Unreachable statement") - } - } - - protected open fun connectInput(producer: Producer) { - launch { - while (isActive) { - send(producer.receive()) - } - } - } -} - -abstract class AbstractProcessor(scope: CoroutineScope) : Processor, AbstractProducer(scope) { - - override var producer: Producer? = null - protected set - - override fun connect(producer: Producer) { - //Ignore if already connected to specific consumer - if (producer != this.producer) { - if (inputIsConnected) error("The input slot of consumer is occupied") - if (producer.outputIsConnected) error("The input slot of producer is occupied") - this.producer = producer - //No need to save the job, it will be canceled on scope cancel - if (producer.consumer != null) { - connectInput(producer) - // connect back - producer.connect(this) - } else error("Unreachable statement") - } - } - - protected open fun connectInput(producer: Producer) { - launch { - while (isActive) { - send(producer.receive()) - } - } - } -} - -/** - * A simple [produce]-based producer - */ -class GenericProducer( - scope: CoroutineScope, - capacity: Int = Channel.UNLIMITED, - block: suspend ProducerScope.() -> Unit -) : AbstractProducer(scope) { - - private val channel: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } - - override suspend fun receive(): T = channel.receive() -} - -/** - * A simple pipeline [Processor] block - */ -class PipeProcessor( - scope: CoroutineScope, - capacity: Int = Channel.RENDEZVOUS, - process: suspend (T) -> R -) : AbstractProcessor(scope) { - - private val input = Channel(capacity) - private val output: ReceiveChannel = input.map(coroutineContext, process) - - override suspend fun receive(): R = output.receive() - - override suspend fun send(value: T) { - input.send(value) - } -} - - -/** - * A moving window [Processor] with circular buffer - */ -class WindowedProcessor( - scope: CoroutineScope, - window: Int, - val process: suspend (Buffer) -> R -) : AbstractProcessor(scope) { - - private val ringBuffer = RingBuffer.boxing(window) - - private val channel = Channel(Channel.RENDEZVOUS) - - override suspend fun receive(): R { - return channel.receive() - } - - override suspend fun send(value: T) { - ringBuffer.push(value) - channel.send(process(ringBuffer.snapshot())) - } -} - -/** - * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure - * to incorporate them into state on-arrival. - * The current aggregated state could be accessed by [state]. The input channel is inactive unless requested - * @param T - the type of the input element - * @param S - the type of the aggregator - */ -class Reducer( - scope: CoroutineScope, - initialState: S, - val fold: suspend (S, T) -> S -) : AbstractConsumer(scope) { - - var state: S = initialState - private set - - private val mutex = Mutex() - - override suspend fun send(value: T) = mutex.withLock { - state = fold(state, value) - } -} - -/** - * Collector that accumulates all values in a list. List could be accessed from non-suspending environment via [list] value. - */ -class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { - - private val _list = ArrayList() - private val mutex = Mutex() - val list: List get() = _list - - override suspend fun send(value: T) { - mutex.withLock { - _list.add(value) - } - } -} - -/** - * Convert a sequence to [Producer] - */ -fun Sequence.produce(scope: CoroutineScope = GlobalScope) = - GenericProducer(scope) { forEach { send(it) } } - -/** - * Convert a [ReceiveChannel] to [Producer] - */ -fun ReceiveChannel.produce(scope: CoroutineScope = GlobalScope) = - GenericProducer(scope) { for (e in this@produce) send(e) } - - -fun > Producer.consumer(consumerFactory: () -> C): C = - consumerFactory().also { connect(it) } - -fun Producer.map(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = - PipeProcessor(this, capacity, process).also { connect(it) } - -/** - * Create a reducer and connect this producer to reducer - */ -fun Producer.reduce(initialState: S, fold: suspend (S, T) -> S) = - Reducer(this, initialState, fold).also { connect(it) } - -/** - * Create a [Collector] and attach it to this [Producer] - */ -fun Producer.collect() = - Collector(this).also { connect(it) } - -fun > Producer.process(processorBuilder: () -> P): P = - processorBuilder().also { connect(it) } - -fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = - PipeProcessor(this, capacity, process).also { connect(it) } - - -fun Producer.windowed(window: Int, process: suspend (Buffer) -> R) = - WindowedProcessor(this, window, process).also { connect(it) } \ No newline at end of file From d138ce38898e54ff78959591a7e44ee124384201 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 28 Apr 2019 13:56:19 +0300 Subject: [PATCH 24/34] Parallel flow --- .../scientifik/kmath/CoroutinesExtra.kt | 42 ++++++++++++++----- .../kmath/streaming/BufferFlowTest.kt | 5 +-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt index a0e11390d..8a48893eb 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt @@ -1,13 +1,32 @@ package scientifik.kmath import kotlinx.coroutines.* +import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.produce -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default +/** + * An imitator of [Deferred] which holds a suspended function block and dispatcher + */ +class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: suspend CoroutineScope.() -> T) { + private var deferred: Deferred? = null + + fun CoroutineScope.start() { + if(deferred==null) { + deferred = async(dispatcher, block = block) + } + } + + suspend fun await(): T = deferred?.await() ?: error("Coroutine not started") +} + @FlowPreview -inline class AsyncFlow(val deferredFlow: Flow>) : Flow { +inline class AsyncFlow(val deferredFlow: Flow>) : Flow { override suspend fun collect(collector: FlowCollector) { deferredFlow.collect { collector.emit((it.await())) @@ -18,32 +37,32 @@ inline class AsyncFlow(val deferredFlow: Flow>) : Flow { @FlowPreview fun Flow.async( dispatcher: CoroutineDispatcher = Dispatchers.Default, - block: suspend (T) -> R + block: suspend CoroutineScope.(T) -> R ): AsyncFlow { val flow = map { - coroutineScope { - async(dispatcher, start = CoroutineStart.LAZY) { block(it) } - } + LazyDeferred(dispatcher) { block(it) } } return AsyncFlow(flow) } @FlowPreview fun AsyncFlow.map(action: (T) -> R) = deferredFlow.map { input -> - coroutineScope { - async(start = CoroutineStart.LAZY) { action(input.await()) } + //TODO add actual composition + LazyDeferred(input.dispatcher) { + input.run { start() } + action(input.await()) } } @ExperimentalCoroutinesApi @FlowPreview -suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector){ +suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector) { require(concurrency >= 0) { "Buffer size should be positive, but was $concurrency" } coroutineScope { //Starting up to N deferred coroutines ahead of time val channel = produce(capacity = concurrency) { deferredFlow.collect { value -> - value.start() + value.run { start() } send(value) } } @@ -66,8 +85,9 @@ suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector< @ExperimentalCoroutinesApi @FlowPreview -suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T) -> Unit): Unit{ +suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T) -> Unit): Unit { collect(concurrency, object : FlowCollector { override suspend fun emit(value: T) = action(value) }) } + diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index e3a38ff43..0102d615f 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -11,11 +11,10 @@ import scientifik.kmath.collect @FlowPreview class BufferFlowTest { - - @Test + @Test(timeout = 2000) fun mapParallel() { runBlocking { - (1..20).asFlow().async(Dispatchers.IO) { + (1..20).asFlow().async(Dispatchers.Default) { println("Started $it") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) From 3ddff86e24ca06aae4c34e6e9e3a2bb28193a851 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 28 Apr 2019 17:54:50 +0300 Subject: [PATCH 25/34] Windowed flow test --- .../kmath/transform/Transformations.kt | 16 +- .../scientifik/kmath/structures/Buffers.kt | 5 +- .../scientifik/kmath/CoroutinesExtra.kt | 50 +++- .../scientifik/kmath/streaming/BufferFlow.kt | 19 +- .../kmath/streaming/BufferStreaming.kt | 82 ------ .../scientifik/kmath/streaming/RingBuffer.kt | 16 +- .../scientifik/kmath/streaming/Streaming.kt | 273 ------------------ .../kmath/streaming/RingBufferTest.kt | 21 +- 8 files changed, 97 insertions(+), 385 deletions(-) delete mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt delete mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt index 89ecf5f9f..113b8ea75 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt @@ -1,10 +1,10 @@ package scientifik.kmath.transform +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import org.apache.commons.math3.transform.* import scientifik.kmath.operations.Complex -import scientifik.kmath.streaming.Processor -import scientifik.kmath.streaming.Producer -import scientifik.kmath.streaming.map import scientifik.kmath.structures.* @@ -68,19 +68,21 @@ object Transformations { /** * Process given [Producer] with commons-math fft transformation */ -fun Producer>.FFT( +@FlowPreview +fun Flow>.FFT( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD -): Processor, Buffer> { +): Flow> { val transform = Transformations.fourier(normalization, direction) return map { transform(it) } } +@FlowPreview @JvmName("realFFT") -fun Producer>.FFT( +fun Flow>.FFT( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD -): Processor, Buffer> { +): Flow> { val transform = Transformations.realFourier(normalization, direction) return map { transform(it) } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index c8d091017..4b127a5b4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -249,7 +249,10 @@ inline class ReadOnlyBuffer(val buffer: MutableBuffer) : Buffer { * Useful when one needs single element from the buffer. */ class VirtualBuffer(override val size: Int, private val generator: (Int) -> T) : Buffer { - override fun get(index: Int): T = generator(index) + override fun get(index: Int): T { + if (index < 0 || index >= size) throw IndexOutOfBoundsException("Expected index from 0 to ${size - 1}, but found $index") + return generator(index) + } override fun iterator(): Iterator = (0 until size).asSequence().map(generator).iterator() diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt index 8a48893eb..db44f878b 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt @@ -13,12 +13,12 @@ val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default /** * An imitator of [Deferred] which holds a suspended function block and dispatcher */ -class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: suspend CoroutineScope.() -> T) { +internal class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: suspend CoroutineScope.() -> T) { private var deferred: Deferred? = null - fun CoroutineScope.start() { + internal fun start(scope: CoroutineScope) { if(deferred==null) { - deferred = async(dispatcher, block = block) + deferred = scope.async(dispatcher, block = block) } } @@ -26,7 +26,7 @@ class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: suspend Co } @FlowPreview -inline class AsyncFlow(val deferredFlow: Flow>) : Flow { +class AsyncFlow internal constructor(internal val deferredFlow: Flow>) : Flow { override suspend fun collect(collector: FlowCollector) { deferredFlow.collect { collector.emit((it.await())) @@ -46,23 +46,23 @@ fun Flow.async( } @FlowPreview -fun AsyncFlow.map(action: (T) -> R) = deferredFlow.map { input -> - //TODO add actual composition +fun AsyncFlow.map(action: (T) -> R) = AsyncFlow(deferredFlow.map { input -> + //TODO add function composition LazyDeferred(input.dispatcher) { - input.run { start() } + input.start(this) action(input.await()) } -} +}) @ExperimentalCoroutinesApi @FlowPreview suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector) { - require(concurrency >= 0) { "Buffer size should be positive, but was $concurrency" } + require(concurrency >= 1) { "Buffer size should be more than 1, but was $concurrency" } coroutineScope { //Starting up to N deferred coroutines ahead of time - val channel = produce(capacity = concurrency) { + val channel = produce(capacity = concurrency-1) { deferredFlow.collect { value -> - value.run { start() } + value.start(this@coroutineScope) send(value) } } @@ -91,3 +91,31 @@ suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T }) } +//suspend fun Flow.collect(concurrency: Int, dispatcher: CoroutineDispatcher, collector: FlowCollector){ +// require(concurrency >= 1) { "Buffer size should be more than 1, but was $concurrency" } +// coroutineScope { +// //Starting up to N deferred coroutines ahead of time +// val channel = produce(capacity = concurrency-1) { +// this@collect. +// deferredFlow.collect { value -> +// value.start(this@coroutineScope) +// send(value) +// } +// } +// +// (channel as Job).invokeOnCompletion { +// if (it is CancellationException && it.cause == null) cancel() +// } +// +// for (element in channel) { +// collector.emit(element.await()) +// } +// +// val producer = channel as Job +// if (producer.isCancelled) { +// producer.join() +// //throw producer.getCancellationException() +// } +// } +//} + diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt index 67b74d7de..aa0aca460 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt @@ -1,13 +1,10 @@ package scientifik.kmath.streaming -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.BufferFactory import scientifik.kmath.structures.DoubleBuffer -import kotlin.coroutines.coroutineContext /** * Create a [Flow] from buffer @@ -59,4 +56,18 @@ fun Flow.chunked(bufferSize: Int) = flow { emit(buffer) } } +} + +/** + * Map a flow to a moving window buffer. The window step is one. + * In order to get different steps, one could use skip operation. + */ +@FlowPreview +fun Flow.windowed(window: Int): Flow> = flow { + require(window > 1) { "Window size must be more than one" } + val ringBuffer = RingBuffer.boxing(window) + this@windowed.collect { element -> + ringBuffer.push(element) + emit(ringBuffer.snapshot()) + } } \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt deleted file mode 100644 index 023199452..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferStreaming.kt +++ /dev/null @@ -1,82 +0,0 @@ -package scientifik.kmath.streaming - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.produce -import kotlinx.coroutines.isActive -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.BufferFactory - -/** - * A processor that collects incoming elements into fixed size buffers - */ -@ExperimentalCoroutinesApi -class JoinProcessor( - scope: CoroutineScope, - bufferSize: Int, - bufferFactory: BufferFactory = Buffer.Companion::boxing -) : AbstractProcessor>(scope) { - - private val input = Channel(bufferSize) - - private val output = produce(coroutineContext) { - val list = ArrayList(bufferSize) - while (isActive) { - list.clear() - repeat(bufferSize) { - list.add(input.receive()) - } - val buffer = bufferFactory(bufferSize) { list[it] } - send(buffer) - } - } - - override suspend fun receive(): Buffer = output.receive() - - override suspend fun send(value: T) { - input.send(value) - } -} - -/** - * A processor that splits incoming buffers into individual elements - */ -class SplitProcessor(scope: CoroutineScope) : AbstractProcessor, T>(scope) { - - private val input = Channel>() - - private val mutex = Mutex() - - private var currentBuffer: Buffer? = null - - private var pos = 0 - - - override suspend fun receive(): T { - mutex.withLock { - while (currentBuffer == null || pos == currentBuffer!!.size) { - currentBuffer = input.receive() - pos = 0 - } - return currentBuffer!![pos].also { pos++ } - } - } - - override suspend fun send(value: Buffer) { - input.send(value) - } -} - -@ExperimentalCoroutinesApi -fun Producer.chunked(chunkSize: Int, bufferFactory: BufferFactory) = - JoinProcessor(this, chunkSize, bufferFactory).also { connect(it) } - -@ExperimentalCoroutinesApi -inline fun Producer.chunked(chunkSize: Int) = - JoinProcessor(this, chunkSize, Buffer.Companion::auto).also { connect(it) } - - - diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt index ac64f921f..6b99e34ff 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/RingBuffer.kt @@ -5,12 +5,14 @@ import kotlinx.coroutines.sync.withLock import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.MutableBuffer import scientifik.kmath.structures.VirtualBuffer +import kotlin.reflect.KClass /** * Thread-safe ring buffer */ +@Suppress("UNCHECKED_CAST") internal class RingBuffer( - private val buffer: MutableBuffer, + private val buffer: MutableBuffer, private var startIndex: Int = 0, size: Int = 0 ) : Buffer { @@ -23,7 +25,7 @@ internal class RingBuffer( override fun get(index: Int): T { require(index >= 0) { "Index must be positive" } require(index < size) { "Index $index is out of circular buffer size $size" } - return buffer[startIndex.forward(index)] + return buffer[startIndex.forward(index)] as T } fun isFull() = size == buffer.size @@ -40,7 +42,7 @@ internal class RingBuffer( if (count == 0) { done() } else { - setNext(copy[index]) + setNext(copy[index] as T) index = index.forward(1) count-- } @@ -53,7 +55,9 @@ internal class RingBuffer( suspend fun snapshot(): Buffer { mutex.withLock { val copy = buffer.copy() - return VirtualBuffer(size) { i -> copy[startIndex.forward(i)] } + return VirtualBuffer(size) { i -> + copy[startIndex.forward(i)] as T + } } } @@ -74,14 +78,14 @@ internal class RingBuffer( companion object { inline fun build(size: Int, empty: T): RingBuffer { - val buffer = MutableBuffer.auto(size) { empty } + val buffer = MutableBuffer.auto(size) { empty } as MutableBuffer return RingBuffer(buffer) } /** * Slow yet universal buffer */ - fun boxing(size: Int): RingBuffer { + fun boxing(size: Int): RingBuffer { val buffer: MutableBuffer = MutableBuffer.boxing(size) { null } return RingBuffer(buffer) } diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt deleted file mode 100644 index 0382b6a89..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/Streaming.kt +++ /dev/null @@ -1,273 +0,0 @@ -package scientifik.kmath.streaming - -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import scientifik.kmath.structures.Buffer -import kotlin.coroutines.CoroutineContext - -/** - * Initial chain block. Could produce an element sequence and be connected to single [Consumer] - * - * The general rule is that channel is created on first call. Also each element is responsible for its connection so - * while the connections are symmetric, the scope, used for making the connection is responsible for cancelation. - * - * Also connections are not reversible. Once connected block stays faithful until it finishes processing. - * Manually putting elements to connected block could lead to undetermined behavior and must be avoided. - */ -interface Producer : CoroutineScope { - fun connect(consumer: Consumer) - - suspend fun receive(): T - - val consumer: Consumer? - - val outputIsConnected: Boolean get() = consumer != null - - //fun close() -} - -/** - * Terminal chain block. Could consume an element sequence and be connected to signle [Producer] - */ -interface Consumer : CoroutineScope { - fun connect(producer: Producer) - - suspend fun send(value: T) - - val producer: Producer? - - val inputIsConnected: Boolean get() = producer != null - - //fun close() -} - -interface Processor : Consumer, Producer - -abstract class AbstractProducer(scope: CoroutineScope) : Producer { - override val coroutineContext: CoroutineContext = scope.coroutineContext - - override var consumer: Consumer? = null - protected set - - override fun connect(consumer: Consumer) { - //Ignore if already connected to specific consumer - if (consumer != this.consumer) { - if (outputIsConnected) error("The output slot of producer is occupied") - if (consumer.inputIsConnected) error("The input slot of consumer is occupied") - this.consumer = consumer - if (consumer.producer != null) { - //No need to save the job, it will be canceled on scope cancel - connectOutput(consumer) - // connect back, consumer is already set so no circular reference - consumer.connect(this) - } else error("Unreachable statement") - } - } - - protected open fun connectOutput(consumer: Consumer) { - launch { - while (this.isActive) { - consumer.send(receive()) - } - } - } -} - -abstract class AbstractConsumer(scope: CoroutineScope) : Consumer { - override val coroutineContext: CoroutineContext = scope.coroutineContext - - override var producer: Producer? = null - protected set - - override fun connect(producer: Producer) { - //Ignore if already connected to specific consumer - if (producer != this.producer) { - if (inputIsConnected) error("The input slot of consumer is occupied") - if (producer.outputIsConnected) error("The input slot of producer is occupied") - this.producer = producer - //No need to save the job, it will be canceled on scope cancel - if (producer.consumer != null) { - connectInput(producer) - // connect back - producer.connect(this) - } else error("Unreachable statement") - } - } - - protected open fun connectInput(producer: Producer) { - launch { - while (isActive) { - send(producer.receive()) - } - } - } -} - -abstract class AbstractProcessor(scope: CoroutineScope) : Processor, AbstractProducer(scope) { - - override var producer: Producer? = null - protected set - - override fun connect(producer: Producer) { - //Ignore if already connected to specific consumer - if (producer != this.producer) { - if (inputIsConnected) error("The input slot of consumer is occupied") - if (producer.outputIsConnected) error("The input slot of producer is occupied") - this.producer = producer - //No need to save the job, it will be canceled on scope cancel - if (producer.consumer != null) { - connectInput(producer) - // connect back - producer.connect(this) - } else error("Unreachable statement") - } - } - - protected open fun connectInput(producer: Producer) { - launch { - while (isActive) { - send(producer.receive()) - } - } - } -} - -/** - * A simple [produce]-based producer - */ -@ExperimentalCoroutinesApi -class GenericProducer( - scope: CoroutineScope, - capacity: Int = Channel.UNLIMITED, - block: suspend ProducerScope.() -> Unit -) : AbstractProducer(scope) { - - private val channel: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } - - override suspend fun receive(): T = channel.receive() -} - -/** - * A simple pipeline [Processor] block - */ -class PipeProcessor( - scope: CoroutineScope, - capacity: Int = Channel.RENDEZVOUS, - process: suspend (T) -> R -) : AbstractProcessor(scope) { - - private val input = Channel(capacity) - private val output: ReceiveChannel = input.map(coroutineContext, process) - - override suspend fun receive(): R = output.receive() - - override suspend fun send(value: T) { - input.send(value) - } -} - - -/** - * A moving window [Processor] with circular buffer - */ -class WindowedProcessor( - scope: CoroutineScope, - window: Int, - val process: suspend (Buffer) -> R -) : AbstractProcessor(scope) { - - private val ringBuffer = RingBuffer.boxing(window) - - private val channel = Channel(Channel.RENDEZVOUS) - - override suspend fun receive(): R { - return channel.receive() - } - - override suspend fun send(value: T) { - ringBuffer.push(value) - channel.send(process(ringBuffer.snapshot())) - } -} - -/** - * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure - * to incorporate them into state on-arrival. - * The current aggregated state could be accessed by [state]. The input channel is inactive unless requested - * @param T - the type of the input element - * @param S - the type of the aggregator - */ -class Reducer( - scope: CoroutineScope, - initialState: S, - val fold: suspend (S, T) -> S -) : AbstractConsumer(scope) { - - var state: S = initialState - private set - - private val mutex = Mutex() - - override suspend fun send(value: T) = mutex.withLock { - state = fold(state, value) - } -} - -/** - * Collector that accumulates all values in a list. List could be accessed from non-suspending environment via [list] value. - */ -class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { - - private val _list = ArrayList() - private val mutex = Mutex() - val list: List get() = _list - - override suspend fun send(value: T) { - mutex.withLock { - _list.add(value) - } - } -} - -/** - * Convert a sequence to [Producer] - */ -fun Sequence.produce(scope: CoroutineScope = GlobalScope) = - GenericProducer(scope) { forEach { send(it) } } - -/** - * Convert a [ReceiveChannel] to [Producer] - */ -fun ReceiveChannel.produce(scope: CoroutineScope = GlobalScope) = - GenericProducer(scope) { for (e in this@produce) send(e) } - - -fun > Producer.consumer(consumerFactory: () -> C): C = - consumerFactory().also { connect(it) } - -fun Producer.map(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = - PipeProcessor(this, capacity, process).also { connect(it) } - -/** - * Create a reducer and connect this producer to reducer - */ -fun Producer.reduce(initialState: S, fold: suspend (S, T) -> S) = - Reducer(this, initialState, fold).also { connect(it) } - -/** - * Create a [Collector] and attach it to this [Producer] - */ -fun Producer.collect() = - Collector(this).also { connect(it) } - -fun > Producer.process(processorBuilder: () -> P): P = - processorBuilder().also { connect(it) } - -fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = - PipeProcessor(this, capacity, process).also { connect(it) } - - -fun Producer.windowed(window: Int, process: suspend (Buffer) -> R) = - WindowedProcessor(this, window, process).also { connect(it) } \ No newline at end of file diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt index 068859eb5..25af1f589 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt @@ -1,5 +1,6 @@ package scientifik.kmath.streaming +import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import org.junit.Test import scientifik.kmath.structures.asSequence @@ -7,7 +8,7 @@ import kotlin.test.assertEquals class RingBufferTest { @Test - fun testPush() { + fun push() { val buffer = RingBuffer.build(20, Double.NaN) runBlocking { for (i in 1..30) { @@ -16,4 +17,22 @@ class RingBufferTest { assertEquals(410.0, buffer.asSequence().sum()) } } + + @Test + fun windowed(){ + val flow = flow{ + var i = 0 + while(true){ + emit(i++) + } + } + val windowed = flow.windowed(10) + runBlocking { + val first = windowed.take(1).single() + val res = windowed.take(15).map { it -> it.asSequence().average() }.toList() + assertEquals(0.0, res[0]) + assertEquals(4.5, res[9]) + assertEquals(9.5, res[14]) + } + } } \ No newline at end of file From 6f4f6580306c668f603ac5542cb3d917438d68fb Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 29 Apr 2019 21:48:05 +0300 Subject: [PATCH 26/34] Concurrent map for flows --- .../scientifik/kmath/CoroutinesExtra.kt | 49 ++++++------------- .../kmath/streaming/BufferFlowTest.kt | 20 +++++++- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt index db44f878b..1290e6ea6 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt @@ -1,12 +1,8 @@ package scientifik.kmath import kotlinx.coroutines.* -import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.produce -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.* val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default @@ -17,7 +13,7 @@ internal class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: s private var deferred: Deferred? = null internal fun start(scope: CoroutineScope) { - if(deferred==null) { + if (deferred == null) { deferred = scope.async(dispatcher, block = block) } } @@ -60,7 +56,7 @@ suspend fun AsyncFlow.collect(concurrency: Int, collector: FlowCollector< require(concurrency >= 1) { "Buffer size should be more than 1, but was $concurrency" } coroutineScope { //Starting up to N deferred coroutines ahead of time - val channel = produce(capacity = concurrency-1) { + val channel = produce(capacity = concurrency - 1) { deferredFlow.collect { value -> value.start(this@coroutineScope) send(value) @@ -91,31 +87,16 @@ suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T }) } -//suspend fun Flow.collect(concurrency: Int, dispatcher: CoroutineDispatcher, collector: FlowCollector){ -// require(concurrency >= 1) { "Buffer size should be more than 1, but was $concurrency" } -// coroutineScope { -// //Starting up to N deferred coroutines ahead of time -// val channel = produce(capacity = concurrency-1) { -// this@collect. -// deferredFlow.collect { value -> -// value.start(this@coroutineScope) -// send(value) -// } -// } -// -// (channel as Job).invokeOnCompletion { -// if (it is CancellationException && it.cause == null) cancel() -// } -// -// for (element in channel) { -// collector.emit(element.await()) -// } -// -// val producer = channel as Job -// if (producer.isCancelled) { -// producer.join() -// //throw producer.getCancellationException() -// } -// } -//} +@FlowPreview +fun Flow.map( + concurrencyLevel: Int, + dispatcher: CoroutineDispatcher = Dispatchers.Default, + bufferSize: Int = concurrencyLevel, + transform: suspend (T) -> R +): Flow { + return flatMapMerge(concurrencyLevel, bufferSize) { value -> + flow { emit(transform(value)) } + }.flowOn(dispatcher) +} + diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index 0102d615f..5b04886d7 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -1,20 +1,36 @@ package scientifik.kmath.streaming import kotlinx.coroutines.* -import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.* import org.junit.Test import scientifik.kmath.async import scientifik.kmath.collect +import scientifik.kmath.map + @ExperimentalCoroutinesApi @InternalCoroutinesApi @FlowPreview class BufferFlowTest { + @Test(timeout = 2000) + fun concurrentMap() { + runBlocking { + (1..20).asFlow().map(4) { + println("Started $it") + @Suppress("BlockingMethodInNonBlockingContext") + Thread.sleep(200) + it + }.collect { + println("Completed $it") + } + } + } + @Test(timeout = 2000) fun mapParallel() { runBlocking { - (1..20).asFlow().async(Dispatchers.Default) { + (1..20).asFlow().async { println("Started $it") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) From f79a9e86a1806637e7b6f2f0704276e55dd4b7a8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 29 Apr 2019 22:14:04 +0300 Subject: [PATCH 27/34] Concurrent map for flows --- .../scientifik/kmath/CoroutinesExtra.kt | 4 ++-- .../kmath/streaming/BufferFlowTest.kt | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt index 1290e6ea6..51cc07511 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/CoroutinesExtra.kt @@ -89,8 +89,8 @@ suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T @FlowPreview fun Flow.map( - concurrencyLevel: Int, - dispatcher: CoroutineDispatcher = Dispatchers.Default, + dispatcher: CoroutineDispatcher, + concurrencyLevel: Int = 16, bufferSize: Int = concurrencyLevel, transform: suspend (T) -> R ): Flow { diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index 5b04886d7..63b716c01 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -6,6 +6,7 @@ import org.junit.Test import scientifik.kmath.async import scientifik.kmath.collect import scientifik.kmath.map +import java.util.concurrent.Executors @ExperimentalCoroutinesApi @@ -13,30 +14,32 @@ import scientifik.kmath.map @FlowPreview class BufferFlowTest { + val dispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() + @Test(timeout = 2000) - fun concurrentMap() { + fun map() { runBlocking { - (1..20).asFlow().map(4) { - println("Started $it") + (1..20).asFlow().map( dispatcher) { + println("Started $it on ${Thread.currentThread().name}") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) it }.collect { - println("Completed $it") + println("Completed $it on ${Thread.currentThread().name}") } } } @Test(timeout = 2000) - fun mapParallel() { + fun async() { runBlocking { - (1..20).asFlow().async { - println("Started $it") + (1..20).asFlow().async(dispatcher) { + println("Started $it on ${Thread.currentThread().name}") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) it }.collect(4) { - println("Completed $it") + println("Completed $it on ${Thread.currentThread().name}") } } } From fcab05b683aa6d12e688ca9d285949dd7db4247a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 30 Apr 2019 09:15:31 +0300 Subject: [PATCH 28/34] Fixes in flow transformations --- .../kmath/transform/Transformations.kt | 37 +++++++++++++++---- .../scientifik/kmath/structures/Buffers.kt | 4 +- .../scientifik/kmath/streaming/BufferFlow.kt | 7 ++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt index 113b8ea75..ac6922262 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt @@ -5,6 +5,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import org.apache.commons.math3.transform.* import scientifik.kmath.operations.Complex +import scientifik.kmath.streaming.chunked +import scientifik.kmath.streaming.spread import scientifik.kmath.structures.* @@ -33,40 +35,40 @@ object Transformations { fun fourier( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD - ): BufferTransform = { + ): SuspendBufferTransform = { FastFourierTransformer(normalization).transform(it.toArray(), direction).asBuffer() } fun realFourier( normalization: DftNormalization = DftNormalization.STANDARD, direction: TransformType = TransformType.FORWARD - ): BufferTransform = { + ): SuspendBufferTransform = { FastFourierTransformer(normalization).transform(it.asArray(), direction).asBuffer() } fun sine( normalization: DstNormalization = DstNormalization.STANDARD_DST_I, direction: TransformType = TransformType.FORWARD - ): BufferTransform = { + ): SuspendBufferTransform = { FastSineTransformer(normalization).transform(it.asArray(), direction).asBuffer() } fun cosine( normalization: DctNormalization = DctNormalization.STANDARD_DCT_I, direction: TransformType = TransformType.FORWARD - ): BufferTransform = { + ): SuspendBufferTransform = { FastCosineTransformer(normalization).transform(it.asArray(), direction).asBuffer() } fun hadamard( direction: TransformType = TransformType.FORWARD - ): BufferTransform = { + ): SuspendBufferTransform = { FastHadamardTransformer().transform(it.asArray(), direction).asBuffer() } } /** - * Process given [Producer] with commons-math fft transformation + * Process given [Flow] with commons-math fft transformation */ @FlowPreview fun Flow>.FFT( @@ -84,5 +86,24 @@ fun Flow>.FFT( direction: TransformType = TransformType.FORWARD ): Flow> { val transform = Transformations.realFourier(normalization, direction) - return map { transform(it) } -} \ No newline at end of file + return map(transform) +} + +/** + * Process a continous flow of real numbers in FFT splitting it in chunks of [bufferSize]. + */ +@FlowPreview +@JvmName("realFFT") +fun Flow.FFT( + bufferSize: Int = Int.MAX_VALUE, + normalization: DftNormalization = DftNormalization.STANDARD, + direction: TransformType = TransformType.FORWARD +): Flow { + return chunked(bufferSize).FFT(normalization,direction).spread() +} + +/** + * Map a complex flow into real flow by taking real part of each number + */ +@FlowPreview +fun Flow.real(): Flow = map{it.re} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 4b127a5b4..e521df86e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -277,4 +277,6 @@ fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { /** * Typealias for buffer transformations */ -typealias BufferTransform = (Buffer) -> Buffer \ No newline at end of file +typealias BufferTransform = (Buffer) -> Buffer + +typealias SuspendBufferTransform = suspend (Buffer) -> Buffer \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt index aa0aca460..d5e94fc3b 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt @@ -37,6 +37,9 @@ fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory) = flow counter = 0 } } + if (counter > 0) { + emit(bufferFactory(counter) { list[it] }) + } } /** @@ -54,8 +57,12 @@ fun Flow.chunked(bufferSize: Int) = flow { if (counter == bufferSize) { val buffer = DoubleBuffer(array) emit(buffer) + counter = 0 } } + if (counter > 0) { + emit(DoubleBuffer(counter) { array[it] }) + } } /** From c77d9cbb08a2db30c32dee559ef813db30847b73 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 2 May 2019 10:10:04 +0300 Subject: [PATCH 29/34] Bump kotlin verision to 1.3.31 --- build.gradle.kts | 4 ---- buildSrc/build.gradle.kts | 2 +- .../kotlin/scientifik/kmath/streaming/BufferFlowTest.kt | 7 ++++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f078c1de8..5f60bc2f0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,6 @@ val kmathVersion by extra("0.1.2-dev-3") allprojects { -// apply(plugin = "maven") -// apply(plugin = "maven-publish") -// apply(plugin = "com.jfrog.artifactory") - repositories { jcenter() maven("https://kotlin.bintray.com/kotlinx") diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 65827214c..087a7b8a4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,7 +7,7 @@ repositories { jcenter() } -val kotlinVersion = "1.3.30" +val kotlinVersion = "1.3.31" // Add plugins used in buildSrc as dependencies, also we should specify version only here dependencies { diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index 63b716c01..5b65c2120 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -1,7 +1,8 @@ package scientifik.kmath.streaming import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.collect import org.junit.Test import scientifik.kmath.async import scientifik.kmath.collect @@ -20,7 +21,7 @@ class BufferFlowTest { fun map() { runBlocking { (1..20).asFlow().map( dispatcher) { - println("Started $it on ${Thread.currentThread().name}") + //println("Started $it on ${Thread.currentThread().name}") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) it @@ -34,7 +35,7 @@ class BufferFlowTest { fun async() { runBlocking { (1..20).asFlow().async(dispatcher) { - println("Started $it on ${Thread.currentThread().name}") + //println("Started $it on ${Thread.currentThread().name}") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) it From d0a724771d80e60ae3e8025a8e2db20cff885563 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 4 May 2019 22:21:22 +0300 Subject: [PATCH 30/34] MPP Univariate Autodiff from Roman Elizarov --- .../scientifik/kmath/operations/AutoDiff.kt | 182 ++++++++++++++++++ .../kmath/operations/AutoDiffTest.kt | 155 +++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt new file mode 100644 index 000000000..4b0070545 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt @@ -0,0 +1,182 @@ +package scientifik.kmath.operations + +import kotlin.math.pow +import kotlin.math.sqrt + +/* + * Implementation of backward-mode automatic differentiation. + * Initial gist by Roman Elizarov: https://gist.github.com/elizarov/1ad3a8583e88cb6ea7a0ad09bb591d3d + */ + +/** + * Differentiable variable with value and derivative of differentiation ([deriv]) result + * with respect to this variable. + */ +data class ValueWithDeriv(var x: Double, var d: Double = 0.0) { + constructor(x: Number) : this(x.toDouble()) +} + +/** + * Runs differentiation and establishes [Field] context inside the block of code. + * + * Example: + * ``` + * val x = ValueWithDeriv(2) // define variable(s) and their values + * val y = deriv { sqr(x) + 5 * x + 3 } // write formulate in deriv context + * assertEquals(17.0, y.x) // the value of result (y) + * assertEquals(9.0, x.d) // dy/dx + * ``` + */ +fun deriv(body: AutoDiffField.() -> ValueWithDeriv): ValueWithDeriv = + ValueWithDerivField().run { + val result = body() + result.d = 1.0 // computing derivative w.r.t result + runBackwardPass() + result + } + + +abstract class AutoDiffField : Field { + /** + * Performs update of derivative after the rest of the formula in the back-pass. + * + * For example, implementation of `sin` function is: + * + * ``` + * fun AD.sin(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(sin(x.x)) { z -> // call derive with function result + * x.d += z.d * cos(x.x) // update derivative using chain rule and derivative of the function + * } + * ``` + */ + abstract fun derive(value: R, block: (R) -> Unit): R + + // Overloads for Double constants + + operator fun Number.plus(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() + that.x)) { z -> + that.d += z.d + } + + operator fun ValueWithDeriv.plus(b: Number): ValueWithDeriv = b.plus(this) + + operator fun Number.minus(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() - that.x)) { z -> + that.d -= z.d + } + + operator fun ValueWithDeriv.minus(that: Number): ValueWithDeriv = derive(ValueWithDeriv(this.x - that.toDouble())) { z -> + this.d += z.d + } + + override operator fun Number.times(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() * that.x)) { z -> + that.d += z.d * this.toDouble() + } + + override operator fun ValueWithDeriv.times(b: Number): ValueWithDeriv = b.times(this) + + override operator fun Number.div(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() / that.x)) { z -> + that.d -= z.d * this.toDouble() / (that.x * that.x) + } + + override operator fun ValueWithDeriv.div(that: Number): ValueWithDeriv = derive(ValueWithDeriv(this.x / that.toDouble())) { z -> + this.d += z.d / that.toDouble() + } +} + +/** + * Automatic Differentiation context class. + */ +private class ValueWithDerivField : AutoDiffField() { + + // this stack contains pairs of blocks and values to apply them to + private var stack = arrayOfNulls(8) + private var sp = 0 + + + @Suppress("UNCHECKED_CAST") + override fun derive(value: R, block: (R) -> Unit): R { + // save block to stack for backward pass + if (sp >= stack.size) stack = stack.copyOf(stack.size * 2) + stack[sp++] = block + stack[sp++] = value + return value + } + + @Suppress("UNCHECKED_CAST") + fun runBackwardPass() { + while (sp > 0) { + val value = stack[--sp] + val block = stack[--sp] as (Any?) -> Unit + block(value) + } + } + + // Basic math (+, -, *, /) + + + override fun add(a: ValueWithDeriv, b: ValueWithDeriv): ValueWithDeriv = + derive(ValueWithDeriv(a.x + b.x)) { z -> + a.d += z.d + b.d += z.d + } + + override fun multiply(a: ValueWithDeriv, b: ValueWithDeriv): ValueWithDeriv = + derive(ValueWithDeriv(a.x * b.x)) { z -> + a.d += z.d * b.x + b.d += z.d * a.x + } + + override fun divide(a: ValueWithDeriv, b: ValueWithDeriv): ValueWithDeriv = + derive(ValueWithDeriv(a.x / b.x)) { z -> + a.d += z.d / b.x + b.d -= z.d * a.x / (b.x * b.x) + } + + override fun multiply(a: ValueWithDeriv, k: Number): ValueWithDeriv = + derive(ValueWithDeriv(k.toDouble() * a.x)) { z -> + a.d += z.d * k.toDouble() + } + + override val zero: ValueWithDeriv get() = ValueWithDeriv(0.0, 0.0) + override val one: ValueWithDeriv get() = ValueWithDeriv(1.0, 0.0) +} + +// Extensions for differentiation of various basic mathematical functions + +// x ^ 2 +fun AutoDiffField.sqr(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(x.x * x.x)) { z -> + x.d += z.d * 2 * x.x +} + +// x ^ 1/2 +fun AutoDiffField.sqrt(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(sqrt(x.x))) { z -> + x.d += z.d * 0.5 / z.x +} + +// x ^ y (const) +fun AutoDiffField.pow(x: ValueWithDeriv, y: Double): ValueWithDeriv = derive(ValueWithDeriv(x.x.pow(y))) { z -> + x.d += z.d * y * x.x.pow(y - 1) +} + +fun AutoDiffField.pow(x: ValueWithDeriv, y: Int): ValueWithDeriv = pow(x, y.toDouble()) + +// exp(x) +fun AutoDiffField.exp(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.exp(x.x))) { z -> + x.d += z.d * z.x +} + +// ln(x) +fun AutoDiffField.ln(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.ln(x.x))) { z -> + x.d += z.d / x.x +} + +// x ^ y (any) +fun AutoDiffField.pow(x: ValueWithDeriv, y: ValueWithDeriv): ValueWithDeriv = exp(y * ln(x)) + +// sin(x) +fun AutoDiffField.sin(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.sin(x.x))) { z -> + x.d += z.d * kotlin.math.cos(x.x) +} + +// cos(x) +fun AutoDiffField.cos(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.cos(x.x))) { z -> + x.d -= z.d * kotlin.math.sin(x.x) +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt new file mode 100644 index 000000000..6f7299e9e --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt @@ -0,0 +1,155 @@ +package scientifik.kmath.operations + +import kotlin.math.PI +import kotlin.test.Test +import kotlin.test.assertEquals + +class AutoDiffTest { + @Test + fun testPlusX2() { + val x = ValueWithDeriv(3) // diff w.r.t this x at 3 + val y = deriv { x + x } + assertEquals(6.0, y.x) // y = x + x = 6 + assertEquals(2.0, x.d) // dy/dx = 2 + } + + @Test + fun testPlus() { + // two variables + val x = ValueWithDeriv(2) + val y = ValueWithDeriv(3) + val z = deriv { x + y } + assertEquals(5.0, z.x) // z = x + y = 5 + assertEquals(1.0, x.d) // dz/dx = 1 + assertEquals(1.0, y.d) // dz/dy = 1 + } + + @Test + fun testMinus() { + // two variables + val x = ValueWithDeriv(7) + val y = ValueWithDeriv(3) + val z = deriv { x - y } + assertEquals(4.0, z.x) // z = x - y = 4 + assertEquals(1.0, x.d) // dz/dx = 1 + assertEquals(-1.0, y.d) // dz/dy = -1 + } + + @Test + fun testMulX2() { + val x = ValueWithDeriv(3) // diff w.r.t this x at 3 + val y = deriv { x * x } + assertEquals(9.0, y.x) // y = x * x = 9 + assertEquals(6.0, x.d) // dy/dx = 2 * x = 7 + } + + @Test + fun testSqr() { + val x = ValueWithDeriv(3) + val y = deriv { sqr(x) } + assertEquals(9.0, y.x) // y = x ^ 2 = 9 + assertEquals(6.0, x.d) // dy/dx = 2 * x = 7 + } + + @Test + fun testSqrSqr() { + val x = ValueWithDeriv(2) + val y = deriv { sqr(sqr(x)) } + assertEquals(16.0, y.x) // y = x ^ 4 = 16 + assertEquals(32.0, x.d) // dy/dx = 4 * x^3 = 32 + } + + @Test + fun testX3() { + val x = ValueWithDeriv(2) // diff w.r.t this x at 2 + val y = deriv { x * x * x } + assertEquals(8.0, y.x) // y = x * x * x = 8 + assertEquals(12.0, x.d) // dy/dx = 3 * x * x = 12 + } + + @Test + fun testDiv() { + val x = ValueWithDeriv(5) + val y = ValueWithDeriv(2) + val z = deriv { x / y } + assertEquals(2.5, z.x) // z = x / y = 2.5 + assertEquals(0.5, x.d) // dz/dx = 1 / y = 0.5 + assertEquals(-1.25, y.d) // dz/dy = -x / y^2 = -1.25 + } + + @Test + fun testPow3() { + val x = ValueWithDeriv(2) // diff w.r.t this x at 2 + val y = deriv { pow(x, 3) } + assertEquals(8.0, y.x) // y = x ^ 3 = 8 + assertEquals(12.0, x.d) // dy/dx = 3 * x ^ 2 = 12 + } + + @Test + fun testPowFull() { + val x = ValueWithDeriv(2) + val y = ValueWithDeriv(3) + val z = deriv { pow(x, y) } + assertApprox(8.0, z.x) // z = x ^ y = 8 + assertApprox(12.0, x.d) // dz/dx = y * x ^ (y - 1) = 12 + assertApprox(8.0 * kotlin.math.ln(2.0), y.d) // dz/dy = x ^ y * ln(x) + } + + @Test + fun testFromPaper() { + val x = ValueWithDeriv(3) + val y = deriv { 2 * x + x * x * x } + assertEquals(33.0, y.x) // y = 2 * x + x * x * x = 33 + assertEquals(29.0, x.d) // dy/dx = 2 + 3 * x * x = 29 + } + + @Test + fun testLongChain() { + val n = 10_000 + val x = ValueWithDeriv(1) + val y = deriv { + var pow = ValueWithDeriv(1) + for (i in 1..n) pow *= x + pow + } + assertEquals(1.0, y.x) // y = x ^ n = 1 + assertEquals(n.toDouble(), x.d) // dy/dx = n * x ^ (n - 1) = n - 1 + } + + @Test + fun testExample() { + val x = ValueWithDeriv(2) + val y = deriv { sqr(x) + 5 * x + 3*one } + assertEquals(17.0, y.x) // the value of result (y) + assertEquals(9.0, x.d) // dy/dx + } + + @Test + fun testSqrt() { + val x = ValueWithDeriv(16) + val y = deriv { sqrt(x) } + assertEquals(4.0, y.x) // y = x ^ 1/2 = 4 + assertEquals(1.0 / 8, x.d) // dy/dx = 1/2 / x ^ 1/4 = 1/8 + } + + @Test + fun testSin() { + val x = ValueWithDeriv(PI / 6) + val y = deriv { sin(x) } + assertApprox(0.5, y.x) // y = sin(PI/6) = 0.5 + assertApprox(kotlin.math.sqrt(3.0) / 2, x.d) // dy/dx = cos(PI/6) = sqrt(3)/2 + } + + @Test + fun testCos() { + val x = ValueWithDeriv(PI / 6) + val y = deriv { cos(x) } + assertApprox(kotlin.math.sqrt(3.0) / 2, y.x) // y = cos(PI/6) = sqrt(3)/2 + assertApprox(-0.5, x.d) // dy/dx = -sin(PI/6) = -0.5 + } + + private fun assertApprox(a: Double, b: Double) { + if ((a - b) > 1e-10) assertEquals(a, b) + } + +} \ No newline at end of file From 765097cbbe2b254a49612c338a6e1caa3ab18d76 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 5 May 2019 08:57:00 +0300 Subject: [PATCH 31/34] MPP Univariate Autodiff from Roman Elizarov --- .../scientifik/kmath/operations/AutoDiff.kt | 32 +++++++------------ .../kmath/operations/AutoDiffTest.kt | 2 +- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt index 4b0070545..ab007b976 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt @@ -12,12 +12,18 @@ import kotlin.math.sqrt * Differentiable variable with value and derivative of differentiation ([deriv]) result * with respect to this variable. */ -data class ValueWithDeriv(var x: Double, var d: Double = 0.0) { +data class ValueWithDeriv(var x: Double) { constructor(x: Number) : this(x.toDouble()) + + //TODO move set accessor inside AutoDiffField + var d: Double = 0.0 + internal set } /** - * Runs differentiation and establishes [Field] context inside the block of code. + * Runs differentiation and establishes [AutoDiffField] context inside the block of code. + * + * The partial derivatives are placed in argument `d` variable * * Example: * ``` @@ -28,7 +34,7 @@ data class ValueWithDeriv(var x: Double, var d: Double = 0.0) { * ``` */ fun deriv(body: AutoDiffField.() -> ValueWithDeriv): ValueWithDeriv = - ValueWithDerivField().run { + AutoDiffFieldImpl().run { val result = body() result.d = 1.0 // computing derivative w.r.t result runBackwardPass() @@ -65,26 +71,12 @@ abstract class AutoDiffField : Field { operator fun ValueWithDeriv.minus(that: Number): ValueWithDeriv = derive(ValueWithDeriv(this.x - that.toDouble())) { z -> this.d += z.d } - - override operator fun Number.times(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() * that.x)) { z -> - that.d += z.d * this.toDouble() - } - - override operator fun ValueWithDeriv.times(b: Number): ValueWithDeriv = b.times(this) - - override operator fun Number.div(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() / that.x)) { z -> - that.d -= z.d * this.toDouble() / (that.x * that.x) - } - - override operator fun ValueWithDeriv.div(that: Number): ValueWithDeriv = derive(ValueWithDeriv(this.x / that.toDouble())) { z -> - this.d += z.d / that.toDouble() - } } /** * Automatic Differentiation context class. */ -private class ValueWithDerivField : AutoDiffField() { +private class AutoDiffFieldImpl : AutoDiffField() { // this stack contains pairs of blocks and values to apply them to private var stack = arrayOfNulls(8) @@ -135,8 +127,8 @@ private class ValueWithDerivField : AutoDiffField() { a.d += z.d * k.toDouble() } - override val zero: ValueWithDeriv get() = ValueWithDeriv(0.0, 0.0) - override val one: ValueWithDeriv get() = ValueWithDeriv(1.0, 0.0) + override val zero: ValueWithDeriv get() = ValueWithDeriv(0.0) + override val one: ValueWithDeriv get() = ValueWithDeriv(1.0) } // Extensions for differentiation of various basic mathematical functions diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt index 6f7299e9e..736c301fd 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt @@ -119,7 +119,7 @@ class AutoDiffTest { @Test fun testExample() { val x = ValueWithDeriv(2) - val y = deriv { sqr(x) + 5 * x + 3*one } + val y = deriv { sqr(x) + 5 * x + 3 } assertEquals(17.0, y.x) // the value of result (y) assertEquals(9.0, x.d) // dy/dx } From 6f9b704aa703799666aa9342266ba2eb9a7a386d Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 5 May 2019 09:39:51 +0300 Subject: [PATCH 32/34] Safe modification of autodiff --- build.gradle.kts | 2 +- .../scientifik/kmath/operations/AutoDiff.kt | 80 +++++++++------- .../kmath/operations/AutoDiffTest.kt | 96 ++++++++++--------- 3 files changed, 100 insertions(+), 78 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5f60bc2f0..4ec4695f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -val kmathVersion by extra("0.1.2-dev-3") +val kmathVersion by extra("0.1.2-dev-4") allprojects { repositories { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt index ab007b976..454d386b0 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt @@ -12,12 +12,12 @@ import kotlin.math.sqrt * Differentiable variable with value and derivative of differentiation ([deriv]) result * with respect to this variable. */ -data class ValueWithDeriv(var x: Double) { +open class Variable(val x: Double) { constructor(x: Number) : this(x.toDouble()) +} - //TODO move set accessor inside AutoDiffField - var d: Double = 0.0 - internal set +class DerivationResult(x: Double, val deriv: Map): Variable(x) { + fun deriv(variable: Variable) = deriv[variable] ?: 0.0 } /** @@ -27,48 +27,53 @@ data class ValueWithDeriv(var x: Double) { * * Example: * ``` - * val x = ValueWithDeriv(2) // define variable(s) and their values + * val x = Variable(2) // define variable(s) and their values * val y = deriv { sqr(x) + 5 * x + 3 } // write formulate in deriv context * assertEquals(17.0, y.x) // the value of result (y) * assertEquals(9.0, x.d) // dy/dx * ``` */ -fun deriv(body: AutoDiffField.() -> ValueWithDeriv): ValueWithDeriv = - AutoDiffFieldImpl().run { +fun deriv(body: AutoDiffField.() -> Variable): DerivationResult = + AutoDiffContext().run { val result = body() result.d = 1.0 // computing derivative w.r.t result runBackwardPass() - result + DerivationResult(result.x, derivatives) } -abstract class AutoDiffField : Field { +abstract class AutoDiffField : Field { /** * Performs update of derivative after the rest of the formula in the back-pass. * * For example, implementation of `sin` function is: * * ``` - * fun AD.sin(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(sin(x.x)) { z -> // call derive with function result + * fun AD.sin(x: Variable): Variable = derive(Variable(sin(x.x)) { z -> // call derive with function result * x.d += z.d * cos(x.x) // update derivative using chain rule and derivative of the function * } * ``` */ abstract fun derive(value: R, block: (R) -> Unit): R + /** + * A variable accessing inner state of derivatives. Use only in extensions + */ + abstract var Variable.d: Double + // Overloads for Double constants - operator fun Number.plus(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() + that.x)) { z -> + operator fun Number.plus(that: Variable): Variable = derive(Variable(this.toDouble() + that.x)) { z -> that.d += z.d } - operator fun ValueWithDeriv.plus(b: Number): ValueWithDeriv = b.plus(this) + operator fun Variable.plus(b: Number): Variable = b.plus(this) - operator fun Number.minus(that: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(this.toDouble() - that.x)) { z -> + operator fun Number.minus(that: Variable): Variable = derive(Variable(this.toDouble() - that.x)) { z -> that.d -= z.d } - operator fun ValueWithDeriv.minus(that: Number): ValueWithDeriv = derive(ValueWithDeriv(this.x - that.toDouble())) { z -> + operator fun Variable.minus(that: Number): Variable = derive(Variable(this.x - that.toDouble())) { z -> this.d += z.d } } @@ -76,12 +81,19 @@ abstract class AutoDiffField : Field { /** * Automatic Differentiation context class. */ -private class AutoDiffFieldImpl : AutoDiffField() { +private class AutoDiffContext : AutoDiffField() { // this stack contains pairs of blocks and values to apply them to private var stack = arrayOfNulls(8) private var sp = 0 + internal val derivatives = HashMap() + + override var Variable.d: Double + get() = derivatives[this] ?: 0.0 + set(value) { + derivatives[this] = value + } @Suppress("UNCHECKED_CAST") override fun derive(value: R, block: (R) -> Unit): R { @@ -104,71 +116,71 @@ private class AutoDiffFieldImpl : AutoDiffField() { // Basic math (+, -, *, /) - override fun add(a: ValueWithDeriv, b: ValueWithDeriv): ValueWithDeriv = - derive(ValueWithDeriv(a.x + b.x)) { z -> + override fun add(a: Variable, b: Variable): Variable = + derive(Variable(a.x + b.x)) { z -> a.d += z.d b.d += z.d } - override fun multiply(a: ValueWithDeriv, b: ValueWithDeriv): ValueWithDeriv = - derive(ValueWithDeriv(a.x * b.x)) { z -> + override fun multiply(a: Variable, b: Variable): Variable = + derive(Variable(a.x * b.x)) { z -> a.d += z.d * b.x b.d += z.d * a.x } - override fun divide(a: ValueWithDeriv, b: ValueWithDeriv): ValueWithDeriv = - derive(ValueWithDeriv(a.x / b.x)) { z -> + override fun divide(a: Variable, b: Variable): Variable = + derive(Variable(a.x / b.x)) { z -> a.d += z.d / b.x b.d -= z.d * a.x / (b.x * b.x) } - override fun multiply(a: ValueWithDeriv, k: Number): ValueWithDeriv = - derive(ValueWithDeriv(k.toDouble() * a.x)) { z -> + override fun multiply(a: Variable, k: Number): Variable = + derive(Variable(k.toDouble() * a.x)) { z -> a.d += z.d * k.toDouble() } - override val zero: ValueWithDeriv get() = ValueWithDeriv(0.0) - override val one: ValueWithDeriv get() = ValueWithDeriv(1.0) + override val zero: Variable get() = Variable(0.0) + override val one: Variable get() = Variable(1.0) } // Extensions for differentiation of various basic mathematical functions // x ^ 2 -fun AutoDiffField.sqr(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(x.x * x.x)) { z -> +fun AutoDiffField.sqr(x: Variable): Variable = derive(Variable(x.x * x.x)) { z -> x.d += z.d * 2 * x.x } // x ^ 1/2 -fun AutoDiffField.sqrt(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(sqrt(x.x))) { z -> +fun AutoDiffField.sqrt(x: Variable): Variable = derive(Variable(sqrt(x.x))) { z -> x.d += z.d * 0.5 / z.x } // x ^ y (const) -fun AutoDiffField.pow(x: ValueWithDeriv, y: Double): ValueWithDeriv = derive(ValueWithDeriv(x.x.pow(y))) { z -> +fun AutoDiffField.pow(x: Variable, y: Double): Variable = derive(Variable(x.x.pow(y))) { z -> x.d += z.d * y * x.x.pow(y - 1) } -fun AutoDiffField.pow(x: ValueWithDeriv, y: Int): ValueWithDeriv = pow(x, y.toDouble()) +fun AutoDiffField.pow(x: Variable, y: Int): Variable = pow(x, y.toDouble()) // exp(x) -fun AutoDiffField.exp(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.exp(x.x))) { z -> +fun AutoDiffField.exp(x: Variable): Variable = derive(Variable(kotlin.math.exp(x.x))) { z -> x.d += z.d * z.x } // ln(x) -fun AutoDiffField.ln(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.ln(x.x))) { z -> +fun AutoDiffField.ln(x: Variable): Variable = derive(Variable(kotlin.math.ln(x.x))) { z -> x.d += z.d / x.x } // x ^ y (any) -fun AutoDiffField.pow(x: ValueWithDeriv, y: ValueWithDeriv): ValueWithDeriv = exp(y * ln(x)) +fun AutoDiffField.pow(x: Variable, y: Variable): Variable = exp(y * ln(x)) // sin(x) -fun AutoDiffField.sin(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.sin(x.x))) { z -> +fun AutoDiffField.sin(x: Variable): Variable = derive(Variable(kotlin.math.sin(x.x))) { z -> x.d += z.d * kotlin.math.cos(x.x) } // cos(x) -fun AutoDiffField.cos(x: ValueWithDeriv): ValueWithDeriv = derive(ValueWithDeriv(kotlin.math.cos(x.x))) { z -> +fun AutoDiffField.cos(x: Variable): Variable = derive(Variable(kotlin.math.cos(x.x))) { z -> x.d -= z.d * kotlin.math.sin(x.x) } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt index 736c301fd..9adc2f977 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt @@ -7,145 +7,155 @@ import kotlin.test.assertEquals class AutoDiffTest { @Test fun testPlusX2() { - val x = ValueWithDeriv(3) // diff w.r.t this x at 3 + val x = Variable(3) // diff w.r.t this x at 3 val y = deriv { x + x } assertEquals(6.0, y.x) // y = x + x = 6 - assertEquals(2.0, x.d) // dy/dx = 2 + assertEquals(2.0, y.deriv(x)) // dy/dx = 2 } @Test fun testPlus() { // two variables - val x = ValueWithDeriv(2) - val y = ValueWithDeriv(3) + val x = Variable(2) + val y = Variable(3) val z = deriv { x + y } assertEquals(5.0, z.x) // z = x + y = 5 - assertEquals(1.0, x.d) // dz/dx = 1 - assertEquals(1.0, y.d) // dz/dy = 1 + assertEquals(1.0, z.deriv(x)) // dz/dx = 1 + assertEquals(1.0, z.deriv(y)) // dz/dy = 1 } @Test fun testMinus() { // two variables - val x = ValueWithDeriv(7) - val y = ValueWithDeriv(3) + val x = Variable(7) + val y = Variable(3) val z = deriv { x - y } assertEquals(4.0, z.x) // z = x - y = 4 - assertEquals(1.0, x.d) // dz/dx = 1 - assertEquals(-1.0, y.d) // dz/dy = -1 + assertEquals(1.0, z.deriv(x)) // dz/dx = 1 + assertEquals(-1.0, z.deriv(y)) // dz/dy = -1 } @Test fun testMulX2() { - val x = ValueWithDeriv(3) // diff w.r.t this x at 3 + val x = Variable(3) // diff w.r.t this x at 3 val y = deriv { x * x } assertEquals(9.0, y.x) // y = x * x = 9 - assertEquals(6.0, x.d) // dy/dx = 2 * x = 7 + assertEquals(6.0, y.deriv(x)) // dy/dx = 2 * x = 7 } @Test fun testSqr() { - val x = ValueWithDeriv(3) + val x = Variable(3) val y = deriv { sqr(x) } assertEquals(9.0, y.x) // y = x ^ 2 = 9 - assertEquals(6.0, x.d) // dy/dx = 2 * x = 7 + assertEquals(6.0, y.deriv(x)) // dy/dx = 2 * x = 7 } @Test fun testSqrSqr() { - val x = ValueWithDeriv(2) + val x = Variable(2) val y = deriv { sqr(sqr(x)) } assertEquals(16.0, y.x) // y = x ^ 4 = 16 - assertEquals(32.0, x.d) // dy/dx = 4 * x^3 = 32 + assertEquals(32.0, y.deriv(x)) // dy/dx = 4 * x^3 = 32 } @Test fun testX3() { - val x = ValueWithDeriv(2) // diff w.r.t this x at 2 + val x = Variable(2) // diff w.r.t this x at 2 val y = deriv { x * x * x } assertEquals(8.0, y.x) // y = x * x * x = 8 - assertEquals(12.0, x.d) // dy/dx = 3 * x * x = 12 + assertEquals(12.0, y.deriv(x)) // dy/dx = 3 * x * x = 12 } @Test fun testDiv() { - val x = ValueWithDeriv(5) - val y = ValueWithDeriv(2) + val x = Variable(5) + val y = Variable(2) val z = deriv { x / y } assertEquals(2.5, z.x) // z = x / y = 2.5 - assertEquals(0.5, x.d) // dz/dx = 1 / y = 0.5 - assertEquals(-1.25, y.d) // dz/dy = -x / y^2 = -1.25 + assertEquals(0.5, z.deriv(x)) // dz/dx = 1 / y = 0.5 + assertEquals(-1.25, z.deriv(y)) // dz/dy = -x / y^2 = -1.25 } @Test fun testPow3() { - val x = ValueWithDeriv(2) // diff w.r.t this x at 2 + val x = Variable(2) // diff w.r.t this x at 2 val y = deriv { pow(x, 3) } assertEquals(8.0, y.x) // y = x ^ 3 = 8 - assertEquals(12.0, x.d) // dy/dx = 3 * x ^ 2 = 12 + assertEquals(12.0, y.deriv(x)) // dy/dx = 3 * x ^ 2 = 12 } @Test fun testPowFull() { - val x = ValueWithDeriv(2) - val y = ValueWithDeriv(3) + val x = Variable(2) + val y = Variable(3) val z = deriv { pow(x, y) } assertApprox(8.0, z.x) // z = x ^ y = 8 - assertApprox(12.0, x.d) // dz/dx = y * x ^ (y - 1) = 12 - assertApprox(8.0 * kotlin.math.ln(2.0), y.d) // dz/dy = x ^ y * ln(x) + assertApprox(12.0, z.deriv(x)) // dz/dx = y * x ^ (y - 1) = 12 + assertApprox(8.0 * kotlin.math.ln(2.0), z.deriv(y)) // dz/dy = x ^ y * ln(x) } @Test fun testFromPaper() { - val x = ValueWithDeriv(3) + val x = Variable(3) val y = deriv { 2 * x + x * x * x } assertEquals(33.0, y.x) // y = 2 * x + x * x * x = 33 - assertEquals(29.0, x.d) // dy/dx = 2 + 3 * x * x = 29 + assertEquals(29.0, y.deriv(x)) // dy/dx = 2 + 3 * x * x = 29 + } + + @Test + fun testInnerVariable() { + val x = Variable(1) + val y = deriv { + Variable(1) * x + } + assertEquals(1.0, y.x) // y = x ^ n = 1 + assertEquals(1.0, y.deriv(x)) // dy/dx = n * x ^ (n - 1) = n - 1 } @Test fun testLongChain() { val n = 10_000 - val x = ValueWithDeriv(1) + val x = Variable(1) val y = deriv { - var pow = ValueWithDeriv(1) - for (i in 1..n) pow *= x - pow + var res = Variable(1) + for (i in 1..n) res *= x + res } assertEquals(1.0, y.x) // y = x ^ n = 1 - assertEquals(n.toDouble(), x.d) // dy/dx = n * x ^ (n - 1) = n - 1 + assertEquals(n.toDouble(), y.deriv(x)) // dy/dx = n * x ^ (n - 1) = n - 1 } @Test fun testExample() { - val x = ValueWithDeriv(2) + val x = Variable(2) val y = deriv { sqr(x) + 5 * x + 3 } assertEquals(17.0, y.x) // the value of result (y) - assertEquals(9.0, x.d) // dy/dx + assertEquals(9.0, y.deriv(x)) // dy/dx } @Test fun testSqrt() { - val x = ValueWithDeriv(16) + val x = Variable(16) val y = deriv { sqrt(x) } assertEquals(4.0, y.x) // y = x ^ 1/2 = 4 - assertEquals(1.0 / 8, x.d) // dy/dx = 1/2 / x ^ 1/4 = 1/8 + assertEquals(1.0 / 8, y.deriv(x)) // dy/dx = 1/2 / x ^ 1/4 = 1/8 } @Test fun testSin() { - val x = ValueWithDeriv(PI / 6) + val x = Variable(PI / 6) val y = deriv { sin(x) } assertApprox(0.5, y.x) // y = sin(PI/6) = 0.5 - assertApprox(kotlin.math.sqrt(3.0) / 2, x.d) // dy/dx = cos(PI/6) = sqrt(3)/2 + assertApprox(kotlin.math.sqrt(3.0) / 2, y.deriv(x)) // dy/dx = cos(PI/6) = sqrt(3)/2 } @Test fun testCos() { - val x = ValueWithDeriv(PI / 6) + val x = Variable(PI / 6) val y = deriv { cos(x) } assertApprox(kotlin.math.sqrt(3.0) / 2, y.x) // y = cos(PI/6) = sqrt(3)/2 - assertApprox(-0.5, x.d) // dy/dx = -sin(PI/6) = -0.5 + assertApprox(-0.5, y.deriv(x)) // dy/dx = -sin(PI/6) = -0.5 } private fun assertApprox(a: Double, b: Double) { From c3f0dbe16186d6641767f415d69441efc8cb40d5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 5 May 2019 10:14:05 +0300 Subject: [PATCH 33/34] Safe modification of autodiff --- .../scientifik/kmath/operations/AutoDiff.kt | 90 ++++++++++++------- .../scientifik/kmath/structures/Buffers.kt | 5 ++ .../kmath/operations/AutoDiffTest.kt | 45 ++++++---- 3 files changed, 93 insertions(+), 47 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt index 454d386b0..cd50815e3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt @@ -1,5 +1,7 @@ package scientifik.kmath.operations +import scientifik.kmath.linear.Point +import scientifik.kmath.structures.asBuffer import kotlin.math.pow import kotlin.math.sqrt @@ -12,12 +14,26 @@ import kotlin.math.sqrt * Differentiable variable with value and derivative of differentiation ([deriv]) result * with respect to this variable. */ -open class Variable(val x: Double) { +open class Variable(val value: Double) { constructor(x: Number) : this(x.toDouble()) } -class DerivationResult(x: Double, val deriv: Map): Variable(x) { +class DerivationResult(value: Double, val deriv: Map) : Variable(value) { fun deriv(variable: Variable) = deriv[variable] ?: 0.0 + + /** + * compute divergence + */ + fun div() = deriv.values.sum() + + /** + * Compute a gradient for variables in given order + */ + fun grad(vararg variables: Variable): Point = if (variables.isEmpty()) { + error("Variable order is not provided for gradient construction") + } else { + variables.map(::deriv).toDoubleArray().asBuffer() + } } /** @@ -38,7 +54,7 @@ fun deriv(body: AutoDiffField.() -> Variable): DerivationResult = val result = body() result.d = 1.0 // computing derivative w.r.t result runBackwardPass() - DerivationResult(result.x, derivatives) + DerivationResult(result.value, derivatives) } @@ -61,19 +77,21 @@ abstract class AutoDiffField : Field { */ abstract var Variable.d: Double + abstract fun variable(value: Double): Variable + // Overloads for Double constants - operator fun Number.plus(that: Variable): Variable = derive(Variable(this.toDouble() + that.x)) { z -> + operator fun Number.plus(that: Variable): Variable = derive(variable(this.toDouble() + that.value)) { z -> that.d += z.d } operator fun Variable.plus(b: Number): Variable = b.plus(this) - operator fun Number.minus(that: Variable): Variable = derive(Variable(this.toDouble() - that.x)) { z -> + operator fun Number.minus(that: Variable): Variable = derive(variable(this.toDouble() - that.value)) { z -> that.d -= z.d } - operator fun Variable.minus(that: Number): Variable = derive(Variable(this.x - that.toDouble())) { z -> + operator fun Variable.minus(that: Number): Variable = derive(variable(this.value - that.toDouble())) { z -> this.d += z.d } } @@ -89,10 +107,22 @@ private class AutoDiffContext : AutoDiffField() { internal val derivatives = HashMap() + + /** + * A variable coupled with its derivative. For internal use only + */ + class VariableWithDeriv(x: Double, var d: Double = 0.0): Variable(x) + + override fun variable(value: Double): Variable = VariableWithDeriv(value) + override var Variable.d: Double - get() = derivatives[this] ?: 0.0 + get() = (this as? VariableWithDeriv)?.d ?: derivatives[this] ?: 0.0 set(value) { - derivatives[this] = value + if(this is VariableWithDeriv){ + d = value + }else { + derivatives[this] = value + } } @Suppress("UNCHECKED_CAST") @@ -117,25 +147,25 @@ private class AutoDiffContext : AutoDiffField() { override fun add(a: Variable, b: Variable): Variable = - derive(Variable(a.x + b.x)) { z -> + derive(variable(a.value + b.value)) { z -> a.d += z.d b.d += z.d } override fun multiply(a: Variable, b: Variable): Variable = - derive(Variable(a.x * b.x)) { z -> - a.d += z.d * b.x - b.d += z.d * a.x + derive(variable(a.value * b.value)) { z -> + a.d += z.d * b.value + b.d += z.d * a.value } override fun divide(a: Variable, b: Variable): Variable = - derive(Variable(a.x / b.x)) { z -> - a.d += z.d / b.x - b.d -= z.d * a.x / (b.x * b.x) + derive(Variable(a.value / b.value)) { z -> + a.d += z.d / b.value + b.d -= z.d * a.value / (b.value * b.value) } override fun multiply(a: Variable, k: Number): Variable = - derive(Variable(k.toDouble() * a.x)) { z -> + derive(variable(k.toDouble() * a.value)) { z -> a.d += z.d * k.toDouble() } @@ -146,41 +176,41 @@ private class AutoDiffContext : AutoDiffField() { // Extensions for differentiation of various basic mathematical functions // x ^ 2 -fun AutoDiffField.sqr(x: Variable): Variable = derive(Variable(x.x * x.x)) { z -> - x.d += z.d * 2 * x.x +fun AutoDiffField.sqr(x: Variable): Variable = derive(variable(x.value * x.value)) { z -> + x.d += z.d * 2 * x.value } // x ^ 1/2 -fun AutoDiffField.sqrt(x: Variable): Variable = derive(Variable(sqrt(x.x))) { z -> - x.d += z.d * 0.5 / z.x +fun AutoDiffField.sqrt(x: Variable): Variable = derive(variable(sqrt(x.value))) { z -> + x.d += z.d * 0.5 / z.value } // x ^ y (const) -fun AutoDiffField.pow(x: Variable, y: Double): Variable = derive(Variable(x.x.pow(y))) { z -> - x.d += z.d * y * x.x.pow(y - 1) +fun AutoDiffField.pow(x: Variable, y: Double): Variable = derive(variable(x.value.pow(y))) { z -> + x.d += z.d * y * x.value.pow(y - 1) } fun AutoDiffField.pow(x: Variable, y: Int): Variable = pow(x, y.toDouble()) // exp(x) -fun AutoDiffField.exp(x: Variable): Variable = derive(Variable(kotlin.math.exp(x.x))) { z -> - x.d += z.d * z.x +fun AutoDiffField.exp(x: Variable): Variable = derive(variable(kotlin.math.exp(x.value))) { z -> + x.d += z.d * z.value } // ln(x) -fun AutoDiffField.ln(x: Variable): Variable = derive(Variable(kotlin.math.ln(x.x))) { z -> - x.d += z.d / x.x +fun AutoDiffField.ln(x: Variable): Variable = derive(Variable(kotlin.math.ln(x.value))) { z -> + x.d += z.d / x.value } // x ^ y (any) fun AutoDiffField.pow(x: Variable, y: Variable): Variable = exp(y * ln(x)) // sin(x) -fun AutoDiffField.sin(x: Variable): Variable = derive(Variable(kotlin.math.sin(x.x))) { z -> - x.d += z.d * kotlin.math.cos(x.x) +fun AutoDiffField.sin(x: Variable): Variable = derive(variable(kotlin.math.sin(x.value))) { z -> + x.d += z.d * kotlin.math.cos(x.value) } // cos(x) -fun AutoDiffField.cos(x: Variable): Variable = derive(Variable(kotlin.math.cos(x.x))) { z -> - x.d -= z.d * kotlin.math.sin(x.x) +fun AutoDiffField.cos(x: Variable): Variable = derive(variable(kotlin.math.cos(x.value))) { z -> + x.d -= z.d * kotlin.math.sin(x.value) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index e521df86e..6aaa2da8f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -37,6 +37,11 @@ interface Buffer { companion object { + inline fun real(size: Int, initializer: (Int) -> Double): DoubleBuffer { + val array = DoubleArray(size) { initializer(it) } + return DoubleBuffer(array) + } + /** * Create a boxing buffer of given type */ diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt index 9adc2f977..e4fad888e 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/AutoDiffTest.kt @@ -1,15 +1,17 @@ package scientifik.kmath.operations +import scientifik.kmath.structures.asBuffer import kotlin.math.PI import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue class AutoDiffTest { @Test fun testPlusX2() { val x = Variable(3) // diff w.r.t this x at 3 val y = deriv { x + x } - assertEquals(6.0, y.x) // y = x + x = 6 + assertEquals(6.0, y.value) // y = x + x = 6 assertEquals(2.0, y.deriv(x)) // dy/dx = 2 } @@ -19,7 +21,7 @@ class AutoDiffTest { val x = Variable(2) val y = Variable(3) val z = deriv { x + y } - assertEquals(5.0, z.x) // z = x + y = 5 + assertEquals(5.0, z.value) // z = x + y = 5 assertEquals(1.0, z.deriv(x)) // dz/dx = 1 assertEquals(1.0, z.deriv(y)) // dz/dy = 1 } @@ -30,7 +32,7 @@ class AutoDiffTest { val x = Variable(7) val y = Variable(3) val z = deriv { x - y } - assertEquals(4.0, z.x) // z = x - y = 4 + assertEquals(4.0, z.value) // z = x - y = 4 assertEquals(1.0, z.deriv(x)) // dz/dx = 1 assertEquals(-1.0, z.deriv(y)) // dz/dy = -1 } @@ -39,7 +41,7 @@ class AutoDiffTest { fun testMulX2() { val x = Variable(3) // diff w.r.t this x at 3 val y = deriv { x * x } - assertEquals(9.0, y.x) // y = x * x = 9 + assertEquals(9.0, y.value) // y = x * x = 9 assertEquals(6.0, y.deriv(x)) // dy/dx = 2 * x = 7 } @@ -47,7 +49,7 @@ class AutoDiffTest { fun testSqr() { val x = Variable(3) val y = deriv { sqr(x) } - assertEquals(9.0, y.x) // y = x ^ 2 = 9 + assertEquals(9.0, y.value) // y = x ^ 2 = 9 assertEquals(6.0, y.deriv(x)) // dy/dx = 2 * x = 7 } @@ -55,7 +57,7 @@ class AutoDiffTest { fun testSqrSqr() { val x = Variable(2) val y = deriv { sqr(sqr(x)) } - assertEquals(16.0, y.x) // y = x ^ 4 = 16 + assertEquals(16.0, y.value) // y = x ^ 4 = 16 assertEquals(32.0, y.deriv(x)) // dy/dx = 4 * x^3 = 32 } @@ -63,7 +65,7 @@ class AutoDiffTest { fun testX3() { val x = Variable(2) // diff w.r.t this x at 2 val y = deriv { x * x * x } - assertEquals(8.0, y.x) // y = x * x * x = 8 + assertEquals(8.0, y.value) // y = x * x * x = 8 assertEquals(12.0, y.deriv(x)) // dy/dx = 3 * x * x = 12 } @@ -72,7 +74,7 @@ class AutoDiffTest { val x = Variable(5) val y = Variable(2) val z = deriv { x / y } - assertEquals(2.5, z.x) // z = x / y = 2.5 + assertEquals(2.5, z.value) // z = x / y = 2.5 assertEquals(0.5, z.deriv(x)) // dz/dx = 1 / y = 0.5 assertEquals(-1.25, z.deriv(y)) // dz/dy = -x / y^2 = -1.25 } @@ -81,7 +83,7 @@ class AutoDiffTest { fun testPow3() { val x = Variable(2) // diff w.r.t this x at 2 val y = deriv { pow(x, 3) } - assertEquals(8.0, y.x) // y = x ^ 3 = 8 + assertEquals(8.0, y.value) // y = x ^ 3 = 8 assertEquals(12.0, y.deriv(x)) // dy/dx = 3 * x ^ 2 = 12 } @@ -90,7 +92,7 @@ class AutoDiffTest { val x = Variable(2) val y = Variable(3) val z = deriv { pow(x, y) } - assertApprox(8.0, z.x) // z = x ^ y = 8 + assertApprox(8.0, z.value) // z = x ^ y = 8 assertApprox(12.0, z.deriv(x)) // dz/dx = y * x ^ (y - 1) = 12 assertApprox(8.0 * kotlin.math.ln(2.0), z.deriv(y)) // dz/dy = x ^ y * ln(x) } @@ -99,7 +101,7 @@ class AutoDiffTest { fun testFromPaper() { val x = Variable(3) val y = deriv { 2 * x + x * x * x } - assertEquals(33.0, y.x) // y = 2 * x + x * x * x = 33 + assertEquals(33.0, y.value) // y = 2 * x + x * x * x = 33 assertEquals(29.0, y.deriv(x)) // dy/dx = 2 + 3 * x * x = 29 } @@ -109,7 +111,7 @@ class AutoDiffTest { val y = deriv { Variable(1) * x } - assertEquals(1.0, y.x) // y = x ^ n = 1 + assertEquals(1.0, y.value) // y = x ^ n = 1 assertEquals(1.0, y.deriv(x)) // dy/dx = n * x ^ (n - 1) = n - 1 } @@ -122,7 +124,7 @@ class AutoDiffTest { for (i in 1..n) res *= x res } - assertEquals(1.0, y.x) // y = x ^ n = 1 + assertEquals(1.0, y.value) // y = x ^ n = 1 assertEquals(n.toDouble(), y.deriv(x)) // dy/dx = n * x ^ (n - 1) = n - 1 } @@ -130,7 +132,7 @@ class AutoDiffTest { fun testExample() { val x = Variable(2) val y = deriv { sqr(x) + 5 * x + 3 } - assertEquals(17.0, y.x) // the value of result (y) + assertEquals(17.0, y.value) // the value of result (y) assertEquals(9.0, y.deriv(x)) // dy/dx } @@ -138,7 +140,7 @@ class AutoDiffTest { fun testSqrt() { val x = Variable(16) val y = deriv { sqrt(x) } - assertEquals(4.0, y.x) // y = x ^ 1/2 = 4 + assertEquals(4.0, y.value) // y = x ^ 1/2 = 4 assertEquals(1.0 / 8, y.deriv(x)) // dy/dx = 1/2 / x ^ 1/4 = 1/8 } @@ -146,7 +148,7 @@ class AutoDiffTest { fun testSin() { val x = Variable(PI / 6) val y = deriv { sin(x) } - assertApprox(0.5, y.x) // y = sin(PI/6) = 0.5 + assertApprox(0.5, y.value) // y = sin(PI/6) = 0.5 assertApprox(kotlin.math.sqrt(3.0) / 2, y.deriv(x)) // dy/dx = cos(PI/6) = sqrt(3)/2 } @@ -154,10 +156,19 @@ class AutoDiffTest { fun testCos() { val x = Variable(PI / 6) val y = deriv { cos(x) } - assertApprox(kotlin.math.sqrt(3.0) / 2, y.x) // y = cos(PI/6) = sqrt(3)/2 + assertApprox(kotlin.math.sqrt(3.0) / 2, y.value) // y = cos(PI/6) = sqrt(3)/2 assertApprox(-0.5, y.deriv(x)) // dy/dx = -sin(PI/6) = -0.5 } + @Test + fun testDivGrad() { + val x = Variable(1.0) + val y = Variable(2.0) + val res = deriv { x * x + y * y } + assertEquals(6.0, res.div()) + assertTrue(res.grad(x, y).contentEquals(doubleArrayOf(2.0, 4.0).asBuffer())) + } + private fun assertApprox(a: Double, b: Double) { if ((a - b) > 1e-10) assertEquals(a, b) } From 2503cb69d5840f14c0ab91cf65ff0a4ccc590b96 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 31 May 2019 12:32:22 +0300 Subject: [PATCH 34/34] removed unnecessary type-parameter in matrix --- .../scientifik/kmath/linear/FeaturedMatrix.kt | 2 +- .../scientifik/kmath/operations/AutoDiff.kt | 12 +++++----- .../scientifik/kmath/linear/MatrixTest.kt | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt index 0886b3ce5..9f6b9f600 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt @@ -85,7 +85,7 @@ class TransposedFeature(val original: Matrix) : MatrixFeature /** * Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A` */ -fun > Matrix.transpose(): Matrix { +fun Matrix.transpose(): Matrix { return this.getFeature>()?.original ?: VirtualMatrix( this.colNum, this.rowNum, diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt index cd50815e3..9714630c1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AutoDiff.kt @@ -73,7 +73,8 @@ abstract class AutoDiffField : Field { abstract fun derive(value: R, block: (R) -> Unit): R /** - * A variable accessing inner state of derivatives. Use only in extensions + * A variable accessing inner state of derivatives. + * Use this function in inner builders to avoid creating additional derivative bindings */ abstract var Variable.d: Double @@ -111,16 +112,17 @@ private class AutoDiffContext : AutoDiffField() { /** * A variable coupled with its derivative. For internal use only */ - class VariableWithDeriv(x: Double, var d: Double = 0.0): Variable(x) + class VariableWithDeriv(x: Double, var d: Double = 0.0) : Variable(x) - override fun variable(value: Double): Variable = VariableWithDeriv(value) + + override fun variable(value: Double): Variable = VariableWithDeriv(value) override var Variable.d: Double get() = (this as? VariableWithDeriv)?.d ?: derivatives[this] ?: 0.0 set(value) { - if(this is VariableWithDeriv){ + if (this is VariableWithDeriv) { d = value - }else { + } else { derivatives[this] = value } } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index f7d603dab..5370ae960 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -52,4 +52,26 @@ class MatrixTest { assertEquals(2.0, matrix[1, 2]) } + + @Test + fun testMatrixExtension() { + val transitionMatrix: Matrix = VirtualMatrix(6, 6) { row, col -> + when { + col == 0 -> .50 + row + 1 == col -> .50 + row == 5 && col == 5 -> 1.0 + else -> 0.0 + } + } + + infix fun Matrix.pow(power: Int): Matrix { + var res = this + repeat(power - 1) { + res = res dot this + } + return res + } + + val toTenthPower = transitionMatrix pow 10 + } } \ No newline at end of file