diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 000000000..adc74adfe --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,17 @@ +name: Gradle build + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Gradle + run: ./gradlew build diff --git a/README.md b/README.md index 0aeab2ee8..5678fc530 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/scientifik/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) +Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) + [![DOI](https://zenodo.org/badge/129486382.svg)](https://zenodo.org/badge/latestdoi/129486382) # KMath Could be pronounced as `key-math`. The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's `numpy` library. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. +# Goal +* Provide a flexible and powerful API to work with mathematics abstractions in Kotlin-multiplatform (JVM and JS for now and Native in future). +* Provide basic multiplatform implementations for those abstractions (without significant performance optimization). +* Provide bindings and wrappers with those abstractions for popular optimized platform libraries. + +## Non-goals +* Be like Numpy. It was the idea at the beginning, but we decided that we can do better in terms of API. +* Provide best performance out of the box. We have specialized libraries for that. Need only API wrappers for them. +* Cover all cases as immediately and in one bundle. We will modularize everything and add new features gradually. +* Provide specialized behavior in the core. API is made generic on purpose, so one needs to specialize for types, like for `Double` in the core. For that we will have specialization modules like `for-real`, which will give better experience for those, who want to work with specific types. + ## Features Actual feature list is [here](doc/features.md) @@ -48,58 +61,15 @@ The plan is to have wrappers for koma implementations for compatibility with kma ## Multi-platform support -KMath is developed as a multi-platform library, which means that most of interfaces are declared in the [common module](kmath-core/src/commonMain). -Implementation is also done in the common module wherever possible. In some cases, features are delegated to -platform-specific implementations even if they could be done in the common module for performance reasons. -Currently, the JVM is the main focus of development, however Kotlin/Native and Kotlin/JS contributions are also welcome. +KMath is developed as a multi-platform library, which means that most of interfaces are declared in the [common module](kmath-core/src/commonMain). Implementation is also done in the common module wherever possible. In some cases, features are delegated to platform-specific implementations even if they could be done in the common module for performance reasons. Currently, the JVM is the main focus of development, however Kotlin/Native and Kotlin/JS contributions are also welcome. ## Performance -Calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve -both performance and flexibility. We expect to focus on creating convenient universal API first and then work on -increasing performance for specific cases. We expect the worst KMath benchmarks will perform better than native Python, -but worse than optimized native/SciPy (mostly due to boxing operations on primitive numbers). The best performance -of optimized parts should be better than SciPy. +Calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve both performance and flexibility. We expect to focus on creating convenient universal API first and then work on increasing performance for specific cases. We expect the worst KMath benchmarks will perform better than native Python, but worse than optimized native/SciPy (mostly due to boxing operations on primitive numbers). The best performance of optimized parts could be better than SciPy. -## Releases +### Dependency -Working builds can be obtained here: [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). - -### Development - -The project is currently in pre-release stage. Nightly builds can be used by adding an additional repository to the Gradle config like so: - -```groovy -repositories { - maven { url = "http://npm.mipt.ru:8081/artifactory/gradle-dev" } - mavenCentral() -} -``` - -or for the Gradle Kotlin DSL: - -```kotlin -repositories { - maven("http://npm.mipt.ru:8081/artifactory/gradle-dev") - mavenCentral() -} -``` - -Then use a regular dependency like so: - -```groovy -api "scientifik:kmath-core-jvm:0.1.3-dev" -``` - -or in the Gradle Kotlin DSL: - -```kotlin -api("scientifik:kmath-core-jvm:0.1.3-dev") -``` - -### Release - -Release artifacts are accessible from bintray with following configuration: +Release artifacts are accessible from bintray with following configuration (see documentation for [kotlin-multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html) form more details): ```kotlin repositories{ @@ -107,10 +77,23 @@ repositories{ } dependencies{ - api("scientifik:kmath-core-jvm:0.1.3") + api("scientifik:kmath-core:${kmathVersion}") + //api("scientifik:kmath-core-jvm:${kmathVersion}") for jvm-specific version } ``` +Gradle `5.2+` is required for multiplatform artifacts. + +### Development + +Development builds are accessible from the reposirtory +```kotlin +repositories{ + maven("https://dl.bintray.com/mipt-npm/dev") +} +``` +with the same artifact names. + ## Contributing The project requires a lot of additional work. Please feel free to contribute in any way and propose new features. diff --git a/build.gradle.kts b/build.gradle.kts index edc4bed7b..a63966dfe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,16 @@ -val kmathVersion by extra("0.1.3") +plugins { + id("scientifik.publish") version "0.2.6" apply false +} + +val kmathVersion by extra("0.1.4-dev-1") + +val bintrayRepo by extra("scientifik") +val githubProject by extra("kmath") allprojects { repositories { jcenter() - maven("https://kotlin.bintray.com/kotlinx") + maven("https://dl.bintray.com/kotlin/kotlinx") } group = "scientifik" @@ -11,8 +18,7 @@ allprojects { } subprojects { - apply(plugin = "dokka-publish") if (name.startsWith("kmath")) { - apply(plugin = "npm-publish") + apply(plugin = "scientifik.publish") } -} +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 318189162..000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - `kotlin-dsl` -} - -repositories { - gradlePluginPortal() - jcenter() -} - -val kotlinVersion = "1.3.31" - -// 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.6") - implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") - implementation("org.jetbrains.dokka:dokka-gradle-plugin:0.9.18") - implementation("com.moowork.gradle:gradle-node-plugin:1.3.1") - implementation("org.openjfx:javafx-plugin:0.0.7") -} 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/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt deleted file mode 100644 index 883af1203..000000000 --- a/buildSrc/src/main/kotlin/Versions.kt +++ /dev/null @@ -1,9 +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 Versions { - val ioVersion = "0.1.8" - val coroutinesVersion = "1.2.1" - val atomicfuVersion = "0.12.6" - val serializationVersion = "0.11.0" -} diff --git a/buildSrc/src/main/kotlin/dokka-publish.gradle.kts b/buildSrc/src/main/kotlin/dokka-publish.gradle.kts deleted file mode 100644 index b7b48fb68..000000000 --- a/buildSrc/src/main/kotlin/dokka-publish.gradle.kts +++ /dev/null @@ -1,75 +0,0 @@ -import org.jetbrains.dokka.gradle.DokkaTask -import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension - -plugins { - id("org.jetbrains.dokka") - `maven-publish` -} - -afterEvaluate { - - extensions.findByType()?.apply{ - val dokka by tasks.getting(DokkaTask::class) { - outputFormat = "html" - outputDirectory = "$buildDir/javadoc" - jdkVersion = 8 - - kotlinTasks { - // dokka fails to retrieve sources from MPP-tasks so we only define the jvm task - listOf(tasks.getByPath("compileKotlinJvm")) - } - sourceRoot { - // assuming only single source dir - path = sourceSets["commonMain"].kotlin.srcDirs.first().toString() - platforms = listOf("Common") - } - // although the JVM sources are now taken from the task, - // we still define the jvm source root to get the JVM marker in the generated html - sourceRoot { - // assuming only single source dir - path = sourceSets["jvmMain"].kotlin.srcDirs.first().toString() - platforms = listOf("JVM") - } - } - - val kdocJar by tasks.registering(Jar::class) { - group = JavaBasePlugin.DOCUMENTATION_GROUP - dependsOn(dokka) - archiveClassifier.set("javadoc") - from("$buildDir/javadoc") - } - - configure { - - targets.all { - val publication = publications.findByName(name) as MavenPublication - - // Patch publications with fake javadoc - publication.artifact(kdocJar.get()) - } - } - } - - - extensions.findByType()?.apply{ - val dokka by tasks.getting(DokkaTask::class) { - outputFormat = "html" - outputDirectory = "$buildDir/javadoc" - jdkVersion = 8 - } - - val kdocJar by tasks.registering(Jar::class) { - group = JavaBasePlugin.DOCUMENTATION_GROUP - dependsOn(dokka) - archiveClassifier.set("javadoc") - from("$buildDir/javadoc") - } - - configure { - publications.filterIsInstance().forEach { publication -> - publication.artifact(kdocJar.get()) - } - } - } -} \ 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 deleted file mode 100644 index 61759a28a..000000000 --- a/buildSrc/src/main/kotlin/js-test.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -import com.moowork.gradle.node.npm.NpmTask -import com.moowork.gradle.node.task.NodeTask -import org.gradle.kotlin.dsl.* -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile - -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/npm-multiplatform.gradle.kts b/buildSrc/src/main/kotlin/npm-multiplatform.gradle.kts deleted file mode 100644 index 20b096ab8..000000000 --- a/buildSrc/src/main/kotlin/npm-multiplatform.gradle.kts +++ /dev/null @@ -1,82 +0,0 @@ -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 { - 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") - } - } - - // Apply JS test configuration - val runJsTests by ext(false) - - if (runJsTests) { - apply(plugin = "js-test") - } - -} diff --git a/buildSrc/src/main/kotlin/npm-publish.gradle.kts b/buildSrc/src/main/kotlin/npm-publish.gradle.kts deleted file mode 100644 index 175f15f0b..000000000 --- a/buildSrc/src/main/kotlin/npm-publish.gradle.kts +++ /dev/null @@ -1,136 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -import com.jfrog.bintray.gradle.tasks.BintrayUploadTask -import groovy.lang.GroovyObject -import org.gradle.api.publish.maven.internal.artifact.FileBasedMavenArtifact -import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig -import org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig - -// 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 { - `maven-publish` - id("com.jfrog.bintray") - id("com.jfrog.artifactory") -} - -val vcs = "https://github.com/mipt-npm/kmath" -val bintrayRepo = "https://bintray.com/mipt-npm/scientifik" - -// Configure publishing -publishing { - repositories { - maven(bintrayRepo) - } - - // Process each publication we have in this project - publications.filterIsInstance().forEach { publication -> - - // use type safe pom config GSL instead 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 { - user = findProperty("bintrayUser") as? String ?: System.getenv("BINTRAY_USER") - key = findProperty("bintrayApiKey") as? String? ?: 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.apply { - userOrg = "mipt-npm" - repo = "scientifik" - name = project.name - issueTrackerUrl = "$vcs/issues" - setLicenses("Apache-2.0") - vcsUrl = vcs - version.apply { - name = project.version.toString() - vcsTag = project.version.toString() - released = java.util.Date().toString() - } - } - - //workaround bintray bug - afterEvaluate { - setPublications(*publishing.publications.names.toTypedArray()) - } - - tasks { - bintrayUpload { - dependsOn(publishToMavenLocal) - } - } -} - -//workaround for bintray -tasks.withType { - doFirst { - publishing.publications - .filterIsInstance() - .forEach { publication -> - val moduleFile = buildDir.resolve("publications/${publication.name}/module.json") - if (moduleFile.exists()) { - publication.artifact(object : FileBasedMavenArtifact(moduleFile) { - override fun getDefaultExtension() = "module" - }) - } - } - } -} - -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")) - }) - }) - resolve(delegateClosureOf { - repository(delegateClosureOf { - setProperty("repoKey", "gradle-dev") - setProperty("username", artifactoryUser) - setProperty("password", artifactoryPassword) - }) - }) -} diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 7f6bec31f..ad59afc5c 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -1,12 +1,11 @@ -import org.jetbrains.gradle.benchmarks.JvmBenchmarkTarget import org.jetbrains.kotlin.allopen.gradle.AllOpenExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java kotlin("jvm") - kotlin("plugin.allopen") version "1.3.31" - id("org.jetbrains.gradle.benchmarks.plugin") version "0.1.7-dev-24" + kotlin("plugin.allopen") version "1.3.60" + id("kotlinx.benchmark") version "0.2.0-dev-5" } configure { @@ -16,7 +15,8 @@ configure { repositories { maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("http://dl.bintray.com/kyonifer/maven") - maven("https://dl.bintray.com/orangy/maven") + maven ("https://dl.bintray.com/orangy/maven") + mavenCentral() } @@ -30,9 +30,9 @@ dependencies { implementation(project(":kmath-commons")) implementation(project(":kmath-koma")) implementation("com.kyonifer:koma-core-ejml:0.12") - implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:0.1.5") + implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:${Scientifik.ioVersion}") - implementation("org.jetbrains.gradle.benchmarks:runtime:0.1.7-dev-24") + implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-2") "benchmarksCompile"(sourceSets.main.get().compileClasspath) @@ -43,10 +43,7 @@ benchmark { // Setup configurations targets { // This one matches sourceSet name above - register("benchmarks") { - this as JvmBenchmarkTarget - jmhVersion = "1.21" - } + register("benchmarks") } configurations { @@ -62,6 +59,6 @@ benchmark { tasks.withType { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = Scientifik.JVM_VERSION } } \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt b/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt index 401dde780..3c5f53e13 100644 --- a/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt +++ b/examples/src/main/kotlin/scientifik/kmath/commons/prob/DistributionDemo.kt @@ -2,17 +2,17 @@ package scientifik.kmath.commons.prob import kotlinx.coroutines.runBlocking import scientifik.kmath.chains.Chain -import scientifik.kmath.chains.mapWithState +import scientifik.kmath.chains.collectWithState import scientifik.kmath.prob.Distribution import scientifik.kmath.prob.RandomGenerator data class AveragingChainState(var num: Int = 0, var value: Double = 0.0) -fun Chain.mean(): Chain = mapWithState(AveragingChainState(),{it.copy()}){chain-> +fun Chain.mean(): Chain = collectWithState(AveragingChainState(),{it.copy()}){ chain-> val next = chain.next() num++ value += next - return@mapWithState value / num + return@collectWithState value / num } diff --git a/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt b/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt index 86b28303d..4841f9dd8 100644 --- a/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt +++ b/examples/src/main/kotlin/scientifik/kmath/operations/ComplexDemo.kt @@ -1,10 +1,21 @@ package scientifik.kmath.operations import scientifik.kmath.structures.NDElement +import scientifik.kmath.structures.NDField import scientifik.kmath.structures.complex fun main() { val element = NDElement.complex(2, 2) { index: IntArray -> Complex(index[0].toDouble() - index[1].toDouble(), index[0].toDouble() + index[1].toDouble()) } + + + val compute = NDField.complex(8).run { + val a = produce { (it) -> i * it - it.toDouble() } + val b = 3 + val c = Complex(1.0, 1.0) + + (a pow b) + c + } + } \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt b/examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt index b57e9db79..cc8b68d85 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/ComplexND.kt @@ -2,7 +2,8 @@ package scientifik.kmath.structures import scientifik.kmath.linear.transpose import scientifik.kmath.operations.Complex -import scientifik.kmath.operations.toComplex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.invoke import kotlin.system.measureTimeMillis fun main() { @@ -39,19 +40,21 @@ fun main() { fun complexExample() { //Create a context for 2-d structure with complex values - NDField.complex(4, 8).run { - //a constant real-valued structure - val x = one * 2.5 - operator fun Number.plus(other: Complex) = Complex(this.toDouble() + other.re, other.im) - //a structure generator specific to this context - val matrix = produce { (k, l) -> - k + l*i + ComplexField { + nd(4, 8) { + //a constant real-valued structure + val x = one * 2.5 + operator fun Number.plus(other: Complex) = Complex(this.toDouble() + other.re, other.im) + //a structure generator specific to this context + val matrix = produce { (k, l) -> + k + l * i + } + + //Perform sum + val sum = matrix + x + 1.0 + + //Represent the sum as 2d-structure and transpose + sum.as2D().transpose() } - - //Perform sum - val sum = matrix + x + 1.0 - - //Represent the sum as 2d-structure and transpose - sum.as2D().transpose() } } 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.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf01..cc4fdc293 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5f1b1201a..6ce793f21 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-6.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index b0d6d0ab5..2fe81a7d9 100755 --- a/gradlew +++ b/gradlew @@ -7,7 +7,7 @@ # 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 +# https://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, @@ -125,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -154,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9991c5032..9618d8d96 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -5,7 +5,7 @@ @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 https://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, diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index ffdc27d43..9b446f872 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -1,6 +1,5 @@ plugins { - kotlin("jvm") - `maven-publish` + id("scientifik.jvm") } description = "Commons math binding for kmath" @@ -12,19 +11,4 @@ dependencies { api("org.apache.commons:commons-math3:3.6.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") -} - - -val sourcesJar by tasks.registering(Jar::class) { - classifier = "sources" - from(sourceSets.main.get().allSource) -} - -publishing { - publications { - register("jvm", MavenPublication::class) { - from(components["java"]) - artifact(sourcesJar.get()) - } - } } \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt index ce51735fe..bcb3ea87b 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/commons/transform/Transformations.kt @@ -11,7 +11,7 @@ import scientifik.kmath.structures.* /** - * + * Streaming and buffer transformations */ object Transformations { diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 25507968c..092f3deb7 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } kotlin.sourceSets { @@ -8,6 +8,4 @@ kotlin.sourceSets { api(project(":kmath-memory")) } } - //mingwMain {} - //mingwTest {} } \ No newline at end of file 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 910ed4551..87e0ef027 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -183,7 +183,7 @@ fun LUPDecomposition.solve(type: KClass, matrix: Matrix): Mat elementContext.run { // Apply permutations to b - val bp = create { i, j -> zero } + val bp = create { _, _ -> zero } for (row in 0 until pivot.size) { val bpRow = bp.row(row) 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 7e631f5fc..0456ffebb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -3,10 +3,12 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.VirtualBuffer import scientifik.kmath.structures.asSequence +typealias Point = Buffer /** * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors @@ -17,20 +19,6 @@ interface LinearSolver { fun inverse(a: Matrix): Matrix } -/** - * Convert vector to array (copying content of array) - */ -fun Array.toVector(field: Field) = Vector.generic(size, field) { this[it] } - -fun DoubleArray.toVector() = Vector.real(this.size) { this[it] } -fun List.toVector() = Vector.real(this.size) { this[it] } - -object VectorL2Norm : Norm, Double> { - override fun norm(arg: Point): Double = - kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) -} - -typealias RealVector = Vector typealias RealMatrix = Matrix /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt new file mode 100644 index 000000000..d3cf07e79 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Norm +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.DoubleBuffer +import scientifik.kmath.structures.asBuffer +import scientifik.kmath.structures.asSequence + +fun DoubleArray.asVector() = RealVector(this.asBuffer()) +fun List.asVector() = RealVector(this.asBuffer()) + + +object VectorL2Norm : Norm, Double> { + override fun norm(arg: Point): Double = + kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) +} + +inline class RealVector(val point: Point) : + SpaceElement, RealVector, VectorSpace>, Point { + override val context: VectorSpace get() = space(point.size) + + override fun unwrap(): Point = point + + override fun Point.wrap(): RealVector = RealVector(this) + + override val size: Int get() = point.size + + override fun get(index: Int): Double = point[index] + + override fun iterator(): Iterator = point.iterator() + + companion object { + + private val spaceCache = HashMap>() + + inline operator fun invoke(dim:Int, initalizer: (Int)-> Double) = RealVector(DoubleBuffer(dim, initalizer)) + + operator fun invoke(vararg values: Double) = values.asVector() + + fun space(dim: Int) = + spaceCache.getOrPut(dim) { + BufferVectorSpace(dim, RealField) { size, init -> Buffer.real(size, init) } + } + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt deleted file mode 100644 index 18a0021c3..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ /dev/null @@ -1,71 +0,0 @@ -package scientifik.kmath.linear - -import scientifik.kmath.operations.RealField -import scientifik.kmath.operations.Space -import scientifik.kmath.operations.SpaceElement -import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.asSequence -import kotlin.jvm.JvmName - -typealias Point = Buffer - - -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 - - override operator fun plus(b: Point): Vector = context.add(this, b).wrap() - override operator fun minus(b: Point): Vector = context.add(this, context.multiply(b, -1.0)).wrap() - override operator fun times(k: Number): Vector = context.multiply(this, k.toDouble()).wrap() - override operator fun div(k: Number): Vector = context.multiply(this, 1.0 / k.toDouble()).wrap() - - companion object { - /** - * Create vector with custom field - */ - fun > generic(size: Int, field: S, initializer: (Int) -> T): Vector = - VectorSpace.buffered(size, field).produceElement(initializer) - - fun real(size: Int, initializer: (Int) -> Double): Vector = - VectorSpace.real(size).produceElement(initializer) - - fun ofReal(vararg elements: Double): Vector = - VectorSpace.real(elements.size).produceElement { elements[it] } - - } -} - -@Deprecated("Use VectorContext instead") -data class BufferVector>(override val context: VectorSpace, val buffer: Buffer) : - Vector { - - init { - if (context.size != buffer.size) { - error("Array dimension mismatch") - } - } - - override fun get(index: Int): T { - return buffer[index] - } - - override fun unwrap(): Point = this - - override fun Point.wrap(): Vector = BufferVector(context, this) - - override fun iterator(): Iterator = (0 until size).map { buffer[it] }.iterator() - - override fun toString(): String = - this.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } -} - diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/cumulative.kt similarity index 86% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/cumulative.kt index 314696262..c3cfc448a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/cumulative.kt @@ -10,29 +10,32 @@ import kotlin.jvm.JvmName * @param R type of resulting iterable * @param initial lazy evaluated */ -fun Iterator.cumulative(initial: R, operation: (T, R) -> R): Iterator = object : Iterator { +fun Iterator.cumulative(initial: R, operation: (R, T) -> R): Iterator = object : Iterator { var state: R = initial override fun hasNext(): Boolean = this@cumulative.hasNext() override fun next(): R { - state = operation.invoke(this@cumulative.next(), state) + state = operation(state, this@cumulative.next()) return state } } -fun Iterable.cumulative(initial: R, operation: (T, R) -> R): Iterable = object : Iterable { +fun Iterable.cumulative(initial: R, operation: (R, T) -> R): Iterable = object : Iterable { override fun iterator(): Iterator = this@cumulative.iterator().cumulative(initial, operation) } -fun Sequence.cumulative(initial: R, operation: (T, R) -> R): Sequence = object : Sequence { +fun Sequence.cumulative(initial: R, operation: (R, T) -> R): Sequence = object : Sequence { override fun iterator(): Iterator = this@cumulative.iterator().cumulative(initial, operation) } -fun List.cumulative(initial: R, operation: (T, R) -> R): List = +fun List.cumulative(initial: R, operation: (R, T) -> R): List = this.iterator().cumulative(initial, operation).asSequence().toList() //Cumulative sum +/** + * Cumulative sum with custom space + */ fun Iterable.cumulativeSum(space: Space) = with(space) { cumulative(zero) { element: T, sum: T -> sum + element } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/functions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/functions.kt new file mode 100644 index 000000000..cef8209ed --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/functions.kt @@ -0,0 +1,37 @@ +package scientifik.kmath.coroutines + +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.SpaceOperations +import kotlin.jvm.JvmName + +/** + * A suspendable univariate function defined in algebraic context + */ +interface UFunction> { + suspend operator fun C.invoke(arg: T): T +} + +suspend fun UFunction.invoke(arg: Double) = RealField.invoke(arg) + +/** + * A suspendable multivariate (N->1) function defined on algebraic context + */ +interface MFunction> { + /** + * The input dimension of the function + */ + val dimension: UInt + + suspend operator fun C.invoke(vararg args: T): T +} + +suspend fun MFunction.invoke(args: DoubleArray) = RealField.invoke(*args.toTypedArray()) +@JvmName("varargInvoke") +suspend fun MFunction.invoke(vararg args: Double) = RealField.invoke(*args.toTypedArray()) + +/** + * A suspendable univariate function with parameter + */ +interface ParametricUFunction> { + suspend operator fun C.invoke(arg: T, parameter: P): T +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index ee9833623..0ed769db8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,7 +1,19 @@ package scientifik.kmath.operations +@DslMarker +annotation class KMathContext -interface SpaceOperations { +/** + * Marker interface for any algebra + */ +interface Algebra + +inline operator fun T.invoke(block: T.() -> R): R = run(block) + +/** + * Space-like operations without neutral element + */ +interface SpaceOperations : Algebra { /** * Addition operation for two context elements */ @@ -38,6 +50,9 @@ interface Space : SpaceOperations { val zero: T } +/** + * Operations on ring without multiplication neutral element + */ interface RingOperations : SpaceOperations { /** * Multiplication for two field elements diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt index 6dec8bd79..4e8dbf36d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt @@ -1,7 +1,4 @@ package scientifik.kmath.operations -import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.asSequence - fun Space.sum(data : Iterable): T = data.fold(zero) { left, right -> add(left,right) } fun Space.sum(data : Sequence): T = data.fold(zero) { left, right -> add(left, right) } \ 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 8f373b8de..6c529f55e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -26,7 +26,7 @@ object ComplexField : ExtendedFieldOperations, Field { Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) override fun divide(a: Complex, b: Complex): Complex { - val norm = b.square + val norm = b.re * b.re + b.im * b.im return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm) } @@ -35,11 +35,11 @@ object ComplexField : ExtendedFieldOperations, Field { override fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2 override fun power(arg: Complex, pow: Number): Complex = - arg.abs.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta)) + arg.r.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta)) override fun exp(arg: Complex): Complex = exp(arg.re) * (cos(arg.im) + i * sin(arg.im)) - override fun ln(arg: Complex): Complex = ln(arg.abs) + i * atan2(arg.im, arg.re) + override fun ln(arg: Complex): Complex = ln(arg.r) + i * atan2(arg.im, arg.re) operator fun Double.plus(c: Complex) = add(this.toComplex(), c) @@ -56,13 +56,15 @@ object ComplexField : ExtendedFieldOperations, Field { * Complex number class */ data class Complex(val re: Double, val im: Double) : FieldElement, Comparable { + constructor(re: Number, im: Number) : this(re.toDouble(), im.toDouble()) + override fun unwrap(): Complex = this override fun Complex.wrap(): Complex = this override val context: ComplexField get() = ComplexField - override fun compareTo(other: Complex): Int = abs.compareTo(other.abs) + override fun compareTo(other: Complex): Int = r.compareTo(other.r) companion object : MemorySpec { override val objectSize: Int = 16 @@ -82,15 +84,10 @@ data class Complex(val re: Double, val im: Double) : FieldElement, Norm { override inline fun sin(arg: Double) = kotlin.math.sin(arg) override inline fun cos(arg: Double) = kotlin.math.cos(arg) - override inline fun power(arg: Double, pow: Number) = arg.pow(pow.toDouble()) + override inline fun power(arg: Double, pow: Number) = arg.kpow(pow.toDouble()) override inline fun exp(arg: Double) = kotlin.math.exp(arg) override inline fun ln(arg: Double) = kotlin.math.ln(arg) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt index ffbbf69dd..2a77e36ef 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -32,6 +32,8 @@ fun >> ctg(arg: T): T = arg.conte interface PowerOperations { fun power(arg: T, pow: Number): T fun sqrt(arg: T) = power(arg, 0.5) + + infix fun T.pow(pow: Number) = power(this, pow) } infix fun >> T.pow(power: Double): T = context.power(this, power) @@ -48,7 +50,7 @@ interface ExponentialOperations { fun >> exp(arg: T): T = arg.context.exp(arg) fun >> ln(arg: T): T = arg.context.ln(arg) -interface Norm { +interface Norm { fun norm(arg: T): R } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt index a73c58939..e6d4b226d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt @@ -70,4 +70,13 @@ class BoxingNDField>( override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = BufferedNDFieldElement(this@BoxingNDField, buffer) +} + +inline fun , R> F.nd( + noinline bufferFactory: BufferFactory, + vararg shape: Int, + action: NDField.() -> R +): R { + val ndfield: BoxingNDField = NDField.boxing(this, *shape, bufferFactory = bufferFactory) + return ndfield.action() } \ 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 a38f09c8d..c63384fb9 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -69,9 +69,9 @@ interface Buffer { } } -fun Buffer.asSequence(): Sequence = iterator().asSequence() +fun Buffer.asSequence(): Sequence = Sequence(::iterator) -fun Buffer.asIterable(): Iterable = iterator().asSequence().asIterable() +fun Buffer.asIterable(): Iterable = asSequence().asIterable() interface MutableBuffer : Buffer { operator fun set(index: Int, value: T) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt index e9d518bd5..a79366a99 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt @@ -134,4 +134,11 @@ operator fun ComplexNDElement.minus(arg: Double) = fun NDField.Companion.complex(vararg shape: Int): ComplexNDField = ComplexNDField(shape) fun NDElement.Companion.complex(vararg shape: Int, initializer: ComplexField.(IntArray) -> Complex): ComplexNDElement = - NDField.complex(*shape).produce(initializer) \ No newline at end of file + NDField.complex(*shape).produce(initializer) + +/** + * Produce a context for n-dimensional operations inside this real field + */ +inline fun ComplexField.nd(vararg shape: Int, action: ComplexNDField.() -> R): R { + return NDField.complex(*shape).run(action) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 82a237817..8c1bd4239 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -119,3 +119,10 @@ operator fun RealNDElement.plus(arg: Double) = */ operator fun RealNDElement.minus(arg: Double) = map { it - arg } + +/** + * Produce a context for n-dimensional operations inside this real field + */ +inline fun RealField.nd(vararg shape: Int, action: RealNDField.() -> R): R { + return NDField.real(*shape).run(action) +} \ 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 5370ae960..de13c9888 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -8,15 +8,15 @@ class MatrixTest { @Test fun testSum() { - val vector1 = Vector.real(5) { it.toDouble() } - val vector2 = Vector.real(5) { 5 - it.toDouble() } + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } val sum = vector1 + vector2 assertEquals(5.0, sum[2]) } @Test fun testVectorToMatrix() { - val vector = Vector.real(5) { it.toDouble() } + val vector = RealVector(5) { it.toDouble() } val matrix = vector.asMatrix() assertEquals(4.0, matrix[4, 0]) } @@ -31,8 +31,8 @@ class MatrixTest { @Test fun testDot() { - val vector1 = Vector.real(5) { it.toDouble() } - val vector2 = Vector.real(5) { 5 - it.toDouble() } + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } val matrix1 = vector1.asMatrix() val matrix2 = vector2.asMatrix().transpose() diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt index aedbf6655..779cfc4b8 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class RealFieldTest { @Test fun testSqrt() { - val sqrt = with(RealField) { + val sqrt = RealField { sqrt(25 * one) } assertEquals(5.0, sqrt) diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts index 6ffaf4df6..373d9b8ac 100644 --- a/kmath-coroutines/build.gradle.kts +++ b/kmath-coroutines/build.gradle.kts @@ -1,26 +1,23 @@ plugins { - `npm-multiplatform` - id("kotlinx-atomicfu") version Versions.atomicfuVersion + id("scientifik.mpp") + //id("scientifik.atomic") } kotlin.sourceSets { commonMain { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Versions.coroutinesVersion}") - compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Versions.atomicfuVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Scientifik.coroutinesVersion}") } } jvmMain { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutinesVersion}") - compileOnly("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfuVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Scientifik.coroutinesVersion}") } } jsMain { dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Versions.coroutinesVersion}") - compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Versions.atomicfuVersion}") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Scientifik.coroutinesVersion}") } } } diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt index 317cdcea0..8dba9e215 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt @@ -16,10 +16,9 @@ package scientifik.kmath.chains -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.updateAndGet -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock /** @@ -44,9 +43,7 @@ interface Chain { /** * Chain as a coroutine flow. The flow emit affects chain state and vice versa */ -@FlowPreview -val Chain.flow: Flow - get() = kotlinx.coroutines.flow.flow { while (true) emit(next()) } +fun Chain.flow(): Flow = kotlinx.coroutines.flow.flow { while (true) emit(next()) } fun Iterator.asChain(): Chain = SimpleChain { next() } fun Sequence.asChain(): Chain = iterator().asChain() @@ -64,16 +61,20 @@ class SimpleChain(private val gen: suspend () -> R) : Chain { */ class MarkovChain(private val seed: suspend () -> R, private val gen: suspend (R) -> R) : Chain { - constructor(seedValue: R, gen: suspend (R) -> R) : this({ seedValue }, gen) + private val mutex = Mutex() - private val value = atomic(null) + private var value: R? = null override suspend fun next(): R { - return value.updateAndGet { prev -> gen(prev ?: seed()) }!! + mutex.withLock { + val newValue = gen(value ?: seed()) + value = newValue + return newValue + } } override fun fork(): Chain { - return MarkovChain(seed = { value.value ?: seed() }, gen = gen) + return MarkovChain(seed = { value ?: seed() }, gen = gen) } } @@ -89,17 +90,16 @@ class StatefulChain( private val gen: suspend S.(R) -> R ) : Chain { - constructor(state: S, seedValue: R, forkState: ((S) -> S), gen: suspend S.(R) -> R) : this( - state, - { seedValue }, - forkState, - gen - ) + private val mutex = Mutex() - private val atomicValue = atomic(null) + private var value: R? = null override suspend fun next(): R { - return atomicValue.updateAndGet { prev -> state.gen(prev ?: state.seed()) }!! + mutex.withLock { + val newValue = state.gen(value ?: state.seed()) + value = newValue + return newValue + } } override fun fork(): Chain { @@ -122,23 +122,23 @@ class ConstantChain(val value: T) : Chain { * Map the chain result using suspended transformation. Initial chain result can no longer be safely consumed * since mapped chain consumes tokens. Accepts regular transformation function */ -fun Chain.pipe(func: suspend (T) -> R): Chain = object : Chain { - override suspend fun next(): R = func(this@pipe.next()) - override fun fork(): Chain = this@pipe.fork().pipe(func) +fun Chain.map(func: suspend (T) -> R): Chain = object : Chain { + override suspend fun next(): R = func(this@map.next()) + override fun fork(): Chain = this@map.fork().map(func) } /** * Map the whole chain */ -fun Chain.map(mapper: suspend (Chain) -> R): Chain = object : Chain { - override suspend fun next(): R = mapper(this@map) - override fun fork(): Chain = this@map.fork().map(mapper) +fun Chain.collect(mapper: suspend (Chain) -> R): Chain = object : Chain { + override suspend fun next(): R = mapper(this@collect) + override fun fork(): Chain = this@collect.fork().collect(mapper) } -fun Chain.mapWithState(state: S, stateFork: (S) -> S, mapper: suspend S.(Chain) -> R): Chain = +fun Chain.collectWithState(state: S, stateFork: (S) -> S, mapper: suspend S.(Chain) -> R): Chain = object : Chain { - override suspend fun next(): R = state.mapper(this@mapWithState) - override fun fork(): Chain = this@mapWithState.fork().mapWithState(stateFork(state), stateFork, mapper) + override suspend fun next(): R = state.mapper(this@collectWithState) + override fun fork(): Chain = this@collectWithState.fork().collectWithState(stateFork(state), stateFork, mapper) } /** diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/flowExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/flowExtra.kt new file mode 100644 index 000000000..bfd16d763 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/flowExtra.kt @@ -0,0 +1,27 @@ +package scientifik.kmath.chains + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.scanReduce +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.SpaceOperations + + +@ExperimentalCoroutinesApi +fun Flow.cumulativeSum(space: SpaceOperations): Flow = with(space) { + scanReduce { sum: T, element: T -> sum + element } +} + +@ExperimentalCoroutinesApi +fun Flow.mean(space: Space): Flow = with(space) { + class Accumulator(var sum: T, var num: Int) + + scan(Accumulator(zero, 0)) { sum, element -> + sum.apply { + this.sum += element + this.num += 1 + } + }.map { it.sum / it.num } +} \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/coroutinesExtra.kt similarity index 92% rename from kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/CoroutinesExtra.kt rename to kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/coroutinesExtra.kt index dc82d1881..fdde62304 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/coroutines/coroutinesExtra.kt @@ -21,8 +21,8 @@ internal class LazyDeferred(val dispatcher: CoroutineDispatcher, val block: s suspend fun await(): T = deferred?.await() ?: error("Coroutine not started") } -@FlowPreview class AsyncFlow internal constructor(internal val deferredFlow: Flow>) : Flow { + @InternalCoroutinesApi override suspend fun collect(collector: FlowCollector) { deferredFlow.collect { collector.emit((it.await())) @@ -88,14 +88,13 @@ suspend fun AsyncFlow.collect(concurrency: Int, action: suspend (value: T }) } +@ExperimentalCoroutinesApi @FlowPreview -fun Flow.map( - dispatcher: CoroutineDispatcher, - concurrencyLevel: Int = 16, - bufferSize: Int = concurrencyLevel, +fun Flow.mapParallel( + dispatcher: CoroutineDispatcher = Dispatchers.Default, transform: suspend (T) -> R ): Flow { - return flatMapMerge(concurrencyLevel, bufferSize) { value -> + return flatMapMerge{ value -> flow { emit(transform(value)) } }.flowOn(dispatcher) } 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 85ce73c4b..cf4d4cc17 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/streaming/BufferFlow.kt @@ -9,7 +9,6 @@ import scientifik.kmath.structures.DoubleBuffer /** * Create a [Flow] from buffer */ -@FlowPreview fun Buffer.asFlow() = iterator().asFlow() /** @@ -21,7 +20,6 @@ fun Flow>.spread(): Flow = flatMapConcat { it.asFlow() } /** * Collect incoming flow into fixed size chunks */ -@FlowPreview fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory): Flow> = flow { require(bufferSize > 0) { "Resulting chunk size must be more than zero" } val list = ArrayList(bufferSize) @@ -45,7 +43,6 @@ fun Flow.chunked(bufferSize: Int, bufferFactory: BufferFactory): Flow< /** * Specialized flow chunker for real buffer */ -@FlowPreview fun Flow.chunked(bufferSize: Int): Flow = flow { require(bufferSize > 0) { "Resulting chunk size must be more than zero" } val array = DoubleArray(bufferSize) @@ -69,7 +66,6 @@ fun Flow.chunked(bufferSize: Int): Flow = flow { * 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) 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 4fd397993..af2d6cd43 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.collect import org.junit.Test import scientifik.kmath.coroutines.async import scientifik.kmath.coroutines.collect -import scientifik.kmath.coroutines.map +import scientifik.kmath.coroutines.mapParallel import java.util.concurrent.Executors @@ -20,8 +20,8 @@ class BufferFlowTest { @Test(timeout = 2000) fun map() { runBlocking { - (1..20).asFlow().map( dispatcher) { - //println("Started $it on ${Thread.currentThread().name}") + (1..20).asFlow().mapParallel( dispatcher) { + println("Started $it on ${Thread.currentThread().name}") @Suppress("BlockingMethodInNonBlockingContext") Thread.sleep(200) it @@ -35,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 diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 7eaa5e174..39aa833ad 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } kotlin.sourceSets.commonMain { diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt index 75296ba27..a2f0fc516 100644 --- a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt @@ -1,7 +1,7 @@ package scientifik.kmath.histogram import scientifik.kmath.linear.Point -import scientifik.kmath.linear.toVector +import scientifik.kmath.linear.asVector import scientifik.kmath.operations.SpaceOperations import scientifik.kmath.structures.* import kotlin.math.floor @@ -132,8 +132,8 @@ class RealHistogram( */ fun fromRanges(vararg ranges: ClosedFloatingPointRange): RealHistogram { return RealHistogram( - ranges.map { it.start }.toVector(), - ranges.map { it.endInclusive }.toVector() + ranges.map { it.start }.asVector(), + ranges.map { it.endInclusive }.asVector() ) } diff --git a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt index 678b8eba7..4dc4dfc74 100644 --- a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt @@ -3,7 +3,7 @@ package scietifik.kmath.histogram import scientifik.kmath.histogram.RealHistogram import scientifik.kmath.histogram.fill import scientifik.kmath.histogram.put -import scientifik.kmath.linear.Vector +import scientifik.kmath.linear.RealVector import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -19,9 +19,9 @@ class MultivariateHistogramTest { ) histogram.put(0.55, 0.55) val bin = histogram.find { it.value.toInt() > 0 }!! - assertTrue { bin.contains(Vector.ofReal(0.55, 0.55)) } - assertTrue { bin.contains(Vector.ofReal(0.6, 0.5)) } - assertFalse { bin.contains(Vector.ofReal(-0.55, 0.55)) } + assertTrue { bin.contains(RealVector(0.55, 0.55)) } + assertTrue { bin.contains(RealVector(0.6, 0.5)) } + assertFalse { bin.contains(RealVector(-0.55, 0.55)) } } @Test @@ -39,7 +39,7 @@ class MultivariateHistogramTest { histogram.fill { repeat(n) { - yield(Vector.ofReal(nextDouble(), nextDouble(), nextDouble())) + yield(RealVector(nextDouble(), nextDouble(), nextDouble())) } } assertEquals(n, histogram.sumBy { it.value.toInt() }) diff --git a/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt index 53c2da641..90b9aff5e 100644 --- a/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -1,7 +1,7 @@ package scientifik.kmath.histogram import scientifik.kmath.linear.RealVector -import scientifik.kmath.linear.toVector +import scientifik.kmath.linear.asVector import scientifik.kmath.structures.Buffer import java.util.* import kotlin.math.floor @@ -12,7 +12,7 @@ class UnivariateBin(val position: Double, val size: Double, val counter: LongCou //TODO add weighting override val value: Number get() = counter.sum() - override val center: RealVector get() = doubleArrayOf(position).toVector() + override val center: RealVector get() = doubleArrayOf(position).asVector() operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2) diff --git a/kmath-io/build.gradle b/kmath-io/build.gradle deleted file mode 100644 index 28fb7eee5..000000000 --- a/kmath-io/build.gradle +++ /dev/null @@ -1,55 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -kotlin { - targets { - fromPreset(presets.jvm, 'jvm') - fromPreset(presets.js, 'js') - // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 - // For Linux, preset should be changed to e.g. presets.linuxX64 - // For MacOS, preset should be changed to e.g. presets.macosX64 - //fromPreset(presets.mingwX64, 'mingw') - } - sourceSets { - commonMain { - dependencies { - api project(":kmath-core") - implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' - api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" - } - } - commonTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test-common' - implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common' - } - } - jvmMain { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" - } - } - jvmTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test' - implementation 'org.jetbrains.kotlin:kotlin-test-junit' - } - } - jsMain { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' - } - } - jsTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test-js' - } - } -// mingwMain { -// } -// mingwTest { -// } - } -} diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index 9b07fdcf2..26955bca7 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } repositories { diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index 3fb56989c..1f34a4f17 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -1,3 +1,3 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } diff --git a/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt b/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt index e6da316cf..0896f0dcb 100644 --- a/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt +++ b/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt @@ -1,7 +1,5 @@ package scientifik.memory -import kotlin.reflect.KClass - /** * A specification to read or write custom objects with fixed size in bytes */ @@ -27,7 +25,7 @@ inline fun MemoryReader.readArray(spec: MemorySpec, offset: fun MemoryWriter.writeArray(spec: MemorySpec, offset: Int, array: Array) { spec.run { - for (i in 0 until array.size) { + for (i in array.indices) { write(offset + i * objectSize, array[i]) } } diff --git a/kmath-prob/build.gradle.kts b/kmath-prob/build.gradle.kts index 972b9bba0..59b25d340 100644 --- a/kmath-prob/build.gradle.kts +++ b/kmath-prob/build.gradle.kts @@ -1,30 +1,11 @@ plugins { - `npm-multiplatform` - id("kotlinx-atomicfu") version Versions.atomicfuVersion + id("scientifik.mpp") } kotlin.sourceSets { commonMain { dependencies { api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:${Versions.atomicfuVersion}") } } - jvmMain { - dependencies { - // https://mvnrepository.com/artifact/org.apache.commons/commons-rng-simple - //api("org.apache.commons:commons-rng-sampling:1.2") - compileOnly("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfuVersion}") - } - } - jsMain { - dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu-js:${Versions.atomicfuVersion}") - } - } - -} - -atomicfu { - variant = "VH" -} +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt index 487e13876..44af84d5d 100644 --- a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt @@ -1,8 +1,9 @@ package scientifik.kmath.prob import scientifik.kmath.chains.Chain -import scientifik.kmath.chains.map -import kotlin.jvm.JvmName +import scientifik.kmath.chains.collect +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory interface Sampler { fun sample(generator: RandomGenerator): Chain @@ -20,7 +21,7 @@ interface Distribution : Sampler { /** * Create a chain of samples from this distribution. - * The chain is not guaranteed to be stateless. + * The chain is not guaranteed to be stateless, but different sample chains should be independent. */ override fun sample(generator: RandomGenerator): Chain @@ -32,7 +33,7 @@ interface Distribution : Sampler { interface UnivariateDistribution> : Distribution { /** - * Cumulative distribution for ordered parameter + * Cumulative distribution for ordered parameter (CDF) */ fun cumulative(arg: T): Double } @@ -45,24 +46,27 @@ fun > UnivariateDistribution.integral(from: T, to: T): Doub return cumulative(to) - cumulative(from) } - /** * Sample a bunch of values */ -fun Sampler.sampleBunch(generator: RandomGenerator, size: Int): Chain> { +fun Sampler.sampleBuffer( + generator: RandomGenerator, + size: Int, + bufferFactory: BufferFactory = Buffer.Companion::boxing +): Chain> { require(size > 1) - return sample(generator).map{chain -> - List(size){chain.next()} + //creating temporary storage once + val tmp = ArrayList(size) + return sample(generator).collect { chain -> + for (i in tmp.indices) { + tmp[i] = chain.next() + } + bufferFactory(size) { tmp[it] } } } /** * Generate a bunch of samples from real distributions */ -@JvmName("realSampleBunch") -fun Sampler.sampleBunch(generator: RandomGenerator, size: Int): Chain { - require(size > 1) - return sample(generator).map{chain -> - DoubleArray(size){chain.next()} - } -} \ No newline at end of file +fun Sampler.sampleBuffer(generator: RandomGenerator, size: Int) = + sampleBuffer(generator, size, Buffer.Companion::real) \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/FactorizedDistribution.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/FactorizedDistribution.kt new file mode 100644 index 000000000..ea526c058 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/FactorizedDistribution.kt @@ -0,0 +1,47 @@ +package scientifik.kmath.prob + +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.SimpleChain + +/** + * A multivariate distribution which takes a map of parameters + */ +interface NamedDistribution : Distribution> + +/** + * A multivariate distribution that has independent distributions for separate axis + */ +class FactorizedDistribution(val distributions: Collection>) : NamedDistribution { + + override fun probability(arg: Map): Double { + return distributions.fold(1.0) { acc, distr -> acc * distr.probability(arg) } + } + + override fun sample(generator: RandomGenerator): Chain> { + val chains = distributions.map { it.sample(generator) } + return SimpleChain> { + chains.fold(emptyMap()) { acc, chain -> acc + chain.next() } + } + } +} + +class NamedDistributionWrapper(val name: String, val distribution: Distribution) : NamedDistribution { + override fun probability(arg: Map): Double = distribution.probability( + arg[name] ?: error("Argument with name $name not found in input parameters") + ) + + override fun sample(generator: RandomGenerator): Chain> { + val chain = distribution.sample(generator) + return SimpleChain { + mapOf(name to chain.next()) + } + } +} + +class DistributionBuilder{ + private val distributions = ArrayList>() + + infix fun String.to(distribution: Distribution){ + distributions.add(NamedDistributionWrapper(this,distribution)) + } +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt index d65b9530a..9b581afd7 100644 --- a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt @@ -1,6 +1,5 @@ package scientifik.kmath.prob -import kotlinx.atomicfu.atomic import scientifik.kmath.chains.Chain /** @@ -10,4 +9,7 @@ class RandomChain(val generator: RandomGenerator, private val gen: suspen override suspend fun next(): R = generator.gen() override fun fork(): Chain = RandomChain(generator.fork(), gen) -} \ No newline at end of file +} + +fun RandomGenerator.chain(gen: suspend RandomGenerator.() -> R): RandomChain = RandomChain(this, gen) +fun RandomGenerator.flow(gen: suspend RandomGenerator.() -> R) = chain(gen).fork() \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt index ca9d74aab..3a60c0bda 100644 --- a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/SamplerAlgebra.kt @@ -2,7 +2,7 @@ package scientifik.kmath.prob import scientifik.kmath.chains.Chain import scientifik.kmath.chains.ConstantChain -import scientifik.kmath.chains.pipe +import scientifik.kmath.chains.map import scientifik.kmath.chains.zip import scientifik.kmath.operations.Space @@ -26,6 +26,6 @@ class SamplerSpace(val space: Space) : Space> { } override fun multiply(a: Sampler, k: Number): Sampler = BasicSampler { generator -> - a.sample(generator).pipe { space.run { it * k.toDouble() } } + a.sample(generator).map { space.run { it * k.toDouble() } } } } \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt new file mode 100644 index 000000000..1af2570b0 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt @@ -0,0 +1,94 @@ +package scientifik.kmath.prob + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scanReduce +import scientifik.kmath.coroutines.mapParallel +import scientifik.kmath.operations.* +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asIterable + +/** + * A function, that transforms a buffer of random quantities to some resulting value + */ +interface Statistic { + suspend operator fun invoke(data: Buffer): R +} + +/** + * A statistic tha could be computed separately on different blocks of data and then composed + * @param T - source type + * @param I - intermediate block type + * @param R - result type + */ +interface ComposableStatistic : Statistic { + //compute statistic on a single block + suspend fun computeIntermediate(data: Buffer): I + //Compose two blocks + suspend fun composeIntermediate(first: I, second: I): I + //Transform block to result + suspend fun toResult(intermediate: I): R + + override suspend fun invoke(data: Buffer): R = toResult(computeIntermediate(data)) +} + +@FlowPreview +@ExperimentalCoroutinesApi +private fun ComposableStatistic.flowIntermediate( + flow: Flow>, + dispatcher: CoroutineDispatcher = Dispatchers.Default +): Flow = flow + .mapParallel(dispatcher) { computeIntermediate(it) } + .scanReduce(::composeIntermediate) + + +/** + * Perform a streaming statistical analysis on a chunked data. The computation of inner representation is done in parallel + * if [dispatcher] allows it. + * + * The resulting flow contains values that include the whole previous statistics, not only the last chunk. + */ +@FlowPreview +@ExperimentalCoroutinesApi +fun ComposableStatistic.flow( + flow: Flow>, + dispatcher: CoroutineDispatcher = Dispatchers.Default +): Flow = flowIntermediate(flow,dispatcher).map(::toResult) + +/** + * Arithmetic mean + */ +class Mean(val space: Space) : ComposableStatistic, T> { + override suspend fun computeIntermediate(data: Buffer): Pair = + space.run { sum(data.asIterable()) } to data.size + + override suspend fun composeIntermediate(first: Pair, second: Pair): Pair = + space.run { first.first + second.first } to (first.second + second.second) + + override suspend fun toResult(intermediate: Pair): T = + space.run { intermediate.first / intermediate.second } + + companion object { + //TODO replace with optimized version which respects overflow + val real = Mean(RealField) + val int = Mean(IntRing) + val long = Mean(LongRing) + } +} + +/** + * Non-composable median + */ +class Median(comparator: Comparator) : Statistic { + override suspend fun invoke(data: Buffer): T { + return data.asIterable().toList()[data.size / 2] //TODO check if this is correct + } + + companion object { + val real = Median(Comparator { a: Double, b: Double -> a.compareTo(b) }) + } +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/distributions.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/distributions.kt new file mode 100644 index 000000000..ff6e2a551 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/distributions.kt @@ -0,0 +1,31 @@ +package scientifik.kmath.prob + +import scientifik.kmath.chains.Chain +import scientifik.kmath.chains.SimpleChain + +class UniformDistribution(val range: ClosedFloatingPointRange) : UnivariateDistribution { + + private val length = range.endInclusive - range.start + + override fun probability(arg: Double): Double { + return if (arg in range) { + return 1.0 / length + } else { + 0.0 + } + } + + override fun sample(generator: RandomGenerator): Chain { + return SimpleChain { + range.start + generator.nextDouble() * length + } + } + + override fun cumulative(arg: Double): Double { + return when { + arg < range.start -> 0.0 + arg >= range.endInclusive -> 1.0 + else -> (arg - range.start) / length + } + } +} \ No newline at end of file diff --git a/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt new file mode 100644 index 000000000..8b46cac52 --- /dev/null +++ b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt @@ -0,0 +1,28 @@ +package scientifik.kmath.prob + +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import scientifik.kmath.chains.flow +import scientifik.kmath.streaming.chunked +import kotlin.test.Test + +class StatisticTest { + //create a random number generator. + val generator = DefaultGenerator(1) + //Create a stateless chain from generator. + val data = generator.chain { nextDouble() } + //Convert a chaint to Flow and break it into chunks. + val chunked = data.flow().chunked(1000) + + @Test + fun testParallelMean() { + runBlocking { + val average = Mean.real + .flow(chunked) //create a flow with results + .drop(99) // Skip first 99 values and use one with total data + .first() //get 1e5 data samples average + println(average) + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 004b432fd..f52edfac5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,24 +1,30 @@ pluginManagement { + + plugins { + id("scientifik.mpp") version "0.2.5" + id("scientifik.jvm") version "0.2.5" + id("scientifik.atomic") version "0.2.5" + id("scientifik.publish") version "0.2.5" + } + repositories { + mavenLocal() jcenter() gradlePluginPortal() maven("https://dl.bintray.com/kotlin/kotlin-eap") - maven("https://dl.bintray.com/orangy/maven") + maven("https://dl.bintray.com/mipt-npm/scientifik") + maven("https://dl.bintray.com/kotlin/kotlinx") } + resolutionStrategy { 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}") - "kotlin2js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") - //"org.jetbrains.kotlin.frontend" -> useModule("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") + "scientifik.mpp", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}") } } } } -enableFeaturePreview("GRADLE_METADATA") - rootProject.name = "kmath" include( ":kmath-memory", @@ -29,5 +35,6 @@ include( ":kmath-commons", ":kmath-koma", ":kmath-prob", + ":kmath-io", ":examples" )