diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f39e12a12..9a9f04621 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,9 +13,11 @@ jobs: - name: Checkout the repo uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: DeLaGuardo/setup-graalvm@4.0 with: - java-version: 11 + graalvm: 21.1.0 + java: java11 + arch: amd64 - name: Add msys to path if: matrix.os == 'windows-latest' run: SETX PATH "%PATH%;C:\msys64\mingw64\bin" diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 82b0fb303..86fdac6a6 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -15,24 +15,8 @@ jobs: uses: actions/setup-java@v1 with: java-version: 11 - - name: Cache gradle - uses: actions/cache@v2 - with: - path: ~/.gradle/caches - key: ubuntu-20.04-gradle-${{ hashFiles('*.gradle.kts') }} - restore-keys: | - ubuntu-20.04-gradle- - - name: Cache konan - uses: actions/cache@v2 - with: - path: ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} - restore-keys: | - ${{ runner.os }}-gradle- - name: Build - run: | - ./gradlew dokkaHtmlMultiModule --no-daemon --no-parallel --stacktrace - mv build/dokka/htmlMultiModule/-modules.html build/dokka/htmlMultiModule/index.html + run: ./gradlew dokkaHtmlMultiModule --no-daemon --no-parallel --stacktrace - name: Deploy to GitHub Pages uses: JamesIves/github-pages-deploy-action@4.1.0 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ca374574e..c5c110e89 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,9 +18,11 @@ jobs: - name: Checkout the repo uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: DeLaGuardo/setup-graalvm@4.0 with: - java-version: 11 + graalvm: 21.1.0 + java: java11 + arch: amd64 - name: Add msys to path if: matrix.os == 'windows-latest' run: SETX PATH "%PATH%;C:\msys64\mingw64\bin" diff --git a/CHANGELOG.md b/CHANGELOG.md index c41eda374..9c6b14b95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,20 +10,31 @@ - Blocking chains and Statistics - Multiplatform integration - Integration for any Field element -- Extendend operations for ND4J fields +- Extended operations for ND4J fields +- Jupyter Notebook integration module (kmath-jupyter) +- `@PerformancePitfall` annotation to mark possibly slow API +- BigInt operation performance improvement and fixes by @zhelenskiy (#328) ### Changed - Exponential operations merged with hyperbolic functions - Space is replaced by Group. Space is reserved for vector spaces. - VectorSpace is now a vector space - Buffer factories for primitives moved to MutableBuffer.Companion -- NDStructure and NDAlgebra to StructureND and AlgebraND respectively -- Real -> Double +- Rename `NDStructure` and `NDAlgebra` to `StructureND` and `AlgebraND` respectively +- `Real` -> `Double` - DataSets are moved from functions to core - Redesign advanced Chain API -- Redesign MST. Remove MSTExpression. -- Move MST to core +- Redesign `MST`. Remove `MstExpression`. +- Move `MST` to core - Separated benchmarks and examples +- Rewrite `kmath-ejml` without `ejml-simple` artifact, support sparse matrices +- Promote stability of kmath-ast and kmath-kotlingrad to EXPERIMENTAL. +- ColumnarData returns nullable column +- `MST` is made sealed interface +- Replace `MST.Symbolic` by `Symbol`, `Symbol` now implements MST +- Remove Any restriction on polynomials +- Add `out` variance to type parameters of `StructureND` and its implementations where possible +- Rename `DifferentiableMstExpression` to `KotlingradExpression` ### Deprecated @@ -33,6 +44,7 @@ - `contentEquals` from Buffer. It moved to the companion. - MSTExpression - Expression algebra builders +- Complex and Quaternion no longer are elements. ### Fixed - Ring inherits RingOperations, not GroupOperations diff --git a/README.md b/README.md index 773eb6398..9117582ac 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ KMath is a modular library. Different modules provide different features with di * **PROTOTYPE**. On this level there are no compatibility guarantees. All methods and classes form those modules could break any moment. You can still use it, but be sure to fix the specific version. * **EXPERIMENTAL**. The general API is decided, but some changes could be made. Volatile API is marked with `@UnstableKmathAPI` or other stability warning annotations. -* **DEVELOPMENT**. API breaking genrally follows semantic versioning ideology. There could be changes in minor versions, but not in patch versions. API is protected with [binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) tool. +* **DEVELOPMENT**. API breaking generally follows semantic versioning ideology. There could be changes in minor versions, but not in patch versions. API is protected with [binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) tool. * **STABLE**. The API stabilized. Breaking changes are allowed only in major releases. @@ -91,7 +91,7 @@ KMath is a modular library. Different modules provide different features with di * ### [kmath-ast](kmath-ast) > > -> **Maturity**: PROTOTYPE +> **Maturity**: EXPERIMENTAL > > **Features:** > - [expression-language](kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt) : Expression language and its parser @@ -154,9 +154,9 @@ performance calculations to code generation. > **Maturity**: PROTOTYPE > > **Features:** -> - [ejml-vector](kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt) : The Point implementation using SimpleMatrix. -> - [ejml-matrix](kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt) : The Matrix implementation using SimpleMatrix. -> - [ejml-linear-space](kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt) : The LinearSpace implementation using SimpleMatrix. +> - [ejml-vector](kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt) : Point implementations. +> - [ejml-matrix](kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt) : Matrix implementation. +> - [ejml-linear-space](kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt) : LinearSpace implementations.
@@ -200,12 +200,23 @@ One can still use generic algebras though. > **Maturity**: PROTOTYPE
-* ### [kmath-kotlingrad](kmath-kotlingrad) +* ### [kmath-jupyter](kmath-jupyter) > > > **Maturity**: PROTOTYPE
+* ### [kmath-kotlingrad](kmath-kotlingrad) +> Functions, integration and interpolation +> +> **Maturity**: EXPERIMENTAL +> +> **Features:** +> - [differentiable-mst-expression](kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt) : MST based DifferentiableExpression. +> - [differentiable-mst-expression](kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt) : Conversions between Kotlin∇'s SFun and MST + +
+ * ### [kmath-memory](kmath-memory) > An API and basic implementation for arranging objects in a continous memory block. > @@ -230,6 +241,18 @@ One can still use generic algebras though. > **Maturity**: EXPERIMENTAL
+* ### [kmath-tensors](kmath-tensors) +> +> +> **Maturity**: PROTOTYPE +> +> **Features:** +> - [tensor algebra](kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt) : Basic linear algebra operations on tensors (plus, dot, etc.) +> - [tensor algebra with broadcasting](kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/BroadcastDoubleTensorAlgebra.kt) : Basic linear algebra operations implemented with broadcasting. +> - [linear algebra operations](kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/LinearOpsTensorAlgebra.kt) : Advanced linear algebra operations like LU decomposition, SVD, etc. + +
+ * ### [kmath-viktor](kmath-viktor) > > @@ -270,8 +293,8 @@ repositories { } dependencies { - api("space.kscience:kmath-core:0.3.0-dev-7") - // api("space.kscience:kmath-core-jvm:0.3.0-dev-7") for jvm-specific version + api("space.kscience:kmath-core:0.3.0-dev-11") + // api("space.kscience:kmath-core-jvm:0.3.0-dev-11") for jvm-specific version } ``` diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index 88f034a2a..98ffc5a96 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -9,14 +9,10 @@ sourceSets.register("benchmarks") repositories { mavenCentral() - jcenter() maven("https://repo.kotlin.link") maven("https://clojars.org/repo") - maven("https://dl.bintray.com/egor-bogomolov/astminer/") - maven("https://dl.bintray.com/hotkeytlt/maven") maven("https://jitpack.io") - maven { - setUrl("http://logicrunch.research.it.uu.se/maven/") + maven("http://logicrunch.research.it.uu.se/maven") { isAllowInsecureProtocol = true } } diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt index 2076aedc7..749cd5e75 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt @@ -10,20 +10,19 @@ import kotlinx.benchmark.Blackhole import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State -import space.kscience.kmath.operations.BigInt -import space.kscience.kmath.operations.BigIntField -import space.kscience.kmath.operations.JBigIntegerField -import space.kscience.kmath.operations.invoke +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.* +import java.math.BigInteger -private fun BigInt.pow(power: Int): BigInt = modPow(BigIntField.number(power), BigInt.ZERO) +@UnstableKMathAPI @State(Scope.Benchmark) internal class BigIntBenchmark { val kmNumber = BigIntField.number(Int.MAX_VALUE) val jvmNumber = JBigIntegerField.number(Int.MAX_VALUE) - val largeKmNumber = BigIntField { number(11).pow(100_000) } - val largeJvmNumber = JBigIntegerField { number(11).pow(100_000) } + val largeKmNumber = BigIntField { number(11).pow(100_000U) } + val largeJvmNumber: BigInteger = JBigIntegerField { number(11).pow(100_000) } val bigExponent = 50_000 @Benchmark @@ -36,6 +35,16 @@ internal class BigIntBenchmark { blackhole.consume(jvmNumber + jvmNumber + jvmNumber) } + @Benchmark + fun kmAddLarge(blackhole: Blackhole) = BigIntField { + blackhole.consume(largeKmNumber + largeKmNumber + largeKmNumber) + } + + @Benchmark + fun jvmAddLarge(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume(largeJvmNumber + largeJvmNumber + largeJvmNumber) + } + @Benchmark fun kmMultiply(blackhole: Blackhole) = BigIntField { blackhole.consume(kmNumber * kmNumber * kmNumber) @@ -56,13 +65,33 @@ internal class BigIntBenchmark { blackhole.consume(largeJvmNumber*largeJvmNumber) } -// @Benchmark -// fun kmPower(blackhole: Blackhole) = BigIntField { -// blackhole.consume(kmNumber.pow(bigExponent)) -// } -// -// @Benchmark -// fun jvmPower(blackhole: Blackhole) = JBigIntegerField { -// blackhole.consume(jvmNumber.pow(bigExponent)) -// } + @Benchmark + fun kmPower(blackhole: Blackhole) = BigIntField { + blackhole.consume(kmNumber.pow(bigExponent.toUInt())) + } + + @Benchmark + fun jvmPower(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume(jvmNumber.pow(bigExponent)) + } + + @Benchmark + fun kmParsing16(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("0x7f57ed8b89c29a3b9a85c7a5b84ca3929c7b7488593".parseBigInteger()) + } + + @Benchmark + fun kmParsing10(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("236656783929183747565738292847574838922010".parseBigInteger()) + } + + @Benchmark + fun jvmParsing10(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("236656783929183747565738292847574838922010".toBigInteger(10)) + } + + @Benchmark + fun jvmParsing16(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("7f57ed8b89c29a3b9a85c7a5b84ca3929c7b7488593".toBigInteger(16)) + } } diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt index 23e73cb5f..2c5a03a97 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt @@ -10,7 +10,7 @@ import kotlinx.benchmark.Blackhole import kotlinx.benchmark.Scope import kotlinx.benchmark.State import space.kscience.kmath.commons.linear.CMLinearSpace -import space.kscience.kmath.ejml.EjmlLinearSpace +import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.invoke import space.kscience.kmath.operations.DoubleField @@ -29,8 +29,8 @@ internal class DotBenchmark { val cmMatrix1 = CMLinearSpace { matrix1.toCM() } val cmMatrix2 = CMLinearSpace { matrix2.toCM() } - val ejmlMatrix1 = EjmlLinearSpace { matrix1.toEjml() } - val ejmlMatrix2 = EjmlLinearSpace { matrix2.toEjml() } + val ejmlMatrix1 = EjmlLinearSpaceDDRM { matrix1.toEjml() } + val ejmlMatrix2 = EjmlLinearSpaceDDRM { matrix2.toEjml() } } @Benchmark @@ -42,14 +42,14 @@ internal class DotBenchmark { @Benchmark fun ejmlDot(blackhole: Blackhole) { - EjmlLinearSpace { + EjmlLinearSpaceDDRM { blackhole.consume(ejmlMatrix1 dot ejmlMatrix2) } } @Benchmark fun ejmlDotWithConversion(blackhole: Blackhole) { - EjmlLinearSpace { + EjmlLinearSpaceDDRM { blackhole.consume(matrix1 dot matrix2) } } diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ExpressionsInterpretersBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ExpressionsInterpretersBenchmark.kt index 942fba308..15cd14399 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ExpressionsInterpretersBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/ExpressionsInterpretersBenchmark.kt @@ -11,8 +11,6 @@ import kotlinx.benchmark.Scope import kotlinx.benchmark.State import space.kscience.kmath.asm.compileToExpression import space.kscience.kmath.expressions.* -import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.bindSymbol import space.kscience.kmath.operations.invoke diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt index d1803e389..7bb32af28 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt @@ -11,25 +11,26 @@ import kotlinx.benchmark.Scope import kotlinx.benchmark.State import space.kscience.kmath.commons.linear.CMLinearSpace import space.kscience.kmath.commons.linear.inverse -import space.kscience.kmath.ejml.EjmlLinearSpace -import space.kscience.kmath.ejml.inverse +import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM +import space.kscience.kmath.linear.InverseMatrixFeature import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.inverseWithLup import space.kscience.kmath.linear.invoke +import space.kscience.kmath.nd.getFeature import kotlin.random.Random @State(Scope.Benchmark) internal class MatrixInverseBenchmark { - companion object { - val random = Random(1224) - const val dim = 100 + private companion object { + private val random = Random(1224) + private const val dim = 100 private val space = LinearSpace.real //creating invertible matrix - val u = space.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } - val l = space.buildMatrix(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 } - val matrix = space { l dot u } + private val u = space.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + private val l = space.buildMatrix(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 } + private val matrix = space { l dot u } } @Benchmark @@ -46,8 +47,8 @@ internal class MatrixInverseBenchmark { @Benchmark fun ejmlInverse(blackhole: Blackhole) { - with(EjmlLinearSpace) { - blackhole.consume(inverse(matrix)) + with(EjmlLinearSpaceDDRM) { + blackhole.consume(matrix.getFeature>()?.inverse) } } } diff --git a/build.gradle.kts b/build.gradle.kts index 4e0b6f256..4de6d8bad 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,44 +1,37 @@ plugins { id("ru.mipt.npm.gradle.project") + kotlin("jupyter.api") apply false } allprojects { repositories { - jcenter() maven("https://clojars.org/repo") - maven("https://dl.bintray.com/egor-bogomolov/astminer/") - maven("https://dl.bintray.com/hotkeytlt/maven") maven("https://jitpack.io") - maven("http://logicrunch.research.it.uu.se/maven/") { + maven("http://logicrunch.research.it.uu.se/maven") { isAllowInsecureProtocol = true } + maven("https://oss.sonatype.org/content/repositories/snapshots") mavenCentral() } group = "space.kscience" - version = "0.3.0-dev-7" + version = "0.3.0-dev-13" } subprojects { if (name.startsWith("kmath")) apply() afterEvaluate { - tasks.withType { - dokkaSourceSets.all { - val readmeFile = File(this@subprojects.projectDir, "./README.md") - if (readmeFile.exists()) - includes.setFrom(includes + readmeFile.absolutePath) + tasks.withType { + dependsOn(tasks.getByName("assemble")) - arrayOf( - "http://ejml.org/javadoc/", - "https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/", - "https://deeplearning4j.org/api/latest/" - ).map { java.net.URL("${it}package-list") to java.net.URL(it) }.forEach { (a, b) -> - externalDocumentationLink { - packageListUrl.set(a) - url.set(b) - } - } + dokkaSourceSets.all { + val readmeFile = File(this@subprojects.projectDir, "README.md") + if (readmeFile.exists()) includes.setFrom(includes + readmeFile.absolutePath) + externalDocumentationLink("http://ejml.org/javadoc/") + externalDocumentationLink("https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/") + externalDocumentationLink("https://deeplearning4j.org/api/latest/") + externalDocumentationLink("https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/") } } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 000000000..7ca4df19d --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,5 @@ +plugins { + `kotlin-dsl` +} + +repositories.mavenCentral() diff --git a/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt b/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt new file mode 100644 index 000000000..5da7d0f67 --- /dev/null +++ b/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt @@ -0,0 +1,425 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("KDocUnresolvedReference") + +package space.kscience.kmath.ejml.codegen + +import org.intellij.lang.annotations.Language +import java.io.File + +private fun Appendable.appendEjmlVector(type: String, ejmlMatrixType: String) { + @Language("kotlin") val text = """/** + * [EjmlVector] specialization for [$type]. + */ +public class Ejml${type}Vector(public override val origin: M) : EjmlVector<$type, M>(origin) { + init { + require(origin.numRows == 1) { "The origin matrix must have only one row to form a vector" } + } + + public override operator fun get(index: Int): $type = origin[0, index] +}""" + appendLine(text) + appendLine() +} + +private fun Appendable.appendEjmlMatrix(type: String, ejmlMatrixType: String) { + val text = """/** + * [EjmlMatrix] specialization for [$type]. + */ +public class Ejml${type}Matrix(public override val origin: M) : EjmlMatrix<$type, M>(origin) { + public override operator fun get(i: Int, j: Int): $type = origin[i, j] +}""" + appendLine(text) + appendLine() +} + +private fun Appendable.appendEjmlLinearSpace( + type: String, + kmathAlgebra: String, + ejmlMatrixParentTypeMatrix: String, + ejmlMatrixType: String, + ejmlMatrixDenseType: String, + ops: String, + denseOps: String, + isDense: Boolean, +) { + @Language("kotlin") val text = """/** + * [EjmlLinearSpace] implementation based on [CommonOps_$ops], [DecompositionFactory_${ops}] operations and + * [${ejmlMatrixType}] matrices. + */ +public object EjmlLinearSpace${ops} : EjmlLinearSpace<${type}, ${kmathAlgebra}, $ejmlMatrixType>() { + /** + * The [${kmathAlgebra}] reference. + */ + public override val elementAlgebra: $kmathAlgebra get() = $kmathAlgebra + + @Suppress("UNCHECKED_CAST") + public override fun Matrix<${type}>.toEjml(): Ejml${type}Matrix<${ejmlMatrixType}> = when { + this is Ejml${type}Matrix<*> && origin is $ejmlMatrixType -> this as Ejml${type}Matrix<${ejmlMatrixType}> + else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } + } + + @Suppress("UNCHECKED_CAST") + public override fun Point<${type}>.toEjml(): Ejml${type}Vector<${ejmlMatrixType}> = when { + this is Ejml${type}Vector<*> && origin is $ejmlMatrixType -> this as Ejml${type}Vector<${ejmlMatrixType}> + else -> Ejml${type}Vector(${ejmlMatrixType}(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } + }) + } + + public override fun buildMatrix( + rows: Int, + columns: Int, + initializer: ${kmathAlgebra}.(i: Int, j: Int) -> ${type}, + ): Ejml${type}Matrix<${ejmlMatrixType}> = ${ejmlMatrixType}(rows, columns).also { + (0 until rows).forEach { row -> + (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } + } + }.wrapMatrix() + + public override fun buildVector( + size: Int, + initializer: ${kmathAlgebra}.(Int) -> ${type}, + ): Ejml${type}Vector<${ejmlMatrixType}> = Ejml${type}Vector(${ejmlMatrixType}(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } + }) + + private fun T.wrapMatrix() = Ejml${type}Matrix(this) + private fun T.wrapVector() = Ejml${type}Vector(this) + + public override fun Matrix<${type}>.unaryMinus(): Matrix<${type}> = this * elementAlgebra { -one } + + public override fun Matrix<${type}>.dot(other: Matrix<${type}>): Ejml${type}Matrix<${ejmlMatrixType}> { + val out = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.mult(toEjml().origin, other.toEjml().origin, out) + return out.wrapMatrix() + } + + public override fun Matrix<${type}>.dot(vector: Point<${type}>): Ejml${type}Vector<${ejmlMatrixType}> { + val out = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.mult(toEjml().origin, vector.toEjml().origin, out) + return out.wrapVector() + } + + public override operator fun Matrix<${type}>.minus(other: Matrix<${type}>): Ejml${type}Matrix<${ejmlMatrixType}> { + val out = ${ejmlMatrixType}(1, 1) + + CommonOps_${ops}.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out,${ + if (isDense) "" else + """ + null, + null,""" + } + ) + + return out.wrapMatrix() + } + + public override operator fun Matrix<${type}>.times(value: ${type}): Ejml${type}Matrix<${ejmlMatrixType}> { + val res = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.scale(value, toEjml().origin, res) + return res.wrapMatrix() + } + + public override fun Point<${type}>.unaryMinus(): Ejml${type}Vector<${ejmlMatrixType}> { + val res = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.changeSign(toEjml().origin, res) + return res.wrapVector() + } + + public override fun Matrix<${type}>.plus(other: Matrix<${type}>): Ejml${type}Matrix<${ejmlMatrixType}> { + val out = ${ejmlMatrixType}(1, 1) + + CommonOps_${ops}.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out,${ + if (isDense) "" else + """ + null, + null,""" + } + ) + + return out.wrapMatrix() + } + + public override fun Point<${type}>.plus(other: Point<${type}>): Ejml${type}Vector<${ejmlMatrixType}> { + val out = ${ejmlMatrixType}(1, 1) + + CommonOps_${ops}.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out,${ + if (isDense) "" else + """ + null, + null,""" + } + ) + + return out.wrapVector() + } + + public override fun Point<${type}>.minus(other: Point<${type}>): Ejml${type}Vector<${ejmlMatrixType}> { + val out = ${ejmlMatrixType}(1, 1) + + CommonOps_${ops}.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out,${ + if (isDense) "" else + """ + null, + null,""" + } + ) + + return out.wrapVector() + } + + public override fun ${type}.times(m: Matrix<${type}>): Ejml${type}Matrix<${ejmlMatrixType}> = m * this + + public override fun Point<${type}>.times(value: ${type}): Ejml${type}Vector<${ejmlMatrixType}> { + val res = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.scale(value, toEjml().origin, res) + return res.wrapVector() + } + + public override fun ${type}.times(v: Point<${type}>): Ejml${type}Vector<${ejmlMatrixType}> = v * this + + @UnstableKMathAPI + public override fun getFeature(structure: Matrix<${type}>, type: KClass): F? { + structure.getFeature(type)?.let { return it } + val origin = structure.toEjml().origin + + return when (type) { + ${ + if (isDense) + """ InverseMatrixFeature::class -> object : InverseMatrixFeature<${type}> { + override val inverse: Matrix<${type}> by lazy { + val res = origin.copy() + CommonOps_${ops}.invert(res) + res.wrapMatrix() + } + } + + DeterminantFeature::class -> object : DeterminantFeature<${type}> { + override val determinant: $type by lazy { CommonOps_${ops}.det(origin) } + } + + SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<${type}> { + private val svd by lazy { + DecompositionFactory_${ops}.svd(origin.numRows, origin.numCols, true, true, false) + .apply { decompose(origin.copy()) } + } + + override val u: Matrix<${type}> by lazy { svd.getU(null, false).wrapMatrix() } + override val s: Matrix<${type}> by lazy { svd.getW(null).wrapMatrix() } + override val v: Matrix<${type}> by lazy { svd.getV(null, false).wrapMatrix() } + override val singularValues: Point<${type}> by lazy { ${type}Buffer(svd.singularValues) } + } + + QRDecompositionFeature::class -> object : QRDecompositionFeature<${type}> { + private val qr by lazy { + DecompositionFactory_${ops}.qr().apply { decompose(origin.copy()) } + } + + override val q: Matrix<${type}> by lazy { + qr.getQ(null, false).wrapMatrix() + OrthogonalFeature + } + + override val r: Matrix<${type}> by lazy { qr.getR(null, false).wrapMatrix() + UFeature } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<${type}> { + override val l: Matrix<${type}> by lazy { + val cholesky = + DecompositionFactory_${ops}.chol(structure.rowNum, true).apply { decompose(origin.copy()) } + + cholesky.getT(null).wrapMatrix() + LFeature + } + } + + LupDecompositionFeature::class -> object : LupDecompositionFeature<${type}> { + private val lup by lazy { + DecompositionFactory_${ops}.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) } + } + + override val l: Matrix<${type}> by lazy { + lup.getLower(null).wrapMatrix() + LFeature + } + + override val u: Matrix<${type}> by lazy { + lup.getUpper(null).wrapMatrix() + UFeature + } + + override val p: Matrix<${type}> by lazy { lup.getRowPivot(null).wrapMatrix() } + }""" else """ QRDecompositionFeature::class -> object : QRDecompositionFeature<$type> { + private val qr by lazy { + DecompositionFactory_${ops}.qr(FillReducing.NONE).apply { decompose(origin.copy()) } + } + + override val q: Matrix<${type}> by lazy { + qr.getQ(null, false).wrapMatrix() + OrthogonalFeature + } + + override val r: Matrix<${type}> by lazy { qr.getR(null, false).wrapMatrix() + UFeature } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<${type}> { + override val l: Matrix<${type}> by lazy { + val cholesky = + DecompositionFactory_${ops}.cholesky().apply { decompose(origin.copy()) } + + (cholesky.getT(null) as ${ejmlMatrixParentTypeMatrix}).wrapMatrix() + LFeature + } + } + + LUDecompositionFeature::class, DeterminantFeature::class, InverseMatrixFeature::class -> object : + LUDecompositionFeature<${type}>, DeterminantFeature<${type}>, InverseMatrixFeature<${type}> { + private val lu by lazy { + DecompositionFactory_${ops}.lu(FillReducing.NONE).apply { decompose(origin.copy()) } + } + + override val l: Matrix<${type}> by lazy { + lu.getLower(null).wrapMatrix() + LFeature + } + + override val u: Matrix<${type}> by lazy { + lu.getUpper(null).wrapMatrix() + UFeature + } + + override val inverse: Matrix<${type}> by lazy { + var a = origin + val inverse = ${ejmlMatrixDenseType}(1, 1) + val solver = LinearSolverFactory_${ops}.lu(FillReducing.NONE) + if (solver.modifiesA()) a = a.copy() + val i = CommonOps_${denseOps}.identity(a.numRows) + solver.solve(i, inverse) + inverse.wrapMatrix() + } + + override val determinant: $type by lazy { elementAlgebra.number(lu.computeDeterminant().real) } + }""" + } + + else -> null + }?.let(type::cast) + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p matrix. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix<${type}>, b: Matrix<${type}>): Ejml${type}Matrix<${ejmlMatrixType}> { + val res = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.solve(${ejmlMatrixType}(a.toEjml().origin), ${ejmlMatrixType}(b.toEjml().origin), res) + return res.wrapMatrix() + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p vector. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix<${type}>, b: Point<${type}>): Ejml${type}Vector<${ejmlMatrixType}> { + val res = ${ejmlMatrixType}(1, 1) + CommonOps_${ops}.solve(${ejmlMatrixType}(a.toEjml().origin), ${ejmlMatrixType}(b.toEjml().origin), res) + return Ejml${type}Vector(res) + } +}""" + appendLine(text) + appendLine() +} + + +/** + * Generates routine EJML classes. + */ +fun ejmlCodegen(outputFile: String): Unit = File(outputFile).run { + parentFile.mkdirs() + + writer().use { + it.appendLine("/*") + it.appendLine(" * Copyright 2018-2021 KMath contributors.") + it.appendLine(" * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.") + it.appendLine(" */") + it.appendLine() + it.appendLine("/* This file is generated with buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt */") + it.appendLine() + it.appendLine("package space.kscience.kmath.ejml") + it.appendLine() + it.appendLine("""import org.ejml.data.* +import org.ejml.dense.row.CommonOps_DDRM +import org.ejml.dense.row.CommonOps_FDRM +import org.ejml.dense.row.factory.DecompositionFactory_DDRM +import org.ejml.dense.row.factory.DecompositionFactory_FDRM +import org.ejml.sparse.FillReducing +import org.ejml.sparse.csc.CommonOps_DSCC +import org.ejml.sparse.csc.CommonOps_FSCC +import org.ejml.sparse.csc.factory.DecompositionFactory_DSCC +import org.ejml.sparse.csc.factory.DecompositionFactory_FSCC +import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC +import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC +import space.kscience.kmath.linear.* +import space.kscience.kmath.linear.Matrix +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.nd.StructureFeature +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.FloatField +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.FloatBuffer +import kotlin.reflect.KClass +import kotlin.reflect.cast""") + it.appendLine() + it.appendEjmlVector("Double", "DMatrix") + it.appendEjmlVector("Float", "FMatrix") + it.appendEjmlMatrix("Double", "DMatrix") + it.appendEjmlMatrix("Float", "FMatrix") + it.appendEjmlLinearSpace("Double", "DoubleField", "DMatrix", "DMatrixRMaj", "DMatrixRMaj", "DDRM", "DDRM", true) + it.appendEjmlLinearSpace("Float", "FloatField", "FMatrix", "FMatrixRMaj", "FMatrixRMaj", "FDRM", "FDRM", true) + + it.appendEjmlLinearSpace( + type = "Double", + kmathAlgebra = "DoubleField", + ejmlMatrixParentTypeMatrix = "DMatrix", + ejmlMatrixType = "DMatrixSparseCSC", + ejmlMatrixDenseType = "DMatrixRMaj", + ops = "DSCC", + denseOps = "DDRM", + isDense = false, + ) + + it.appendEjmlLinearSpace( + type = "Float", + kmathAlgebra = "FloatField", + ejmlMatrixParentTypeMatrix = "FMatrix", + ejmlMatrixType = "FMatrixSparseCSC", + ejmlMatrixDenseType = "FMatrixRMaj", + ops = "FSCC", + denseOps = "FDRM", + isDense = false, + ) + } +} diff --git a/docs/templates/ARTIFACT-TEMPLATE.md b/docs/templates/ARTIFACT-TEMPLATE.md index 01d9c51da..1bac2a8ff 100644 --- a/docs/templates/ARTIFACT-TEMPLATE.md +++ b/docs/templates/ARTIFACT-TEMPLATE.md @@ -6,8 +6,7 @@ The Maven coordinates of this project are `${group}:${name}:${version}`. ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { @@ -18,8 +17,7 @@ dependencies { ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { diff --git a/docs/templates/README-TEMPLATE.md b/docs/templates/README-TEMPLATE.md index 99951b4d6..6bb1e9085 100644 --- a/docs/templates/README-TEMPLATE.md +++ b/docs/templates/README-TEMPLATE.md @@ -40,7 +40,7 @@ KMath is a modular library. Different modules provide different features with di * **PROTOTYPE**. On this level there are no compatibility guarantees. All methods and classes form those modules could break any moment. You can still use it, but be sure to fix the specific version. * **EXPERIMENTAL**. The general API is decided, but some changes could be made. Volatile API is marked with `@UnstableKmathAPI` or other stability warning annotations. -* **DEVELOPMENT**. API breaking genrally follows semantic versioning ideology. There could be changes in minor versions, but not in patch versions. API is protected with [binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) tool. +* **DEVELOPMENT**. API breaking generally follows semantic versioning ideology. There could be changes in minor versions, but not in patch versions. API is protected with [binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) tool. * **STABLE**. The API stabilized. Breaking changes are allowed only in major releases. diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 56feee9dc..d095db1ba 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -4,14 +4,11 @@ plugins { repositories { mavenCentral() - jcenter() maven("https://repo.kotlin.link") maven("https://clojars.org/repo") - maven("https://dl.bintray.com/egor-bogomolov/astminer/") - maven("https://dl.bintray.com/hotkeytlt/maven") maven("https://jitpack.io") - maven{ - setUrl("http://logicrunch.research.it.uu.se/maven/") + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers") + maven("http://logicrunch.research.it.uu.se/maven") { isAllowInsecureProtocol = true } } @@ -28,6 +25,7 @@ dependencies { implementation(project(":kmath-dimensions")) implementation(project(":kmath-ejml")) implementation(project(":kmath-nd4j")) + implementation(project(":kmath-tensors")) implementation(project(":kmath-for-real")) @@ -45,7 +43,7 @@ dependencies { implementation("org.slf4j:slf4j-simple:1.7.30") // plotting - implementation("space.kscience:plotlykt-server:0.4.0-dev-2") + implementation("space.kscience:plotlykt-server:0.4.0") } kotlin.sourceSets.all { @@ -59,7 +57,7 @@ kotlin.sourceSets.all { tasks.withType { kotlinOptions{ jvmTarget = "11" - freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" + "-Xopt-in=kotlin.RequiresOptIn" } } diff --git a/examples/src/main/kotlin/space/kscience/kmath/ast/expressions.kt b/examples/src/main/kotlin/space/kscience/kmath/ast/expressions.kt index 918134e04..d5a82590f 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/ast/expressions.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/ast/expressions.kt @@ -6,8 +6,8 @@ package space.kscience.kmath.ast import space.kscience.kmath.expressions.MstField +import space.kscience.kmath.expressions.Symbol.Companion.x import space.kscience.kmath.expressions.interpret -import space.kscience.kmath.misc.Symbol.Companion.x import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.bindSymbol import space.kscience.kmath.operations.invoke diff --git a/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt b/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt index 25f42f5a9..420b23f9f 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt @@ -8,8 +8,8 @@ package space.kscience.kmath.ast import space.kscience.kmath.asm.compileToExpression import space.kscience.kmath.expressions.derivative import space.kscience.kmath.expressions.invoke +import space.kscience.kmath.expressions.symbol import space.kscience.kmath.kotlingrad.toDiffExpression -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField /** diff --git a/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt b/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt index 028985260..5e64235e3 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt @@ -10,7 +10,7 @@ import kotlinx.html.h3 import space.kscience.kmath.commons.optimization.chiSquared import space.kscience.kmath.commons.optimization.minimize import space.kscience.kmath.distributions.NormalDistribution -import space.kscience.kmath.misc.symbol +import space.kscience.kmath.expressions.symbol import space.kscience.kmath.optimization.FunctionOptimization import space.kscience.kmath.optimization.OptimizationResult import space.kscience.kmath.real.DoubleVector diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt index 7cdf7bef6..f60b1ab45 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt @@ -5,7 +5,8 @@ package space.kscience.kmath.functions -import space.kscience.kmath.integration.process +import space.kscience.kmath.integration.gaussIntegrator +import space.kscience.kmath.integration.integrate import space.kscience.kmath.integration.value import space.kscience.kmath.operations.DoubleField import kotlin.math.pow @@ -15,7 +16,7 @@ fun main() { val function: UnivariateFunction = { x -> 3 * x.pow(2) + 2 * x + 1 } //get the result of the integration - val result = DoubleField.process(0.0..10.0, function = function) + val result = DoubleField.gaussIntegrator.integrate(0.0..10.0, function = function) //the value is nullable because in some cases the integration could not succeed println(result.value) diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/interpolate.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/interpolate.kt new file mode 100644 index 000000000..8dbc7b7a4 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/interpolate.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import space.kscience.kmath.interpolation.SplineInterpolator +import space.kscience.kmath.interpolation.interpolatePolynomials +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.plotly.Plotly +import space.kscience.plotly.UnstablePlotlyAPI +import space.kscience.plotly.makeFile +import space.kscience.plotly.models.functionXY +import space.kscience.plotly.scatter +import kotlin.math.PI +import kotlin.math.sin + +@OptIn(UnstablePlotlyAPI::class) +fun main() { + val data = (0..10).map { + val x = it.toDouble() / 5 * PI + x to sin(x) + } + + val polynomial: PiecewisePolynomial = SplineInterpolator( + DoubleField, ::DoubleBuffer + ).interpolatePolynomials(data) + + val function = polynomial.asFunction(DoubleField, 0.0) + + val cmInterpolate = org.apache.commons.math3.analysis.interpolation.SplineInterpolator().interpolate( + data.map { it.first }.toDoubleArray(), + data.map { it.second }.toDoubleArray() + ) + + Plotly.plot { + scatter { + name = "interpolated" + x.numbers = data.map { it.first } + y.numbers = x.doubles.map { function(it) } + } + scatter { + name = "original" + functionXY(0.0..(2 * PI), 0.1) { sin(it) } + } + scatter { + name = "cm" + x.numbers = data.map { it.first } + y.numbers = x.doubles.map { cmInterpolate.value(it) } + } + }.makeFile() +} \ No newline at end of file diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/interpolateSquare.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/interpolateSquare.kt new file mode 100644 index 000000000..33973c880 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/interpolateSquare.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import space.kscience.kmath.interpolation.SplineInterpolator +import space.kscience.kmath.interpolation.interpolatePolynomials +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.real.step +import space.kscience.kmath.structures.map +import space.kscience.plotly.Plotly +import space.kscience.plotly.UnstablePlotlyAPI +import space.kscience.plotly.makeFile +import space.kscience.plotly.models.functionXY +import space.kscience.plotly.scatter + +@OptIn(UnstablePlotlyAPI::class) +fun main() { + val function: UnivariateFunction = { x -> + if (x in 30.0..50.0) { + 1.0 + } else { + 0.0 + } + } + val xs = 0.0..100.0 step 0.5 + val ys = xs.map(function) + + val polynomial: PiecewisePolynomial = SplineInterpolator.double.interpolatePolynomials(xs, ys) + + val polyFunction = polynomial.asFunction(DoubleField, 0.0) + + Plotly.plot { + scatter { + name = "interpolated" + functionXY(25.0..55.0, 0.1) { polyFunction(it) } + } + scatter { + name = "original" + functionXY(25.0..55.0, 0.1) { function(it) } + } + }.makeFile() +} \ No newline at end of file diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt index 206ba3054..2619d3d74 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt @@ -5,7 +5,8 @@ package space.kscience.kmath.functions -import space.kscience.kmath.integration.process +import space.kscience.kmath.integration.gaussIntegrator +import space.kscience.kmath.integration.integrate import space.kscience.kmath.integration.value import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.nd @@ -24,7 +25,7 @@ fun main(): Unit = DoubleField { val function: (Double) -> StructureND = { x: Double -> 3 * number(x).pow(2) + 2 * diagonal(x) + 1 } //get the result of the integration - val result = process(0.0..10.0, function = function) + val result = gaussIntegrator.integrate(0.0..10.0, function = function) //the value is nullable because in some cases the integration could not succeed println(result.value) diff --git a/examples/src/main/kotlin/space/kscience/kmath/structures/NDField.kt b/examples/src/main/kotlin/space/kscience/kmath/structures/NDField.kt index cc1f5f680..501bf98db 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/structures/NDField.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/structures/NDField.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.structures +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import org.nd4j.linalg.factory.Nd4j import space.kscience.kmath.nd.* @@ -22,6 +23,7 @@ internal inline fun measureAndPrint(title: String, block: () -> Unit) { println("$title completed in $time millis") } +@OptIn(DelicateCoroutinesApi::class) fun main() { // initializing Nd4j Nd4j.zeros(0) diff --git a/examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt b/examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt new file mode 100644 index 000000000..74795cc68 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/tensors/DataSetNormalization.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra + + +// Dataset normalization + +fun main() = BroadcastDoubleTensorAlgebra { // work in context with broadcast methods + // take dataset of 5-element vectors from normal distribution + val dataset = randomNormal(intArrayOf(100, 5)) * 1.5 // all elements from N(0, 1.5) + + dataset += fromArray( + intArrayOf(5), + doubleArrayOf(0.0, 1.0, 1.5, 3.0, 5.0) // rows means + ) + + + // find out mean and standard deviation of each column + val mean = dataset.mean(0, false) + val std = dataset.std(0, false) + + println("Mean:\n$mean") + println("Standard deviation:\n$std") + + // also we can calculate other statistic as minimum and maximum of rows + println("Minimum:\n${dataset.min(0, false)}") + println("Maximum:\n${dataset.max(0, false)}") + + // now we can scale dataset with mean normalization + val datasetScaled = (dataset - mean) / std + + // find out mean and std of scaled dataset + + println("Mean of scaled:\n${datasetScaled.mean(0, false)}") + println("Mean of scaled:\n${datasetScaled.std(0, false)}") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/space/kscience/kmath/tensors/LinearSystemSolvingWithLUP.kt b/examples/src/main/kotlin/space/kscience/kmath/tensors/LinearSystemSolvingWithLUP.kt new file mode 100644 index 000000000..6453ca44e --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/tensors/LinearSystemSolvingWithLUP.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra +import space.kscience.kmath.tensors.core.DoubleTensor + +// solving linear system with LUP decomposition + +fun main() = BroadcastDoubleTensorAlgebra {// work in context with linear operations + + // set true value of x + val trueX = fromArray( + intArrayOf(4), + doubleArrayOf(-2.0, 1.5, 6.8, -2.4) + ) + + // and A matrix + val a = fromArray( + intArrayOf(4, 4), + doubleArrayOf( + 0.5, 10.5, 4.5, 1.0, + 8.5, 0.9, 12.8, 0.1, + 5.56, 9.19, 7.62, 5.45, + 1.0, 2.0, -3.0, -2.5 + ) + ) + + // calculate y value + val b = a dot trueX + + // check out A and b + println("A:\n$a") + println("b:\n$b") + + // solve `Ax = b` system using LUP decomposition + + // get P, L, U such that PA = LU + val (p, l, u) = a.lu() + + // check that P is permutation matrix + println("P:\n$p") + // L is lower triangular matrix and U is upper triangular matrix + println("L:\n$l") + println("U:\n$u") + // and PA = LU + println("PA:\n${p dot a}") + println("LU:\n${l dot u}") + + /* Ax = b; + PAx = Pb; + LUx = Pb; + let y = Ux, then + Ly = Pb -- this system can be easily solved, since the matrix L is lower triangular; + Ux = y can be solved the same way, since the matrix L is upper triangular + */ + + + + // this function returns solution x of a system lx = b, l should be lower triangular + fun solveLT(l: DoubleTensor, b: DoubleTensor): DoubleTensor { + val n = l.shape[0] + val x = zeros(intArrayOf(n)) + for (i in 0 until n) { + x[intArrayOf(i)] = (b[intArrayOf(i)] - l[i].dot(x).value()) / l[intArrayOf(i, i)] + } + return x + } + + val y = solveLT(l, p dot b) + + // solveLT(l, b) function can be easily adapted for upper triangular matrix by the permutation matrix revMat + // create it by placing ones on side diagonal + val revMat = u.zeroesLike() + val n = revMat.shape[0] + for (i in 0 until n) { + revMat[intArrayOf(i, n - 1 - i)] = 1.0 + } + + // solution of system ux = b, u should be upper triangular + fun solveUT(u: DoubleTensor, b: DoubleTensor): DoubleTensor = revMat dot solveLT( + revMat dot u dot revMat, revMat dot b + ) + + val x = solveUT(u, y) + + println("True x:\n$trueX") + println("x founded with LU method:\n$x") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/space/kscience/kmath/tensors/NeuralNetwork.kt b/examples/src/main/kotlin/space/kscience/kmath/tensors/NeuralNetwork.kt new file mode 100644 index 000000000..b262bee02 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/tensors/NeuralNetwork.kt @@ -0,0 +1,239 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra +import space.kscience.kmath.tensors.core.DoubleTensor +import space.kscience.kmath.tensors.core.DoubleTensorAlgebra +import space.kscience.kmath.tensors.core.toDoubleArray +import kotlin.math.sqrt + +const val seed = 100500L + +// Simple feedforward neural network with backpropagation training + +// interface of network layer +interface Layer { + fun forward(input: DoubleTensor): DoubleTensor + fun backward(input: DoubleTensor, outputError: DoubleTensor): DoubleTensor +} + +// activation layer +open class Activation( + val activation: (DoubleTensor) -> DoubleTensor, + val activationDer: (DoubleTensor) -> DoubleTensor, +) : Layer { + override fun forward(input: DoubleTensor): DoubleTensor { + return activation(input) + } + + override fun backward(input: DoubleTensor, outputError: DoubleTensor): DoubleTensor { + return DoubleTensorAlgebra { outputError * activationDer(input) } + } +} + +fun relu(x: DoubleTensor): DoubleTensor = DoubleTensorAlgebra { + x.map { if (it > 0) it else 0.0 } +} + +fun reluDer(x: DoubleTensor): DoubleTensor = DoubleTensorAlgebra { + x.map { if (it > 0) 1.0 else 0.0 } +} + +// activation layer with relu activator +class ReLU : Activation(::relu, ::reluDer) + +fun sigmoid(x: DoubleTensor): DoubleTensor = DoubleTensorAlgebra { + 1.0 / (1.0 + (-x).exp()) +} + +fun sigmoidDer(x: DoubleTensor): DoubleTensor = DoubleTensorAlgebra { + sigmoid(x) * (1.0 - sigmoid(x)) +} + +// activation layer with sigmoid activator +class Sigmoid : Activation(::sigmoid, ::sigmoidDer) + +// dense layer +class Dense( + private val inputUnits: Int, + private val outputUnits: Int, + private val learningRate: Double = 0.1, +) : Layer { + + private val weights: DoubleTensor = DoubleTensorAlgebra { + randomNormal( + intArrayOf(inputUnits, outputUnits), + seed + ) * sqrt(2.0 / (inputUnits + outputUnits)) + } + + private val bias: DoubleTensor = DoubleTensorAlgebra { zeros(intArrayOf(outputUnits)) } + + override fun forward(input: DoubleTensor): DoubleTensor = BroadcastDoubleTensorAlgebra { + (input dot weights) + bias + } + + override fun backward(input: DoubleTensor, outputError: DoubleTensor): DoubleTensor = DoubleTensorAlgebra { + val gradInput = outputError dot weights.transpose() + + val gradW = input.transpose() dot outputError + val gradBias = outputError.mean(dim = 0, keepDim = false) * input.shape[0].toDouble() + + weights -= learningRate * gradW + bias -= learningRate * gradBias + + gradInput + } + +} + +// simple accuracy equal to the proportion of correct answers +fun accuracy(yPred: DoubleTensor, yTrue: DoubleTensor): Double { + check(yPred.shape contentEquals yTrue.shape) + val n = yPred.shape[0] + var correctCnt = 0 + for (i in 0 until n) { + if (yPred[intArrayOf(i, 0)] == yTrue[intArrayOf(i, 0)]) { + correctCnt += 1 + } + } + return correctCnt.toDouble() / n.toDouble() +} + +// neural network class +@OptIn(ExperimentalStdlibApi::class) +class NeuralNetwork(private val layers: List) { + private fun softMaxLoss(yPred: DoubleTensor, yTrue: DoubleTensor): DoubleTensor = BroadcastDoubleTensorAlgebra { + + val onesForAnswers = yPred.zeroesLike() + yTrue.toDoubleArray().forEachIndexed { index, labelDouble -> + val label = labelDouble.toInt() + onesForAnswers[intArrayOf(index, label)] = 1.0 + } + + val softmaxValue = yPred.exp() / yPred.exp().sum(dim = 1, keepDim = true) + + (-onesForAnswers + softmaxValue) / (yPred.shape[0].toDouble()) + } + + + private fun forward(x: DoubleTensor): List { + var input = x + + return buildList { + layers.forEach { layer -> + val output = layer.forward(input) + add(output) + input = output + } + } + } + + private fun train(xTrain: DoubleTensor, yTrain: DoubleTensor) { + val layerInputs = buildList { + add(xTrain) + addAll(forward(xTrain)) + } + + var lossGrad = softMaxLoss(layerInputs.last(), yTrain) + + layers.zip(layerInputs).reversed().forEach { (layer, input) -> + lossGrad = layer.backward(input, lossGrad) + } + } + + fun fit(xTrain: DoubleTensor, yTrain: DoubleTensor, batchSize: Int, epochs: Int) = DoubleTensorAlgebra { + fun iterBatch(x: DoubleTensor, y: DoubleTensor): Sequence> = sequence { + val n = x.shape[0] + val shuffledIndices = (0 until n).shuffled() + for (i in 0 until n step batchSize) { + val excerptIndices = shuffledIndices.drop(i).take(batchSize).toIntArray() + val batch = x.rowsByIndices(excerptIndices) to y.rowsByIndices(excerptIndices) + yield(batch) + } + } + + for (epoch in 0 until epochs) { + println("Epoch ${epoch + 1}/$epochs") + for ((xBatch, yBatch) in iterBatch(xTrain, yTrain)) { + train(xBatch, yBatch) + } + println("Accuracy:${accuracy(yTrain, predict(xTrain).argMax(1, true))}") + } + } + + fun predict(x: DoubleTensor): DoubleTensor { + return forward(x).last() + } + +} + + +@OptIn(ExperimentalStdlibApi::class) +fun main() = BroadcastDoubleTensorAlgebra { + val features = 5 + val sampleSize = 250 + val trainSize = 180 + //val testSize = sampleSize - trainSize + + // take sample of features from normal distribution + val x = randomNormal(intArrayOf(sampleSize, features), seed) * 2.5 + + x += fromArray( + intArrayOf(5), + doubleArrayOf(0.0, -1.0, -2.5, -3.0, 5.5) // rows means + ) + + + // define class like '1' if the sum of features > 0 and '0' otherwise + val y = fromArray( + intArrayOf(sampleSize, 1), + DoubleArray(sampleSize) { i -> + if (x[i].sum() > 0.0) { + 1.0 + } else { + 0.0 + } + } + ) + + // split train ans test + val trainIndices = (0 until trainSize).toList().toIntArray() + val testIndices = (trainSize until sampleSize).toList().toIntArray() + + val xTrain = x.rowsByIndices(trainIndices) + val yTrain = y.rowsByIndices(trainIndices) + + val xTest = x.rowsByIndices(testIndices) + val yTest = y.rowsByIndices(testIndices) + + // build model + val layers = buildList { + add(Dense(features, 64)) + add(ReLU()) + add(Dense(64, 16)) + add(ReLU()) + add(Dense(16, 2)) + add(Sigmoid()) + } + val model = NeuralNetwork(layers) + + // fit it with train data + model.fit(xTrain, yTrain, batchSize = 20, epochs = 10) + + // make prediction + val prediction = model.predict(xTest) + + // process raw prediction via argMax + val predictionLabels = prediction.argMax(1, true) + + // find out accuracy + val acc = accuracy(yTest, predictionLabels) + println("Test accuracy:$acc") + +} diff --git a/examples/src/main/kotlin/space/kscience/kmath/tensors/OLSWithSVD.kt b/examples/src/main/kotlin/space/kscience/kmath/tensors/OLSWithSVD.kt new file mode 100644 index 000000000..b42602988 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/tensors/OLSWithSVD.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.DoubleTensor +import space.kscience.kmath.tensors.core.DoubleTensorAlgebra + +import kotlin.math.abs + +// OLS estimator using SVD + +fun main() { + //seed for random + val randSeed = 100500L + + // work in context with linear operations + DoubleTensorAlgebra { + // take coefficient vector from normal distribution + val alpha = randomNormal( + intArrayOf(5), + randSeed + ) + fromArray( + intArrayOf(5), + doubleArrayOf(1.0, 2.5, 3.4, 5.0, 10.1) + ) + + println("Real alpha:\n$alpha") + + // also take sample of size 20 from normal distribution for x + val x = randomNormal( + intArrayOf(20, 5), + randSeed + ) + + // calculate y and add gaussian noise (N(0, 0.05)) + val y = x dot alpha + y += y.randomNormalLike(randSeed) * 0.05 + + // now restore the coefficient vector with OSL estimator with SVD + val (u, singValues, v) = x.svd() + + // we have to make sure the singular values of the matrix are not close to zero + println("Singular values:\n$singValues") + + + // inverse Sigma matrix can be restored from singular values with diagonalEmbedding function + val sigma = diagonalEmbedding(singValues.map{ if (abs(it) < 1e-3) 0.0 else 1.0/it }) + + val alphaOLS = v dot sigma dot u.transpose() dot y + println("Estimated alpha:\n" + + "$alphaOLS") + + // figure out MSE of approximation + fun mse(yTrue: DoubleTensor, yPred: DoubleTensor): Double { + require(yTrue.shape.size == 1) + require(yTrue.shape contentEquals yPred.shape) + + val diff = yTrue - yPred + return diff.dot(diff).sqrt().value() + } + + println("MSE: ${mse(alpha, alphaOLS)}") + } +} \ No newline at end of file diff --git a/examples/src/main/kotlin/space/kscience/kmath/tensors/PCA.kt b/examples/src/main/kotlin/space/kscience/kmath/tensors/PCA.kt new file mode 100644 index 000000000..411e048d7 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/tensors/PCA.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.BroadcastDoubleTensorAlgebra + + +// simple PCA + +fun main(): Unit = BroadcastDoubleTensorAlgebra { // work in context with broadcast methods + val seed = 100500L + + // assume x is range from 0 until 10 + val x = fromArray( + intArrayOf(10), + (0 until 10).toList().map { it.toDouble() }.toDoubleArray() + ) + + // take y dependent on x with noise + val y = 2.0 * x + (3.0 + x.randomNormalLike(seed) * 1.5) + + println("x:\n$x") + println("y:\n$y") + + // stack them into single dataset + val dataset = stack(listOf(x, y)).transpose() + + // normalize both x and y + val xMean = x.mean() + val yMean = y.mean() + + val xStd = x.std() + val yStd = y.std() + + val xScaled = (x - xMean) / xStd + val yScaled = (y - yMean) / yStd + + // save means ans standard deviations for further recovery + val mean = fromArray( + intArrayOf(2), + doubleArrayOf(xMean, yMean) + ) + println("Means:\n$mean") + + val std = fromArray( + intArrayOf(2), + doubleArrayOf(xStd, yStd) + ) + println("Standard deviations:\n$std") + + // calculate the covariance matrix of scaled x and y + val covMatrix = cov(listOf(xScaled, yScaled)) + println("Covariance matrix:\n$covMatrix") + + // and find out eigenvector of it + val (_, evecs) = covMatrix.symEig() + val v = evecs[0] + println("Eigenvector:\n$v") + + // reduce dimension of dataset + val datasetReduced = v dot stack(listOf(xScaled, yScaled)) + println("Reduced data:\n$datasetReduced") + + // we can restore original data from reduced data. + // for example, find 7th element of dataset + val n = 7 + val restored = (datasetReduced[n] dot v.view(intArrayOf(1, 2))) * std + mean + println("Original value:\n${dataset[n]}") + println("Restored value:\n$restored") +} diff --git a/kmath-ast/README.md b/kmath-ast/README.md index eedba16fa..b0f2d59e5 100644 --- a/kmath-ast/README.md +++ b/kmath-ast/README.md @@ -1,6 +1,6 @@ # Module kmath-ast -Abstract syntax tree expression representation and related optimizations. +Performance and visualization extensions to MST API. - [expression-language](src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt) : Expression language and its parser - [mst-jvm-codegen](src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt) : Dynamic MST to JVM bytecode compiler @@ -10,30 +10,28 @@ Abstract syntax tree expression representation and related optimizations. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-ast:0.3.0-dev-7' + implementation 'space.kscience:kmath-ast:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-ast:0.3.0-dev-7") + implementation("space.kscience:kmath-ast:0.3.0-dev-11") } ``` @@ -41,21 +39,26 @@ dependencies { ### On JVM -`kmath-ast` JVM module supports runtime code generation to eliminate overhead of tree traversal. Code generator builds -a special implementation of `Expression` with implemented `invoke` function. +`kmath-ast` JVM module supports runtime code generation to eliminate overhead of tree traversal. Code generator builds a +special implementation of `Expression` with implemented `invoke` function. For example, the following builder: ```kotlin +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.* +import space.kscience.kmath.asm.* + MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField) ``` -… leads to generation of bytecode, which can be decompiled to the following Java class: +... leads to generation of bytecode, which can be decompiled to the following Java class: ```java package space.kscience.kmath.asm.generated; import java.util.Map; + import kotlin.jvm.functions.Function2; import space.kscience.kmath.asm.internal.MapIntrinsics; import space.kscience.kmath.expressions.Expression; @@ -65,7 +68,7 @@ public final class AsmCompiledExpression_45045_0 implements Expression { private final Object[] constants; public final Double invoke(Map arguments) { - return (Double)((Function2)this.constants[0]).invoke((Double)MapIntrinsics.getOrFail(arguments, "x"), 2); + return (Double) ((Function2) this.constants[0]).invoke((Double) MapIntrinsics.getOrFail(arguments, "x"), 2); } public AsmCompiledExpression_45045_0(Object[] constants) { @@ -77,8 +80,8 @@ public final class AsmCompiledExpression_45045_0 implements Expression { #### Known issues -- The same classes may be generated and loaded twice, so it is recommended to cache compiled expressions to avoid - class loading overhead. +- The same classes may be generated and loaded twice, so it is recommended to cache compiled expressions to avoid class + loading overhead. - This API is not supported by non-dynamic JVM implementations (like TeaVM and GraalVM) because of using class loaders. ### On JS @@ -86,6 +89,10 @@ public final class AsmCompiledExpression_45045_0 implements Expression { A similar feature is also available on JS. ```kotlin +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.* +import space.kscience.kmath.estree.* + MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField) ``` @@ -93,18 +100,22 @@ The code above returns expression implemented with such a JS function: ```js var executable = function (constants, arguments) { - return constants[1](constants[0](arguments, "x"), 2); + return constants[1](constants[0](arguments, "x"), 2); }; ``` +JS also supports very experimental expression optimization with [WebAssembly](https://webassembly.org/) IR generation. +Currently, only expressions inside `DoubleField` and `IntRing` are supported. ```kotlin +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.* import space.kscience.kmath.wasm.* MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField) ``` -An example of emitted WASM IR in the form of WAT: +An example of emitted Wasm IR in the form of WAT: ```lisp (func $executable (param $0 f64) (result f64) @@ -129,9 +140,11 @@ Example usage: ```kotlin import space.kscience.kmath.ast.* import space.kscience.kmath.ast.rendering.* +import space.kscience.kmath.misc.* +@OptIn(UnstableKMathAPI::class) public fun main() { - val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath() + val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(12)+x^(2/3)".parseMath() val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst) val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax) println("LaTeX:") @@ -145,13 +158,78 @@ public fun main() { Result LaTeX: -![](http://chart.googleapis.com/chart?cht=tx&chl=e%5E%7B%5Csqrt%7Bx%7D%7D-%5Cfrac%7B%5Cfrac%7B%5Coperatorname%7Bsin%7D%5E%7B-1%7D%5C,%5Cleft(2%5C,x%5Cright)%7D%7B2%5Ctimes10%5E%7B10%7D%2Bx%5E%7B3%7D%7D%7D%7B-12%7D) +![](https://latex.codecogs.com/gif.latex?%5Coperatorname{exp}%5C,%5Cleft(%5Csqrt{x}%5Cright)-%5Cfrac{%5Cfrac{%5Coperatorname{arcsin}%5C,%5Cleft(2%5C,x%5Cright)}{2%5Ctimes10^{10}%2Bx^{3}}}{12}+x^{2/3}) -Result MathML (embedding MathML is not allowed by GitHub Markdown): +Result MathML (can be used with MathJax or other renderers): + +
```html -ex-sin-12x2×1010+x3-12 + + + exp + + + + x + + + - + + + + + arcsin + + + 2 + + x + + + + 2 + × + + + 10 + + + 10 + + + + + + + x + + + 3 + + + + + + + 12 + + + + + + + x + + + 2 + / + 3 + + + + ``` +
+ It is also possible to create custom algorithms of render, and even add support of other markup languages (see API reference). diff --git a/kmath-ast/build.gradle.kts b/kmath-ast/build.gradle.kts index b4a0b28ac..508374d82 100644 --- a/kmath-ast/build.gradle.kts +++ b/kmath-ast/build.gradle.kts @@ -18,6 +18,10 @@ kotlin.js { } kotlin.sourceSets { + filter { it.name.contains("test", true) } + .map(org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet::languageSettings) + .forEach { it.useExperimentalAnnotation("space.kscience.kmath.misc.UnstableKMathAPI") } + commonMain { dependencies { api("com.github.h0tk3y.betterParse:better-parse:0.4.2") @@ -54,7 +58,7 @@ tasks.dokkaHtml { } readme { - maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE + maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) feature( diff --git a/kmath-ast/docs/README-TEMPLATE.md b/kmath-ast/docs/README-TEMPLATE.md index b38311ea1..80ea31642 100644 --- a/kmath-ast/docs/README-TEMPLATE.md +++ b/kmath-ast/docs/README-TEMPLATE.md @@ -1,6 +1,6 @@ # Module kmath-ast -Abstract syntax tree expression representation and related optimizations. +Performance and visualization extensions to MST API. ${features} @@ -10,21 +10,26 @@ ${artifact} ### On JVM -`kmath-ast` JVM module supports runtime code generation to eliminate overhead of tree traversal. Code generator builds -a special implementation of `Expression` with implemented `invoke` function. +`kmath-ast` JVM module supports runtime code generation to eliminate overhead of tree traversal. Code generator builds a +special implementation of `Expression` with implemented `invoke` function. For example, the following builder: ```kotlin +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.* +import space.kscience.kmath.asm.* + MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField) ``` -… leads to generation of bytecode, which can be decompiled to the following Java class: +... leads to generation of bytecode, which can be decompiled to the following Java class: ```java package space.kscience.kmath.asm.generated; import java.util.Map; + import kotlin.jvm.functions.Function2; import space.kscience.kmath.asm.internal.MapIntrinsics; import space.kscience.kmath.expressions.Expression; @@ -34,7 +39,7 @@ public final class AsmCompiledExpression_45045_0 implements Expression { private final Object[] constants; public final Double invoke(Map arguments) { - return (Double)((Function2)this.constants[0]).invoke((Double)MapIntrinsics.getOrFail(arguments, "x"), 2); + return (Double) ((Function2) this.constants[0]).invoke((Double) MapIntrinsics.getOrFail(arguments, "x"), 2); } public AsmCompiledExpression_45045_0(Object[] constants) { @@ -46,8 +51,8 @@ public final class AsmCompiledExpression_45045_0 implements Expression { #### Known issues -- The same classes may be generated and loaded twice, so it is recommended to cache compiled expressions to avoid - class loading overhead. +- The same classes may be generated and loaded twice, so it is recommended to cache compiled expressions to avoid class + loading overhead. - This API is not supported by non-dynamic JVM implementations (like TeaVM and GraalVM) because of using class loaders. ### On JS @@ -55,6 +60,10 @@ public final class AsmCompiledExpression_45045_0 implements Expression { A similar feature is also available on JS. ```kotlin +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.* +import space.kscience.kmath.estree.* + MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField) ``` @@ -62,18 +71,22 @@ The code above returns expression implemented with such a JS function: ```js var executable = function (constants, arguments) { - return constants[1](constants[0](arguments, "x"), 2); + return constants[1](constants[0](arguments, "x"), 2); }; ``` +JS also supports very experimental expression optimization with [WebAssembly](https://webassembly.org/) IR generation. +Currently, only expressions inside `DoubleField` and `IntRing` are supported. ```kotlin +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.* import space.kscience.kmath.wasm.* MstField { bindSymbol("x") + 2 }.compileToExpression(DoubleField) ``` -An example of emitted WASM IR in the form of WAT: +An example of emitted Wasm IR in the form of WAT: ```lisp (func \$executable (param \$0 f64) (result f64) @@ -98,9 +111,11 @@ Example usage: ```kotlin import space.kscience.kmath.ast.* import space.kscience.kmath.ast.rendering.* +import space.kscience.kmath.misc.* +@OptIn(UnstableKMathAPI::class) public fun main() { - val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath() + val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(12)+x^(2/3)".parseMath() val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst) val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax) println("LaTeX:") @@ -114,13 +129,78 @@ public fun main() { Result LaTeX: -![](http://chart.googleapis.com/chart?cht=tx&chl=e%5E%7B%5Csqrt%7Bx%7D%7D-%5Cfrac%7B%5Cfrac%7B%5Coperatorname%7Bsin%7D%5E%7B-1%7D%5C,%5Cleft(2%5C,x%5Cright)%7D%7B2%5Ctimes10%5E%7B10%7D%2Bx%5E%7B3%7D%7D%7D%7B-12%7D) +![](https://latex.codecogs.com/gif.latex?%5Coperatorname{exp}%5C,%5Cleft(%5Csqrt{x}%5Cright)-%5Cfrac{%5Cfrac{%5Coperatorname{arcsin}%5C,%5Cleft(2%5C,x%5Cright)}{2%5Ctimes10^{10}%2Bx^{3}}}{12}+x^{2/3}) -Result MathML (embedding MathML is not allowed by GitHub Markdown): +Result MathML (can be used with MathJax or other renderers): + +
```html -ex-sin-12x2×1010+x3-12 + + + exp + + + + x + + + - + + + + + arcsin + + + 2 + + x + + + + 2 + × + + + 10 + + + 10 + + + + + + + x + + + 3 + + + + + + + 12 + + + + + + + x + + + 2 + / + 3 + + + + ``` +
+ It is also possible to create custom algorithms of render, and even add support of other markup languages (see API reference). diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt index d2e92c37f..5201fec38 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt @@ -17,6 +17,7 @@ import com.github.h0tk3y.betterParse.lexer.regexToken import com.github.h0tk3y.betterParse.parser.ParseResult import com.github.h0tk3y.betterParse.parser.Parser import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.StringSymbol import space.kscience.kmath.operations.FieldOperations import space.kscience.kmath.operations.GroupOperations import space.kscience.kmath.operations.PowerOperations @@ -29,7 +30,6 @@ import space.kscience.kmath.operations.RingOperations * @author Iaroslav Postovalov */ public object ArithmeticsEvaluator : Grammar() { - // TODO replace with "...".toRegex() when better-parse 0.4.1 is released private val num: Token by regexToken("[\\d.]+(?:[eE][-+]?\\d+)?".toRegex()) private val id: Token by regexToken("[a-z_A-Z][\\da-z_A-Z]*".toRegex()) private val lpar: Token by literalToken("(") @@ -43,7 +43,7 @@ public object ArithmeticsEvaluator : Grammar() { private val ws: Token by regexToken("\\s+".toRegex(), ignore = true) private val number: Parser by num use { MST.Numeric(text.toDouble()) } - private val singular: Parser by id use { MST.Symbolic(text) } + private val singular: Parser by id use { StringSymbol(text) } private val unaryFunction: Parser by (id and -lpar and parser(ArithmeticsEvaluator::subSumChain) and -rpar) .map { (id, term) -> MST.Unary(id.text, term) } diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt index 1c82bd6e7..01717b0f9 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt @@ -5,6 +5,8 @@ package space.kscience.kmath.ast.rendering +import space.kscience.kmath.misc.UnstableKMathAPI + /** * [SyntaxRenderer] implementation for LaTeX. * @@ -23,6 +25,7 @@ package space.kscience.kmath.ast.rendering * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public object LatexSyntaxRenderer : SyntaxRenderer { public override fun render(node: MathSyntax, output: Appendable): Unit = output.run { fun render(syntax: MathSyntax) = render(syntax, output) @@ -115,7 +118,11 @@ public object LatexSyntaxRenderer : SyntaxRenderer { render(node.right) } - is FractionSyntax -> { + is FractionSyntax -> if (node.infix) { + render(node.left) + append('/') + render(node.right) + } else { append("\\frac{") render(node.left) append("}{") diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt index decd4ba46..cda8e2322 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt @@ -5,6 +5,8 @@ package space.kscience.kmath.ast.rendering +import space.kscience.kmath.misc.UnstableKMathAPI + /** * [SyntaxRenderer] implementation for MathML. * @@ -12,14 +14,18 @@ package space.kscience.kmath.ast.rendering * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public object MathMLSyntaxRenderer : SyntaxRenderer { public override fun render(node: MathSyntax, output: Appendable) { - output.append("") - render0(node, output) + output.append("") + renderPart(node, output) output.append("") } - private fun render0(node: MathSyntax, output: Appendable): Unit = output.run { + /** + * Renders a part of syntax returning a correct MathML tag not the whole MathML instance. + */ + public fun renderPart(node: MathSyntax, output: Appendable): Unit = output.run { fun tag(tagName: String, vararg attr: Pair, block: () -> Unit = {}) { append('<') append(tagName) @@ -44,7 +50,7 @@ public object MathMLSyntaxRenderer : SyntaxRenderer { append('>') } - fun render(syntax: MathSyntax) = render0(syntax, output) + fun render(syntax: MathSyntax) = renderPart(syntax, output) when (node) { is NumberSyntax -> tag("mn") { append(node.string) } @@ -127,14 +133,13 @@ public object MathMLSyntaxRenderer : SyntaxRenderer { render(node.right) } - is FractionSyntax -> tag("mfrac") { - tag("mrow") { - render(node.left) - } - - tag("mrow") { - render(node.right) - } + is FractionSyntax -> if (node.infix) { + render(node.left) + tag("mo") { append('/') } + render(node.right) + } else tag("mfrac") { + tag("mrow") { render(node.left) } + tag("mrow") { render(node.right) } } is RadicalWithIndexSyntax -> tag("mroot") { diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt index 9df2c54dd..c33f95483 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt @@ -6,12 +6,14 @@ package space.kscience.kmath.ast.rendering import space.kscience.kmath.expressions.MST +import space.kscience.kmath.misc.UnstableKMathAPI /** * Renders [MST] to [MathSyntax]. * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun interface MathRenderer { /** * Renders [MST] to [MathSyntax]. @@ -25,6 +27,7 @@ public fun interface MathRenderer { * @property features The applied features. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public open class FeaturedMathRenderer(public val features: List) : MathRenderer { public override fun render(mst: MST): MathSyntax { for (feature in features) feature.render(this, mst)?.let { return it } @@ -48,6 +51,7 @@ public open class FeaturedMathRenderer(public val features: List) * @property stages The applied stages. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public open class FeaturedMathRendererWithPostProcess( features: List, public val stages: List, @@ -85,6 +89,7 @@ public open class FeaturedMathRendererWithPostProcess( SquareRoot.Default, Exponent.Default, InverseTrigonometricOperations.Default, + InverseHyperbolicOperations.Default, // Fallback option for unknown operations - printing them as operator BinaryOperator.Default, @@ -101,6 +106,7 @@ public open class FeaturedMathRendererWithPostProcess( ), listOf( BetterExponent, + BetterFraction, SimplifyParentheses.Default, BetterMultiplication, ), diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt index 6a46bf535..a71985fbc 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt @@ -5,11 +5,14 @@ package space.kscience.kmath.ast.rendering +import space.kscience.kmath.misc.UnstableKMathAPI + /** * Mathematical typography syntax node. * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public sealed class MathSyntax { /** * The parent node of this syntax node. @@ -22,6 +25,7 @@ public sealed class MathSyntax { * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public sealed class TerminalSyntax : MathSyntax() /** @@ -29,6 +33,7 @@ public sealed class TerminalSyntax : MathSyntax() * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public sealed class OperationSyntax : MathSyntax() { /** * The operation token. @@ -41,6 +46,7 @@ public sealed class OperationSyntax : MathSyntax() { * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public sealed class UnarySyntax : OperationSyntax() { /** * The operand of this node. @@ -53,6 +59,7 @@ public sealed class UnarySyntax : OperationSyntax() { * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public sealed class BinarySyntax : OperationSyntax() { /** * The left-hand side operand. @@ -71,6 +78,7 @@ public sealed class BinarySyntax : OperationSyntax() { * @property string The digits of number. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class NumberSyntax(public var string: String) : TerminalSyntax() /** @@ -79,6 +87,7 @@ public data class NumberSyntax(public var string: String) : TerminalSyntax() * @property string The symbol. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class SymbolSyntax(public var string: String) : TerminalSyntax() /** @@ -89,14 +98,16 @@ public data class SymbolSyntax(public var string: String) : TerminalSyntax() * @see UnaryOperatorSyntax * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class OperatorNameSyntax(public var name: String) : TerminalSyntax() /** - * Represents a usage of special symbols. + * Represents a usage of special symbols (e.g., *∞*). * * @property kind The kind of symbol. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class SpecialSymbolSyntax(public var kind: Kind) : TerminalSyntax() { /** * The kind of symbol. @@ -121,6 +132,7 @@ public data class SpecialSymbolSyntax(public var kind: Kind) : TerminalSyntax() * @property parentheses Whether the operand should be wrapped with parentheses. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class OperandSyntax( public val operand: MathSyntax, public var parentheses: Boolean, @@ -131,11 +143,12 @@ public data class OperandSyntax( } /** - * Represents unary, prefix operator syntax (like f x). + * Represents unary, prefix operator syntax (like *f(x)*). * * @property prefix The prefix. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class UnaryOperatorSyntax( public override val operation: String, public var prefix: MathSyntax, @@ -147,10 +160,11 @@ public data class UnaryOperatorSyntax( } /** - * Represents prefix, unary plus operator. + * Represents prefix, unary plus operator (*+x*). * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class UnaryPlusSyntax( public override val operation: String, public override val operand: OperandSyntax, @@ -161,10 +175,11 @@ public data class UnaryPlusSyntax( } /** - * Represents prefix, unary minus operator. + * Represents prefix, unary minus operator (*-x*). * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class UnaryMinusSyntax( public override val operation: String, public override val operand: OperandSyntax, @@ -175,11 +190,12 @@ public data class UnaryMinusSyntax( } /** - * Represents radical with a node inside it. + * Represents radical with a node inside it (*√x*). * * @property operand The radicand. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class RadicalSyntax( public override val operation: String, public override val operand: MathSyntax, @@ -197,6 +213,7 @@ public data class RadicalSyntax( * (*ex*). * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class ExponentSyntax( public override val operation: String, public override val operand: OperandSyntax, @@ -208,12 +225,13 @@ public data class ExponentSyntax( } /** - * Represents a syntax node with superscript (usually, for exponentiation). + * Represents a syntax node with superscript (*x2*). * * @property left The node. * @property right The superscript. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class SuperscriptSyntax( public override val operation: String, public override val left: MathSyntax, @@ -226,12 +244,13 @@ public data class SuperscriptSyntax( } /** - * Represents a syntax node with subscript. + * Represents a syntax node with subscript (*xi*). * * @property left The node. * @property right The subscript. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class SubscriptSyntax( public override val operation: String, public override val left: MathSyntax, @@ -244,11 +263,12 @@ public data class SubscriptSyntax( } /** - * Represents binary, prefix operator syntax (like f(a, b)). + * Represents binary, prefix operator syntax (like *f(a, b)*). * * @property prefix The prefix. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class BinaryOperatorSyntax( public override val operation: String, public var prefix: MathSyntax, @@ -262,12 +282,13 @@ public data class BinaryOperatorSyntax( } /** - * Represents binary, infix addition. + * Represents binary, infix addition (*42 + 42*). * * @param left The augend. * @param right The addend. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class BinaryPlusSyntax( public override val operation: String, public override val left: OperandSyntax, @@ -280,12 +301,13 @@ public data class BinaryPlusSyntax( } /** - * Represents binary, infix subtraction. + * Represents binary, infix subtraction (*42 - 42*). * * @param left The minuend. * @param right The subtrahend. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class BinaryMinusSyntax( public override val operation: String, public override val left: OperandSyntax, @@ -302,12 +324,15 @@ public data class BinaryMinusSyntax( * * @property left The numerator. * @property right The denominator. + * @property infix Whether infix (*1 / 2*) or normal (*½*) fraction should be made. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class FractionSyntax( public override val operation: String, - public override val left: MathSyntax, - public override val right: MathSyntax, + public override val left: OperandSyntax, + public override val right: OperandSyntax, + public var infix: Boolean, ) : BinarySyntax() { init { left.parent = this @@ -316,12 +341,13 @@ public data class FractionSyntax( } /** - * Represents radical syntax with index. + * Represents radical syntax with index (*3√x*). * * @property left The index. * @property right The radicand. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class RadicalWithIndexSyntax( public override val operation: String, public override val left: MathSyntax, @@ -334,13 +360,14 @@ public data class RadicalWithIndexSyntax( } /** - * Represents binary, infix multiplication in the form of coefficient (2 x) or with operator (x×2). + * Represents binary, infix multiplication in the form of coefficient (*2 x*) or with operator (*x × 2*). * * @property left The multiplicand. * @property right The multiplier. - * @property times whether the times (×) symbol should be used. + * @property times Whether the times (×) symbol should be used. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public data class MultiplicationSyntax( public override val operation: String, public override val left: OperandSyntax, diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt index 7fa91e158..fb2b3b66f 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt @@ -5,12 +5,15 @@ package space.kscience.kmath.ast.rendering +import space.kscience.kmath.misc.UnstableKMathAPI + /** * Abstraction of writing [MathSyntax] as a string of an actual markup language. Typical implementation should * involve traversal of MathSyntax with handling each its subtype. * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun interface SyntaxRenderer { /** * Renders the [MathSyntax] to [output]. @@ -23,6 +26,7 @@ public fun interface SyntaxRenderer { * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun SyntaxRenderer.renderWithStringBuilder(node: MathSyntax): String { val sb = StringBuilder() render(node, sb) diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt index c09282bb6..863825799 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt @@ -7,6 +7,8 @@ package space.kscience.kmath.ast.rendering import space.kscience.kmath.ast.rendering.FeaturedMathRenderer.RenderFeature import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.* import kotlin.reflect.KClass @@ -15,11 +17,11 @@ import kotlin.reflect.KClass * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public object PrintSymbolic : RenderFeature { - public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { - if (node !is MST.Symbolic) return null - return SymbolSyntax(string = node.value) - } + public override fun render(renderer: FeaturedMathRenderer, node: MST): SymbolSyntax? = + if (node !is Symbol) null + else SymbolSyntax(string = node.identity) } /** @@ -27,35 +29,38 @@ public object PrintSymbolic : RenderFeature { * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public object PrintNumeric : RenderFeature { - public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { - if (node !is MST.Numeric) return null - return NumberSyntax(string = node.value.toString()) - } + public override fun render(renderer: FeaturedMathRenderer, node: MST): NumberSyntax? = if (node !is MST.Numeric) + null + else + NumberSyntax(string = node.value.toString()) } -private fun printSignedNumberString(s: String): MathSyntax { - if (s.startsWith('-')) - return UnaryMinusSyntax( - operation = GroupOperations.MINUS_OPERATION, - operand = OperandSyntax( - operand = NumberSyntax(string = s.removePrefix("-")), - parentheses = true, - ), - ) - - return NumberSyntax(string = s) -} +@UnstableKMathAPI +private fun printSignedNumberString(s: String): MathSyntax = if (s.startsWith('-')) + UnaryMinusSyntax( + operation = GroupOperations.MINUS_OPERATION, + operand = OperandSyntax( + operand = NumberSyntax(string = s.removePrefix("-")), + parentheses = true, + ), + ) +else + NumberSyntax(string = s) /** * Special printing for numeric types which are printed in form of * *('-'? (DIGIT+ ('.' DIGIT+)? ('E' '-'? DIGIT+)? | 'Infinity')) | 'NaN'*. * * @property types The suitable types. + * @author Iaroslav Postovalov */ +@UnstableKMathAPI public class PrettyPrintFloats(public val types: Set>) : RenderFeature { public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { if (node !is MST.Numeric || node.value::class !in types) return null + val toString = when (val v = node.value) { is Float -> v.multiplatformToString() is Double -> v.multiplatformToString() @@ -109,12 +114,15 @@ public class PrettyPrintFloats(public val types: Set>) : Rend * Special printing for numeric types which are printed in form of *'-'? DIGIT+*. * * @property types The suitable types. + * @author Iaroslav Postovalov */ +@UnstableKMathAPI public class PrettyPrintIntegers(public val types: Set>) : RenderFeature { - public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { - if (node !is MST.Numeric || node.value::class !in types) return null - return printSignedNumberString(node.value.toString()) - } + public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? = + if (node !is MST.Numeric || node.value::class !in types) + null + else + printSignedNumberString(node.value.toString()) public companion object { /** @@ -129,12 +137,15 @@ public class PrettyPrintIntegers(public val types: Set>) : Re * Special printing for symbols meaning Pi. * * @property symbols The allowed symbols. + * @author Iaroslav Postovalov */ +@UnstableKMathAPI public class PrettyPrintPi(public val symbols: Set) : RenderFeature { - public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { - if (node !is MST.Symbolic || node.value !in symbols) return null - return SpecialSymbolSyntax(kind = SpecialSymbolSyntax.Kind.SMALL_PI) - } + public override fun render(renderer: FeaturedMathRenderer, node: MST): SpecialSymbolSyntax? = + if (node !is Symbol || node.identity !in symbols) + null + else + SpecialSymbolSyntax(kind = SpecialSymbolSyntax.Kind.SMALL_PI) public companion object { /** @@ -149,17 +160,20 @@ public class PrettyPrintPi(public val symbols: Set) : RenderFeature { * not [MST.Unary]. * * @param operations the allowed operations. If `null`, any operation is accepted. + * @author Iaroslav Postovalov */ +@UnstableKMathAPI public abstract class Unary(public val operations: Collection?) : RenderFeature { /** - * The actual render function. + * The actual render function specialized for [MST.Unary]. */ - protected abstract fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax? + protected abstract fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax? - public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { - if (node !is MST.Unary || operations != null && node.operation !in operations) return null - return render0(renderer, node) - } + public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? = + if (node !is MST.Unary || operations != null && node.operation !in operations) + null + else + renderUnary(renderer, node) } /** @@ -167,169 +181,301 @@ public abstract class Unary(public val operations: Collection?) : Render * not [MST.Binary]. * * @property operations the allowed operations. If `null`, any operation is accepted. + * @author Iaroslav Postovalov */ +@UnstableKMathAPI public abstract class Binary(public val operations: Collection?) : RenderFeature { /** - * The actual render function. + * The actual render function specialized for [MST.Binary]. */ - protected abstract fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax? + protected abstract fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax? public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? { if (node !is MST.Binary || operations != null && node.operation !in operations) return null - return render0(renderer, node) + return renderBinary(renderer, node) } } +/** + * Handles binary nodes by producing [BinaryPlusSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class BinaryPlus(operations: Collection?) : Binary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryPlusSyntax( - operation = node.operation, - left = OperandSyntax(parent.render(node.left), true), - right = OperandSyntax(parent.render(node.right), true), - ) + public override fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): BinaryPlusSyntax = + BinaryPlusSyntax( + operation = node.operation, + left = OperandSyntax(parent.render(node.left), true), + right = OperandSyntax(parent.render(node.right), true), + ) public companion object { + /** + * The default instance configured with [GroupOperations.PLUS_OPERATION]. + */ public val Default: BinaryPlus = BinaryPlus(setOf(GroupOperations.PLUS_OPERATION)) } } +/** + * Handles binary nodes by producing [BinaryMinusSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class BinaryMinus(operations: Collection?) : Binary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryMinusSyntax( - operation = node.operation, - left = OperandSyntax(operand = parent.render(node.left), parentheses = true), - right = OperandSyntax(operand = parent.render(node.right), parentheses = true), - ) + public override fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): BinaryMinusSyntax = + BinaryMinusSyntax( + operation = node.operation, + left = OperandSyntax(operand = parent.render(node.left), parentheses = true), + right = OperandSyntax(operand = parent.render(node.right), parentheses = true), + ) public companion object { + /** + * The default instance configured with [GroupOperations.MINUS_OPERATION]. + */ public val Default: BinaryMinus = BinaryMinus(setOf(GroupOperations.MINUS_OPERATION)) } } +/** + * Handles unary nodes by producing [UnaryPlusSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class UnaryPlus(operations: Collection?) : Unary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryPlusSyntax( + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): UnaryPlusSyntax = UnaryPlusSyntax( operation = node.operation, operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), ) public companion object { + /** + * The default instance configured with [GroupOperations.PLUS_OPERATION]. + */ public val Default: UnaryPlus = UnaryPlus(setOf(GroupOperations.PLUS_OPERATION)) } } +/** + * Handles binary nodes by producing [UnaryMinusSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class UnaryMinus(operations: Collection?) : Unary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryMinusSyntax( + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): UnaryMinusSyntax = UnaryMinusSyntax( operation = node.operation, operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), ) public companion object { + /** + * The default instance configured with [GroupOperations.MINUS_OPERATION]. + */ public val Default: UnaryMinus = UnaryMinus(setOf(GroupOperations.MINUS_OPERATION)) } } +/** + * Handles binary nodes by producing [FractionSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class Fraction(operations: Collection?) : Binary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = FractionSyntax( + public override fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): FractionSyntax = FractionSyntax( operation = node.operation, - left = parent.render(node.left), - right = parent.render(node.right), + left = OperandSyntax(operand = parent.render(node.left), parentheses = true), + right = OperandSyntax(operand = parent.render(node.right), parentheses = true), + infix = true, ) public companion object { + /** + * The default instance configured with [FieldOperations.DIV_OPERATION]. + */ public val Default: Fraction = Fraction(setOf(FieldOperations.DIV_OPERATION)) } } +/** + * Handles binary nodes by producing [BinaryOperatorSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class BinaryOperator(operations: Collection?) : Binary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryOperatorSyntax( - operation = node.operation, - prefix = OperatorNameSyntax(name = node.operation), - left = parent.render(node.left), - right = parent.render(node.right), - ) + public override fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): BinaryOperatorSyntax = + BinaryOperatorSyntax( + operation = node.operation, + prefix = OperatorNameSyntax(name = node.operation), + left = parent.render(node.left), + right = parent.render(node.right), + ) public companion object { + /** + * The default instance configured with `null`. + */ public val Default: BinaryOperator = BinaryOperator(null) } } +/** + * Handles unary nodes by producing [UnaryOperatorSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class UnaryOperator(operations: Collection?) : Unary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryOperatorSyntax( - operation = node.operation, - prefix = OperatorNameSyntax(node.operation), - operand = OperandSyntax(parent.render(node.value), true), - ) + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): UnaryOperatorSyntax = + UnaryOperatorSyntax( + operation = node.operation, + prefix = OperatorNameSyntax(node.operation), + operand = OperandSyntax(parent.render(node.value), true), + ) public companion object { + /** + * The default instance configured with `null`. + */ public val Default: UnaryOperator = UnaryOperator(null) } } +/** + * Handles binary nodes by producing [SuperscriptSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class Power(operations: Collection?) : Binary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = SuperscriptSyntax( - operation = node.operation, - left = OperandSyntax(parent.render(node.left), true), - right = OperandSyntax(parent.render(node.right), true), - ) + public override fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): SuperscriptSyntax = + SuperscriptSyntax( + operation = node.operation, + left = OperandSyntax(parent.render(node.left), true), + right = OperandSyntax(parent.render(node.right), true), + ) public companion object { + /** + * The default instance configured with [PowerOperations.POW_OPERATION]. + */ public val Default: Power = Power(setOf(PowerOperations.POW_OPERATION)) } } +/** + * Handles binary nodes by producing [RadicalSyntax] with no index. + */ +@UnstableKMathAPI public class SquareRoot(operations: Collection?) : Unary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): RadicalSyntax = RadicalSyntax(operation = node.operation, operand = parent.render(node.value)) public companion object { + /** + * The default instance configured with [PowerOperations.SQRT_OPERATION]. + */ public val Default: SquareRoot = SquareRoot(setOf(PowerOperations.SQRT_OPERATION)) } } +/** + * Handles unary nodes by producing [ExponentSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class Exponent(operations: Collection?) : Unary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = ExponentSyntax( + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): ExponentSyntax = ExponentSyntax( operation = node.operation, operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), useOperatorForm = true, ) public companion object { + /** + * The default instance configured with [ExponentialOperations.EXP_OPERATION]. + */ public val Default: Exponent = Exponent(setOf(ExponentialOperations.EXP_OPERATION)) } } +/** + * Handles binary nodes by producing [MultiplicationSyntax]. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class Multiplication(operations: Collection?) : Binary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = MultiplicationSyntax( - operation = node.operation, - left = OperandSyntax(operand = parent.render(node.left), parentheses = true), - right = OperandSyntax(operand = parent.render(node.right), parentheses = true), - times = true, - ) + public override fun renderBinary(parent: FeaturedMathRenderer, node: MST.Binary): MultiplicationSyntax = + MultiplicationSyntax( + operation = node.operation, + left = OperandSyntax(operand = parent.render(node.left), parentheses = true), + right = OperandSyntax(operand = parent.render(node.right), parentheses = true), + times = true, + ) public companion object { - public val Default: Multiplication = Multiplication(setOf( - RingOperations.TIMES_OPERATION, - )) + /** + * The default instance configured with [RingOperations.TIMES_OPERATION]. + */ + public val Default: Multiplication = Multiplication(setOf(RingOperations.TIMES_OPERATION)) } } +/** + * Handles binary nodes by producing inverse [UnaryOperatorSyntax] with *arc* prefix instead of *a*. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI public class InverseTrigonometricOperations(operations: Collection?) : Unary(operations) { - public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryOperatorSyntax( - operation = node.operation, - prefix = SuperscriptSyntax( - operation = PowerOperations.POW_OPERATION, - left = OperatorNameSyntax(name = node.operation.removePrefix("a")), - right = UnaryMinusSyntax( - operation = GroupOperations.MINUS_OPERATION, - operand = OperandSyntax(operand = NumberSyntax(string = "1"), parentheses = true), - ), - ), - operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), - ) + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): UnaryOperatorSyntax = + UnaryOperatorSyntax( + operation = node.operation, + prefix = OperatorNameSyntax(name = node.operation.replaceFirst("a", "arc")), + operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), + ) public companion object { + /** + * The default instance configured with [TrigonometricOperations.ACOS_OPERATION], + * [TrigonometricOperations.ASIN_OPERATION], [TrigonometricOperations.ATAN_OPERATION]. + */ public val Default: InverseTrigonometricOperations = InverseTrigonometricOperations(setOf( TrigonometricOperations.ACOS_OPERATION, TrigonometricOperations.ASIN_OPERATION, TrigonometricOperations.ATAN_OPERATION, + )) + } +} + +/** + * Handles binary nodes by producing inverse [UnaryOperatorSyntax] with *ar* prefix instead of *a*. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI +public class InverseHyperbolicOperations(operations: Collection?) : Unary(operations) { + public override fun renderUnary(parent: FeaturedMathRenderer, node: MST.Unary): UnaryOperatorSyntax = + UnaryOperatorSyntax( + operation = node.operation, + prefix = OperatorNameSyntax(name = node.operation.replaceFirst("a", "ar")), + operand = OperandSyntax(operand = parent.render(node.value), parentheses = true), + ) + + public companion object { + /** + * The default instance configured with [ExponentialOperations.ACOSH_OPERATION], + * [ExponentialOperations.ASINH_OPERATION], and [ExponentialOperations.ATANH_OPERATION]. + */ + public val Default: InverseHyperbolicOperations = InverseHyperbolicOperations(setOf( ExponentialOperations.ACOSH_OPERATION, ExponentialOperations.ASINH_OPERATION, ExponentialOperations.ATANH_OPERATION, diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt index a08f089f1..1f31af853 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.ast.rendering +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.FieldOperations import space.kscience.kmath.operations.GroupOperations import space.kscience.kmath.operations.PowerOperations @@ -15,6 +16,7 @@ import space.kscience.kmath.operations.RingOperations * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostProcessStage { public override fun perform(node: MathSyntax): Unit = when (node) { is NumberSyntax -> Unit @@ -81,6 +83,75 @@ public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostPro } } +/** + * Chooses [FractionSyntax.infix] depending on the context. + * + * @author Iaroslav Postovalov + */ +@UnstableKMathAPI +public object BetterFraction : FeaturedMathRendererWithPostProcess.PostProcessStage { + private fun perform0(node: MathSyntax, infix: Boolean = false): Unit = when (node) { + is NumberSyntax -> Unit + is SymbolSyntax -> Unit + is OperatorNameSyntax -> Unit + is SpecialSymbolSyntax -> Unit + is OperandSyntax -> perform0(node.operand, infix) + + is UnaryOperatorSyntax -> { + perform0(node.prefix, infix) + perform0(node.operand, infix) + } + + is UnaryPlusSyntax -> perform0(node.operand, infix) + is UnaryMinusSyntax -> perform0(node.operand, infix) + is RadicalSyntax -> perform0(node.operand, infix) + is ExponentSyntax -> perform0(node.operand, infix) + + is SuperscriptSyntax -> { + perform0(node.left, true) + perform0(node.right, true) + } + + is SubscriptSyntax -> { + perform0(node.left, true) + perform0(node.right, true) + } + + is BinaryOperatorSyntax -> { + perform0(node.prefix, infix) + perform0(node.left, infix) + perform0(node.right, infix) + } + + is BinaryPlusSyntax -> { + perform0(node.left, infix) + perform0(node.right, infix) + } + + is BinaryMinusSyntax -> { + perform0(node.left, infix) + perform0(node.right, infix) + } + + is FractionSyntax -> { + node.infix = infix + perform0(node.left, infix) + perform0(node.right, infix) + } + + is RadicalWithIndexSyntax -> { + perform0(node.left, true) + perform0(node.right, true) + } + + is MultiplicationSyntax -> { + perform0(node.left, infix) + perform0(node.right, infix) + } + } + + public override fun perform(node: MathSyntax): Unit = perform0(node) +} /** * Applies [ExponentSyntax.useOperatorForm] to [ExponentSyntax] when the operand contains a fraction, a @@ -88,6 +159,7 @@ public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostPro * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public object BetterExponent : FeaturedMathRendererWithPostProcess.PostProcessStage { private fun perform0(node: MathSyntax): Boolean { return when (node) { @@ -99,7 +171,7 @@ public object BetterExponent : FeaturedMathRendererWithPostProcess.PostProcessSt is UnaryOperatorSyntax -> perform0(node.prefix) || perform0(node.operand) is UnaryPlusSyntax -> perform0(node.operand) is UnaryMinusSyntax -> perform0(node.operand) - is RadicalSyntax -> perform0(node.operand) + is RadicalSyntax -> true is ExponentSyntax -> { val r = perform0(node.operand) @@ -113,7 +185,7 @@ public object BetterExponent : FeaturedMathRendererWithPostProcess.PostProcessSt is BinaryPlusSyntax -> perform0(node.left) || perform0(node.right) is BinaryMinusSyntax -> perform0(node.left) || perform0(node.right) is FractionSyntax -> true - is RadicalWithIndexSyntax -> perform0(node.left) || perform0(node.right) + is RadicalWithIndexSyntax -> true is MultiplicationSyntax -> perform0(node.left) || perform0(node.right) } } @@ -129,6 +201,7 @@ public object BetterExponent : FeaturedMathRendererWithPostProcess.PostProcessSt * @property precedenceFunction Returns the precedence number for syntax node. Higher number is lower priority. * @author Iaroslav Postovalov */ +@UnstableKMathAPI public class SimplifyParentheses(public val precedenceFunction: (MathSyntax) -> Int) : FeaturedMathRendererWithPostProcess.PostProcessStage { public override fun perform(node: MathSyntax): Unit = when (node) { @@ -159,8 +232,11 @@ public class SimplifyParentheses(public val precedenceFunction: (MathSyntax) -> val isInsideExpOperator = node.parent is ExponentSyntax && (node.parent as ExponentSyntax).useOperatorForm + val isOnOrUnderNormalFraction = node.parent is FractionSyntax && !((node.parent as FractionSyntax).infix) + node.parentheses = !isRightOfSuperscript && (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax || isInsideExpOperator) + && !isOnOrUnderNormalFraction perform(node.operand) } diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmConsistencyWithInterpreter.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerConsistencyWithInterpreter.kt similarity index 76% rename from kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmConsistencyWithInterpreter.kt rename to kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerConsistencyWithInterpreter.kt index f3e0726d6..6209661b3 100644 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmConsistencyWithInterpreter.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerConsistencyWithInterpreter.kt @@ -3,12 +3,12 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package space.kscience.kmath.wasm +package space.kscience.kmath.ast import space.kscience.kmath.expressions.MstField import space.kscience.kmath.expressions.MstRing +import space.kscience.kmath.expressions.Symbol.Companion.x import space.kscience.kmath.expressions.interpret -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.IntRing import space.kscience.kmath.operations.bindSymbol @@ -16,45 +16,41 @@ import space.kscience.kmath.operations.invoke import kotlin.test.Test import kotlin.test.assertEquals -internal class TestWasmConsistencyWithInterpreter { +internal class TestCompilerConsistencyWithInterpreter { @Test - fun intRing() { + fun intRing() = runCompilerTest { val mst = MstRing { binaryOperationFunction("+")( unaryOperationFunction("+")( (bindSymbol(x) - (2.toByte() + (scale( add(number(1), number(1)), - 2.0 + 2.0, ) + 1.toByte()))) * 3.0 - 1.toByte() ), - number(1) + number(1), ) * number(2) } assertEquals( mst.interpret(IntRing, x to 3), - mst.compile(IntRing, x to 3) + mst.compile(IntRing, x to 3), ) } @Test - fun doubleField() { + fun doubleField() = runCompilerTest { val mst = MstField { +(3 - 2 + 2 * number(1) + 1.0) + binaryOperationFunction("+")( (3.0 - (bindSymbol(x) + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0 + number(1), - number(1) / 2 + number(2.0) * one + number(1) / 2 + number(2.0) * one, ) + zero } assertEquals( mst.interpret(DoubleField, x to 2.0), - mst.compile(DoubleField, x to 2.0) + mst.compile(DoubleField, x to 2.0), ) } - - private companion object { - private val x by symbol - } } diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerOperations.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerOperations.kt new file mode 100644 index 000000000..073a03f14 --- /dev/null +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerOperations.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ast + +import space.kscience.kmath.expressions.MstExtendedField +import space.kscience.kmath.expressions.Symbol.Companion.x +import space.kscience.kmath.expressions.invoke +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.bindSymbol +import space.kscience.kmath.operations.invoke +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class TestCompilerOperations { + @Test + fun testUnaryPlus() = runCompilerTest { + val expr = MstExtendedField { +x }.compileToExpression(DoubleField) + assertEquals(2.0, expr(x to 2.0)) + } + + @Test + fun testUnaryMinus() = runCompilerTest { + val expr = MstExtendedField { -x }.compileToExpression(DoubleField) + assertEquals(-2.0, expr(x to 2.0)) + } + + @Test + fun testAdd() = runCompilerTest { + val expr = MstExtendedField { x + x }.compileToExpression(DoubleField) + assertEquals(4.0, expr(x to 2.0)) + } + + @Test + fun testSine() = runCompilerTest { + val expr = MstExtendedField { sin(x) }.compileToExpression(DoubleField) + assertEquals(0.0, expr(x to 0.0)) + } + + @Test + fun testCosine() = runCompilerTest { + val expr = MstExtendedField { cos(x) }.compileToExpression(DoubleField) + assertEquals(1.0, expr(x to 0.0)) + } + + @Test + fun testSubtract() = runCompilerTest { + val expr = MstExtendedField { bindSymbol(x) - bindSymbol(x) }.compileToExpression(DoubleField) + assertEquals(0.0, expr(x to 2.0)) + } + + @Test + fun testDivide() = runCompilerTest { + val expr = MstExtendedField { bindSymbol(x) / bindSymbol(x) }.compileToExpression(DoubleField) + assertEquals(1.0, expr(x to 2.0)) + } + + @Test + fun testPower() = runCompilerTest { + val expr = MstExtendedField { bindSymbol(x) pow 2 }.compileToExpression(DoubleField) + assertEquals(4.0, expr(x to 2.0)) + } +} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmVariables.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerVariables.kt similarity index 76% rename from kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmVariables.kt rename to kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerVariables.kt index 406ba8c8d..dcc15b311 100644 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmVariables.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestCompilerVariables.kt @@ -3,11 +3,11 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package space.kscience.kmath.wasm +package space.kscience.kmath.ast import space.kscience.kmath.expressions.MstRing +import space.kscience.kmath.expressions.Symbol.Companion.x import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.IntRing import space.kscience.kmath.operations.bindSymbol import space.kscience.kmath.operations.invoke @@ -15,20 +15,16 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith -internal class TestWasmVariables { +internal class TestCompilerVariables { @Test - fun testVariable() { + fun testVariable() = runCompilerTest { val expr = MstRing { bindSymbol(x) }.compileToExpression(IntRing) assertEquals(1, expr(x to 1)) } @Test - fun testUndefinedVariableFails() { + fun testUndefinedVariableFails() = runCompilerTest { val expr = MstRing { bindSymbol(x) }.compileToExpression(IntRing) assertFailsWith { expr() } } - - private companion object { - private val x by symbol - } } diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/ParserTest.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestParser.kt similarity index 98% rename from kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/ParserTest.kt rename to kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestParser.kt index 185659a1f..b838245e1 100644 --- a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/ParserTest.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestParser.kt @@ -13,7 +13,7 @@ import space.kscience.kmath.operations.DoubleField import kotlin.test.Test import kotlin.test.assertEquals -internal class ParserTest { +internal class TestParser { @Test fun evaluateParsedMst() { val mst = "2+2*(2+2)".parseMath() diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/ParserPrecedenceTest.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestParserPrecedence.kt similarity index 93% rename from kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/ParserPrecedenceTest.kt rename to kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestParserPrecedence.kt index ca3a95bc8..bb6bb3ce1 100644 --- a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/ParserPrecedenceTest.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/TestParserPrecedence.kt @@ -5,13 +5,12 @@ package space.kscience.kmath.ast -import space.kscience.kmath.ast.parseMath import space.kscience.kmath.expressions.evaluate import space.kscience.kmath.operations.DoubleField import kotlin.test.Test import kotlin.test.assertEquals -internal class ParserPrecedenceTest { +internal class TestParserPrecedence { @Test fun test1(): Unit = assertEquals(6.0, f.evaluate("2*2+2".parseMath())) diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt index 1ab20ed85..a40c785b9 100644 --- a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt @@ -99,13 +99,17 @@ internal class TestFeatures { fun multiplication() = testLatex("x*1", "x\\times1") @Test - fun inverseTrigonometry() { - testLatex("asin(x)", "\\operatorname{sin}^{-1}\\,\\left(x\\right)") - testLatex("asinh(x)", "\\operatorname{sinh}^{-1}\\,\\left(x\\right)") - testLatex("acos(x)", "\\operatorname{cos}^{-1}\\,\\left(x\\right)") - testLatex("acosh(x)", "\\operatorname{cosh}^{-1}\\,\\left(x\\right)") - testLatex("atan(x)", "\\operatorname{tan}^{-1}\\,\\left(x\\right)") - testLatex("atanh(x)", "\\operatorname{tanh}^{-1}\\,\\left(x\\right)") + fun inverseTrigonometric() { + testLatex("asin(x)", "\\operatorname{arcsin}\\,\\left(x\\right)") + testLatex("acos(x)", "\\operatorname{arccos}\\,\\left(x\\right)") + testLatex("atan(x)", "\\operatorname{arctan}\\,\\left(x\\right)") + } + + @Test + fun inverseHyperbolic() { + testLatex("asinh(x)", "\\operatorname{arsinh}\\,\\left(x\\right)") + testLatex("acosh(x)", "\\operatorname{arcosh}\\,\\left(x\\right)") + testLatex("atanh(x)", "\\operatorname{artanh}\\,\\left(x\\right)") } // @Test diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt index 599e43eb2..09ec127c7 100644 --- a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt @@ -37,4 +37,10 @@ internal class TestStages { testLatex("exp(x/2)", "\\operatorname{exp}\\,\\left(\\frac{x}{2}\\right)") testLatex("exp(x^2)", "\\operatorname{exp}\\,\\left(x^{2}\\right)") } + + @Test + fun fraction() { + testLatex("x/y", "\\frac{x}{y}") + testLatex("x^(x/y)", "x^{x/y}") + } } diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt index 7c9400532..bf87b6fd0 100644 --- a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt @@ -30,17 +30,17 @@ internal object TestUtils { ) internal fun testMathML(mst: MST, expectedMathML: String) = assertEquals( - expected = "$expectedMathML", + expected = "$expectedMathML", actual = mathML(mst), ) internal fun testMathML(expression: String, expectedMathML: String) = assertEquals( - expected = "$expectedMathML", + expected = "$expectedMathML", actual = mathML(expression.parseMath()), ) internal fun testMathML(expression: MathSyntax, expectedMathML: String) = assertEquals( - expected = "$expectedMathML", + expected = "$expectedMathML", actual = MathMLSyntaxRenderer.renderWithStringBuilder(expression), ) } diff --git a/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/utils.kt b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/utils.kt new file mode 100644 index 000000000..ec7436188 --- /dev/null +++ b/kmath-ast/src/commonTest/kotlin/space/kscience/kmath/ast/utils.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ast + +import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.IntRing + +internal interface CompilerTestContext { + fun MST.compileToExpression(algebra: IntRing): Expression + fun MST.compile(algebra: IntRing, arguments: Map): Int + fun MST.compile(algebra: IntRing, vararg arguments: Pair): Int = compile(algebra, mapOf(*arguments)) + fun MST.compileToExpression(algebra: DoubleField): Expression + fun MST.compile(algebra: DoubleField, arguments: Map): Double + + fun MST.compile(algebra: DoubleField, vararg arguments: Pair): Double = + compile(algebra, mapOf(*arguments)) +} + +internal expect inline fun runCompilerTest(action: CompilerTestContext.() -> Unit) diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt index 40468f5ab..0c15e994c 100644 --- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt +++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt @@ -9,22 +9,23 @@ import space.kscience.kmath.estree.internal.ESTreeBuilder import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.MST import space.kscience.kmath.expressions.MST.* +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.expressions.invoke import space.kscience.kmath.internal.estree.BaseExpression -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.operations.Algebra import space.kscience.kmath.operations.NumericAlgebra +import space.kscience.kmath.operations.bindSymbolOrNull @PublishedApi internal fun MST.compileWith(algebra: Algebra): Expression { fun ESTreeBuilder.visit(node: MST): BaseExpression = when (node) { - is Symbolic -> { - val symbol = algebra.bindSymbolOrNull(node.value) + is Symbol -> { + val symbol = algebra.bindSymbolOrNull(node) if (symbol != null) constant(symbol) else - variable(node.value) + variable(node.identity) } is Numeric -> constant(node.value) diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/internal/ESTreeBuilder.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/internal/ESTreeBuilder.kt index ac20484a4..4907d8225 100644 --- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/internal/ESTreeBuilder.kt +++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/internal/ESTreeBuilder.kt @@ -6,14 +6,9 @@ package space.kscience.kmath.estree.internal import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.internal.astring.generate import space.kscience.kmath.internal.estree.* -import space.kscience.kmath.internal.estree.BaseExpression -import space.kscience.kmath.internal.estree.BlockStatement -import space.kscience.kmath.internal.estree.Program -import space.kscience.kmath.internal.estree.VariableDeclaration -import space.kscience.kmath.internal.estree.VariableDeclarator -import space.kscience.kmath.misc.Symbol internal class ESTreeBuilder(val bodyCallback: ESTreeBuilder.() -> BaseExpression) { private class GeneratedExpression(val executable: dynamic, val constants: Array) : Expression { diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/internal/webassembly/nonDeclarations.WebAssembly.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/internal/webassembly/nonDeclarations.WebAssembly.kt index 59733299a..c5023c384 100644 --- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/internal/webassembly/nonDeclarations.WebAssembly.kt +++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/internal/webassembly/nonDeclarations.WebAssembly.kt @@ -3,7 +3,8 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -@file:Suppress("INTERFACE_WITH_SUPERCLASS", +@file:Suppress( + "INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/internal/WasmBuilder.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/internal/WasmBuilder.kt index bd2b340a0..95ace1bad 100644 --- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/internal/WasmBuilder.kt +++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/internal/WasmBuilder.kt @@ -8,9 +8,9 @@ package space.kscience.kmath.wasm.internal import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.MST import space.kscience.kmath.expressions.MST.* +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.internal.binaryen.* import space.kscience.kmath.internal.webassembly.Instance -import space.kscience.kmath.misc.StringSymbol import space.kscience.kmath.operations.* import space.kscience.kmath.internal.binaryen.Module as BinaryenModule import space.kscience.kmath.internal.webassembly.Module as WasmModule @@ -23,20 +23,20 @@ internal sealed class WasmBuilder( val algebra: Algebra, val target: MST, ) where T : Number { - val keys: MutableList = mutableListOf() + val keys: MutableList = mutableListOf() lateinit var ctx: BinaryenModule - open fun visitSymbolic(mst: MST.Symbolic): ExpressionRef { + open fun visitSymbolic(mst: Symbol): ExpressionRef { try { - algebra.bindSymbol(mst.value) + algebra.bindSymbol(mst) } catch (ignored: Throwable) { null }?.let { return visitNumeric(Numeric(it)) } - var idx = keys.indexOf(mst.value) + var idx = keys.indexOf(mst) if (idx == -1) { - keys += mst.value + keys += mst idx = keys.lastIndex } @@ -54,7 +54,7 @@ internal sealed class WasmBuilder( open fun createModule(): BinaryenModule = js("new \$module\$binaryen.Module()") fun visit(mst: MST): ExpressionRef = when (mst) { - is Symbolic -> visitSymbolic(mst) + is Symbol -> visitSymbolic(mst) is Numeric -> visitNumeric(mst) is Unary -> when { @@ -96,7 +96,7 @@ internal sealed class WasmBuilder( }) val i = Instance(c, js("{}") as Any) - val symbols = keys.map(::StringSymbol) + val symbols = keys keys.clear() Expression { args -> diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/wasm.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/wasm.kt index d44c4e49d..6ea8f26c1 100644 --- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/wasm.kt +++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/wasm/wasm.kt @@ -8,8 +8,9 @@ package space.kscience.kmath.wasm import space.kscience.kmath.estree.compileWith import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.IntRing import space.kscience.kmath.wasm.internal.DoubleWasmBuilder @@ -20,6 +21,7 @@ import space.kscience.kmath.wasm.internal.IntWasmBuilder * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun DoubleField.expression(mst: MST): Expression = DoubleWasmBuilder(mst).instance @@ -28,6 +30,7 @@ public fun DoubleField.expression(mst: MST): Expression = * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun IntRing.expression(mst: MST): Expression = IntWasmBuilder(mst).instance @@ -36,6 +39,7 @@ public fun IntRing.expression(mst: MST): Expression = * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun MST.compileToExpression(algebra: IntRing): Expression = compileWith(algebra) @@ -44,6 +48,7 @@ public fun MST.compileToExpression(algebra: IntRing): Expression = compileW * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun MST.compile(algebra: IntRing, arguments: Map): Int = compileToExpression(algebra).invoke(arguments) @@ -53,6 +58,7 @@ public fun MST.compile(algebra: IntRing, arguments: Map): Int = * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun MST.compile(algebra: IntRing, vararg arguments: Pair): Int = compileToExpression(algebra)(*arguments) @@ -61,6 +67,7 @@ public fun MST.compile(algebra: IntRing, vararg arguments: Pair): I * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun MST.compileToExpression(algebra: DoubleField): Expression = compileWith(algebra) @@ -69,6 +76,7 @@ public fun MST.compileToExpression(algebra: DoubleField): Expression = c * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun MST.compile(algebra: DoubleField, arguments: Map): Double = compileToExpression(algebra).invoke(arguments) @@ -78,5 +86,6 @@ public fun MST.compile(algebra: DoubleField, arguments: Map): Do * * @author Iaroslav Postovalov */ +@UnstableKMathAPI public fun MST.compile(algebra: DoubleField, vararg arguments: Pair): Double = compileToExpression(algebra).invoke(*arguments) diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/TestExecutionTime.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/TestExecutionTime.kt index 01746ddb6..6cb378182 100644 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/TestExecutionTime.kt +++ b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/TestExecutionTime.kt @@ -6,7 +6,6 @@ package space.kscience.kmath.ast import space.kscience.kmath.expressions.* -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.bindSymbol diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt new file mode 100644 index 000000000..93b7e9449 --- /dev/null +++ b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/ast/utils.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ast + +import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.IntRing +import space.kscience.kmath.estree.compile as estreeCompile +import space.kscience.kmath.estree.compileToExpression as estreeCompileToExpression +import space.kscience.kmath.wasm.compile as wasmCompile +import space.kscience.kmath.wasm.compileToExpression as wasmCompileToExpression + +private object WasmCompilerTestContext : CompilerTestContext { + override fun MST.compileToExpression(algebra: IntRing): Expression = wasmCompileToExpression(algebra) + override fun MST.compile(algebra: IntRing, arguments: Map): Int = wasmCompile(algebra, arguments) + override fun MST.compileToExpression(algebra: DoubleField): Expression = wasmCompileToExpression(algebra) + + override fun MST.compile(algebra: DoubleField, arguments: Map): Double = + wasmCompile(algebra, arguments) +} + +private object ESTreeCompilerTestContext : CompilerTestContext { + override fun MST.compileToExpression(algebra: IntRing): Expression = estreeCompileToExpression(algebra) + override fun MST.compile(algebra: IntRing, arguments: Map): Int = estreeCompile(algebra, arguments) + override fun MST.compileToExpression(algebra: DoubleField): Expression = estreeCompileToExpression(algebra) + + override fun MST.compile(algebra: DoubleField, arguments: Map): Double = + estreeCompile(algebra, arguments) +} + +internal actual inline fun runCompilerTest(action: CompilerTestContext.() -> Unit) { + action(WasmCompilerTestContext) + action(ESTreeCompilerTestContext) +} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeConsistencyWithInterpreter.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeConsistencyWithInterpreter.kt deleted file mode 100644 index d80318db8..000000000 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeConsistencyWithInterpreter.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.estree - -import space.kscience.kmath.complex.ComplexField -import space.kscience.kmath.complex.toComplex -import space.kscience.kmath.expressions.* -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.ByteRing -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestESTreeConsistencyWithInterpreter { - @Test - fun mstSpace() { - val mst = MstGroup { - binaryOperationFunction("+")( - unaryOperationFunction("+")( - number(3.toByte()) - (number(2.toByte()) + (scale( - add(number(1), number(1)), - 2.0 - ) + number(1.toByte()) * 3.toByte() - number(1.toByte()))) - ), - - number(1) - ) + bindSymbol(x) + zero - } - - assertEquals( - mst.interpret(MstGroup, x to MST.Numeric(2)), - mst.compile(MstGroup, x to MST.Numeric(2)) - ) - } - - @Test - fun byteRing() { - val mst = MstRing { - binaryOperationFunction("+")( - unaryOperationFunction("+")( - (bindSymbol(x) - (2.toByte() + (scale( - add(number(1), number(1)), - 2.0 - ) + 1.toByte()))) * 3.0 - 1.toByte() - ), - - number(1) - ) * number(2) - } - - assertEquals( - mst.interpret(ByteRing, x to 3.toByte()), - mst.compile(ByteRing, x to 3.toByte()) - ) - } - - @Test - fun doubleField() { - val mst = MstField { - +(3 - 2 + 2 * number(1) + 1.0) + binaryOperationFunction("+")( - (3.0 - (bindSymbol(x) + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0 - + number(1), - number(1) / 2 + number(2.0) * one - ) + zero - } - - assertEquals( - mst.interpret(DoubleField, x to 2.0), - mst.compile(DoubleField, x to 2.0) - ) - } - - @Test - fun complexField() { - val mst = MstField { - +(3 - 2 + 2 * number(1) + 1.0) + binaryOperationFunction("+")( - (3.0 - (bindSymbol(x) + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0 - + number(1), - number(1) / 2 + number(2.0) * one - ) + zero - } - - assertEquals( - mst.interpret(ComplexField, x to 2.0.toComplex()), - mst.compile(ComplexField, x to 2.0.toComplex()), - ) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeOperationsSupport.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeOperationsSupport.kt deleted file mode 100644 index a0b68a811..000000000 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeOperationsSupport.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.estree - -import space.kscience.kmath.expressions.MstField -import space.kscience.kmath.expressions.MstGroup -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestESTreeOperationsSupport { - @Test - fun testUnaryOperationInvocation() { - val expression = MstGroup { -bindSymbol(x) }.compileToExpression(DoubleField) - val res = expression(x to 2.0) - assertEquals(-2.0, res) - } - - @Test - fun testBinaryOperationInvocation() { - val expression = MstGroup { -bindSymbol(x) + number(1.0) }.compileToExpression(DoubleField) - val res = expression(x to 2.0) - assertEquals(-1.0, res) - } - - @Test - fun testConstProductInvocation() { - val res = MstField { bindSymbol(x) * 2 }.compileToExpression(DoubleField)(x to 2.0) - assertEquals(4.0, res) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeSpecialization.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeSpecialization.kt deleted file mode 100644 index 6756fd8c7..000000000 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeSpecialization.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.estree - -import space.kscience.kmath.expressions.MstExtendedField -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestESTreeSpecialization { - @Test - fun testUnaryPlus() { - val expr = MstExtendedField { unaryOperationFunction("+")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(2.0, expr(x to 2.0)) - } - - @Test - fun testUnaryMinus() { - val expr = MstExtendedField { unaryOperationFunction("-")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(-2.0, expr(x to 2.0)) - } - - @Test - fun testAdd() { - val expr = MstExtendedField { - binaryOperationFunction("+")( - bindSymbol(x), - bindSymbol(x), - ) - }.compileToExpression(DoubleField) - assertEquals(4.0, expr(x to 2.0)) - } - - @Test - fun testSine() { - val expr = MstExtendedField { unaryOperationFunction("sin")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(0.0, expr(x to 0.0)) - } - - @Test - fun testSubtract() { - val expr = MstExtendedField { - binaryOperationFunction("-")(bindSymbol(x), - bindSymbol(x)) - }.compileToExpression(DoubleField) - assertEquals(0.0, expr(x to 2.0)) - } - - @Test - fun testDivide() { - val expr = MstExtendedField { - binaryOperationFunction("/")(bindSymbol(x), bindSymbol(x)) - }.compileToExpression(DoubleField) - assertEquals(1.0, expr(x to 2.0)) - } - - @Test - fun testPower() { - val expr = MstExtendedField { - binaryOperationFunction("pow")(bindSymbol(x), number(2)) - }.compileToExpression(DoubleField) - - assertEquals(4.0, expr(x to 2.0)) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeVariables.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeVariables.kt deleted file mode 100644 index e1830d9df..000000000 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/estree/TestESTreeVariables.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.estree - -import space.kscience.kmath.expressions.MstRing -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.ByteRing -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -internal class TestESTreeVariables { - @Test - fun testVariable() { - val expr = MstRing { bindSymbol(x) }.compileToExpression(ByteRing) - assertEquals(1.toByte(), expr(x to 1.toByte())) - } - - @Test - fun testUndefinedVariableFails() { - val expr = MstRing { bindSymbol(x) }.compileToExpression(ByteRing) - assertFailsWith { expr() } - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmOperationsSupport.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmOperationsSupport.kt deleted file mode 100644 index 2946592f4..000000000 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmOperationsSupport.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.wasm - -import space.kscience.kmath.expressions.MstField -import space.kscience.kmath.expressions.MstGroup -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestWasmOperationsSupport { - @Test - fun testUnaryOperationInvocation() { - val expression = MstGroup { -bindSymbol(x) }.compileToExpression(DoubleField) - val res = expression(x to 2.0) - assertEquals(-2.0, res) - } - - @Test - fun testBinaryOperationInvocation() { - val expression = MstGroup { -bindSymbol(x) + number(1.0) }.compileToExpression(DoubleField) - val res = expression(x to 2.0) - assertEquals(-1.0, res) - } - - @Test - fun testConstProductInvocation() { - val res = MstField { bindSymbol(x) * 2 }.compileToExpression(DoubleField)(x to 2.0) - assertEquals(4.0, res) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecialization.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecialization.kt deleted file mode 100644 index e1f7b603a..000000000 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecialization.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.wasm - -import space.kscience.kmath.expressions.MstExtendedField -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestWasmSpecialization { - @Test - fun testUnaryPlus() { - val expr = MstExtendedField { unaryOperationFunction("+")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(2.0, expr(x to 2.0)) - } - - @Test - fun testUnaryMinus() { - val expr = MstExtendedField { unaryOperationFunction("-")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(-2.0, expr(x to 2.0)) - } - - @Test - fun testAdd() { - val expr = MstExtendedField { - binaryOperationFunction("+")( - bindSymbol(x), - bindSymbol(x), - ) - }.compileToExpression(DoubleField) - assertEquals(4.0, expr(x to 2.0)) - } - - @Test - fun testSine() { - val expr = MstExtendedField { unaryOperationFunction("sin")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(0.0, expr(x to 0.0)) - } - - @Test - fun testSubtract() { - val expr = MstExtendedField { - binaryOperationFunction("-")(bindSymbol(x), - bindSymbol(x)) - }.compileToExpression(DoubleField) - assertEquals(0.0, expr(x to 2.0)) - } - - @Test - fun testDivide() { - val expr = MstExtendedField { - binaryOperationFunction("/")(bindSymbol(x), bindSymbol(x)) - }.compileToExpression(DoubleField) - assertEquals(1.0, expr(x to 2.0)) - } - - @Test - fun testPower() { - val expr = MstExtendedField { - binaryOperationFunction("pow")(bindSymbol(x), number(2)) - }.compileToExpression(DoubleField) - - assertEquals(4.0, expr(x to 2.0)) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecific.kt b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecific.kt index dd5452d04..abdf865c7 100644 --- a/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecific.kt +++ b/kmath-ast/src/jsTest/kotlin/space/kscience/kmath/wasm/TestWasmSpecific.kt @@ -8,7 +8,7 @@ package space.kscience.kmath.wasm import space.kscience.kmath.expressions.MstExtendedField import space.kscience.kmath.expressions.MstRing import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol +import space.kscience.kmath.expressions.symbol import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.IntRing import space.kscience.kmath.operations.bindSymbol diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt index dbce893d1..4147324ee 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt @@ -10,10 +10,11 @@ import space.kscience.kmath.asm.internal.buildName import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.MST import space.kscience.kmath.expressions.MST.* +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.operations.Algebra import space.kscience.kmath.operations.NumericAlgebra +import space.kscience.kmath.operations.bindSymbolOrNull /** * Compiles given MST to an Expression using AST compiler. @@ -26,13 +27,13 @@ import space.kscience.kmath.operations.NumericAlgebra @PublishedApi internal fun MST.compileWith(type: Class, algebra: Algebra): Expression { fun AsmBuilder.visit(node: MST): Unit = when (node) { - is Symbolic -> { - val symbol = algebra.bindSymbolOrNull(node.value) + is Symbol -> { + val symbol = algebra.bindSymbolOrNull(node) if (symbol != null) loadObjectConstant(symbol as Any) else - loadVariable(node.value) + loadVariable(node.identity) } is Numeric -> loadNumberConstant(node.value) diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/AsmBuilder.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/AsmBuilder.kt index bdd8f52b6..a796ae2a5 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/AsmBuilder.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/AsmBuilder.kt @@ -342,7 +342,7 @@ internal class AsmBuilder( val MAP_INTRINSICS_TYPE: Type by lazy { getObjectType("space/kscience/kmath/asm/internal/MapIntrinsics") } /** - * ASM Type for [kscience.kmath.expressions.Symbol]. + * ASM Type for [space.kscience.kmath.expressions.Symbol]. */ val SYMBOL_TYPE: Type by lazy { getObjectType("space/kscience/kmath/expressions/Symbol") } } diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/codegenUtils.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/codegenUtils.kt index 560780f99..cfac59847 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/codegenUtils.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/codegenUtils.kt @@ -52,7 +52,7 @@ internal inline fun MethodVisitor.instructionAdapter(block: InstructionAdapter.( * * @author Iaroslav Postovalov */ -internal fun MethodVisitor.label(): Label = Label().also { visitLabel(it) } +internal fun MethodVisitor.label(): Label = Label().also(::visitLabel) /** * Creates a class name for [Expression] subclassed to implement [mst] provided. diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt index dc8f19fb6..8f4daecf9 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt @@ -7,8 +7,8 @@ package space.kscience.kmath.asm.internal -import space.kscience.kmath.misc.StringSymbol -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.StringSymbol +import space.kscience.kmath.expressions.Symbol /** * Gets value with given [key] or throws [NoSuchElementException] whenever it is not present. diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmConsistencyWithInterpreter.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmConsistencyWithInterpreter.kt deleted file mode 100644 index f94d36602..000000000 --- a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmConsistencyWithInterpreter.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.asm - -import space.kscience.kmath.complex.ComplexField -import space.kscience.kmath.complex.toComplex -import space.kscience.kmath.expressions.* -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.ByteRing -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestAsmConsistencyWithInterpreter { - @Test - fun mstSpace() { - val mst = MstGroup { - binaryOperationFunction("+")( - unaryOperationFunction("+")( - number(3.toByte()) - (number(2.toByte()) + (scale( - add(number(1), number(1)), - 2.0 - ) + number(1.toByte()) * 3.toByte() - number(1.toByte()))) - ), - - number(1) - ) + bindSymbol(x) + zero - } - - assertEquals( - mst.interpret(MstGroup, x to MST.Numeric(2)), - mst.compile(MstGroup, x to MST.Numeric(2)) - ) - } - - @Test - fun byteRing() { - val mst = MstRing { - binaryOperationFunction("+")( - unaryOperationFunction("+")( - (bindSymbol(x) - (2.toByte() + (scale( - add(number(1), number(1)), - 2.0 - ) + 1.toByte()))) * 3.0 - 1.toByte() - ), - - number(1) - ) * number(2) - } - - assertEquals( - mst.interpret(ByteRing, x to 3.toByte()), - mst.compile(ByteRing, x to 3.toByte()) - ) - } - - @Test - fun doubleField() { - val mst = MstField { - +(3 - 2 + 2 * number(1) + 1.0) + binaryOperationFunction("+")( - (3.0 - (bindSymbol(x) + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0 - + number(1), - number(1) / 2 + number(2.0) * one - ) + zero - } - - assertEquals( - mst.interpret(DoubleField, x to 2.0), - mst.compile(DoubleField, x to 2.0) - ) - } - - @Test - fun complexField() { - val mst = MstField { - +(3 - 2 + 2 * number(1) + 1.0) + binaryOperationFunction("+")( - (3.0 - (bindSymbol(x) + (scale(add(number(1.0), number(1.0)), 2.0) + 1.0))) * 3 - 1.0 - + number(1), - number(1) / 2 + number(2.0) * one - ) + zero - } - - assertEquals( - mst.interpret(ComplexField, x to 2.0.toComplex()), - mst.compile(ComplexField, x to 2.0.toComplex()) - ) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmOperationsSupport.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmOperationsSupport.kt deleted file mode 100644 index 147639f7c..000000000 --- a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmOperationsSupport.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.asm - -import space.kscience.kmath.expressions.MstField -import space.kscience.kmath.expressions.MstGroup -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestAsmOperationsSupport { - @Test - fun testUnaryOperationInvocation() { - val expression = MstGroup { -bindSymbol(x) }.compileToExpression(DoubleField) - val res = expression(x to 2.0) - assertEquals(-2.0, res) - } - - @Test - fun testBinaryOperationInvocation() { - val expression = MstGroup { -bindSymbol(x) + number(1.0) }.compileToExpression(DoubleField) - val res = expression(x to 2.0) - assertEquals(-1.0, res) - } - - @Test - fun testConstProductInvocation() { - val res = MstField { bindSymbol(x) * 2 }.compileToExpression(DoubleField)(x to 2.0) - assertEquals(4.0, res) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmSpecialization.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmSpecialization.kt deleted file mode 100644 index 3a681e482..000000000 --- a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmSpecialization.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.asm - -import space.kscience.kmath.expressions.MstExtendedField -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals - -internal class TestAsmSpecialization { - @Test - fun testUnaryPlus() { - val expr = MstExtendedField { unaryOperationFunction("+")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(2.0, expr(x to 2.0)) - } - - @Test - fun testUnaryMinus() { - val expr = MstExtendedField { unaryOperationFunction("-")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(-2.0, expr(x to 2.0)) - } - - @Test - fun testAdd() { - val expr = MstExtendedField { - binaryOperationFunction("+")( - bindSymbol(x), - bindSymbol(x), - ) - }.compileToExpression(DoubleField) - assertEquals(4.0, expr(x to 2.0)) - } - - @Test - fun testSine() { - val expr = MstExtendedField { unaryOperationFunction("sin")(bindSymbol(x)) }.compileToExpression(DoubleField) - assertEquals(0.0, expr(x to 0.0)) - } - - @Test - fun testSubtract() { - val expr = MstExtendedField { - binaryOperationFunction("-")(bindSymbol(x), - bindSymbol(x)) - }.compileToExpression(DoubleField) - assertEquals(0.0, expr(x to 2.0)) - } - - @Test - fun testDivide() { - val expr = MstExtendedField { - binaryOperationFunction("/")(bindSymbol(x), bindSymbol(x)) - }.compileToExpression(DoubleField) - assertEquals(1.0, expr(x to 2.0)) - } - - @Test - fun testPower() { - val expr = MstExtendedField { - binaryOperationFunction("pow")(bindSymbol(x), number(2)) - }.compileToExpression(DoubleField) - - assertEquals(4.0, expr(x to 2.0)) - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmVariables.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmVariables.kt deleted file mode 100644 index 89b98d720..000000000 --- a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/asm/TestAsmVariables.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.asm - -import space.kscience.kmath.expressions.MstRing -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol -import space.kscience.kmath.operations.ByteRing -import space.kscience.kmath.operations.bindSymbol -import space.kscience.kmath.operations.invoke -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -internal class TestAsmVariables { - @Test - fun testVariable() { - val expr = MstRing { bindSymbol(x) }.compileToExpression(ByteRing) - assertEquals(1.toByte(), expr(x to 1.toByte())) - } - - @Test - fun testUndefinedVariableFails() { - val expr = MstRing { bindSymbol(x) }.compileToExpression(ByteRing) - assertFailsWith { expr() } - } - - private companion object { - private val x by symbol - } -} diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/utils.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/utils.kt new file mode 100644 index 000000000..d3b554efd --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/utils.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.ast + +import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.IntRing +import space.kscience.kmath.asm.compile as asmCompile +import space.kscience.kmath.asm.compileToExpression as asmCompileToExpression + +private object AsmCompilerTestContext : CompilerTestContext { + override fun MST.compileToExpression(algebra: IntRing): Expression = asmCompileToExpression(algebra) + override fun MST.compile(algebra: IntRing, arguments: Map): Int = asmCompile(algebra, arguments) + override fun MST.compileToExpression(algebra: DoubleField): Expression = asmCompileToExpression(algebra) + + override fun MST.compile(algebra: DoubleField, arguments: Map): Double = + asmCompile(algebra, arguments) +} + +internal actual inline fun runCompilerTest(action: CompilerTestContext.() -> Unit) = action(AsmCompilerTestContext) diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt index 736685789..89e216601 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt @@ -7,7 +7,6 @@ package space.kscience.kmath.commons.expressions import org.apache.commons.math3.analysis.differentiation.DerivativeStructure import space.kscience.kmath.expressions.* -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.NumbersAddOperations diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt similarity index 92% rename from kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt rename to kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt index 1361b1079..4e174723d 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt @@ -11,12 +11,12 @@ import space.kscience.kmath.integration.* /** * A simple one-pass integrator based on Gauss rule */ -public class GaussRuleIntegrator( +public class CMGaussRuleIntegrator( private val numpoints: Int, private var type: GaussRule = GaussRule.LEGANDRE, ) : UnivariateIntegrator { - override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { val range = integrand.getFeature()?.range ?: error("Integration range is not provided") val integrator: GaussIntegrator = getIntegrator(range) @@ -76,8 +76,8 @@ public class GaussRuleIntegrator( numPoints: Int = 100, type: GaussRule = GaussRule.LEGANDRE, function: (Double) -> Double, - ): Double = GaussRuleIntegrator(numPoints, type).process( + ): Double = CMGaussRuleIntegrator(numPoints, type).integrate( UnivariateIntegrand(function, IntegrationRange(range)) - ).value!! + ).valueOrNull!! } } \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt index 9da35a7c0..bcddccdc4 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt @@ -18,13 +18,7 @@ public class CMIntegrator( public val integratorBuilder: (Integrand) -> org.apache.commons.math3.analysis.integration.UnivariateIntegrator, ) : UnivariateIntegrator { - public class TargetRelativeAccuracy(public val value: Double) : IntegrandFeature - public class TargetAbsoluteAccuracy(public val value: Double) : IntegrandFeature - - public class MinIterations(public val value: Int) : IntegrandFeature - public class MaxIterations(public val value: Int) : IntegrandFeature - - override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { val integrator = integratorBuilder(integrand) val maxCalls = integrand.getFeature()?.maxCalls ?: defaultMaxCalls val remainingCalls = maxCalls - integrand.calls @@ -32,12 +26,11 @@ public class CMIntegrator( ?: error("Integration range is not provided") val res = integrator.integrate(remainingCalls, integrand.function, range.start, range.endInclusive) - return integrand.with( - IntegrandValue(res), - IntegrandAbsoluteAccuracy(integrator.absoluteAccuracy), - IntegrandRelativeAccuracy(integrator.relativeAccuracy), - IntegrandCallsPerformed(integrator.evaluations + integrand.calls) - ) + return integrand + + IntegrandValue(res) + + IntegrandAbsoluteAccuracy(integrator.absoluteAccuracy) + + IntegrandRelativeAccuracy(integrator.relativeAccuracy) + + IntegrandCallsPerformed(integrator.evaluations + integrand.calls) } @@ -46,16 +39,15 @@ public class CMIntegrator( * Create a Simpson integrator based on [SimpsonIntegrator] */ public fun simpson(defaultMaxCalls: Int = 200): CMIntegrator = CMIntegrator(defaultMaxCalls) { integrand -> - val absoluteAccuracy = integrand.getFeature()?.value + val absoluteAccuracy = integrand.getFeature()?.accuracy ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val relativeAccuracy = integrand.getFeature()?.value + val relativeAccuracy = integrand.getFeature()?.accuracy ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val minIterations = integrand.getFeature()?.value - ?: SimpsonIntegrator.DEFAULT_MIN_ITERATIONS_COUNT - val maxIterations = integrand.getFeature()?.value - ?: SimpsonIntegrator.SIMPSON_MAX_ITERATIONS_COUNT + val iterations = integrand.getFeature()?.range + ?: SimpsonIntegrator.DEFAULT_MIN_ITERATIONS_COUNT..SimpsonIntegrator.SIMPSON_MAX_ITERATIONS_COUNT - SimpsonIntegrator(relativeAccuracy, absoluteAccuracy, minIterations, maxIterations) + + SimpsonIntegrator(relativeAccuracy, absoluteAccuracy, iterations.first, iterations.last) } /** @@ -63,21 +55,19 @@ public class CMIntegrator( */ public fun legandre(numPoints: Int, defaultMaxCalls: Int = numPoints * 5): CMIntegrator = CMIntegrator(defaultMaxCalls) { integrand -> - val absoluteAccuracy = integrand.getFeature()?.value + val absoluteAccuracy = integrand.getFeature()?.accuracy ?: IterativeLegendreGaussIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val relativeAccuracy = integrand.getFeature()?.value + val relativeAccuracy = integrand.getFeature()?.accuracy ?: IterativeLegendreGaussIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val minIterations = integrand.getFeature()?.value - ?: IterativeLegendreGaussIntegrator.DEFAULT_MIN_ITERATIONS_COUNT - val maxIterations = integrand.getFeature()?.value - ?: IterativeLegendreGaussIntegrator.DEFAULT_MAX_ITERATIONS_COUNT + val iterations = integrand.getFeature()?.range + ?: IterativeLegendreGaussIntegrator.DEFAULT_MIN_ITERATIONS_COUNT..IterativeLegendreGaussIntegrator.DEFAULT_MAX_ITERATIONS_COUNT IterativeLegendreGaussIntegrator( numPoints, relativeAccuracy, absoluteAccuracy, - minIterations, - maxIterations + iterations.first, + iterations.last ) } } @@ -85,14 +75,14 @@ public class CMIntegrator( @UnstableKMathAPI public var MutableList.targetAbsoluteAccuracy: Double? - get() = filterIsInstance().lastOrNull()?.value + get() = filterIsInstance().lastOrNull()?.accuracy set(value) { - value?.let { add(CMIntegrator.TargetAbsoluteAccuracy(value)) } + value?.let { add(IntegrandAbsoluteAccuracy(value)) } } @UnstableKMathAPI public var MutableList.targetRelativeAccuracy: Double? - get() = filterIsInstance().lastOrNull()?.value + get() = filterIsInstance().lastOrNull()?.accuracy set(value) { - value?.let { add(CMIntegrator.TargetRelativeAccuracy(value)) } + value?.let { add(IntegrandRelativeAccuracy(value)) } } \ No newline at end of file diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt index 879cd75b1..966675062 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt @@ -5,11 +5,7 @@ package space.kscience.kmath.commons.expressions -import space.kscience.kmath.expressions.binding -import space.kscience.kmath.expressions.derivative -import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.misc.symbol +import space.kscience.kmath.expressions.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.test.Test diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt index 9d475d04d..c5573fef1 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt @@ -7,6 +7,7 @@ package space.kscience.kmath.commons.integration import org.junit.jupiter.api.Test import space.kscience.kmath.integration.integrate +import space.kscience.kmath.integration.value import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.DoubleField.sin import kotlin.math.PI @@ -19,16 +20,16 @@ internal class IntegrationTest { @Test fun simpson() { - val res = CMIntegrator.simpson().integrate(0.0..2 * PI, function) + val res = CMIntegrator.simpson().integrate(0.0..2 * PI, function = function).value assertTrue { abs(res) < 1e-3 } } @Test fun customSimpson() { - val res = CMIntegrator.simpson().integrate(0.0..PI, function) { + val res = CMIntegrator.simpson().integrate(0.0..PI, { targetRelativeAccuracy = 1e-4 targetAbsoluteAccuracy = 1e-4 - } + }, function).value assertTrue { abs(res - 2) < 1e-3 } assertTrue { abs(res - 2) > 1e-12 } } diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt index 9b92eaac5..b47d7da24 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt @@ -8,7 +8,7 @@ package space.kscience.kmath.commons.optimization import kotlinx.coroutines.runBlocking import space.kscience.kmath.commons.expressions.DerivativeStructureExpression import space.kscience.kmath.distributions.NormalDistribution -import space.kscience.kmath.misc.symbol +import space.kscience.kmath.expressions.symbol import space.kscience.kmath.optimization.FunctionOptimization import space.kscience.kmath.stat.RandomGenerator import kotlin.math.pow diff --git a/kmath-complex/README.md b/kmath-complex/README.md index 70beab95a..04431cf6c 100644 --- a/kmath-complex/README.md +++ b/kmath-complex/README.md @@ -8,29 +8,27 @@ Complex and hypercomplex number systems in KMath. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-complex:0.3.0-dev-7' + implementation 'space.kscience:kmath-complex:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-complex:0.3.0-dev-7") + implementation("space.kscience:kmath-complex:0.3.0-dev-11") } ``` diff --git a/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Complex.kt b/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Complex.kt index 8d626c17d..a96d046c9 100644 --- a/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Complex.kt +++ b/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Complex.kt @@ -9,7 +9,10 @@ import space.kscience.kmath.memory.MemoryReader import space.kscience.kmath.memory.MemorySpec import space.kscience.kmath.memory.MemoryWriter import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.* +import space.kscience.kmath.operations.ExtendedField +import space.kscience.kmath.operations.Norm +import space.kscience.kmath.operations.NumbersAddOperations +import space.kscience.kmath.operations.ScaleOperations import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.MemoryBuffer import space.kscience.kmath.structures.MutableBuffer @@ -180,13 +183,11 @@ public object ComplexField : ExtendedField, Norm, Num * @property im The imaginary part. */ @OptIn(UnstableKMathAPI::class) -public data class Complex(val re: Double, val im: Double) : FieldElement { +public data class Complex(val re: Double, val im: Double) { public constructor(re: Number, im: Number) : this(re.toDouble(), im.toDouble()) public constructor(re: Number) : this(re.toDouble(), 0.0) - public override val context: ComplexField get() = ComplexField - - public override fun toString(): String = "($re + i*$im)" + public override fun toString(): String = "($re + i * $im)" public companion object : MemorySpec { public override val objectSize: Int diff --git a/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Quaternion.kt b/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Quaternion.kt index 382b5cc05..c59aabdcb 100644 --- a/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Quaternion.kt +++ b/kmath-complex/src/commonMain/kotlin/space/kscience/kmath/complex/Quaternion.kt @@ -27,8 +27,10 @@ public val Quaternion.conjugate: Quaternion */ public val Quaternion.reciprocal: Quaternion get() { - val n = QuaternionField { norm(this@reciprocal) } - return conjugate / (n * n) + QuaternionField { + val n = norm(this@reciprocal) + return conjugate / (n * n) + } } /** @@ -198,7 +200,7 @@ public object QuaternionField : Field, Norm, @OptIn(UnstableKMathAPI::class) public data class Quaternion( val w: Double, val x: Double, val y: Double, val z: Double, -) : FieldElement { +) { public constructor(w: Number, x: Number, y: Number, z: Number) : this( w.toDouble(), x.toDouble(), @@ -219,8 +221,6 @@ public data class Quaternion( require(!z.isNaN()) { "x-component of quaternion is not-a-number" } } - public override val context: QuaternionField get() = QuaternionField - /** * Returns a string representation of this quaternion. */ diff --git a/kmath-complex/src/commonTest/kotlin/space/kscience/kmath/complex/ExpressionFieldForComplexTest.kt b/kmath-complex/src/commonTest/kotlin/space/kscience/kmath/complex/ExpressionFieldForComplexTest.kt index e023fcb81..4279471d4 100644 --- a/kmath-complex/src/commonTest/kotlin/space/kscience/kmath/complex/ExpressionFieldForComplexTest.kt +++ b/kmath-complex/src/commonTest/kotlin/space/kscience/kmath/complex/ExpressionFieldForComplexTest.kt @@ -7,7 +7,7 @@ package space.kscience.kmath.complex import space.kscience.kmath.expressions.FunctionalExpressionField import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol +import space.kscience.kmath.expressions.symbol import space.kscience.kmath.operations.bindSymbol import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/README.md b/kmath-core/README.md index e28873045..700eaef38 100644 --- a/kmath-core/README.md +++ b/kmath-core/README.md @@ -15,29 +15,27 @@ performance calculations to code generation. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-core:0.3.0-dev-7' + implementation 'space.kscience:kmath-core:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-core:0.3.0-dev-7") + implementation("space.kscience:kmath-core:0.3.0-dev-11") } ``` diff --git a/kmath-core/api/kmath-core.api b/kmath-core/api/kmath-core.api index efb626c12..b424b59ff 100644 --- a/kmath-core/api/kmath-core.api +++ b/kmath-core/api/kmath-core.api @@ -21,7 +21,7 @@ public class space/kscience/kmath/expressions/AutoDiffValue { public final class space/kscience/kmath/expressions/DerivationResult { public fun (Ljava/lang/Object;Ljava/util/Map;Lspace/kscience/kmath/operations/Field;)V - public final fun derivative (Lspace/kscience/kmath/misc/Symbol;)Ljava/lang/Object; + public final fun derivative (Lspace/kscience/kmath/expressions/Symbol;)Ljava/lang/Object; public final fun div ()Ljava/lang/Object; public final fun getContext ()Lspace/kscience/kmath/operations/Field; public final fun getValue ()Ljava/lang/Object; @@ -34,7 +34,7 @@ public abstract interface class space/kscience/kmath/expressions/DifferentiableE public final class space/kscience/kmath/expressions/DifferentiableExpressionKt { public static final fun derivative (Lspace/kscience/kmath/expressions/DifferentiableExpression;Ljava/lang/String;)Lspace/kscience/kmath/expressions/Expression; public static final fun derivative (Lspace/kscience/kmath/expressions/DifferentiableExpression;Ljava/util/List;)Lspace/kscience/kmath/expressions/Expression; - public static final fun derivative (Lspace/kscience/kmath/expressions/DifferentiableExpression;[Lspace/kscience/kmath/misc/Symbol;)Lspace/kscience/kmath/expressions/Expression; + public static final fun derivative (Lspace/kscience/kmath/expressions/DifferentiableExpression;[Lspace/kscience/kmath/expressions/Symbol;)Lspace/kscience/kmath/expressions/Expression; } public abstract interface class space/kscience/kmath/expressions/Expression { @@ -55,7 +55,7 @@ public final class space/kscience/kmath/expressions/ExpressionKt { public abstract class space/kscience/kmath/expressions/FirstDerivativeExpression : space/kscience/kmath/expressions/DifferentiableExpression { public fun ()V public final fun derivativeOrNull (Ljava/util/List;)Lspace/kscience/kmath/expressions/Expression; - public abstract fun derivativeOrNull (Lspace/kscience/kmath/misc/Symbol;)Lspace/kscience/kmath/expressions/Expression; + public abstract fun derivativeOrNull (Lspace/kscience/kmath/expressions/Symbol;)Lspace/kscience/kmath/expressions/Expression; } public abstract class space/kscience/kmath/expressions/FunctionalExpressionAlgebra : space/kscience/kmath/expressions/ExpressionAlgebra { @@ -146,7 +146,7 @@ public class space/kscience/kmath/expressions/FunctionalExpressionRing : space/k public fun unaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; } -public abstract class space/kscience/kmath/expressions/MST { +public abstract interface class space/kscience/kmath/expressions/MST { } public final class space/kscience/kmath/expressions/MST$Binary : space/kscience/kmath/expressions/MST { @@ -175,17 +175,6 @@ public final class space/kscience/kmath/expressions/MST$Numeric : space/kscience public fun toString ()Ljava/lang/String; } -public final class space/kscience/kmath/expressions/MST$Symbolic : space/kscience/kmath/expressions/MST { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; - public static synthetic fun copy$default (Lspace/kscience/kmath/expressions/MST$Symbolic;Ljava/lang/String;ILjava/lang/Object;)Lspace/kscience/kmath/expressions/MST$Symbolic; - public fun equals (Ljava/lang/Object;)Z - public final fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class space/kscience/kmath/expressions/MST$Unary : space/kscience/kmath/expressions/MST { public fun (Ljava/lang/String;Lspace/kscience/kmath/expressions/MST;)V public final fun component1 ()Ljava/lang/String; @@ -206,18 +195,6 @@ public final class space/kscience/kmath/expressions/MSTKt { public static final fun toExpression (Lspace/kscience/kmath/expressions/MST;Lspace/kscience/kmath/operations/Algebra;)Lspace/kscience/kmath/expressions/Expression; } -public final class space/kscience/kmath/expressions/MstAlgebra : space/kscience/kmath/operations/NumericAlgebra { - public static final field INSTANCE Lspace/kscience/kmath/expressions/MstAlgebra; - public fun binaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function2; - public synthetic fun bindSymbol (Ljava/lang/String;)Ljava/lang/Object; - public fun bindSymbol (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; - public synthetic fun bindSymbolOrNull (Ljava/lang/String;)Ljava/lang/Object; - public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; - public synthetic fun number (Ljava/lang/Number;)Ljava/lang/Object; - public fun number (Ljava/lang/Number;)Lspace/kscience/kmath/expressions/MST$Numeric; - public fun unaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; -} - public final class space/kscience/kmath/expressions/MstExtendedField : space/kscience/kmath/operations/ExtendedField, space/kscience/kmath/operations/NumericAlgebra { public static final field INSTANCE Lspace/kscience/kmath/expressions/MstExtendedField; public synthetic fun acos (Ljava/lang/Object;)Ljava/lang/Object; @@ -236,7 +213,7 @@ public final class space/kscience/kmath/expressions/MstExtendedField : space/ksc public fun atanh (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Unary; public fun binaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function2; public synthetic fun bindSymbolOrNull (Ljava/lang/String;)Ljava/lang/Object; - public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; + public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/Symbol; public synthetic fun cos (Ljava/lang/Object;)Ljava/lang/Object; public fun cos (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Unary; public synthetic fun cosh (Ljava/lang/Object;)Ljava/lang/Object; @@ -265,6 +242,8 @@ public final class space/kscience/kmath/expressions/MstExtendedField : space/ksc public fun sin (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Unary; public synthetic fun sinh (Ljava/lang/Object;)Ljava/lang/Object; public fun sinh (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Unary; + public synthetic fun sqrt (Ljava/lang/Object;)Ljava/lang/Object; + public fun sqrt (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST; public synthetic fun tan (Ljava/lang/Object;)Ljava/lang/Object; public fun tan (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Unary; public synthetic fun tanh (Ljava/lang/Object;)Ljava/lang/Object; @@ -282,7 +261,7 @@ public final class space/kscience/kmath/expressions/MstField : space/kscience/km public fun add (Lspace/kscience/kmath/expressions/MST;Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Binary; public fun binaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function2; public synthetic fun bindSymbolOrNull (Ljava/lang/String;)Ljava/lang/Object; - public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; + public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/Symbol; public synthetic fun divide (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun divide (Lspace/kscience/kmath/expressions/MST;Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Binary; public synthetic fun getOne ()Ljava/lang/Object; @@ -310,7 +289,7 @@ public final class space/kscience/kmath/expressions/MstGroup : space/kscience/km public fun add (Lspace/kscience/kmath/expressions/MST;Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Binary; public fun binaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function2; public synthetic fun bindSymbolOrNull (Ljava/lang/String;)Ljava/lang/Object; - public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; + public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/Symbol; public synthetic fun getZero ()Ljava/lang/Object; public fun getZero ()Lspace/kscience/kmath/expressions/MST$Numeric; public synthetic fun minus (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; @@ -326,13 +305,25 @@ public final class space/kscience/kmath/expressions/MstGroup : space/kscience/km public fun unaryPlus (Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Unary; } +public final class space/kscience/kmath/expressions/MstNumericAlgebra : space/kscience/kmath/operations/NumericAlgebra { + public static final field INSTANCE Lspace/kscience/kmath/expressions/MstNumericAlgebra; + public fun binaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function2; + public synthetic fun bindSymbol (Ljava/lang/String;)Ljava/lang/Object; + public fun bindSymbol (Ljava/lang/String;)Lspace/kscience/kmath/expressions/Symbol; + public synthetic fun bindSymbolOrNull (Ljava/lang/String;)Ljava/lang/Object; + public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/Symbol; + public synthetic fun number (Ljava/lang/Number;)Ljava/lang/Object; + public fun number (Ljava/lang/Number;)Lspace/kscience/kmath/expressions/MST$Numeric; + public fun unaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; +} + public final class space/kscience/kmath/expressions/MstRing : space/kscience/kmath/operations/NumbersAddOperations, space/kscience/kmath/operations/Ring, space/kscience/kmath/operations/ScaleOperations { public static final field INSTANCE Lspace/kscience/kmath/expressions/MstRing; public synthetic fun add (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun add (Lspace/kscience/kmath/expressions/MST;Lspace/kscience/kmath/expressions/MST;)Lspace/kscience/kmath/expressions/MST$Binary; public fun binaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function2; public synthetic fun bindSymbolOrNull (Ljava/lang/String;)Ljava/lang/Object; - public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/MST$Symbolic; + public fun bindSymbolOrNull (Ljava/lang/String;)Lspace/kscience/kmath/expressions/Symbol; public synthetic fun getOne ()Ljava/lang/Object; public fun getOne ()Lspace/kscience/kmath/expressions/MST$Numeric; public synthetic fun getZero ()Ljava/lang/Object; @@ -354,7 +345,7 @@ public final class space/kscience/kmath/expressions/MstRing : space/kscience/kma public final class space/kscience/kmath/expressions/SimpleAutoDiffExpression : space/kscience/kmath/expressions/FirstDerivativeExpression { public fun (Lspace/kscience/kmath/operations/Field;Lkotlin/jvm/functions/Function1;)V - public fun derivativeOrNull (Lspace/kscience/kmath/misc/Symbol;)Lspace/kscience/kmath/expressions/Expression; + public fun derivativeOrNull (Lspace/kscience/kmath/expressions/Symbol;)Lspace/kscience/kmath/expressions/Expression; public final fun getField ()Lspace/kscience/kmath/operations/Field; public final fun getFunction ()Lkotlin/jvm/functions/Function1; public fun invoke (Ljava/util/Map;)Ljava/lang/Object; @@ -443,7 +434,7 @@ public final class space/kscience/kmath/expressions/SimpleAutoDiffKt { public static final fun cos (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;)Lspace/kscience/kmath/expressions/AutoDiffValue; public static final fun cosh (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;)Lspace/kscience/kmath/expressions/AutoDiffValue; public static final fun exp (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;)Lspace/kscience/kmath/expressions/AutoDiffValue; - public static final fun grad (Lspace/kscience/kmath/expressions/DerivationResult;[Lspace/kscience/kmath/misc/Symbol;)Lspace/kscience/kmath/structures/Buffer; + public static final fun grad (Lspace/kscience/kmath/expressions/DerivationResult;[Lspace/kscience/kmath/expressions/Symbol;)Lspace/kscience/kmath/structures/Buffer; public static final fun ln (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;)Lspace/kscience/kmath/expressions/AutoDiffValue; public static final fun pow (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;D)Lspace/kscience/kmath/expressions/AutoDiffValue; public static final fun pow (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;I)Lspace/kscience/kmath/expressions/AutoDiffValue; @@ -459,9 +450,42 @@ public final class space/kscience/kmath/expressions/SimpleAutoDiffKt { public static final fun tanh (Lspace/kscience/kmath/expressions/SimpleAutoDiffField;Lspace/kscience/kmath/expressions/AutoDiffValue;)Lspace/kscience/kmath/expressions/AutoDiffValue; } +public final class space/kscience/kmath/expressions/StringSymbol : space/kscience/kmath/expressions/Symbol { + public static final synthetic fun box-impl (Ljava/lang/String;)Lspace/kscience/kmath/expressions/StringSymbol; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public fun getIdentity ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public abstract interface class space/kscience/kmath/expressions/Symbol : space/kscience/kmath/expressions/MST { + public static final field Companion Lspace/kscience/kmath/expressions/Symbol$Companion; + public abstract fun getIdentity ()Ljava/lang/String; +} + +public final class space/kscience/kmath/expressions/Symbol$Companion { + public final fun getX-uKgCeAI ()Ljava/lang/String; + public final fun getY-uKgCeAI ()Ljava/lang/String; + public final fun getZ-uKgCeAI ()Ljava/lang/String; +} + public final class space/kscience/kmath/expressions/SymbolIndexerKt { } +public final class space/kscience/kmath/expressions/SymbolKt { + public static final fun get (Ljava/util/Map;Ljava/lang/String;)Ljava/lang/Object; + public static final fun get (Ljava/util/Map;Lspace/kscience/kmath/expressions/Symbol;)Ljava/lang/Object; + public static final fun getSymbol ()Lkotlin/properties/ReadOnlyProperty; + public static final fun set (Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)V + public static final fun set (Ljava/util/Map;Lspace/kscience/kmath/expressions/Symbol;Ljava/lang/Object;)V +} + public final class space/kscience/kmath/linear/BufferedLinearSpace : space/kscience/kmath/linear/LinearSpace { public fun (Lspace/kscience/kmath/operations/Ring;Lkotlin/jvm/functions/Function2;)V public fun buildMatrix (IILkotlin/jvm/functions/Function3;)Lspace/kscience/kmath/nd/Structure2D; @@ -498,6 +522,11 @@ public final class space/kscience/kmath/linear/LFeature : space/kscience/kmath/l public static final field INSTANCE Lspace/kscience/kmath/linear/LFeature; } +public abstract interface class space/kscience/kmath/linear/LUDecompositionFeature : space/kscience/kmath/linear/MatrixFeature { + public abstract fun getL ()Lspace/kscience/kmath/nd/Structure2D; + public abstract fun getU ()Lspace/kscience/kmath/nd/Structure2D; +} + public abstract interface class space/kscience/kmath/linear/LinearSolver { public abstract fun inverse (Lspace/kscience/kmath/nd/Structure2D;)Lspace/kscience/kmath/nd/Structure2D; public abstract fun solve (Lspace/kscience/kmath/nd/Structure2D;Lspace/kscience/kmath/nd/Structure2D;)Lspace/kscience/kmath/nd/Structure2D; @@ -672,37 +701,7 @@ public final class space/kscience/kmath/misc/CumulativeKt { public static final fun cumulativeSumOfLong (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; } -public final class space/kscience/kmath/misc/StringSymbol : space/kscience/kmath/misc/Symbol { - public static final synthetic fun box-impl (Ljava/lang/String;)Lspace/kscience/kmath/misc/StringSymbol; - public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z - public fun getIdentity ()Ljava/lang/String; - public fun hashCode ()I - public static fun hashCode-impl (Ljava/lang/String;)I - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Ljava/lang/String; -} - -public abstract interface class space/kscience/kmath/misc/Symbol { - public static final field Companion Lspace/kscience/kmath/misc/Symbol$Companion; - public abstract fun getIdentity ()Ljava/lang/String; -} - -public final class space/kscience/kmath/misc/Symbol$Companion { - public final fun getX-tWtZOCg ()Ljava/lang/String; - public final fun getY-tWtZOCg ()Ljava/lang/String; - public final fun getZ-tWtZOCg ()Ljava/lang/String; -} - -public final class space/kscience/kmath/misc/SymbolKt { - public static final fun get (Ljava/util/Map;Ljava/lang/String;)Ljava/lang/Object; - public static final fun get (Ljava/util/Map;Lspace/kscience/kmath/misc/Symbol;)Ljava/lang/Object; - public static final fun getSymbol ()Lkotlin/properties/ReadOnlyProperty; - public static final fun set (Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)V - public static final fun set (Ljava/util/Map;Lspace/kscience/kmath/misc/Symbol;Ljava/lang/Object;)V +public abstract interface annotation class space/kscience/kmath/misc/PerformancePitfall : java/lang/annotation/Annotation { } public abstract interface annotation class space/kscience/kmath/misc/UnstableKMathAPI : java/lang/annotation/Annotation { @@ -745,7 +744,7 @@ public final class space/kscience/kmath/nd/BufferAlgebraNDKt { public static final fun ring (Lspace/kscience/kmath/nd/AlgebraND$Companion;Lspace/kscience/kmath/operations/Ring;Lkotlin/jvm/functions/Function2;[I)Lspace/kscience/kmath/nd/BufferedRingND; } -public final class space/kscience/kmath/nd/BufferND : space/kscience/kmath/nd/StructureND { +public class space/kscience/kmath/nd/BufferND : space/kscience/kmath/nd/StructureND { public fun (Lspace/kscience/kmath/nd/Strides;Lspace/kscience/kmath/structures/Buffer;)V public fun elements ()Lkotlin/sequences/Sequence; public fun get ([I)Ljava/lang/Object; @@ -786,10 +785,9 @@ public final class space/kscience/kmath/nd/DefaultStrides : space/kscience/kmath public fun equals (Ljava/lang/Object;)Z public fun getLinearSize ()I public fun getShape ()[I - public fun getStrides ()Ljava/util/List; + public fun getStrides ()[I public fun hashCode ()I public fun index (I)[I - public fun offset ([I)I } public final class space/kscience/kmath/nd/DefaultStrides$Companion { @@ -873,6 +871,22 @@ public abstract interface class space/kscience/kmath/nd/GroupND : space/kscience public final class space/kscience/kmath/nd/GroupND$Companion { } +public final class space/kscience/kmath/nd/MutableBufferND : space/kscience/kmath/nd/BufferND, space/kscience/kmath/nd/MutableStructureND { + public fun (Lspace/kscience/kmath/nd/Strides;Lspace/kscience/kmath/structures/MutableBuffer;)V + public final fun getMutableBuffer ()Lspace/kscience/kmath/structures/MutableBuffer; + public fun set ([ILjava/lang/Object;)V +} + +public abstract interface class space/kscience/kmath/nd/MutableStructure1D : space/kscience/kmath/nd/MutableStructureND, space/kscience/kmath/nd/Structure1D, space/kscience/kmath/structures/MutableBuffer { + public fun set ([ILjava/lang/Object;)V +} + +public abstract interface class space/kscience/kmath/nd/MutableStructure2D : space/kscience/kmath/nd/MutableStructureND, space/kscience/kmath/nd/Structure2D { + public fun getColumns ()Ljava/util/List; + public fun getRows ()Ljava/util/List; + public abstract fun set (IILjava/lang/Object;)V +} + public abstract interface class space/kscience/kmath/nd/MutableStructureND : space/kscience/kmath/nd/StructureND { public abstract fun set ([ILjava/lang/Object;)V } @@ -912,10 +926,10 @@ public final class space/kscience/kmath/nd/ShortRingNDKt { public abstract interface class space/kscience/kmath/nd/Strides { public abstract fun getLinearSize ()I public abstract fun getShape ()[I - public abstract fun getStrides ()Ljava/util/List; + public abstract fun getStrides ()[I public abstract fun index (I)[I public fun indices ()Lkotlin/sequences/Sequence; - public abstract fun offset ([I)I + public fun offset ([I)I } public abstract interface class space/kscience/kmath/nd/Structure1D : space/kscience/kmath/nd/StructureND, space/kscience/kmath/structures/Buffer { @@ -929,6 +943,7 @@ public final class space/kscience/kmath/nd/Structure1D$Companion { } public final class space/kscience/kmath/nd/Structure1DKt { + public static final fun as1D (Lspace/kscience/kmath/nd/MutableStructureND;)Lspace/kscience/kmath/nd/MutableStructure1D; public static final fun as1D (Lspace/kscience/kmath/nd/StructureND;)Lspace/kscience/kmath/nd/Structure1D; public static final fun asND (Lspace/kscience/kmath/structures/Buffer;)Lspace/kscience/kmath/nd/Structure1D; } @@ -949,6 +964,7 @@ public final class space/kscience/kmath/nd/Structure2D$Companion { } public final class space/kscience/kmath/nd/Structure2DKt { + public static final fun as2D (Lspace/kscience/kmath/nd/MutableStructureND;)Lspace/kscience/kmath/nd/MutableStructure2D; public static final fun as2D (Lspace/kscience/kmath/nd/StructureND;)Lspace/kscience/kmath/nd/Structure2D; } @@ -988,14 +1004,7 @@ public abstract interface class space/kscience/kmath/operations/Algebra { public fun unaryOperationFunction (Ljava/lang/String;)Lkotlin/jvm/functions/Function1; } -public abstract interface class space/kscience/kmath/operations/AlgebraElement { - public abstract fun getContext ()Lspace/kscience/kmath/operations/Algebra; -} - public final class space/kscience/kmath/operations/AlgebraElementsKt { - public static final fun div (Lspace/kscience/kmath/operations/AlgebraElement;Lspace/kscience/kmath/operations/AlgebraElement;)Lspace/kscience/kmath/operations/AlgebraElement; - public static final fun plus (Lspace/kscience/kmath/operations/AlgebraElement;Lspace/kscience/kmath/operations/AlgebraElement;)Lspace/kscience/kmath/operations/AlgebraElement; - public static final fun times (Lspace/kscience/kmath/operations/AlgebraElement;Lspace/kscience/kmath/operations/AlgebraElement;)Lspace/kscience/kmath/operations/AlgebraElement; } public final class space/kscience/kmath/operations/AlgebraExtensionsKt { @@ -1005,7 +1014,7 @@ public final class space/kscience/kmath/operations/AlgebraExtensionsKt { public static final fun averageWith (Ljava/lang/Iterable;Lspace/kscience/kmath/operations/Ring;)Ljava/lang/Object; public static final fun averageWith (Lkotlin/sequences/Sequence;Lspace/kscience/kmath/operations/Ring;)Ljava/lang/Object; public static final fun power (Lspace/kscience/kmath/operations/Field;Ljava/lang/Object;I)Ljava/lang/Object; - public static final fun power (Lspace/kscience/kmath/operations/Ring;Ljava/lang/Object;I)Ljava/lang/Object; + public static final fun power-jXDDuk8 (Lspace/kscience/kmath/operations/Ring;Ljava/lang/Object;I)Ljava/lang/Object; public static final fun sum (Lspace/kscience/kmath/operations/Ring;Ljava/lang/Iterable;)Ljava/lang/Object; public static final fun sum (Lspace/kscience/kmath/operations/Ring;Lkotlin/sequences/Sequence;)Ljava/lang/Object; public static final fun sumWith (Ljava/lang/Iterable;Lspace/kscience/kmath/operations/Ring;)Ljava/lang/Object; @@ -1013,8 +1022,8 @@ public final class space/kscience/kmath/operations/AlgebraExtensionsKt { } public final class space/kscience/kmath/operations/AlgebraKt { - public static final fun bindSymbol (Lspace/kscience/kmath/operations/Algebra;Lspace/kscience/kmath/misc/Symbol;)Ljava/lang/Object; - public static final fun bindSymbolOrNull (Lspace/kscience/kmath/operations/Algebra;Lspace/kscience/kmath/misc/Symbol;)Ljava/lang/Object; + public static final fun bindSymbol (Lspace/kscience/kmath/operations/Algebra;Lspace/kscience/kmath/expressions/Symbol;)Ljava/lang/Object; + public static final fun bindSymbolOrNull (Lspace/kscience/kmath/operations/Algebra;Lspace/kscience/kmath/expressions/Symbol;)Ljava/lang/Object; public static final fun invoke (Lspace/kscience/kmath/operations/Algebra;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } @@ -1035,6 +1044,7 @@ public final class space/kscience/kmath/operations/BigInt : java/lang/Comparable public final fun modPow (Lspace/kscience/kmath/operations/BigInt;Lspace/kscience/kmath/operations/BigInt;)Lspace/kscience/kmath/operations/BigInt; public final fun or (Lspace/kscience/kmath/operations/BigInt;)Lspace/kscience/kmath/operations/BigInt; public final fun plus (Lspace/kscience/kmath/operations/BigInt;)Lspace/kscience/kmath/operations/BigInt; + public final fun pow-WZ4Q5Ns (I)Lspace/kscience/kmath/operations/BigInt; public final fun rem (I)I public final fun rem (Lspace/kscience/kmath/operations/BigInt;)Lspace/kscience/kmath/operations/BigInt; public final fun shl (I)Lspace/kscience/kmath/operations/BigInt; @@ -1398,6 +1408,11 @@ public final class space/kscience/kmath/operations/JBigIntegerField : space/ksci public abstract interface annotation class space/kscience/kmath/operations/KMathContext : java/lang/annotation/Annotation { } +public final class space/kscience/kmath/operations/LogicAlgebra$Companion { + public final fun getFALSE ()Lspace/kscience/kmath/expressions/Symbol; + public final fun getTRUE ()Lspace/kscience/kmath/expressions/Symbol; +} + public final class space/kscience/kmath/operations/LongRing : space/kscience/kmath/operations/Norm, space/kscience/kmath/operations/NumericAlgebra, space/kscience/kmath/operations/Ring { public static final field INSTANCE Lspace/kscience/kmath/operations/LongRing; public fun add (JJ)Ljava/lang/Long; diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt index abbb46583..88c14d311 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt @@ -5,7 +5,8 @@ package space.kscience.kmath.data -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.Structure2D import space.kscience.kmath.structures.Buffer @@ -18,13 +19,17 @@ import space.kscience.kmath.structures.Buffer public interface ColumnarData { public val size: Int - public operator fun get(symbol: Symbol): Buffer + /** + * Provide a column by symbol or null if column with given symbol is not defined + */ + public operator fun get(symbol: Symbol): Buffer? } /** * A zero-copy method to represent a [Structure2D] as a two-column x-y data. * There could more than two columns in the structure. */ +@OptIn(PerformancePitfall::class) @UnstableKMathAPI public fun Structure2D.asColumnarData(mapping: Map): ColumnarData { require(shape[1] >= mapping.maxOf { it.value }) { "Column index out of bounds" } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt index 663908e90..08bfd3ca3 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt @@ -5,7 +5,8 @@ package space.kscience.kmath.data -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.Structure2D import space.kscience.kmath.structures.Buffer @@ -26,10 +27,10 @@ public interface XYColumnarData : ColumnarData { */ public val y: Buffer - override fun get(symbol: Symbol): Buffer = when (symbol) { + override fun get(symbol: Symbol): Buffer? = when (symbol) { Symbol.x -> x Symbol.y -> y - else -> error("A column for symbol $symbol not found") + else -> null } } @@ -49,6 +50,7 @@ public fun XYColumnarData(x: Buffer, y: Buffer): XYColum * A zero-copy method to represent a [Structure2D] as a two-column x-y data. * There could more than two columns in the structure. */ +@OptIn(PerformancePitfall::class) @UnstableKMathAPI public fun Structure2D.asXYData(xIndex: Int = 0, yIndex: Int = 1): XYColumnarData { require(shape[1] >= max(xIndex, yIndex)) { "Column index out of bounds" } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt index 2ae7233ec..bc21b7f45 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt @@ -5,7 +5,7 @@ package space.kscience.kmath.data -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer @@ -17,10 +17,10 @@ import space.kscience.kmath.structures.Buffer public interface XYZColumnarData : XYColumnarData { public val z: Buffer - override fun get(symbol: Symbol): Buffer = when (symbol) { + override fun get(symbol: Symbol): Buffer? = when (symbol) { Symbol.x -> x Symbol.y -> y Symbol.z -> z - else -> error("A column for symbol $symbol not found") + else -> null } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt index f2346f483..280238447 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt @@ -5,9 +5,6 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.StringSymbol -import space.kscience.kmath.misc.Symbol - /** * Represents expression which structure can be differentiated. * diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt index a9ab8648f..84e66918f 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt @@ -5,8 +5,6 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.StringSymbol -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.operations.Algebra import kotlin.jvm.JvmName import kotlin.properties.ReadOnlyProperty diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt index 994d52a73..951ec9474 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/FunctionalExpressionAlgebra.kt @@ -5,7 +5,6 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.StringSymbol import space.kscience.kmath.operations.* /** diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MST.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MST.kt index 67881d9af..7533024a1 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MST.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MST.kt @@ -5,30 +5,23 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.StringSymbol -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.operations.Algebra import space.kscience.kmath.operations.NumericAlgebra +import space.kscience.kmath.operations.bindSymbol /** * A Mathematical Syntax Tree (MST) node for mathematical expressions. * * @author Alexander Nozik */ -public sealed class MST { - /** - * A node containing raw string. - * - * @property value the value of this node. - */ - public data class Symbolic(val value: String) : MST() +public sealed interface MST { /** * A node containing a numeric value or scalar. * * @property value the value of this number. */ - public data class Numeric(val value: Number) : MST() + public data class Numeric(val value: Number) : MST /** * A node containing an unary operation. @@ -36,7 +29,7 @@ public sealed class MST { * @property operation the identifier of operation. * @property value the argument of this operation. */ - public data class Unary(val operation: String, val value: MST) : MST() + public data class Unary(val operation: String, val value: MST) : MST /** * A node containing binary operation. @@ -45,7 +38,7 @@ public sealed class MST { * @property left the left operand. * @property right the right operand. */ - public data class Binary(val operation: String, val left: MST, val right: MST) : MST() + public data class Binary(val operation: String, val left: MST, val right: MST) : MST } // TODO add a function with named arguments @@ -62,7 +55,7 @@ public fun Algebra.evaluate(node: MST): T = when (node) { is MST.Numeric -> (this as? NumericAlgebra)?.number(node.value) ?: error("Numeric nodes are not supported by $this") - is MST.Symbolic -> bindSymbol(node.value) + is Symbol -> bindSymbol(node) is MST.Unary -> when { this is NumericAlgebra && node.value is MST.Numeric -> unaryOperationFunction(node.operation)(number(node.value.value)) @@ -83,7 +76,7 @@ public fun Algebra.evaluate(node: MST): T = when (node) { } } -internal class InnerAlgebra(val algebra: Algebra, val arguments: Map) : NumericAlgebra { +internal class InnerAlgebra(val algebra: Algebra, val arguments: Map) : NumericAlgebra { override fun bindSymbolOrNull(value: String): T? = algebra.bindSymbolOrNull(value) ?: arguments[StringSymbol(value)] override fun unaryOperation(operation: String, arg: T): T = @@ -108,7 +101,7 @@ internal class InnerAlgebra(val algebra: Algebra, val arguments: Map /** * Interprets the [MST] node with this [Algebra] and optional [arguments] */ -public fun MST.interpret(algebra: Algebra, arguments: Map): T = +public fun MST.interpret(algebra: Algebra, arguments: Map): T = InnerAlgebra(algebra, arguments).evaluate(this) /** @@ -118,7 +111,7 @@ public fun MST.interpret(algebra: Algebra, arguments: Map MST.interpret(algebra: Algebra, vararg arguments: Pair): T = +public fun MST.interpret(algebra: Algebra, vararg arguments: Pair): T = interpret(algebra, mapOf(*arguments)) /** diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MstAlgebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MstAlgebra.kt index 32a7efc1e..4729f19ea 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MstAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/MstAlgebra.kt @@ -11,10 +11,10 @@ import space.kscience.kmath.operations.* /** * [Algebra] over [MST] nodes. */ -public object MstAlgebra : NumericAlgebra { +public object MstNumericAlgebra : NumericAlgebra { public override fun number(value: Number): MST.Numeric = MST.Numeric(value) - public override fun bindSymbolOrNull(value: String): MST.Symbolic = MST.Symbolic(value) - override fun bindSymbol(value: String): MST.Symbolic = bindSymbolOrNull(value) + public override fun bindSymbolOrNull(value: String): Symbol = StringSymbol(value) + override fun bindSymbol(value: String): Symbol = bindSymbolOrNull(value) public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary = { arg -> MST.Unary(operation, arg) } @@ -29,8 +29,8 @@ public object MstAlgebra : NumericAlgebra { public object MstGroup : Group, NumericAlgebra, ScaleOperations { public override val zero: MST.Numeric = number(0.0) - public override fun number(value: Number): MST.Numeric = MstAlgebra.number(value) - public override fun bindSymbolOrNull(value: String): MST.Symbolic = MstAlgebra.bindSymbolOrNull(value) + public override fun number(value: Number): MST.Numeric = MstNumericAlgebra.number(value) + public override fun bindSymbolOrNull(value: String): Symbol = MstNumericAlgebra.bindSymbolOrNull(value) public override fun add(a: MST, b: MST): MST.Binary = binaryOperationFunction(GroupOperations.PLUS_OPERATION)(a, b) public override operator fun MST.unaryPlus(): MST.Unary = unaryOperationFunction(GroupOperations.PLUS_OPERATION)(this) @@ -45,10 +45,10 @@ public object MstGroup : Group, NumericAlgebra, ScaleOperations { binaryOperationFunction(RingOperations.TIMES_OPERATION)(a, number(value)) public override fun binaryOperationFunction(operation: String): (left: MST, right: MST) -> MST.Binary = - MstAlgebra.binaryOperationFunction(operation) + MstNumericAlgebra.binaryOperationFunction(operation) public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary = - MstAlgebra.unaryOperationFunction(operation) + MstNumericAlgebra.unaryOperationFunction(operation) } /** @@ -61,7 +61,7 @@ public object MstRing : Ring, NumbersAddOperations, ScaleOperations, NumbersAddOperations, ScaleOperations MST.Unary = - MstAlgebra.unaryOperationFunction(operation) + MstNumericAlgebra.unaryOperationFunction(operation) } /** @@ -90,7 +90,7 @@ public object MstField : Field, NumbersAddOperations, ScaleOperations< public override inline val zero: MST.Numeric get() = MstRing.zero public override inline val one: MST.Numeric get() = MstRing.one - public override fun bindSymbolOrNull(value: String): MST.Symbolic = MstAlgebra.bindSymbolOrNull(value) + public override fun bindSymbolOrNull(value: String): Symbol = MstNumericAlgebra.bindSymbolOrNull(value) public override fun number(value: Number): MST.Numeric = MstRing.number(value) public override fun add(a: MST, b: MST): MST.Binary = MstRing.add(a, b) @@ -120,7 +120,7 @@ public object MstExtendedField : ExtendedField, NumericAlgebra { public override inline val zero: MST.Numeric get() = MstField.zero public override inline val one: MST.Numeric get() = MstField.one - public override fun bindSymbolOrNull(value: String): MST.Symbolic = MstAlgebra.bindSymbolOrNull(value) + public override fun bindSymbolOrNull(value: String): Symbol = MstNumericAlgebra.bindSymbolOrNull(value) public override fun number(value: Number): MST.Numeric = MstRing.number(value) public override fun sin(arg: MST): MST.Unary = unaryOperationFunction(TrigonometricOperations.SIN_OPERATION)(arg) public override fun cos(arg: MST): MST.Unary = unaryOperationFunction(TrigonometricOperations.COS_OPERATION)(arg) @@ -135,6 +135,7 @@ public object MstExtendedField : ExtendedField, NumericAlgebra { public override fun acosh(arg: MST): MST.Unary = unaryOperationFunction(ExponentialOperations.ACOSH_OPERATION)(arg) public override fun atanh(arg: MST): MST.Unary = unaryOperationFunction(ExponentialOperations.ATANH_OPERATION)(arg) public override fun add(a: MST, b: MST): MST.Binary = MstField.add(a, b) + public override fun sqrt(arg: MST): MST = unaryOperationFunction(PowerOperations.SQRT_OPERATION)(arg) public override fun scale(a: MST, value: Double): MST = binaryOperation(GroupOperations.PLUS_OPERATION, a, number(value)) @@ -157,3 +158,25 @@ public object MstExtendedField : ExtendedField, NumericAlgebra { public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary = MstField.unaryOperationFunction(operation) } + +/** + * Logic algebra for [MST] + */ +@UnstableKMathAPI +public object MstLogicAlgebra : LogicAlgebra { + public override fun bindSymbolOrNull(value: String): MST = super.bindSymbolOrNull(value) ?: StringSymbol(value) + + override fun const(boolean: Boolean): Symbol = if (boolean) { + LogicAlgebra.TRUE + } else { + LogicAlgebra.FALSE + } + + override fun MST.not(): MST = MST.Unary(Boolean::not.name, this) + + override fun MST.and(other: MST): MST = MST.Binary(Boolean::and.name, this, other) + + override fun MST.or(other: MST): MST = MST.Binary(Boolean::or.name, this, other) + + override fun MST.xor(other: MST): MST = MST.Binary(Boolean::xor.name, this, other) +} diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SimpleAutoDiff.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SimpleAutoDiff.kt index 09a5faa12..254d60b3d 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SimpleAutoDiff.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SimpleAutoDiff.kt @@ -6,7 +6,6 @@ package space.kscience.kmath.expressions import space.kscience.kmath.linear.Point -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.* import space.kscience.kmath.structures.asBuffer diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Symbol.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt similarity index 88% rename from kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Symbol.kt rename to kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt index b9fb6879a..74dc7aedc 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Symbol.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt @@ -3,21 +3,22 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package space.kscience.kmath.misc +package space.kscience.kmath.expressions import kotlin.jvm.JvmInline import kotlin.properties.ReadOnlyProperty /** - * A marker interface for a symbol. A symbol mus have an identity + * A marker interface for a symbol. A symbol must have an identity. + * Ic */ -public interface Symbol { +public interface Symbol : MST { /** * Identity object for the symbol. Two symbols with the same identity are considered to be the same symbol. */ public val identity: String - public companion object{ + public companion object { public val x: StringSymbol = StringSymbol("x") public val y: StringSymbol = StringSymbol("y") public val z: StringSymbol = StringSymbol("z") @@ -47,7 +48,7 @@ public operator fun Map.get(symbol: Symbol): T? = get(symbol.iden /** * Set a value of [String]-keyed map by a [Symbol] */ -public operator fun MutableMap.set(symbol: Symbol, value: T){ +public operator fun MutableMap.set(symbol: Symbol, value: T) { set(symbol.identity, value) } @@ -59,6 +60,6 @@ public operator fun Map.get(string: String): T? = get(StringSymbo /** * Set a value of [String]-keyed map by a [Symbol] */ -public operator fun MutableMap.set(string: String, value: T){ +public operator fun MutableMap.set(string: String, value: T) { set(StringSymbol(string), value) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt index ea72c5b9e..3a3fd7841 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt @@ -6,7 +6,6 @@ package space.kscience.kmath.expressions import space.kscience.kmath.linear.Point -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.Structure2D import space.kscience.kmath.structures.BufferFactory diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/BufferedLinearSpace.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/BufferedLinearSpace.kt index 62d2408e3..9b4451a62 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/BufferedLinearSpace.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/BufferedLinearSpace.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.linear +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.nd.* import space.kscience.kmath.operations.Ring import space.kscience.kmath.operations.invoke @@ -50,6 +51,7 @@ public class BufferedLinearSpace>( this } + @OptIn(PerformancePitfall::class) override fun Matrix.dot(other: Matrix): Matrix { require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" } return elementAlgebra { @@ -67,6 +69,7 @@ public class BufferedLinearSpace>( } } + @OptIn(PerformancePitfall::class) override fun Matrix.dot(vector: Point): Point { require(colNum == vector.size) { "Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})" } return elementAlgebra { diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt index 0798e8763..ec073ac48 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt @@ -19,6 +19,7 @@ import kotlin.reflect.KClass * @param T the type of items. */ public typealias Matrix = Structure2D +public typealias MutableMatrix = MutableStructure2D /** * Alias or using [Buffer] as a point/vector in a many-dimensional space. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt index e6115a1e5..72d22233a 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt @@ -8,7 +8,7 @@ package space.kscience.kmath.linear import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Ring -public class MatrixBuilder>( +public class MatrixBuilder>( public val linearSpace: LinearSpace, public val rows: Int, public val columns: Int, diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt index 4a0ca7dfe..37c93d249 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt @@ -75,6 +75,23 @@ public object LFeature : MatrixFeature */ public object UFeature : MatrixFeature +/** + * Matrices with this feature support LU factorization: *a = [l] · [u]* where *a* is the owning matrix. + * + * @param T the type of matrices' items. + */ +public interface LUDecompositionFeature : MatrixFeature { + /** + * The lower triangular matrix in this decomposition. It may have [LFeature]. + */ + public val l: Matrix + + /** + * The upper triangular matrix in this decomposition. It may have [UFeature]. + */ + public val u: Matrix +} + /** * Matrices with this feature support LU factorization with partial pivoting: *[p] · a = [l] · [u]* where * *a* is the owning matrix. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt index 4d1180c17..16aadab3b 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt @@ -16,7 +16,7 @@ import kotlin.reflect.KClass * * @param T the type of items. */ -public class MatrixWrapper internal constructor( +public class MatrixWrapper internal constructor( public val origin: Matrix, public val features: Set, ) : Matrix by origin { @@ -26,12 +26,11 @@ public class MatrixWrapper internal constructor( */ @UnstableKMathAPI @Suppress("UNCHECKED_CAST") - override fun getFeature(type: KClass): F? = features.singleOrNull { type.isInstance(it) } as? F - ?: origin.getFeature(type) + public override fun getFeature(type: KClass): F? = + features.singleOrNull(type::isInstance) as? F + ?: origin.getFeature(type) - override fun toString(): String { - return "MatrixWrapper(matrix=$origin, features=$features)" - } + public override fun toString(): String = "MatrixWrapper(matrix=$origin, features=$features)" } /** @@ -82,7 +81,7 @@ public fun LinearSpace>.zero( elementAlgebra.zero } + ZeroFeature -public class TransposedFeature(public val original: Matrix) : MatrixFeature +public class TransposedFeature(public val original: Matrix) : MatrixFeature /** * Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A` diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt index e4c2b49f0..3751bd33b 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt @@ -10,7 +10,7 @@ package space.kscience.kmath.linear * * @property generator the function that provides elements. */ -public class VirtualMatrix( +public class VirtualMatrix( override val rowNum: Int, override val colNum: Int, public val generator: (i: Int, j: Int) -> T, diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt index 206e4e000..e521e6237 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt @@ -5,5 +5,27 @@ package space.kscience.kmath.misc +/** + * Marks declarations that are still experimental in the KMath APIs, which means that the design of the corresponding + * declarations has open issues which may (or may not) lead to their changes in the future. Roughly speaking, there is + * a chance that those declarations will be deprecated in the near future or the semantics of their behavior may change + * in some way that may break some code. + */ +@MustBeDocumented +@Retention(value = AnnotationRetention.BINARY) @RequiresOptIn("This API is unstable and could change in future", RequiresOptIn.Level.WARNING) public annotation class UnstableKMathAPI + +/** + * Marks API which could cause performance problems. The code, marked by this API is not necessary slow, but could cause + * slow-down in some cases. Refer to the documentation and benchmark it to be sure. + */ +@MustBeDocumented +@Retention(value = AnnotationRetention.BINARY) +@RequiresOptIn( + "Refer to the documentation to use this API in performance-critical code", + RequiresOptIn.Level.WARNING +) +public annotation class PerformancePitfall( + val message: String = "Potential performance problem" +) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt index 1b3cb9e0a..35bbc44f6 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/AlgebraND.kt @@ -26,7 +26,7 @@ public class ShapeMismatchException(public val expected: IntArray, public val ac * @param C the type of the element context. * @param N the type of the structure. */ -public interface AlgebraND> { +public interface AlgebraND> { /** * The shape of ND-structures this algebra operates on. */ @@ -121,7 +121,7 @@ internal fun > AlgebraND.checkShape(element: StructureND * @param N the type of ND structure. * @param S the type of space of structure elements. */ -public interface GroupND> : Group>, AlgebraND { +public interface GroupND> : Group>, AlgebraND { /** * Element-wise addition. * @@ -189,7 +189,7 @@ public interface GroupND> : Group>, AlgebraND> : Ring>, GroupND { +public interface RingND> : Ring>, GroupND { /** * Element-wise multiplication. * @@ -227,10 +227,9 @@ public interface RingND> : Ring>, GroupND { * Field of [StructureND]. * * @param T the type of the element contained in ND structure. - * @param N the type of ND structure. * @param F the type field of structure elements. */ -public interface FieldND> : Field>, RingND, ScaleOperations> { +public interface FieldND> : Field>, RingND, ScaleOperations> { /** * Element-wise division. * diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferAlgebraND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferAlgebraND.kt index 905a48d5c..2b82a36ae 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferAlgebraND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferAlgebraND.kt @@ -11,7 +11,7 @@ import space.kscience.kmath.structures.BufferFactory import kotlin.contracts.InvocationKind import kotlin.contracts.contract -public interface BufferAlgebraND> : AlgebraND { +public interface BufferAlgebraND> : AlgebraND { public val strides: Strides public val bufferFactory: BufferFactory diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt index 23d961a7e..904419302 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/BufferND.kt @@ -5,8 +5,11 @@ package space.kscience.kmath.nd +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.BufferFactory +import space.kscience.kmath.structures.MutableBuffer +import space.kscience.kmath.structures.MutableBufferFactory /** * Represents [StructureND] over [Buffer]. @@ -15,7 +18,7 @@ import space.kscience.kmath.structures.BufferFactory * @param strides The strides to access elements of [Buffer] by linear indices. * @param buffer The underlying buffer. */ -public class BufferND( +public open class BufferND( public val strides: Strides, public val buffer: Buffer, ) : StructureND { @@ -30,6 +33,7 @@ public class BufferND( override val shape: IntArray get() = strides.shape + @PerformancePitfall override fun elements(): Sequence> = strides.indices().map { it to this[it] } @@ -50,4 +54,35 @@ public inline fun StructureND.mapToBuffer( val strides = DefaultStrides(shape) BufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) } +} + +/** + * Represents [MutableStructureND] over [MutableBuffer]. + * + * @param T the type of items. + * @param strides The strides to access elements of [MutableBuffer] by linear indices. + * @param mutableBuffer The underlying buffer. + */ +public class MutableBufferND( + strides: Strides, + public val mutableBuffer: MutableBuffer, +) : MutableStructureND, BufferND(strides, mutableBuffer) { + override fun set(index: IntArray, value: T) { + mutableBuffer[strides.offset(index)] = value + } +} + +/** + * Transform structure to a new structure using provided [MutableBufferFactory] and optimizing if argument is [MutableBufferND] + */ +public inline fun MutableStructureND.mapToMutableBuffer( + factory: MutableBufferFactory = MutableBuffer.Companion::auto, + crossinline transform: (T) -> R, +): MutableBufferND { + return if (this is MutableBufferND) + MutableBufferND(this.strides, factory.invoke(strides.linearSize) { transform(mutableBuffer[it]) }) + else { + val strides = DefaultStrides(shape) + MutableBufferND(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) + } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt index 0f0588fda..150ebf6fb 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure1D.kt @@ -5,14 +5,17 @@ package space.kscience.kmath.nd +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.MutableBuffer +import space.kscience.kmath.structures.asMutableBuffer import space.kscience.kmath.structures.asSequence import kotlin.jvm.JvmInline /** * A structure that is guaranteed to be one-dimensional */ -public interface Structure1D : StructureND, Buffer { +public interface Structure1D : StructureND, Buffer { public override val dimension: Int get() = 1 public override operator fun get(index: IntArray): T { @@ -25,33 +28,87 @@ public interface Structure1D : StructureND, Buffer { public companion object } +/** + * A mutable structure that is guaranteed to be one-dimensional + */ +public interface MutableStructure1D : Structure1D, MutableStructureND, MutableBuffer { + public override operator fun set(index: IntArray, value: T) { + require(index.size == 1) { "Index dimension mismatch. Expected 1 but found ${index.size}" } + set(index[0], value) + } +} + /** * A 1D wrapper for nd-structure */ @JvmInline -private value class Structure1DWrapper(val structure: StructureND) : Structure1D { +private value class Structure1DWrapper(val structure: StructureND) : Structure1D { override val shape: IntArray get() = structure.shape override val size: Int get() = structure.shape[0] override operator fun get(index: Int): T = structure[index] + + @PerformancePitfall override fun elements(): Sequence> = structure.elements() } +/** + * A 1D wrapper for a mutable nd-structure + */ +private class MutableStructure1DWrapper(val structure: MutableStructureND) : MutableStructure1D { + override val shape: IntArray get() = structure.shape + override val size: Int get() = structure.shape[0] + + @PerformancePitfall + override fun elements(): Sequence> = structure.elements() + + override fun get(index: Int): T = structure[index] + override fun set(index: Int, value: T) { + structure[intArrayOf(index)] = value + } + + @PerformancePitfall + override fun copy(): MutableBuffer = structure + .elements() + .map(Pair::second) + .toMutableList() + .asMutableBuffer() +} + /** * A structure wrapper for buffer */ @JvmInline -private value class Buffer1DWrapper(val buffer: Buffer) : Structure1D { +private value class Buffer1DWrapper(val buffer: Buffer) : Structure1D { override val shape: IntArray get() = intArrayOf(buffer.size) override val size: Int get() = buffer.size - override fun elements(): Sequence> = - buffer.asSequence().mapIndexed { index, value -> intArrayOf(index) to value } + @PerformancePitfall + override fun elements(): Sequence> = buffer.asSequence().mapIndexed { index, value -> + intArrayOf(index) to value + } override operator fun get(index: Int): T = buffer[index] } +internal class MutableBuffer1DWrapper(val buffer: MutableBuffer) : MutableStructure1D { + override val shape: IntArray get() = intArrayOf(buffer.size) + override val size: Int get() = buffer.size + + @PerformancePitfall + override fun elements(): Sequence> = buffer.asSequence().mapIndexed { index, value -> + intArrayOf(index) to value + } + + override operator fun get(index: Int): T = buffer[index] + override fun set(index: Int, value: T) { + buffer[index] = value + } + + override fun copy(): MutableBuffer = buffer.copy() +} + /** * Represent a [StructureND] as [Structure1D]. Throw error in case of dimension mismatch */ @@ -62,6 +119,11 @@ public fun StructureND.as1D(): Structure1D = this as? Structure1D ? } } else error("Can't create 1d-structure from ${shape.size}d-structure") +public fun MutableStructureND.as1D(): MutableStructure1D = + this as? MutableStructure1D ?: if (shape.size == 1) { + MutableStructure1DWrapper(this) + } else error("Can't create 1d-structure from ${shape.size}d-structure") + /** * Represent this buffer as 1D structure */ @@ -75,3 +137,4 @@ internal fun Structure1D.unwrap(): Buffer = when { this is Structure1DWrapper && structure is BufferND -> structure.buffer else -> this } + diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure2D.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure2D.kt index 3eee41832..f353b6974 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure2D.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/Structure2D.kt @@ -5,8 +5,10 @@ package space.kscience.kmath.nd +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.MutableListBuffer import space.kscience.kmath.structures.VirtualBuffer import kotlin.jvm.JvmInline import kotlin.reflect.KClass @@ -16,7 +18,7 @@ import kotlin.reflect.KClass * * @param T the type of items. */ -public interface Structure2D : StructureND { +public interface Structure2D : StructureND { /** * The number of rows in this structure. */ @@ -32,12 +34,14 @@ public interface Structure2D : StructureND { /** * The buffer of rows of this structure. It gets elements from the structure dynamically. */ + @PerformancePitfall public val rows: List> get() = List(rowNum) { i -> VirtualBuffer(colNum) { j -> get(i, j) } } /** * The buffer of columns of this structure. It gets elements from the structure dynamically. */ + @PerformancePitfall public val columns: List> get() = List(colNum) { j -> VirtualBuffer(rowNum) { i -> get(i, j) } } @@ -55,6 +59,7 @@ public interface Structure2D : StructureND { return get(index[0], index[1]) } + @PerformancePitfall 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)) @@ -63,11 +68,39 @@ public interface Structure2D : StructureND { public companion object } +/** + * Represents mutable [Structure2D]. + */ +public interface MutableStructure2D : Structure2D, MutableStructureND { + /** + * Inserts an item at the specified indices. + * + * @param i the first index. + * @param j the second index. + * @param value the value. + */ + public operator fun set(i: Int, j: Int, value: T) + + /** + * The buffer of rows of this structure. It gets elements from the structure dynamically. + */ + @PerformancePitfall + override val rows: List> + get() = List(rowNum) { i -> MutableBuffer1DWrapper(MutableListBuffer(colNum) { j -> get(i, j) })} + + /** + * The buffer of columns of this structure. It gets elements from the structure dynamically. + */ + @PerformancePitfall + override val columns: List> + get() = List(colNum) { j -> MutableBuffer1DWrapper(MutableListBuffer(rowNum) { i -> get(i, j) }) } +} + /** * A 2D wrapper for nd-structure */ @JvmInline -private value class Structure2DWrapper(val structure: StructureND) : Structure2D { +private value class Structure2DWrapper(val structure: StructureND) : Structure2D { override val shape: IntArray get() = structure.shape override val rowNum: Int get() = shape[0] @@ -78,9 +111,38 @@ private value class Structure2DWrapper(val structure: StructureND) : Struc @UnstableKMathAPI override fun getFeature(type: KClass): F? = structure.getFeature(type) + @PerformancePitfall override fun elements(): Sequence> = structure.elements() } +/** + * A 2D wrapper for a mutable nd-structure + */ +private class MutableStructure2DWrapper(val structure: MutableStructureND): MutableStructure2D +{ + override val shape: IntArray get() = structure.shape + + override val rowNum: Int get() = shape[0] + override val colNum: Int get() = shape[1] + + override operator fun get(i: Int, j: Int): T = structure[i, j] + + override fun set(index: IntArray, value: T) { + structure[index] = value + } + + override operator fun set(i: Int, j: Int, value: T){ + structure[intArrayOf(i, j)] = value + } + + @PerformancePitfall + override fun elements(): Sequence> = structure.elements() + + override fun equals(other: Any?): Boolean = false + + override fun hashCode(): Int = 0 +} + /** * Represent a [StructureND] as [Structure1D]. Throw error in case of dimension mismatch */ @@ -89,9 +151,18 @@ public fun StructureND.as2D(): Structure2D = this as? Structure2D ? else -> error("Can't create 2d-structure from ${shape.size}d-structure") } +public fun MutableStructureND.as2D(): MutableStructure2D = this as? MutableStructure2D ?: when (shape.size) { + 2 -> MutableStructure2DWrapper(this) + else -> error("Can't create 2d-structure from ${shape.size}d-structure") +} + /** * Expose inner [StructureND] if possible */ internal fun Structure2D.unwrap(): StructureND = if (this is Structure2DWrapper) structure - else this \ No newline at end of file + else this + +internal fun MutableStructure2D.unwrap(): MutableStructureND = + if (this is MutableStructure2DWrapper) structure else this + diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt index 0656b1f7f..7fc91e321 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.nd +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.BufferFactory @@ -23,7 +24,7 @@ public interface StructureFeature * * @param T the type of items. */ -public interface StructureND { +public interface StructureND { /** * The shape of structure, i.e. non-empty sequence of non-negative integers that specify sizes of dimensions of * this structure. @@ -48,6 +49,7 @@ public interface StructureND { * * @return the lazy sequence of pairs of indices to values. */ + @PerformancePitfall public fun elements(): Sequence> /** @@ -61,6 +63,7 @@ public interface StructureND { /** * Indicates whether some [StructureND] is equal to another one. */ + @PerformancePitfall public fun contentEquals(st1: StructureND, st2: StructureND): Boolean { if (st1 === st2) return true @@ -169,6 +172,7 @@ public interface MutableStructureND : StructureND { /** * Transform a structure element-by element in place. */ +@OptIn(PerformancePitfall::class) public inline fun MutableStructureND.mapInPlace(action: (IntArray, T) -> T): Unit = elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } @@ -184,12 +188,15 @@ public interface Strides { /** * Array strides */ - public val strides: List + public val strides: IntArray /** * Get linear index from multidimensional index */ - public fun offset(index: IntArray): Int + public fun offset(index: IntArray): Int = index.mapIndexed { i, value -> + if (value < 0 || value >= shape[i]) throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") + value * strides[i] + }.sum() /** * Get multidimensional from linear @@ -206,9 +213,7 @@ public interface Strides { /** * Iterate over ND indices in a natural order */ - public fun indices(): Sequence = (0 until linearSize).asSequence().map { - index(it) - } + public fun indices(): Sequence = (0 until linearSize).asSequence().map(::index) } /** @@ -221,7 +226,7 @@ public class DefaultStrides private constructor(override val shape: IntArray) : /** * Strides for memory access */ - override val strides: List by lazy { + override val strides: IntArray by lazy { sequence { var current = 1 yield(1) @@ -230,14 +235,9 @@ public class DefaultStrides private constructor(override val shape: IntArray) : current *= it yield(current) } - }.toList() + }.toList().toIntArray() } - override fun offset(index: IntArray): Int = index.mapIndexed { i, value -> - if (value < 0 || value >= shape[i]) throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") - value * strides[i] - }.sum() - override fun index(offset: Int): IntArray { val res = IntArray(shape.size) var current = offset diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index c994580bd..3a1ec430e 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -5,7 +5,7 @@ package space.kscience.kmath.operations -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.Symbol /** * Stub for DSL the [Algebra] is. @@ -84,7 +84,7 @@ public interface Algebra { * @return an operation. */ public fun binaryOperationFunction(operation: String): (left: T, right: T) -> T = - error("Binary operation $operation not defined in $this") + error("Binary operation '$operation' not defined in $this") /** * Dynamically invokes a binary operation with the certain name. @@ -247,7 +247,7 @@ public interface RingOperations : GroupOperations { */ public interface Ring : Group, RingOperations { /** - * neutral operation for multiplication + * The neutral element of multiplication */ public val one: T } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/AlgebraElements.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/AlgebraElements.kt index d7c87f213..cc058d3fc 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/AlgebraElements.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/AlgebraElements.kt @@ -13,6 +13,8 @@ import space.kscience.kmath.misc.UnstableKMathAPI * @param C the type of mathematical context for this element. * @param T the type wrapped by this wrapper. */ +@UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public interface AlgebraElement> { /** * The context this element belongs to. @@ -45,6 +47,7 @@ public interface AlgebraElement> { * @return the difference. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public operator fun , S : NumbersAddOperations> T.minus(b: T): T = context.add(this, context.run { -b }) @@ -55,6 +58,8 @@ public operator fun , S : NumbersAddOperations> T.mi * @param b the addend. * @return the sum. */ +@UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public operator fun , S : Ring> T.plus(b: T): T = context.add(this, b) @@ -71,6 +76,8 @@ public operator fun , S : Ring> T.plus(b: T): T = * @param b the multiplier. * @return the product. */ +@UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public operator fun , R : Ring> T.times(b: T): T = context.multiply(this, b) @@ -81,6 +88,8 @@ public operator fun , R : Ring> T.times(b: T): T = * @param b the divisor. * @return the quotient. */ +@UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public operator fun , F : Field> T.div(b: T): T = context.divide(this, b) @@ -93,6 +102,7 @@ public operator fun , F : Field> T.div(b: T): T = * @param S the type of space. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public interface GroupElement, S : Group> : AlgebraElement /** @@ -103,6 +113,7 @@ public interface GroupElement, S : Group> : AlgebraEle * @param R the type of ring. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public interface RingElement, R : Ring> : GroupElement /** @@ -113,4 +124,5 @@ public interface RingElement, R : Ring> : GroupElement< * @param F the type of field. */ @UnstableKMathAPI -public interface FieldElement, F : Field> : RingElement \ No newline at end of file +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") +public interface FieldElement, F : Field> : RingElement diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index e74efa9ca..ac53c4d5e 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -56,8 +56,7 @@ public class BigInt internal constructor( else -> sign * compareMagnitudes(magnitude, other.magnitude) } - public override fun equals(other: Any?): Boolean = - if (other is BigInt) compareTo(other) == 0 else error("Can't compare KBigInteger to a different type") + public override fun equals(other: Any?): Boolean = other is BigInt && compareTo(other) == 0 public override fun hashCode(): Int = magnitude.hashCode() + sign @@ -87,20 +86,25 @@ public class BigInt internal constructor( public operator fun times(b: BigInt): BigInt = when { this.sign == 0.toByte() -> ZERO b.sign == 0.toByte() -> ZERO -// TODO: Karatsuba + b.magnitude.size == 1 -> this * b.magnitude[0] * b.sign.toInt() + this.magnitude.size == 1 -> b * this.magnitude[0] * this.sign.toInt() else -> BigInt((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) } public operator fun times(other: UInt): BigInt = when { sign == 0.toByte() -> ZERO other == 0U -> ZERO + other == 1U -> this else -> BigInt(sign, multiplyMagnitudeByUInt(magnitude, other)) } - public operator fun times(other: Int): BigInt = if (other > 0) - this * kotlin.math.abs(other).toUInt() - else - -this * kotlin.math.abs(other).toUInt() + public fun pow(exponent: UInt): BigInt = BigIntField.power(this, exponent) + + public operator fun times(other: Int): BigInt = when { + other > 0 -> this * kotlin.math.abs(other).toUInt() + other != Int.MIN_VALUE -> -this * kotlin.math.abs(other).toUInt() + else -> times(other.toBigInt()) + } public operator fun div(other: UInt): BigInt = BigInt(this.sign, divideMagnitudeByUInt(this.magnitude, other)) @@ -238,6 +242,7 @@ public class BigInt internal constructor( public const val BASE_SIZE: Int = 32 public val ZERO: BigInt = BigInt(0, uintArrayOf()) public val ONE: BigInt = BigInt(1, uintArrayOf(1u)) + private const val KARATSUBA_THRESHOLD = 80 private val hexMapping: HashMap = hashMapOf( 0U to "0", 1U to "1", 2U to "2", 3U to "3", @@ -276,7 +281,7 @@ public class BigInt internal constructor( } result[i] = (res and BASE).toUInt() - carry = (res shr BASE_SIZE) + carry = res shr BASE_SIZE } result[resultLength - 1] = carry.toUInt() @@ -318,7 +323,14 @@ public class BigInt internal constructor( return stripLeadingZeros(result) } - private fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + internal fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude = when { + mag1.size + mag2.size < KARATSUBA_THRESHOLD || mag1.isEmpty() || mag2.isEmpty() -> + naiveMultiplyMagnitudes(mag1, mag2) + // TODO implement Fourier + else -> karatsubaMultiplyMagnitudes(mag1, mag2) + } + + internal fun naiveMultiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { val resultLength = mag1.size + mag2.size val result = Magnitude(resultLength) @@ -337,6 +349,21 @@ public class BigInt internal constructor( return stripLeadingZeros(result) } + internal fun karatsubaMultiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + //https://en.wikipedia.org/wiki/Karatsuba_algorithm + val halfSize = min(mag1.size, mag2.size) / 2 + val x0 = mag1.sliceArray(0 until halfSize).toBigInt(1) + val x1 = mag1.sliceArray(halfSize until mag1.size).toBigInt(1) + val y0 = mag2.sliceArray(0 until halfSize).toBigInt(1) + val y1 = mag2.sliceArray(halfSize until mag2.size).toBigInt(1) + + val z0 = x0 * y0 + val z2 = x1 * y1 + val z1 = (x0 - x1) * (y1 - y0) + z0 + z2 + + return (z2.shl(2 * halfSize * BASE_SIZE) + z1.shl(halfSize * BASE_SIZE) + z0).magnitude + } + private fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { val resultLength = mag.size val result = Magnitude(resultLength) @@ -414,58 +441,90 @@ public fun UIntArray.toBigInt(sign: Byte): BigInt { return BigInt(sign, copyOf()) } -private val hexChToInt: MutableMap = hashMapOf( - '0' to 0, '1' to 1, '2' to 2, '3' to 3, - '4' to 4, '5' to 5, '6' to 6, '7' to 7, - '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, - 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15 -) - /** * Returns null if a valid number can not be read from a string */ public fun String.parseBigInteger(): BigInt? { + if (this.isEmpty()) return null val sign: Int - val sPositive: String - when { - this[0] == '+' -> { + val positivePartIndex = when (this[0]) { + '+' -> { sign = +1 - sPositive = this.substring(1) + 1 } - this[0] == '-' -> { + '-' -> { sign = -1 - sPositive = this.substring(1) + 1 } else -> { - sPositive = this sign = +1 + 0 } } - var res = BigInt.ZERO - var digitValue = BigInt.ONE - val sPositiveUpper = sPositive.uppercase() + var isEmpty = true - if (sPositiveUpper.startsWith("0X")) { // hex representation - val sHex = sPositiveUpper.substring(2) + return if (this.startsWith("0X", startIndex = positivePartIndex, ignoreCase = true)) { + // hex representation - for (ch in sHex.reversed()) { - if (ch == '_') continue - res += digitValue * (hexChToInt[ch] ?: return null) - digitValue *= 16.toBigInt() + val uInts = ArrayList(length).apply { add(0U) } + var offset = 0 + fun addDigit(value: UInt) { + uInts[uInts.lastIndex] += value shl offset + offset += 4 + if (offset == 32) { + uInts.add(0U) + offset = 0 + } } - } else for (ch in sPositiveUpper.reversed()) { + + for (index in lastIndex downTo positivePartIndex + 2) { + when (val ch = this[index]) { + '_' -> continue + in '0'..'9' -> addDigit((ch - '0').toUInt()) + in 'A'..'F' -> addDigit((ch - 'A').toUInt() + 10U) + in 'a'..'f' -> addDigit((ch - 'a').toUInt() + 10U) + else -> return null + } + isEmpty = false + } + + while (uInts.isNotEmpty() && uInts.last() == 0U) + uInts.removeLast() + + if (isEmpty) null else BigInt(sign.toByte(), uInts.toUIntArray()) + } else { // decimal representation - if (ch == '_') continue - if (ch !in '0'..'9') { - return null - } - res += digitValue * (ch.code - '0'.code) - digitValue *= 10.toBigInt() - } - return res * sign + val positivePart = buildList(length) { + for (index in positivePartIndex until length) + when (val a = this@parseBigInteger[index]) { + '_' -> continue + in '0'..'9' -> add(a) + else -> return null + } + } + + val offset = positivePart.size % 9 + isEmpty = offset == 0 + + fun parseUInt(fromIndex: Int, toIndex: Int): UInt? { + var res = 0U + for (i in fromIndex until toIndex) { + res = res * 10U + (positivePart[i].digitToIntOrNull()?.toUInt() ?: return null) + } + return res + } + + var res = parseUInt(0, offset)?.toBigInt() ?: return null + + for (index in offset..positivePart.lastIndex step 9) { + isEmpty = false + res = res * 1_000_000_000U + (parseUInt(index, index + 9) ?: return null).toBigInt() + } + if (isEmpty) null else res * sign + } } public inline fun Buffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): Buffer = diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/LogicAlgebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/LogicAlgebra.kt new file mode 100644 index 000000000..9037525e1 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/LogicAlgebra.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.operations + +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.symbol +import space.kscience.kmath.misc.UnstableKMathAPI + +/** + * An algebra for generic boolean logic + */ +@UnstableKMathAPI +public interface LogicAlgebra : Algebra { + + /** + * Represent constant [Boolean] as [T] + */ + public fun const(boolean: Boolean): T + + override fun bindSymbolOrNull(value: String): T? = value.lowercase().toBooleanStrictOrNull()?.let(::const) + + override fun unaryOperation(operation: String, arg: T): T = when (operation) { + Boolean::not.name -> arg.not() + else -> super.unaryOperation(operation, arg) + } + + override fun unaryOperationFunction(operation: String): (arg: T) -> T = { unaryOperation(operation, it) } + + override fun binaryOperation(operation: String, left: T, right: T): T = when (operation) { + Boolean::and.name -> left.and(right) + Boolean::or.name -> left.or(right) + else -> super.binaryOperation(operation, left, right) + } + + override fun binaryOperationFunction(operation: String): (left: T, right: T) -> T = { l, r -> + binaryOperation(operation, l, r) + } + + /** + * Logic 'not' + */ + public operator fun T.not(): T + + /** + * Logic 'and' + */ + public infix fun T.and(other: T): T + + /** + * Logic 'or' + */ + public infix fun T.or(other: T): T + + /** + * Logic 'xor' + */ + public infix fun T.xor(other: T): T + + + public companion object { + public val TRUE: Symbol by symbol + public val FALSE: Symbol by symbol + } +} + +/** + * An implementation of [LogicAlgebra] for primitive booleans + */ +@UnstableKMathAPI +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +public object BooleanAlgebra : LogicAlgebra { + + override fun const(boolean: Boolean): Boolean = boolean + + override fun Boolean.not(): Boolean = !this + + override fun Boolean.and(other: Boolean): Boolean = this && other + + override fun Boolean.or(other: Boolean): Boolean = this || other + + override fun Boolean.xor(other: Boolean): Boolean = this xor other +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt index 8e3e6c777..86365394f 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt @@ -80,36 +80,42 @@ public interface TrigonometricOperations : Algebra { * Computes the sine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> sin(arg: T): T = arg.context.sin(arg) /** * Computes the cosine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> cos(arg: T): T = arg.context.cos(arg) /** * Computes the tangent of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> tan(arg: T): T = arg.context.tan(arg) /** * Computes the inverse sine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> asin(arg: T): T = arg.context.asin(arg) /** * Computes the inverse cosine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> acos(arg: T): T = arg.context.acos(arg) /** * Computes the inverse tangent of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> atan(arg: T): T = arg.context.atan(arg) /** @@ -147,25 +153,28 @@ public interface PowerOperations : Algebra { } /** - * Raises this element to the power [pow]. + * Raises this element to the power [power]. * * @receiver the base. * @param power the exponent. * @return the base raised to the power. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public infix fun >> T.pow(power: Double): T = context.power(this, power) /** * Computes the square root of the value [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> sqrt(arg: T): T = arg pow 0.5 /** * Computes the square of the value [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> sqr(arg: T): T = arg pow 2.0 /** @@ -261,12 +270,14 @@ public interface ExponentialOperations : Algebra { * The identifier of exponential function. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> exp(arg: T): T = arg.context.exp(arg) /** * The identifier of natural logarithm. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> ln(arg: T): T = arg.context.ln(arg) @@ -280,30 +291,35 @@ public fun >> sinh(arg: T): T * Computes the hyperbolic cosine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> cosh(arg: T): T = arg.context.cosh(arg) /** * Computes the hyperbolic tangent of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> tanh(arg: T): T = arg.context.tanh(arg) /** * Computes the inverse hyperbolic sine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> asinh(arg: T): T = arg.context.asinh(arg) /** * Computes the inverse hyperbolic cosine of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> acosh(arg: T): T = arg.context.acosh(arg) /** * Computes the inverse hyperbolic tangent of [arg]. */ @UnstableKMathAPI +@Deprecated("AlgebraElements are considered odd and will be removed in future releases.") public fun >> atanh(arg: T): T = arg.context.atanh(arg) /** diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt index b8670553d..d52be943a 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt @@ -16,6 +16,8 @@ public fun Ring.sum(data: Iterable): T = data.fold(zero) { left, right add(left, right) } +//TODO replace by sumOf with multi-receivers + /** * Returns the sum of all elements in the sequence in this [Ring]. * @@ -94,37 +96,46 @@ public fun Iterable.averageWith(space: S): T where S : Ring, S : Sc public fun Sequence.averageWith(space: S): T where S : Ring, S : ScaleOperations = space.average(this) -//TODO optimized power operation - /** - * Raises [arg] to the natural power [power]. + * Raises [arg] to the non-negative integer power [exponent]. + * + * Special case: 0 ^ 0 is 1. * * @receiver the algebra to provide multiplication. * @param arg the base. - * @param power the exponent. + * @param exponent the exponent. * @return the base raised to the power. + * @author Evgeniy Zhelenskiy */ -public fun Ring.power(arg: T, power: Int): T { - require(power >= 0) { "The power can't be negative." } - require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." } - if (power == 0) return one - var res = arg - repeat(power - 1) { res *= arg } - return res +public fun Ring.power(arg: T, exponent: UInt): T = when { + arg == zero && exponent > 0U -> zero + arg == one -> arg + arg == -one -> powWithoutOptimization(arg, exponent % 2U) + else -> powWithoutOptimization(arg, exponent) } +private fun Ring.powWithoutOptimization(base: T, exponent: UInt): T = when (exponent) { + 0U -> one + 1U -> base + else -> { + val pre = powWithoutOptimization(base, exponent shr 1).let { it * it } + if (exponent and 1U == 0U) pre else pre * base + } +} + + /** - * Raises [arg] to the integer power [power]. + * Raises [arg] to the integer power [exponent]. + * + * Special case: 0 ^ 0 is 1. * * @receiver the algebra to provide multiplication and division. * @param arg the base. - * @param power the exponent. + * @param exponent the exponent. * @return the base raised to the power. - * @author Iaroslav Postovalov + * @author Iaroslav Postovalov, Evgeniy Zhelenskiy */ -public fun Field.power(arg: T, power: Int): T { - require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." } - if (power == 0) return one - if (power < 0) return one / (this as Ring).power(arg, -power) - return (this as Ring).power(arg, power) +public fun Field.power(arg: T, exponent: Int): T = when { + exponent < 0 -> one / (this as Ring).power(arg, if (exponent == Int.MIN_VALUE) Int.MAX_VALUE.toUInt().inc() else (-exponent).toUInt()) + else -> (this as Ring).power(arg, exponent.toUInt()) } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt index d187beab1..82f17b807 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/Buffer.kt @@ -232,7 +232,7 @@ public value class MutableListBuffer(public val list: MutableList) : Mutab } /** - * Returns an [ListBuffer] that wraps the original list. + * Returns an [MutableListBuffer] that wraps the original list. */ public fun MutableList.asMutableBuffer(): MutableListBuffer = MutableListBuffer(this) @@ -283,7 +283,7 @@ public value class ReadOnlyBuffer(public val buffer: MutableBuffer) : Buff * * @param T the type of elements provided by the buffer. */ -public class VirtualBuffer(override val size: Int, private val generator: (Int) -> T) : Buffer { +public class VirtualBuffer(override val size: Int, private val generator: (Int) -> T) : Buffer { override operator 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) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/FlaggedBuffer.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/FlaggedBuffer.kt index a63f452f5..0b16a3afc 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/FlaggedBuffer.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/FlaggedBuffer.kt @@ -37,7 +37,7 @@ public enum class ValueFlag(public val mask: Byte) { /** * A buffer with flagged values. */ -public interface FlaggedBuffer : Buffer { +public interface FlaggedBuffer : Buffer { public fun getFlag(index: Int): Byte } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt index cf3a565bc..4d1b00b3d 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt @@ -5,9 +5,7 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.invoke import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails @@ -17,7 +15,7 @@ class ExpressionFieldTest { @Test fun testExpression() { - val expression = FunctionalExpressionField(DoubleField).invoke { + val expression = with(FunctionalExpressionField(DoubleField)) { val x by binding() x * x + 2 * x + one } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/InterpretTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/InterpretTest.kt index 980819364..156334b2e 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/InterpretTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/InterpretTest.kt @@ -5,19 +5,32 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.Symbol.Companion.x +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.BooleanAlgebra import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.bindSymbol import space.kscience.kmath.operations.invoke import kotlin.test.Test +import kotlin.test.assertEquals + internal class InterpretTest { @Test fun interpretation() { val expr = MstField { - val x = bindSymbol(Symbol.x) x * 2.0 + number(2.0) / x - 16.0 }.toExpression(DoubleField) - expr(Symbol.x to 2.2) + assertEquals(-10.69, expr(x to 2.2), 0.02) + } + + @Test + @UnstableKMathAPI + fun booleanAlgebra() { + val expr = MstLogicAlgebra { + x and const(true) + }.toExpression(BooleanAlgebra) + + assertEquals(true, expr(x to true)) + assertEquals(false, expr(x to false)) } } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/SimpleAutoDiffTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/SimpleAutoDiffTest.kt index ab509650a..201890933 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/SimpleAutoDiffTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/SimpleAutoDiffTest.kt @@ -5,8 +5,6 @@ package space.kscience.kmath.expressions -import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.bindSymbol import space.kscience.kmath.structures.Buffer @@ -19,7 +17,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -class SimpleAutoDiffTest { +internal class SimpleAutoDiffTest { fun dx( xBinding: Pair, diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt index ebbbbe392..2d2a0952b 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt @@ -5,12 +5,14 @@ package space.kscience.kmath.linear +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureND import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +@OptIn(PerformancePitfall::class) fun assertMatrixEquals(expected: StructureND, actual: StructureND) { assertTrue { StructureND.contentEquals(expected, actual) } } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt index 250fa3433..170f9caf4 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.linear +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.as2D @@ -13,6 +14,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue @UnstableKMathAPI +@OptIn(PerformancePitfall::class) @Suppress("UNUSED_VARIABLE") class MatrixTest { @Test diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt index 5df89a385..0527f5252 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt @@ -5,7 +5,9 @@ package space.kscience.kmath.operations +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.testutils.RingVerifier +import kotlin.math.pow import kotlin.test.Test import kotlin.test.assertEquals @@ -21,6 +23,18 @@ internal class BigIntAlgebraTest { assertEquals(res, 1_000_000.toBigInt()) } + @UnstableKMathAPI + @Test + fun testKBigIntegerRingPow() { + for (num in -5..5) + for (exponent in 0U..10U) + assertEquals( + num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), + num.toBigInt().pow(exponent), + "$num ^ $exponent" + ) + } + @Test fun testKBigIntegerRingSum_100_000_000__100_000_000() { BigIntField { diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt index a2832e531..85f368f3e 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt @@ -7,15 +7,43 @@ package space.kscience.kmath.operations import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull @kotlin.ExperimentalUnsignedTypes class BigIntConversionsTest { + + @Test + fun testEmptyString() { + assertNull("".parseBigInteger()) + assertNull("+".parseBigInteger()) + assertNull("-".parseBigInteger()) + + assertNull("0x".parseBigInteger()) + assertNull("+0x".parseBigInteger()) + assertNull("-0x".parseBigInteger()) + + + assertNull("_".parseBigInteger()) + assertNull("+_".parseBigInteger()) + assertNull("-_".parseBigInteger()) + + assertNull("0x_".parseBigInteger()) + assertNull("+0x_".parseBigInteger()) + assertNull("-0x_".parseBigInteger()) + } + @Test fun testToString0x10() { val x = 0x10.toBigInt() assertEquals("0x10", x.toString()) } + @Test + fun testUnderscores() { + assertEquals("0x10", "0x_1_0_".parseBigInteger().toString()) + assertEquals("0xa", "_1_0_".parseBigInteger().toString()) + } + @Test fun testToString0x17ffffffd() { val x = 0x17ffffffdL.toBigInt() diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt index ae34dbc04..26d6af224 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt @@ -5,8 +5,12 @@ package space.kscience.kmath.operations +import kotlin.random.Random +import kotlin.random.nextUInt import kotlin.test.Test +import kotlin.test.assertContentEquals import kotlin.test.assertEquals +import kotlin.test.assertFalse @kotlin.ExperimentalUnsignedTypes class BigIntOperationsTest { @@ -150,6 +154,17 @@ class BigIntOperationsTest { assertEquals(prod, res) } + @Test + fun testKaratsuba() { + val random = Random(2222) + val x = uintArrayOf(12U, 345U) + val y = uintArrayOf(6U, 789U) + assertContentEquals(BigInt.naiveMultiplyMagnitudes(x, y), BigInt.karatsubaMultiplyMagnitudes(x, y)) + val x1 = UIntArray(Random.nextInt(100, 1000)) { random.nextUInt() } + val y1 = UIntArray(Random.nextInt(100, 1000)) { random.nextUInt() } + assertContentEquals(BigInt.naiveMultiplyMagnitudes(x1, y1), BigInt.karatsubaMultiplyMagnitudes(x1, y1)) + } + @Test fun test_shr_20() { val x = 20.toBigInt() @@ -383,4 +398,12 @@ class BigIntOperationsTest { return assertEquals(res, x % mod) } + + @Test + fun testNotEqualsOtherTypeInstanceButButNotFails() = assertFalse(0.toBigInt().equals("")) + + @Test + fun testIntAbsOverflow() { + assertEquals((-Int.MAX_VALUE.toLong().toBigInt() - 1.toBigInt()) * 2, 2.toBigInt() * Int.MIN_VALUE) + } } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt index c482dc978..76171fedd 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt @@ -18,4 +18,16 @@ internal class DoubleFieldTest { val sqrt = DoubleField { sqrt(25 * one) } assertEquals(5.0, sqrt) } + + @Test + fun testPow() = DoubleField { + val num = 5 * one + assertEquals(5.0, power(num, 1), 0.01) + assertEquals(25.0, power(num, 2), 0.01) + assertEquals(1.0, power(num, 0), 0.01) + assertEquals(0.2, power(num, -1), 0.01) + assertEquals(0.04, power(num, -2), 0.01) + assertEquals(0.0, power(num, Int.MIN_VALUE), 0.01) + assertEquals(1.0, power(zero, 0), 0.01) + } } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt index 99743d879..fb51553f7 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.structures import space.kscience.kmath.linear.LinearSpace +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.nd.* import space.kscience.kmath.operations.Norm import space.kscience.kmath.operations.invoke @@ -73,8 +74,9 @@ class NumberNDFieldTest { val division = array1.combine(array2, Double::div) } - object L2Norm : Norm, Double> { - override fun norm(arg: StructureND): Double = + object L2Norm : Norm, Double> { + @OptIn(PerformancePitfall::class) + override fun norm(arg: StructureND): Double = kotlin.math.sqrt(arg.elements().sumOf { it.second.toDouble() }) } diff --git a/kmath-coroutines/src/jvmMain/kotlin/space/kscience/kmath/structures/LazyStructureND.kt b/kmath-coroutines/src/jvmMain/kotlin/space/kscience/kmath/structures/LazyStructureND.kt index e2ecb4b2f..ded8c9c44 100644 --- a/kmath-coroutines/src/jvmMain/kotlin/space/kscience/kmath/structures/LazyStructureND.kt +++ b/kmath-coroutines/src/jvmMain/kotlin/space/kscience/kmath/structures/LazyStructureND.kt @@ -7,10 +7,11 @@ package space.kscience.kmath.structures import kotlinx.coroutines.* import space.kscience.kmath.coroutines.Math +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.nd.DefaultStrides import space.kscience.kmath.nd.StructureND -public class LazyStructureND( +public class LazyStructureND( public val scope: CoroutineScope, public override val shape: IntArray, public val function: suspend (IntArray) -> T, @@ -24,6 +25,7 @@ public class LazyStructureND( public suspend fun await(index: IntArray): T = deferred(index).await() public override operator fun get(index: IntArray): T = runBlocking { deferred(index).await() } + @OptIn(PerformancePitfall::class) public override fun elements(): Sequence> { val strides = DefaultStrides(shape) val res = runBlocking { strides.indices().toList().map { index -> index to await(index) } } diff --git a/kmath-ejml/README.md b/kmath-ejml/README.md index 3bf29f803..79a28b824 100644 --- a/kmath-ejml/README.md +++ b/kmath-ejml/README.md @@ -2,36 +2,34 @@ EJML based linear algebra implementation. - - [ejml-vector](src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt) : The Point implementation using SimpleMatrix. - - [ejml-matrix](src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt) : The Matrix implementation using SimpleMatrix. - - [ejml-linear-space](src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt) : The LinearSpace implementation using SimpleMatrix. + - [ejml-vector](src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt) : Point implementations. + - [ejml-matrix](src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt) : Matrix implementation. + - [ejml-linear-space](src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt) : LinearSpace implementations. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-ejml:0.3.0-dev-7' + implementation 'space.kscience:kmath-ejml:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-ejml:0.3.0-dev-7") + implementation("space.kscience:kmath-ejml:0.3.0-dev-11") } ``` diff --git a/kmath-ejml/build.gradle.kts b/kmath-ejml/build.gradle.kts index d3a49aeb0..5107cfb68 100644 --- a/kmath-ejml/build.gradle.kts +++ b/kmath-ejml/build.gradle.kts @@ -1,10 +1,15 @@ +import space.kscience.kmath.ejml.codegen.ejmlCodegen + plugins { kotlin("jvm") id("ru.mipt.npm.gradle.common") } dependencies { - api("org.ejml:ejml-simple:0.40") + api("org.ejml:ejml-ddense:0.40") + api("org.ejml:ejml-fdense:0.40") + api("org.ejml:ejml-dsparse:0.40") + api("org.ejml:ejml-fsparse:0.40") api(project(":kmath-core")) } @@ -14,19 +19,24 @@ readme { feature( id = "ejml-vector", - description = "The Point implementation using SimpleMatrix.", ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt" - ) + ) { "Point implementations." } feature( id = "ejml-matrix", - description = "The Matrix implementation using SimpleMatrix.", ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt" - ) + ) { "Matrix implementation." } feature( id = "ejml-linear-space", - description = "The LinearSpace implementation using SimpleMatrix.", ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt" - ) + ) { "LinearSpace implementations." } +} + +kotlin.sourceSets.main { + val codegen by tasks.creating { + ejmlCodegen(kotlin.srcDirs.first().absolutePath + "/space/kscience/kmath/ejml/_generated.kt") + } + + kotlin.srcDirs(files().builtBy(codegen)) } diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt index 4b6421c9b..f88e83369 100644 --- a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt +++ b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt @@ -5,205 +5,35 @@ package space.kscience.kmath.ejml -import org.ejml.dense.row.factory.DecompositionFactory_DDRM -import org.ejml.simple.SimpleMatrix -import space.kscience.kmath.linear.* -import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.nd.StructureFeature -import space.kscience.kmath.nd.getFeature -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.structures.DoubleBuffer -import kotlin.reflect.KClass -import kotlin.reflect.cast +import space.kscience.kmath.linear.LinearSpace +import space.kscience.kmath.linear.Matrix +import space.kscience.kmath.linear.Point +import space.kscience.kmath.operations.Ring /** - * Represents context of basic operations operating with [EjmlMatrix]. + * [LinearSpace] implementation specialized for a certain EJML type. * + * @param T the type of items in the matrices. + * @param A the element context type. + * @param M the EJML matrix type. * @author Iaroslav Postovalov - * @author Alexander Nozik */ -public object EjmlLinearSpace : LinearSpace { - /** - * The [DoubleField] reference. - */ - public override val elementAlgebra: DoubleField get() = DoubleField - +public abstract class EjmlLinearSpace, out M : org.ejml.data.Matrix> : LinearSpace { /** * Converts this matrix to EJML one. */ - @OptIn(UnstableKMathAPI::class) - public fun Matrix.toEjml(): EjmlMatrix = when (val matrix = origin) { - is EjmlMatrix -> matrix - else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } - } + public abstract fun Matrix.toEjml(): EjmlMatrix /** * Converts this vector to EJML one. */ - public fun Point.toEjml(): EjmlVector = when (this) { - is EjmlVector -> this - else -> EjmlVector(SimpleMatrix(size, 1).also { - (0 until it.numRows()).forEach { row -> it[row, 0] = get(row) } - }) - } + public abstract fun Point.toEjml(): EjmlVector - public override fun buildMatrix( + public abstract override fun buildMatrix( rows: Int, columns: Int, - initializer: DoubleField.(i: Int, j: Int) -> Double, - ): EjmlMatrix = EjmlMatrix(SimpleMatrix(rows, columns).also { - (0 until rows).forEach { row -> - (0 until columns).forEach { col -> it[row, col] = DoubleField.initializer(row, col) } - } - }) + initializer: A.(i: Int, j: Int) -> T, + ): EjmlMatrix - public override fun buildVector(size: Int, initializer: DoubleField.(Int) -> Double): Point = - EjmlVector(SimpleMatrix(size, 1).also { - (0 until it.numRows()).forEach { row -> it[row, 0] = DoubleField.initializer(row) } - }) - - private fun SimpleMatrix.wrapMatrix() = EjmlMatrix(this) - private fun SimpleMatrix.wrapVector() = EjmlVector(this) - - public override fun Matrix.unaryMinus(): Matrix = this * (-1.0) - - public override fun Matrix.dot(other: Matrix): EjmlMatrix = - EjmlMatrix(toEjml().origin.mult(other.toEjml().origin)) - - public override fun Matrix.dot(vector: Point): EjmlVector = - EjmlVector(toEjml().origin.mult(vector.toEjml().origin)) - - public override operator fun Matrix.minus(other: Matrix): EjmlMatrix = - (toEjml().origin - other.toEjml().origin).wrapMatrix() - - public override operator fun Matrix.times(value: Double): EjmlMatrix = - toEjml().origin.scale(value).wrapMatrix() - - public override fun Point.unaryMinus(): EjmlVector = - toEjml().origin.negative().wrapVector() - - public override fun Matrix.plus(other: Matrix): EjmlMatrix = - (toEjml().origin + other.toEjml().origin).wrapMatrix() - - public override fun Point.plus(other: Point): EjmlVector = - (toEjml().origin + other.toEjml().origin).wrapVector() - - public override fun Point.minus(other: Point): EjmlVector = - (toEjml().origin - other.toEjml().origin).wrapVector() - - public override fun Double.times(m: Matrix): EjmlMatrix = - m.toEjml().origin.scale(this).wrapMatrix() - - public override fun Point.times(value: Double): EjmlVector = - toEjml().origin.scale(value).wrapVector() - - public override fun Double.times(v: Point): EjmlVector = - v.toEjml().origin.scale(this).wrapVector() - - @UnstableKMathAPI - public override fun getFeature(structure: Matrix, type: KClass): F? { - //Return the feature if it is intrinsic to the structure - structure.getFeature(type)?.let { return it } - - val origin = structure.toEjml().origin - - return when (type) { - InverseMatrixFeature::class -> object : InverseMatrixFeature { - override val inverse: Matrix by lazy { EjmlMatrix(origin.invert()) } - } - - DeterminantFeature::class -> object : DeterminantFeature { - override val determinant: Double by lazy(origin::determinant) - } - - SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { - private val svd by lazy { - DecompositionFactory_DDRM.svd(origin.numRows(), origin.numCols(), true, true, false) - .apply { decompose(origin.ddrm.copy()) } - } - - override val u: Matrix by lazy { EjmlMatrix(SimpleMatrix(svd.getU(null, false))) } - override val s: Matrix by lazy { EjmlMatrix(SimpleMatrix(svd.getW(null))) } - override val v: Matrix by lazy { EjmlMatrix(SimpleMatrix(svd.getV(null, false))) } - override val singularValues: Point by lazy { DoubleBuffer(svd.singularValues) } - } - - QRDecompositionFeature::class -> object : QRDecompositionFeature { - private val qr by lazy { - DecompositionFactory_DDRM.qr().apply { decompose(origin.ddrm.copy()) } - } - - override val q: Matrix by lazy { - EjmlMatrix(SimpleMatrix(qr.getQ(null, false))) + OrthogonalFeature - } - - override val r: Matrix by lazy { EjmlMatrix(SimpleMatrix(qr.getR(null, false))) + UFeature } - } - - CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { - override val l: Matrix by lazy { - val cholesky = - DecompositionFactory_DDRM.chol(structure.rowNum, true).apply { decompose(origin.ddrm.copy()) } - - EjmlMatrix(SimpleMatrix(cholesky.getT(null))) + LFeature - } - } - - LupDecompositionFeature::class -> object : LupDecompositionFeature { - private val lup by lazy { - DecompositionFactory_DDRM.lu(origin.numRows(), origin.numCols()) - .apply { decompose(origin.ddrm.copy()) } - } - - override val l: Matrix by lazy { - EjmlMatrix(SimpleMatrix(lup.getLower(null))) + LFeature - } - - override val u: Matrix by lazy { - EjmlMatrix(SimpleMatrix(lup.getUpper(null))) + UFeature - } - - override val p: Matrix by lazy { EjmlMatrix(SimpleMatrix(lup.getRowPivot(null))) } - } - - else -> null - }?.let(type::cast) - } + public abstract override fun buildVector(size: Int, initializer: A.(Int) -> T): EjmlVector } - -/** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p matrix. - * @return the solution for 'x' that is n by p. - * @author Iaroslav Postovalov - */ -public fun EjmlLinearSpace.solve(a: Matrix, b: Matrix): EjmlMatrix = - EjmlMatrix(a.toEjml().origin.solve(b.toEjml().origin)) - -/** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p vector. - * @return the solution for 'x' that is n by p. - * @author Iaroslav Postovalov - */ -public fun EjmlLinearSpace.solve(a: Matrix, b: Point): EjmlVector = - EjmlVector(a.toEjml().origin.solve(b.toEjml().origin)) - -/** - * Inverts this matrix. - * - * @author Alexander Nozik - */ -@OptIn(UnstableKMathAPI::class) -public fun EjmlMatrix.inverted(): EjmlMatrix = getFeature>()!!.inverse as EjmlMatrix - -/** - * Inverts the given matrix. - * - * @author Alexander Nozik - */ -public fun EjmlLinearSpace.inverse(matrix: Matrix): Matrix = matrix.toEjml().inverted() \ No newline at end of file diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt index 32907d199..cec31eb7d 100644 --- a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt +++ b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt @@ -5,18 +5,18 @@ package space.kscience.kmath.ejml -import org.ejml.simple.SimpleMatrix -import space.kscience.kmath.linear.Matrix +import org.ejml.data.Matrix +import space.kscience.kmath.nd.Structure2D /** - * The matrix implementation over EJML [SimpleMatrix]. + * [space.kscience.kmath.linear.Matrix] implementation based on EJML [Matrix]. * - * @property origin the underlying [SimpleMatrix]. + * @param T the type of elements contained in the buffer. + * @param M the type of EJML matrix. + * @property origin The underlying EJML matrix. * @author Iaroslav Postovalov */ -public class EjmlMatrix(public val origin: SimpleMatrix) : Matrix { - public override val rowNum: Int get() = origin.numRows() - public override val colNum: Int get() = origin.numCols() - - public override operator fun get(i: Int, j: Int): Double = origin[i, j] +public abstract class EjmlMatrix(public open val origin: M) : Structure2D { + public override val rowNum: Int get() = origin.numRows + public override val colNum: Int get() = origin.numCols } diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt index 2f4b4a8e2..5d10d1fbb 100644 --- a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt +++ b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt @@ -5,34 +5,30 @@ package space.kscience.kmath.ejml -import org.ejml.simple.SimpleMatrix +import org.ejml.data.Matrix import space.kscience.kmath.linear.Point /** - * Represents point over EJML [SimpleMatrix]. + * [Point] implementation based on EJML [Matrix]. * - * @property origin the underlying [SimpleMatrix]. + * @param T the type of elements contained in the buffer. + * @param M the type of EJML matrix. + * @property origin The underlying matrix, must have only one row. * @author Iaroslav Postovalov */ -public class EjmlVector internal constructor(public val origin: SimpleMatrix) : Point { +public abstract class EjmlVector(public open val origin: M) : Point { public override val size: Int - get() = origin.numRows() + get() = origin.numCols - init { - require(origin.numCols() == 1) { "Only single column matrices are allowed" } - } - - public override operator fun get(index: Int): Double = origin[index] - - public override operator fun iterator(): Iterator = object : Iterator { + public override operator fun iterator(): Iterator = object : Iterator { private var cursor: Int = 0 - override fun next(): Double { + override fun next(): T { cursor += 1 - return origin[cursor - 1] + return this@EjmlVector[cursor - 1] } - override fun hasNext(): Boolean = cursor < origin.numCols() * origin.numRows() + override fun hasNext(): Boolean = cursor < origin.numCols * origin.numRows } public override fun toString(): String = "EjmlVector(origin=$origin)" diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt new file mode 100644 index 000000000..139c55697 --- /dev/null +++ b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt @@ -0,0 +1,995 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +/* This file is generated with buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt */ + +package space.kscience.kmath.ejml + +import org.ejml.data.* +import org.ejml.dense.row.CommonOps_DDRM +import org.ejml.dense.row.CommonOps_FDRM +import org.ejml.dense.row.factory.DecompositionFactory_DDRM +import org.ejml.dense.row.factory.DecompositionFactory_FDRM +import org.ejml.sparse.FillReducing +import org.ejml.sparse.csc.CommonOps_DSCC +import org.ejml.sparse.csc.CommonOps_FSCC +import org.ejml.sparse.csc.factory.DecompositionFactory_DSCC +import org.ejml.sparse.csc.factory.DecompositionFactory_FSCC +import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC +import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC +import space.kscience.kmath.linear.* +import space.kscience.kmath.linear.Matrix +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.nd.StructureFeature +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.FloatField +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.FloatBuffer +import kotlin.reflect.KClass +import kotlin.reflect.cast + +/** + * [EjmlVector] specialization for [Double]. + */ +public class EjmlDoubleVector(public override val origin: M) : EjmlVector(origin) { + init { + require(origin.numRows == 1) { "The origin matrix must have only one row to form a vector" } + } + + public override operator fun get(index: Int): Double = origin[0, index] +} + +/** + * [EjmlVector] specialization for [Float]. + */ +public class EjmlFloatVector(public override val origin: M) : EjmlVector(origin) { + init { + require(origin.numRows == 1) { "The origin matrix must have only one row to form a vector" } + } + + public override operator fun get(index: Int): Float = origin[0, index] +} + +/** + * [EjmlMatrix] specialization for [Double]. + */ +public class EjmlDoubleMatrix(public override val origin: M) : EjmlMatrix(origin) { + public override operator fun get(i: Int, j: Int): Double = origin[i, j] +} + +/** + * [EjmlMatrix] specialization for [Float]. + */ +public class EjmlFloatMatrix(public override val origin: M) : EjmlMatrix(origin) { + public override operator fun get(i: Int, j: Int): Float = origin[i, j] +} + +/** + * [EjmlLinearSpace] implementation based on [CommonOps_DDRM], [DecompositionFactory_DDRM] operations and + * [DMatrixRMaj] matrices. + */ +public object EjmlLinearSpaceDDRM : EjmlLinearSpace() { + /** + * The [DoubleField] reference. + */ + public override val elementAlgebra: DoubleField get() = DoubleField + + @Suppress("UNCHECKED_CAST") + public override fun Matrix.toEjml(): EjmlDoubleMatrix = when { + this is EjmlDoubleMatrix<*> && origin is DMatrixRMaj -> this as EjmlDoubleMatrix + else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } + } + + @Suppress("UNCHECKED_CAST") + public override fun Point.toEjml(): EjmlDoubleVector = when { + this is EjmlDoubleVector<*> && origin is DMatrixRMaj -> this as EjmlDoubleVector + else -> EjmlDoubleVector(DMatrixRMaj(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } + }) + } + + public override fun buildMatrix( + rows: Int, + columns: Int, + initializer: DoubleField.(i: Int, j: Int) -> Double, + ): EjmlDoubleMatrix = DMatrixRMaj(rows, columns).also { + (0 until rows).forEach { row -> + (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } + } + }.wrapMatrix() + + public override fun buildVector( + size: Int, + initializer: DoubleField.(Int) -> Double, + ): EjmlDoubleVector = EjmlDoubleVector(DMatrixRMaj(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } + }) + + private fun T.wrapMatrix() = EjmlDoubleMatrix(this) + private fun T.wrapVector() = EjmlDoubleVector(this) + + public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } + + public override fun Matrix.dot(other: Matrix): EjmlDoubleMatrix { + val out = DMatrixRMaj(1, 1) + CommonOps_DDRM.mult(toEjml().origin, other.toEjml().origin, out) + return out.wrapMatrix() + } + + public override fun Matrix.dot(vector: Point): EjmlDoubleVector { + val out = DMatrixRMaj(1, 1) + CommonOps_DDRM.mult(toEjml().origin, vector.toEjml().origin, out) + return out.wrapVector() + } + + public override operator fun Matrix.minus(other: Matrix): EjmlDoubleMatrix { + val out = DMatrixRMaj(1, 1) + + CommonOps_DDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + ) + + return out.wrapMatrix() + } + + public override operator fun Matrix.times(value: Double): EjmlDoubleMatrix { + val res = DMatrixRMaj(1, 1) + CommonOps_DDRM.scale(value, toEjml().origin, res) + return res.wrapMatrix() + } + + public override fun Point.unaryMinus(): EjmlDoubleVector { + val res = DMatrixRMaj(1, 1) + CommonOps_DDRM.changeSign(toEjml().origin, res) + return res.wrapVector() + } + + public override fun Matrix.plus(other: Matrix): EjmlDoubleMatrix { + val out = DMatrixRMaj(1, 1) + + CommonOps_DDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + ) + + return out.wrapMatrix() + } + + public override fun Point.plus(other: Point): EjmlDoubleVector { + val out = DMatrixRMaj(1, 1) + + CommonOps_DDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + ) + + return out.wrapVector() + } + + public override fun Point.minus(other: Point): EjmlDoubleVector { + val out = DMatrixRMaj(1, 1) + + CommonOps_DDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + ) + + return out.wrapVector() + } + + public override fun Double.times(m: Matrix): EjmlDoubleMatrix = m * this + + public override fun Point.times(value: Double): EjmlDoubleVector { + val res = DMatrixRMaj(1, 1) + CommonOps_DDRM.scale(value, toEjml().origin, res) + return res.wrapVector() + } + + public override fun Double.times(v: Point): EjmlDoubleVector = v * this + + @UnstableKMathAPI + public override fun getFeature(structure: Matrix, type: KClass): F? { + structure.getFeature(type)?.let { return it } + val origin = structure.toEjml().origin + + return when (type) { + InverseMatrixFeature::class -> object : InverseMatrixFeature { + override val inverse: Matrix by lazy { + val res = origin.copy() + CommonOps_DDRM.invert(res) + res.wrapMatrix() + } + } + + DeterminantFeature::class -> object : DeterminantFeature { + override val determinant: Double by lazy { CommonOps_DDRM.det(origin) } + } + + SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { + private val svd by lazy { + DecompositionFactory_DDRM.svd(origin.numRows, origin.numCols, true, true, false) + .apply { decompose(origin.copy()) } + } + + override val u: Matrix by lazy { svd.getU(null, false).wrapMatrix() } + override val s: Matrix by lazy { svd.getW(null).wrapMatrix() } + override val v: Matrix by lazy { svd.getV(null, false).wrapMatrix() } + override val singularValues: Point by lazy { DoubleBuffer(svd.singularValues) } + } + + QRDecompositionFeature::class -> object : QRDecompositionFeature { + private val qr by lazy { + DecompositionFactory_DDRM.qr().apply { decompose(origin.copy()) } + } + + override val q: Matrix by lazy { + qr.getQ(null, false).wrapMatrix() + OrthogonalFeature + } + + override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { + override val l: Matrix by lazy { + val cholesky = + DecompositionFactory_DDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) } + + cholesky.getT(null).wrapMatrix() + LFeature + } + } + + LupDecompositionFeature::class -> object : LupDecompositionFeature { + private val lup by lazy { + DecompositionFactory_DDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) } + } + + override val l: Matrix by lazy { + lup.getLower(null).wrapMatrix() + LFeature + } + + override val u: Matrix by lazy { + lup.getUpper(null).wrapMatrix() + UFeature + } + + override val p: Matrix by lazy { lup.getRowPivot(null).wrapMatrix() } + } + + else -> null + }?.let(type::cast) + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p matrix. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Matrix): EjmlDoubleMatrix { + val res = DMatrixRMaj(1, 1) + CommonOps_DDRM.solve(DMatrixRMaj(a.toEjml().origin), DMatrixRMaj(b.toEjml().origin), res) + return res.wrapMatrix() + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p vector. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Point): EjmlDoubleVector { + val res = DMatrixRMaj(1, 1) + CommonOps_DDRM.solve(DMatrixRMaj(a.toEjml().origin), DMatrixRMaj(b.toEjml().origin), res) + return EjmlDoubleVector(res) + } +} + +/** + * [EjmlLinearSpace] implementation based on [CommonOps_FDRM], [DecompositionFactory_FDRM] operations and + * [FMatrixRMaj] matrices. + */ +public object EjmlLinearSpaceFDRM : EjmlLinearSpace() { + /** + * The [FloatField] reference. + */ + public override val elementAlgebra: FloatField get() = FloatField + + @Suppress("UNCHECKED_CAST") + public override fun Matrix.toEjml(): EjmlFloatMatrix = when { + this is EjmlFloatMatrix<*> && origin is FMatrixRMaj -> this as EjmlFloatMatrix + else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } + } + + @Suppress("UNCHECKED_CAST") + public override fun Point.toEjml(): EjmlFloatVector = when { + this is EjmlFloatVector<*> && origin is FMatrixRMaj -> this as EjmlFloatVector + else -> EjmlFloatVector(FMatrixRMaj(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } + }) + } + + public override fun buildMatrix( + rows: Int, + columns: Int, + initializer: FloatField.(i: Int, j: Int) -> Float, + ): EjmlFloatMatrix = FMatrixRMaj(rows, columns).also { + (0 until rows).forEach { row -> + (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } + } + }.wrapMatrix() + + public override fun buildVector( + size: Int, + initializer: FloatField.(Int) -> Float, + ): EjmlFloatVector = EjmlFloatVector(FMatrixRMaj(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } + }) + + private fun T.wrapMatrix() = EjmlFloatMatrix(this) + private fun T.wrapVector() = EjmlFloatVector(this) + + public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } + + public override fun Matrix.dot(other: Matrix): EjmlFloatMatrix { + val out = FMatrixRMaj(1, 1) + CommonOps_FDRM.mult(toEjml().origin, other.toEjml().origin, out) + return out.wrapMatrix() + } + + public override fun Matrix.dot(vector: Point): EjmlFloatVector { + val out = FMatrixRMaj(1, 1) + CommonOps_FDRM.mult(toEjml().origin, vector.toEjml().origin, out) + return out.wrapVector() + } + + public override operator fun Matrix.minus(other: Matrix): EjmlFloatMatrix { + val out = FMatrixRMaj(1, 1) + + CommonOps_FDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + ) + + return out.wrapMatrix() + } + + public override operator fun Matrix.times(value: Float): EjmlFloatMatrix { + val res = FMatrixRMaj(1, 1) + CommonOps_FDRM.scale(value, toEjml().origin, res) + return res.wrapMatrix() + } + + public override fun Point.unaryMinus(): EjmlFloatVector { + val res = FMatrixRMaj(1, 1) + CommonOps_FDRM.changeSign(toEjml().origin, res) + return res.wrapVector() + } + + public override fun Matrix.plus(other: Matrix): EjmlFloatMatrix { + val out = FMatrixRMaj(1, 1) + + CommonOps_FDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + ) + + return out.wrapMatrix() + } + + public override fun Point.plus(other: Point): EjmlFloatVector { + val out = FMatrixRMaj(1, 1) + + CommonOps_FDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + ) + + return out.wrapVector() + } + + public override fun Point.minus(other: Point): EjmlFloatVector { + val out = FMatrixRMaj(1, 1) + + CommonOps_FDRM.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + ) + + return out.wrapVector() + } + + public override fun Float.times(m: Matrix): EjmlFloatMatrix = m * this + + public override fun Point.times(value: Float): EjmlFloatVector { + val res = FMatrixRMaj(1, 1) + CommonOps_FDRM.scale(value, toEjml().origin, res) + return res.wrapVector() + } + + public override fun Float.times(v: Point): EjmlFloatVector = v * this + + @UnstableKMathAPI + public override fun getFeature(structure: Matrix, type: KClass): F? { + structure.getFeature(type)?.let { return it } + val origin = structure.toEjml().origin + + return when (type) { + InverseMatrixFeature::class -> object : InverseMatrixFeature { + override val inverse: Matrix by lazy { + val res = origin.copy() + CommonOps_FDRM.invert(res) + res.wrapMatrix() + } + } + + DeterminantFeature::class -> object : DeterminantFeature { + override val determinant: Float by lazy { CommonOps_FDRM.det(origin) } + } + + SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { + private val svd by lazy { + DecompositionFactory_FDRM.svd(origin.numRows, origin.numCols, true, true, false) + .apply { decompose(origin.copy()) } + } + + override val u: Matrix by lazy { svd.getU(null, false).wrapMatrix() } + override val s: Matrix by lazy { svd.getW(null).wrapMatrix() } + override val v: Matrix by lazy { svd.getV(null, false).wrapMatrix() } + override val singularValues: Point by lazy { FloatBuffer(svd.singularValues) } + } + + QRDecompositionFeature::class -> object : QRDecompositionFeature { + private val qr by lazy { + DecompositionFactory_FDRM.qr().apply { decompose(origin.copy()) } + } + + override val q: Matrix by lazy { + qr.getQ(null, false).wrapMatrix() + OrthogonalFeature + } + + override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { + override val l: Matrix by lazy { + val cholesky = + DecompositionFactory_FDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) } + + cholesky.getT(null).wrapMatrix() + LFeature + } + } + + LupDecompositionFeature::class -> object : LupDecompositionFeature { + private val lup by lazy { + DecompositionFactory_FDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) } + } + + override val l: Matrix by lazy { + lup.getLower(null).wrapMatrix() + LFeature + } + + override val u: Matrix by lazy { + lup.getUpper(null).wrapMatrix() + UFeature + } + + override val p: Matrix by lazy { lup.getRowPivot(null).wrapMatrix() } + } + + else -> null + }?.let(type::cast) + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p matrix. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Matrix): EjmlFloatMatrix { + val res = FMatrixRMaj(1, 1) + CommonOps_FDRM.solve(FMatrixRMaj(a.toEjml().origin), FMatrixRMaj(b.toEjml().origin), res) + return res.wrapMatrix() + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p vector. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Point): EjmlFloatVector { + val res = FMatrixRMaj(1, 1) + CommonOps_FDRM.solve(FMatrixRMaj(a.toEjml().origin), FMatrixRMaj(b.toEjml().origin), res) + return EjmlFloatVector(res) + } +} + +/** + * [EjmlLinearSpace] implementation based on [CommonOps_DSCC], [DecompositionFactory_DSCC] operations and + * [DMatrixSparseCSC] matrices. + */ +public object EjmlLinearSpaceDSCC : EjmlLinearSpace() { + /** + * The [DoubleField] reference. + */ + public override val elementAlgebra: DoubleField get() = DoubleField + + @Suppress("UNCHECKED_CAST") + public override fun Matrix.toEjml(): EjmlDoubleMatrix = when { + this is EjmlDoubleMatrix<*> && origin is DMatrixSparseCSC -> this as EjmlDoubleMatrix + else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } + } + + @Suppress("UNCHECKED_CAST") + public override fun Point.toEjml(): EjmlDoubleVector = when { + this is EjmlDoubleVector<*> && origin is DMatrixSparseCSC -> this as EjmlDoubleVector + else -> EjmlDoubleVector(DMatrixSparseCSC(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } + }) + } + + public override fun buildMatrix( + rows: Int, + columns: Int, + initializer: DoubleField.(i: Int, j: Int) -> Double, + ): EjmlDoubleMatrix = DMatrixSparseCSC(rows, columns).also { + (0 until rows).forEach { row -> + (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } + } + }.wrapMatrix() + + public override fun buildVector( + size: Int, + initializer: DoubleField.(Int) -> Double, + ): EjmlDoubleVector = EjmlDoubleVector(DMatrixSparseCSC(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } + }) + + private fun T.wrapMatrix() = EjmlDoubleMatrix(this) + private fun T.wrapVector() = EjmlDoubleVector(this) + + public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } + + public override fun Matrix.dot(other: Matrix): EjmlDoubleMatrix { + val out = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.mult(toEjml().origin, other.toEjml().origin, out) + return out.wrapMatrix() + } + + public override fun Matrix.dot(vector: Point): EjmlDoubleVector { + val out = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.mult(toEjml().origin, vector.toEjml().origin, out) + return out.wrapVector() + } + + public override operator fun Matrix.minus(other: Matrix): EjmlDoubleMatrix { + val out = DMatrixSparseCSC(1, 1) + + CommonOps_DSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapMatrix() + } + + public override operator fun Matrix.times(value: Double): EjmlDoubleMatrix { + val res = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.scale(value, toEjml().origin, res) + return res.wrapMatrix() + } + + public override fun Point.unaryMinus(): EjmlDoubleVector { + val res = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.changeSign(toEjml().origin, res) + return res.wrapVector() + } + + public override fun Matrix.plus(other: Matrix): EjmlDoubleMatrix { + val out = DMatrixSparseCSC(1, 1) + + CommonOps_DSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapMatrix() + } + + public override fun Point.plus(other: Point): EjmlDoubleVector { + val out = DMatrixSparseCSC(1, 1) + + CommonOps_DSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapVector() + } + + public override fun Point.minus(other: Point): EjmlDoubleVector { + val out = DMatrixSparseCSC(1, 1) + + CommonOps_DSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapVector() + } + + public override fun Double.times(m: Matrix): EjmlDoubleMatrix = m * this + + public override fun Point.times(value: Double): EjmlDoubleVector { + val res = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.scale(value, toEjml().origin, res) + return res.wrapVector() + } + + public override fun Double.times(v: Point): EjmlDoubleVector = v * this + + @UnstableKMathAPI + public override fun getFeature(structure: Matrix, type: KClass): F? { + structure.getFeature(type)?.let { return it } + val origin = structure.toEjml().origin + + return when (type) { + QRDecompositionFeature::class -> object : QRDecompositionFeature { + private val qr by lazy { + DecompositionFactory_DSCC.qr(FillReducing.NONE).apply { decompose(origin.copy()) } + } + + override val q: Matrix by lazy { + qr.getQ(null, false).wrapMatrix() + OrthogonalFeature + } + + override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { + override val l: Matrix by lazy { + val cholesky = + DecompositionFactory_DSCC.cholesky().apply { decompose(origin.copy()) } + + (cholesky.getT(null) as DMatrix).wrapMatrix() + LFeature + } + } + + LUDecompositionFeature::class, DeterminantFeature::class, InverseMatrixFeature::class -> object : + LUDecompositionFeature, DeterminantFeature, InverseMatrixFeature { + private val lu by lazy { + DecompositionFactory_DSCC.lu(FillReducing.NONE).apply { decompose(origin.copy()) } + } + + override val l: Matrix by lazy { + lu.getLower(null).wrapMatrix() + LFeature + } + + override val u: Matrix by lazy { + lu.getUpper(null).wrapMatrix() + UFeature + } + + override val inverse: Matrix by lazy { + var a = origin + val inverse = DMatrixRMaj(1, 1) + val solver = LinearSolverFactory_DSCC.lu(FillReducing.NONE) + if (solver.modifiesA()) a = a.copy() + val i = CommonOps_DDRM.identity(a.numRows) + solver.solve(i, inverse) + inverse.wrapMatrix() + } + + override val determinant: Double by lazy { elementAlgebra.number(lu.computeDeterminant().real) } + } + + else -> null + }?.let(type::cast) + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p matrix. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Matrix): EjmlDoubleMatrix { + val res = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.solve(DMatrixSparseCSC(a.toEjml().origin), DMatrixSparseCSC(b.toEjml().origin), res) + return res.wrapMatrix() + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p vector. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Point): EjmlDoubleVector { + val res = DMatrixSparseCSC(1, 1) + CommonOps_DSCC.solve(DMatrixSparseCSC(a.toEjml().origin), DMatrixSparseCSC(b.toEjml().origin), res) + return EjmlDoubleVector(res) + } +} + +/** + * [EjmlLinearSpace] implementation based on [CommonOps_FSCC], [DecompositionFactory_FSCC] operations and + * [FMatrixSparseCSC] matrices. + */ +public object EjmlLinearSpaceFSCC : EjmlLinearSpace() { + /** + * The [FloatField] reference. + */ + public override val elementAlgebra: FloatField get() = FloatField + + @Suppress("UNCHECKED_CAST") + public override fun Matrix.toEjml(): EjmlFloatMatrix = when { + this is EjmlFloatMatrix<*> && origin is FMatrixSparseCSC -> this as EjmlFloatMatrix + else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } + } + + @Suppress("UNCHECKED_CAST") + public override fun Point.toEjml(): EjmlFloatVector = when { + this is EjmlFloatVector<*> && origin is FMatrixSparseCSC -> this as EjmlFloatVector + else -> EjmlFloatVector(FMatrixSparseCSC(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } + }) + } + + public override fun buildMatrix( + rows: Int, + columns: Int, + initializer: FloatField.(i: Int, j: Int) -> Float, + ): EjmlFloatMatrix = FMatrixSparseCSC(rows, columns).also { + (0 until rows).forEach { row -> + (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } + } + }.wrapMatrix() + + public override fun buildVector( + size: Int, + initializer: FloatField.(Int) -> Float, + ): EjmlFloatVector = EjmlFloatVector(FMatrixSparseCSC(size, 1).also { + (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } + }) + + private fun T.wrapMatrix() = EjmlFloatMatrix(this) + private fun T.wrapVector() = EjmlFloatVector(this) + + public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } + + public override fun Matrix.dot(other: Matrix): EjmlFloatMatrix { + val out = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.mult(toEjml().origin, other.toEjml().origin, out) + return out.wrapMatrix() + } + + public override fun Matrix.dot(vector: Point): EjmlFloatVector { + val out = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.mult(toEjml().origin, vector.toEjml().origin, out) + return out.wrapVector() + } + + public override operator fun Matrix.minus(other: Matrix): EjmlFloatMatrix { + val out = FMatrixSparseCSC(1, 1) + + CommonOps_FSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapMatrix() + } + + public override operator fun Matrix.times(value: Float): EjmlFloatMatrix { + val res = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.scale(value, toEjml().origin, res) + return res.wrapMatrix() + } + + public override fun Point.unaryMinus(): EjmlFloatVector { + val res = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.changeSign(toEjml().origin, res) + return res.wrapVector() + } + + public override fun Matrix.plus(other: Matrix): EjmlFloatMatrix { + val out = FMatrixSparseCSC(1, 1) + + CommonOps_FSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapMatrix() + } + + public override fun Point.plus(other: Point): EjmlFloatVector { + val out = FMatrixSparseCSC(1, 1) + + CommonOps_FSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra.one, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapVector() + } + + public override fun Point.minus(other: Point): EjmlFloatVector { + val out = FMatrixSparseCSC(1, 1) + + CommonOps_FSCC.add( + elementAlgebra.one, + toEjml().origin, + elementAlgebra { -one }, + other.toEjml().origin, + out, + null, + null, + ) + + return out.wrapVector() + } + + public override fun Float.times(m: Matrix): EjmlFloatMatrix = m * this + + public override fun Point.times(value: Float): EjmlFloatVector { + val res = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.scale(value, toEjml().origin, res) + return res.wrapVector() + } + + public override fun Float.times(v: Point): EjmlFloatVector = v * this + + @UnstableKMathAPI + public override fun getFeature(structure: Matrix, type: KClass): F? { + structure.getFeature(type)?.let { return it } + val origin = structure.toEjml().origin + + return when (type) { + QRDecompositionFeature::class -> object : QRDecompositionFeature { + private val qr by lazy { + DecompositionFactory_FSCC.qr(FillReducing.NONE).apply { decompose(origin.copy()) } + } + + override val q: Matrix by lazy { + qr.getQ(null, false).wrapMatrix() + OrthogonalFeature + } + + override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } + } + + CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { + override val l: Matrix by lazy { + val cholesky = + DecompositionFactory_FSCC.cholesky().apply { decompose(origin.copy()) } + + (cholesky.getT(null) as FMatrix).wrapMatrix() + LFeature + } + } + + LUDecompositionFeature::class, DeterminantFeature::class, InverseMatrixFeature::class -> object : + LUDecompositionFeature, DeterminantFeature, InverseMatrixFeature { + private val lu by lazy { + DecompositionFactory_FSCC.lu(FillReducing.NONE).apply { decompose(origin.copy()) } + } + + override val l: Matrix by lazy { + lu.getLower(null).wrapMatrix() + LFeature + } + + override val u: Matrix by lazy { + lu.getUpper(null).wrapMatrix() + UFeature + } + + override val inverse: Matrix by lazy { + var a = origin + val inverse = FMatrixRMaj(1, 1) + val solver = LinearSolverFactory_FSCC.lu(FillReducing.NONE) + if (solver.modifiesA()) a = a.copy() + val i = CommonOps_FDRM.identity(a.numRows) + solver.solve(i, inverse) + inverse.wrapMatrix() + } + + override val determinant: Float by lazy { elementAlgebra.number(lu.computeDeterminant().real) } + } + + else -> null + }?.let(type::cast) + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p matrix. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Matrix): EjmlFloatMatrix { + val res = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.solve(FMatrixSparseCSC(a.toEjml().origin), FMatrixSparseCSC(b.toEjml().origin), res) + return res.wrapMatrix() + } + + /** + * Solves for *x* in the following equation: *x = [a] -1 · [b]*. + * + * @param a the base matrix. + * @param b n by p vector. + * @return the solution for *x* that is n by p. + */ + public fun solve(a: Matrix, b: Point): EjmlFloatVector { + val res = FMatrixSparseCSC(1, 1) + CommonOps_FSCC.solve(FMatrixSparseCSC(a.toEjml().origin), FMatrixSparseCSC(b.toEjml().origin), res) + return EjmlFloatVector(res) + } +} + diff --git a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt b/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt index 59f9602d6..50675bdac 100644 --- a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt +++ b/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt @@ -5,16 +5,21 @@ package space.kscience.kmath.ejml +import org.ejml.data.DMatrixRMaj +import org.ejml.dense.row.CommonOps_DDRM +import org.ejml.dense.row.RandomMatrices_DDRM import org.ejml.dense.row.factory.DecompositionFactory_DDRM -import org.ejml.simple.SimpleMatrix -import space.kscience.kmath.linear.* +import space.kscience.kmath.linear.DeterminantFeature +import space.kscience.kmath.linear.LupDecompositionFeature +import space.kscience.kmath.linear.getFeature +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureND -import space.kscience.kmath.nd.getFeature import kotlin.random.Random import kotlin.random.asJavaRandom import kotlin.test.* +@OptIn(PerformancePitfall::class) fun assertMatrixEquals(expected: StructureND, actual: StructureND) { assertTrue { StructureND.contentEquals(expected, actual) } } @@ -22,65 +27,59 @@ fun assertMatrixEquals(expected: StructureND, actual: StructureND = EjmlLinearSpace.getFeature(w) ?: fail() - assertEquals(m.determinant(), det.determinant) - val lup: LupDecompositionFeature = EjmlLinearSpace.getFeature(w) ?: fail() + val w = EjmlDoubleMatrix(m) + val det: DeterminantFeature = EjmlLinearSpaceDDRM.getFeature(w) ?: fail() + assertEquals(CommonOps_DDRM.det(m), det.determinant) + val lup: LupDecompositionFeature = EjmlLinearSpaceDDRM.getFeature(w) ?: fail() - val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows(), m.numCols()) - .also { it.decompose(m.ddrm.copy()) } + val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows, m.numCols) + .also { it.decompose(m.copy()) } - assertMatrixEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getLower(null))), lup.l) - assertMatrixEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getUpper(null))), lup.u) - assertMatrixEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getRowPivot(null))), lup.p) - } - - private object SomeFeature : MatrixFeature {} - - @OptIn(UnstableKMathAPI::class) - @Test - fun suggestFeature() { - assertNotNull((EjmlMatrix(randomMatrix) + SomeFeature).getFeature()) + assertMatrixEquals(EjmlDoubleMatrix(ludecompositionF64.getLower(null)), lup.l) + assertMatrixEquals(EjmlDoubleMatrix(ludecompositionF64.getUpper(null)), lup.u) + assertMatrixEquals(EjmlDoubleMatrix(ludecompositionF64.getRowPivot(null)), lup.p) } @Test fun get() { val m = randomMatrix - assertEquals(m[0, 0], EjmlMatrix(m)[0, 0]) + assertEquals(m[0, 0], EjmlDoubleMatrix(m)[0, 0]) } @Test fun origin() { val m = randomMatrix - assertSame(m, EjmlMatrix(m).origin) + assertSame(m, EjmlDoubleMatrix(m).origin) } } diff --git a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt b/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt index e1bcd269e..9592bfa6c 100644 --- a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt +++ b/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlVectorTest.kt @@ -5,7 +5,8 @@ package space.kscience.kmath.ejml -import org.ejml.simple.SimpleMatrix +import org.ejml.data.DMatrixRMaj +import org.ejml.dense.row.RandomMatrices_DDRM import kotlin.random.Random import kotlin.random.asJavaRandom import kotlin.test.Test @@ -15,30 +16,34 @@ import kotlin.test.assertSame internal class EjmlVectorTest { private val random = Random(0) - private val randomMatrix: SimpleMatrix - get() = SimpleMatrix.random_DDRM(random.nextInt(2, 100), 1, 0.0, 10.0, random.asJavaRandom()) + private val randomMatrix: DMatrixRMaj + get() { + val d = DMatrixRMaj(1, random.nextInt(2, 100)) + RandomMatrices_DDRM.fillUniform(d, random.asJavaRandom()) + return d + } @Test fun size() { val m = randomMatrix - val w = EjmlVector(m) - assertEquals(m.numRows(), w.size) + val w = EjmlDoubleVector(m) + assertEquals(m.numCols, w.size) } @Test fun get() { val m = randomMatrix - val w = EjmlVector(m) + val w = EjmlDoubleVector(m) assertEquals(m[0, 0], w[0]) } @Test fun iterator() { val m = randomMatrix - val w = EjmlVector(m) + val w = EjmlDoubleVector(m) assertEquals( - m.iterator(true, 0, 0, m.numRows() - 1, 0).asSequence().toList(), + m.iterator(true, 0, 0, 0, m.numCols - 1).asSequence().toList(), w.iterator().asSequence().toList() ) } @@ -46,7 +51,7 @@ internal class EjmlVectorTest { @Test fun origin() { val m = randomMatrix - val w = EjmlVector(m) + val w = EjmlDoubleVector(m) assertSame(m, w.origin) } } diff --git a/kmath-for-real/README.md b/kmath-for-real/README.md index 20e52deb2..5ca805093 100644 --- a/kmath-for-real/README.md +++ b/kmath-for-real/README.md @@ -9,29 +9,27 @@ Specialization of KMath APIs for Double numbers. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-for-real:0.3.0-dev-7' + implementation 'space.kscience:kmath-for-real:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-for-real:0.3.0-dev-7") + implementation("space.kscience:kmath-for-real:0.3.0-dev-11") } ``` diff --git a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt index c4656dd8a..8023236ea 100644 --- a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt +++ b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt @@ -3,9 +3,13 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:OptIn(PerformancePitfall::class) +@file:Suppress("unused") + package space.kscience.kmath.real import space.kscience.kmath.linear.* +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.structures.Buffer diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt index 84e604b18..b3e129c2e 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt @@ -7,6 +7,7 @@ package kaceince.kmath.real import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.matrix +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureND import space.kscience.kmath.real.* @@ -15,6 +16,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +@OptIn(PerformancePitfall::class) fun assertMatrixEquals(expected: StructureND, actual: StructureND) { assertTrue { StructureND.contentEquals(expected, actual) } } diff --git a/kmath-functions/README.md b/kmath-functions/README.md index d5907f1c5..2497f7102 100644 --- a/kmath-functions/README.md +++ b/kmath-functions/README.md @@ -11,29 +11,27 @@ Functions and interpolations. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-functions:0.3.0-dev-7' + implementation 'space.kscience:kmath-functions:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-functions:0.3.0-dev-7") + implementation("space.kscience:kmath-functions:0.3.0-dev-11") } ``` diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt index 6d481e334..73fa57c7b 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Piecewise.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.functions +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.operations.Ring /** @@ -22,16 +23,54 @@ public fun interface Piecewise { /** * Represents piecewise-defined function where all the sub-functions are polynomials. + * @param pieces An ordered list of range-polynomial pairs. The list does not in general guarantee that there are no "holes" in it. */ -public fun interface PiecewisePolynomial : Piecewise> +public interface PiecewisePolynomial> : Piecewise> { + public val pieces: Collection, Polynomial>> + + public override fun findPiece(arg: T): Polynomial? +} /** - * Basic [Piecewise] implementation where all the pieces are ordered by the [Comparable] type instances. + * A generic piecewise without constraints on how pieces are placed + */ +@PerformancePitfall("findPiece method of resulting piecewise is slow") +public fun > PiecewisePolynomial( + pieces: Collection, Polynomial>>, +): PiecewisePolynomial = object : PiecewisePolynomial { + override val pieces: Collection, Polynomial>> = pieces + + override fun findPiece(arg: T): Polynomial? = pieces.firstOrNull { arg in it.first }?.second +} + +/** + * An optimized piecewise which uses not separate pieces, but a range separated by delimiters. + * The pices search is logarithmic + */ +private class OrderedPiecewisePolynomial>( + override val pieces: List, Polynomial>>, +) : PiecewisePolynomial { + + override fun findPiece(arg: T): Polynomial? { + val index = pieces.binarySearch { (range, _) -> + when { + arg >= range.endInclusive -> -1 + arg < range.start -> +1 + else -> 0 + } + } + return if (index < 0) null else pieces[index].second + } + +} + +/** + * A [Piecewise] builder where all the pieces are ordered by the [Comparable] type instances. * * @param T the comparable piece key type. + * @param delimiter the initial piecewise separator */ -public class OrderedPiecewisePolynomial>(delimiter: T) : - PiecewisePolynomial { +public class PiecewiseBuilder>(delimiter: T) { private val delimiters: MutableList = arrayListOf(delimiter) private val pieces: MutableList> = arrayListOf() @@ -59,19 +98,19 @@ public class OrderedPiecewisePolynomial>(delimiter: T) : pieces.add(0, piece) } - public override fun findPiece(arg: T): Polynomial? { - if (arg < delimiters.first() || arg >= delimiters.last()) - return null - else { - for (index in 1 until delimiters.size) - if (arg < delimiters[index]) - return pieces[index - 1] - - error("Piece not found") - } - } + public fun build(): PiecewisePolynomial = OrderedPiecewisePolynomial(delimiters.zipWithNext { l, r -> + l..r + }.zip(pieces)) } +/** + * A builder for [PiecewisePolynomial] + */ +public fun > PiecewisePolynomial( + startingPoint: T, + builder: PiecewiseBuilder.() -> Unit, +): PiecewisePolynomial = PiecewiseBuilder(startingPoint).apply(builder).build() + /** * Return a value of polynomial function with given [ring] an given [arg] or null if argument is outside of piecewise * definition. @@ -79,4 +118,13 @@ public class OrderedPiecewisePolynomial>(delimiter: T) : public fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = findPiece(arg)?.value(ring, arg) +/** + * Convert this polynomial to a function returning nullable value (null if argument is outside piecewise range). + */ public fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } + +/** + * Convert this polynomial to a function using [defaultValue] for arguments outside the piecewise range. + */ +public fun , C : Ring> PiecewisePolynomial.asFunction(ring: C, defaultValue: T): (T) -> T = + { value(ring, it) ?: defaultValue } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt index efae71b0e..ba77d7b25 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/Polynomial.kt @@ -5,10 +5,8 @@ package space.kscience.kmath.functions -import space.kscience.kmath.operations.Group -import space.kscience.kmath.operations.Ring -import space.kscience.kmath.operations.ScaleOperations -import space.kscience.kmath.operations.invoke +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.math.max @@ -19,40 +17,76 @@ import kotlin.math.pow * * @param coefficients constant is the leftmost coefficient. */ -public class Polynomial(public val coefficients: List) +public class Polynomial(public val coefficients: List) { + override fun toString(): String = "Polynomial$coefficients" +} /** * Returns a [Polynomial] instance with given [coefficients]. */ @Suppress("FunctionName") -public fun Polynomial(vararg coefficients: T): Polynomial = Polynomial(coefficients.toList()) +public fun Polynomial(vararg coefficients: T): Polynomial = Polynomial(coefficients.toList()) /** * Evaluates the value of the given double polynomial for given double argument. */ -public fun Polynomial.value(): Double = coefficients.reduceIndexed { index, acc, d -> acc + d.pow(index) } +public fun Polynomial.value(arg: Double): Double = coefficients.reduceIndexed { index, acc, c -> + acc + c * arg.pow(index) +} /** * Evaluates the value of the given polynomial for given argument. + * https://en.wikipedia.org/wiki/Horner%27s_method */ -public fun > Polynomial.value(ring: C, arg: T): T = ring { +public fun > Polynomial.value(ring: C, arg: T): T = ring { if (coefficients.isEmpty()) return@ring zero - var res = coefficients.first() - var powerArg = arg - - for (index in 1 until coefficients.size) { - res += coefficients[index] * powerArg - // recalculating power on each step to avoid power costs on long polynomials - powerArg *= arg + var result: T = coefficients.last() + for (j in coefficients.size - 2 downTo 0) { + result = (arg * result) + coefficients[j] } - - res + return result } /** * Represent the polynomial as a regular context-less function. */ -public fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } +public fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } + +/** + * Create a polynomial witch represents differentiated version of this polynomial + */ +@UnstableKMathAPI +public fun Polynomial.differentiate( + algebra: A, +): Polynomial where A : Ring, A : NumericAlgebra = algebra { + Polynomial(coefficients.drop(1).mapIndexed { index, t -> number(index) * t }) +} + +/** + * Create a polynomial witch represents indefinite integral version of this polynomial + */ +@UnstableKMathAPI +public fun Polynomial.integrate( + algebra: A, +): Polynomial where A : Field, A : NumericAlgebra = algebra { + val integratedCoefficients = buildList(coefficients.size + 1) { + add(zero) + coefficients.forEachIndexed{ index, t -> add(t / (number(index) + one)) } + } + Polynomial(integratedCoefficients) +} + +/** + * Compute a definite integral of a given polynomial in a [range] + */ +@UnstableKMathAPI +public fun > Polynomial.integrate( + algebra: Field, + range: ClosedRange, +): T = algebra { + val integral = integrate(algebra) + integral.value(algebra, range.endInclusive) - integral.value(algebra, range.start) +} /** * Space of polynomials. @@ -61,7 +95,7 @@ public fun > Polynomial.asFunction(ring: C): (T) -> T = * @param C the intersection of [Ring] of [T] and [ScaleOperations] of [T]. * @param ring the [C] instance. */ -public class PolynomialSpace( +public class PolynomialSpace( private val ring: C, ) : Group>, ScaleOperations> where C : Ring, C : ScaleOperations { public override val zero: Polynomial = Polynomial(emptyList()) @@ -87,9 +121,12 @@ public class PolynomialSpace( * Evaluates the polynomial for the given value [arg]. */ public operator fun Polynomial.invoke(arg: T): T = value(ring, arg) + + public fun Polynomial.asFunction(): (T) -> T = asFunction(ring) + } -public inline fun C.polynomial(block: PolynomialSpace.() -> R): R where C : Ring, C : ScaleOperations { +public inline fun C.polynomial(block: PolynomialSpace.() -> R): R where C : Ring, C : ScaleOperations { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return PolynomialSpace(this).block() } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt index e3a9e5a4a..283f97557 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt @@ -6,18 +6,19 @@ package space.kscience.kmath.integration import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Field -import space.kscience.kmath.structures.* +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.asBuffer +import space.kscience.kmath.structures.indices + -/** - * Set of univariate integration ranges. First components correspond to ranges themselves, second components to number of - * integration nodes per range - */ -public class UnivariateIntegrandRanges(public val ranges: List, Int>>) : IntegrandFeature { - public constructor(vararg pairs: Pair, Int>) : this(pairs.toList()) -} /** * A simple one-pass integrator based on Gauss rule + * Following integrand features are accepted: + * [GaussIntegratorRuleFactory] - A factory for computing the Gauss integration rule. By default uses [GaussLegendreRuleFactory] + * [IntegrationRange] - the univariate range of integration. By default uses 0..1 interval. + * [IntegrandMaxCalls] - the maximum number of function calls during integration. For non-iterative rules, always uses the maximum number of points. By default uses 10 points. + * [UnivariateIntegrandRanges] - Set of ranges and number of points per range. Defaults to given [IntegrationRange] and [IntegrandMaxCalls] */ public class GaussIntegrator( public val algebra: Field, @@ -50,7 +51,7 @@ public class GaussIntegrator( } } - override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand = with(algebra) { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand = with(algebra) { val f = integrand.function val (points, weights) = buildRule(integrand) var res = zero @@ -63,34 +64,24 @@ public class GaussIntegrator( c = t - res - y res = t } - return integrand.with(IntegrandValue(res),IntegrandCallsPerformed(integrand.calls + points.size)) + return integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + points.size) } - public companion object { - - } + public companion object } /** - * Integrate [T]-valued univariate function using provided set of [IntegrandFeature] - * Following features are evaluated: - * * [GaussIntegratorRuleFactory] - A factory for computing the Gauss integration rule. By default uses [GaussLegendreRuleFactory] - * * [IntegrationRange] - the univariate range of integration. By default uses 0..1 interval. - * * [IntegrandMaxCalls] - the maximum number of function calls during integration. For non-iterative rules, always uses the maximum number of points. By default uses 10 points. - * * [UnivariateIntegrandRanges] - Set of ranges and number of points per range. Defaults to given [IntegrationRange] and [IntegrandMaxCalls] + * Create a Gauss-Legendre integrator for this field + * @see [GaussIntegrator] */ -@UnstableKMathAPI -public fun Field.process( - vararg features: IntegrandFeature, - function: (Double) -> T, -): UnivariateIntegrand = GaussIntegrator(this).process(UnivariateIntegrand(function, *features)) +public val Field.gaussIntegrator: GaussIntegrator get() = GaussIntegrator(this) /** - * Use [GaussIntegrator.Companion.integrate] to integrate the function in the current algebra with given [range] and [numPoints] + * Integrate using [intervals] segments with Gauss-Legendre rule of [order] order */ @UnstableKMathAPI -public fun Field.process( +public fun GaussIntegrator.integrate( range: ClosedRange, order: Int = 10, intervals: Int = 10, @@ -102,9 +93,9 @@ public fun Field.process( require(intervals > 0) { "Number of intervals must be positive" } val rangeSize = (range.endInclusive - range.start) / intervals val ranges = UnivariateIntegrandRanges( - (0 until intervals).map { i -> (rangeSize * i)..(rangeSize * (i + 1)) to order } + (0 until intervals).map { i -> (range.start + rangeSize * i)..(range.start + rangeSize * (i + 1)) to order } ) - return GaussIntegrator(this).process( + return integrate( UnivariateIntegrand( function, IntegrationRange(range), diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt index 133f829e3..594ca9940 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt @@ -164,4 +164,6 @@ public object GaussLegendreRuleFactory : GaussIntegratorRuleFactory { } override fun build(numPoints: Int): Pair, Buffer> = getOrBuildRule(numPoints) + + override fun toString(): String = "GaussLegendreRule" } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt index 1f45e825b..f9c26e88b 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt @@ -5,27 +5,41 @@ package space.kscience.kmath.integration -import space.kscience.kmath.misc.FeatureSet -import space.kscience.kmath.misc.Featured import kotlin.reflect.KClass -public interface IntegrandFeature +public interface IntegrandFeature { + override fun toString(): String +} -public interface Integrand: Featured{ - public val features: FeatureSet - override fun getFeature(type: KClass): T? = features.getFeature(type) +public interface Integrand { + public val features: Set + public fun getFeature(type: KClass): T? } public inline fun Integrand.getFeature(): T? = getFeature(T::class) -public class IntegrandValue(public val value: T) : IntegrandFeature +public class IntegrandValue(public val value: T) : IntegrandFeature { + override fun toString(): String = "Value($value)" +} -public class IntegrandRelativeAccuracy(public val accuracy: Double) : IntegrandFeature +public class IntegrandRelativeAccuracy(public val accuracy: Double) : IntegrandFeature { + override fun toString(): String = "TargetRelativeAccuracy($accuracy)" +} -public class IntegrandAbsoluteAccuracy(public val accuracy: Double) : IntegrandFeature +public class IntegrandAbsoluteAccuracy(public val accuracy: Double) : IntegrandFeature { + override fun toString(): String = "TargetAbsoluteAccuracy($accuracy)" +} -public class IntegrandCallsPerformed(public val calls: Int) : IntegrandFeature +public class IntegrandCallsPerformed(public val calls: Int) : IntegrandFeature { + override fun toString(): String = "Calls($calls)" +} public val Integrand.calls: Int get() = getFeature()?.calls ?: 0 -public class IntegrandMaxCalls(public val maxCalls: Int) : IntegrandFeature +public class IntegrandMaxCalls(public val maxCalls: Int) : IntegrandFeature { + override fun toString(): String = "MaxCalls($maxCalls)" +} + +public class IntegrandIterationsRange(public val range: IntRange) : IntegrandFeature { + override fun toString(): String = "Iterations(${range.first}..${range.last})" +} diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt index b9c1589c0..5ba411bf9 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt @@ -6,21 +6,29 @@ package space.kscience.kmath.integration import space.kscience.kmath.linear.Point -import space.kscience.kmath.misc.FeatureSet import kotlin.reflect.KClass public class MultivariateIntegrand internal constructor( - override val features: FeatureSet, + private val featureMap: Map, IntegrandFeature>, public val function: (Point) -> T, -) : Integrand +) : Integrand { -public fun MultivariateIntegrand.with(vararg newFeatures: IntegrandFeature): MultivariateIntegrand = - MultivariateIntegrand(features.with(*newFeatures), function) + override val features: Set get() = featureMap.values.toSet() + + @Suppress("UNCHECKED_CAST") + override fun getFeature(type: KClass): T? = featureMap[type] as? T + + public operator fun plus(pair: Pair, F>): MultivariateIntegrand = + MultivariateIntegrand(featureMap + pair, function) + + public operator fun plus(feature: F): MultivariateIntegrand = + plus(feature::class to feature) +} @Suppress("FunctionName") public fun MultivariateIntegrand( vararg features: IntegrandFeature, function: (Point) -> T, -): MultivariateIntegrand = MultivariateIntegrand(FeatureSet.of(*features), function) +): MultivariateIntegrand = MultivariateIntegrand(features.associateBy { it::class }, function) public val MultivariateIntegrand.value: T? get() = getFeature>()?.value diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt new file mode 100644 index 000000000..baa9d4af8 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.integration + +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.Field +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.operations.sum + +/** + * Use double pass Simpson rule integration with a fixed number of points. + * Requires [UnivariateIntegrandRanges] or [IntegrationRange] and [IntegrandMaxCalls] + * [IntegrationRange] - the univariate range of integration. By default uses 0..1 interval. + * [IntegrandMaxCalls] - the maximum number of function calls during integration. For non-iterative rules, always uses the maximum number of points. By default uses 10 points. + */ +@UnstableKMathAPI +public class SimpsonIntegrator( + public val algebra: Field, +) : UnivariateIntegrator { + + private fun integrateRange( + integrand: UnivariateIntegrand, range: ClosedRange, numPoints: Int, + ): T = algebra { + val h: Double = (range.endInclusive - range.start) / (numPoints - 1) + val values: List = List(numPoints) { i -> + integrand.function(range.start + i * h) + }// equally distributed point + + //TODO don't use list, reassign values instead + fun simpson(index: Int) = h / 3 * (values[index - 1] + 4 * values[index] + values[index + 1]) + + var res = zero + res += simpson(1) / 1.5 //border points with 1.5 factor + for (i in 2 until (values.size - 2)) { + //each half-interval is computed twice, therefore /2 + res += simpson(i) / 2 + } + res += simpson(values.size - 2) / 1.5 //border points with 1.5 factor + return res + } + + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + val ranges = integrand.getFeature() + return if (ranges != null) { + val res = algebra.sum(ranges.ranges.map { integrateRange(integrand, it.first, it.second) }) + integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + ranges.ranges.sumOf { it.second }) + } else { + val numPoints = integrand.getFeature()?.maxCalls ?: 100 + require(numPoints >= 4) { "Simpson integrator requires at least 4 nodes" } + val range = integrand.getFeature()?.range ?: 0.0..1.0 + val res = integrateRange(integrand, range, numPoints) + integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + numPoints) + } + } +} + +@UnstableKMathAPI +public val Field.simpsonIntegrator: SimpsonIntegrator get() = SimpsonIntegrator(this) + +/** + * Use double pass Simpson rule integration with a fixed number of points. + * Requires [UnivariateIntegrandRanges] or [IntegrationRange] and [IntegrandMaxCalls] + * [IntegrationRange] - the univariate range of integration. By default uses 0..1 interval. + * [IntegrandMaxCalls] - the maximum number of function calls during integration. For non-iterative rules, always uses the maximum number of points. By default uses 10 points. + */ +public object DoubleSimpsonIntegrator : UnivariateIntegrator { + + private fun integrateRange( + integrand: UnivariateIntegrand, range: ClosedRange, numPoints: Int, + ): Double { + val h: Double = (range.endInclusive - range.start) / (numPoints - 1) + val values = DoubleArray(numPoints) { i -> + integrand.function(range.start + i * h) + }// equally distributed point + + fun simpson(index: Int) = h / 3 * (values[index - 1] + 4 * values[index] + values[index + 1]) + + var res = 0.0 + res += simpson(1) / 1.5 //border points with 1.5 factor + for (i in 2 until (values.size - 2)) { + //each half-interval is computed twice, therefore /2 + res += simpson(i) / 2 + } + res += simpson(values.size - 2) / 1.5 //border points with 1.5 factor + return res + } + + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + val ranges = integrand.getFeature() + return if (ranges != null) { + val res = ranges.ranges.sumOf { integrateRange(integrand, it.first, it.second) } + integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + ranges.ranges.sumOf { it.second }) + } else { + val numPoints = integrand.getFeature()?.maxCalls ?: 100 + require(numPoints >= 4) { "Simpson integrator requires at least 4 nodes" } + val range = integrand.getFeature()?.range ?: 0.0..1.0 + val res = integrateRange(integrand, range, numPoints) + integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + numPoints) + } + } +} + +public val DoubleField.simpsonIntegrator: DoubleSimpsonIntegrator get() = DoubleSimpsonIntegrator \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt new file mode 100644 index 000000000..23d7bdd8d --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.integration + +import space.kscience.kmath.functions.PiecewisePolynomial +import space.kscience.kmath.functions.integrate +import space.kscience.kmath.interpolation.PolynomialInterpolator +import space.kscience.kmath.interpolation.SplineInterpolator +import space.kscience.kmath.interpolation.interpolatePolynomials +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.Field +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.operations.sum +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.MutableBufferFactory +import space.kscience.kmath.structures.map + +/** + * Compute analytical indefinite integral of this [PiecewisePolynomial], keeping all intervals intact + */ +@UnstableKMathAPI +public fun > PiecewisePolynomial.integrate(algebra: Field): PiecewisePolynomial = + PiecewisePolynomial(pieces.map { it.first to it.second.integrate(algebra) }) + +/** + * Compute definite integral of given [PiecewisePolynomial] piece by piece in a given [range] + * Requires [UnivariateIntegrationNodes] or [IntegrationRange] and [IntegrandMaxCalls] + */ +@UnstableKMathAPI +public fun > PiecewisePolynomial.integrate( + algebra: Field, range: ClosedRange, +): T = algebra.sum( + pieces.map { (region, poly) -> + val intersectedRange = maxOf(range.start, region.start)..minOf(range.endInclusive, region.endInclusive) + //Check if polynomial range is not used + if (intersectedRange.start == intersectedRange.endInclusive) algebra.zero + else poly.integrate(algebra, intersectedRange) + } +) + +/** + * A generic spline-interpolation-based analytic integration + * [IntegrationRange] - the univariate range of integration. By default uses 0..1 interval. + * [IntegrandMaxCalls] - the maximum number of function calls during integration. For non-iterative rules, always uses the maximum number of points. By default uses 10 points. + */ +@UnstableKMathAPI +public class SplineIntegrator>( + public val algebra: Field, + public val bufferFactory: MutableBufferFactory, +) : UnivariateIntegrator { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand = algebra { + val range = integrand.getFeature()?.range ?: 0.0..1.0 + + val interpolator: PolynomialInterpolator = SplineInterpolator(algebra, bufferFactory) + val nodes: Buffer = integrand.getFeature()?.nodes ?: run { + val numPoints = integrand.getFeature()?.maxCalls ?: 100 + val step = (range.endInclusive - range.start) / (numPoints - 1) + DoubleBuffer(numPoints) { i -> range.start + i * step } + } + + val values = nodes.map(bufferFactory) { integrand.function(it) } + val polynomials = interpolator.interpolatePolynomials( + nodes.map(bufferFactory) { number(it) }, + values + ) + val res = polynomials.integrate(algebra, number(range.start)..number(range.endInclusive)) + integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + nodes.size) + } +} + +/** + * A simplified double-based spline-interpolation-based analytic integration + * [IntegrationRange] - the univariate range of integration. By default uses 0..1 interval. + * [IntegrandMaxCalls] - the maximum number of function calls during integration. For non-iterative rules, always uses the maximum number of points. By default uses 10 points. + */ +@UnstableKMathAPI +public object DoubleSplineIntegrator : UnivariateIntegrator { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + val range = integrand.getFeature()?.range ?: 0.0..1.0 + + val interpolator: PolynomialInterpolator = SplineInterpolator(DoubleField, ::DoubleBuffer) + val nodes: Buffer = integrand.getFeature()?.nodes ?: run { + val numPoints = integrand.getFeature()?.maxCalls ?: 100 + val step = (range.endInclusive - range.start) / (numPoints - 1) + DoubleBuffer(numPoints) { i -> range.start + i * step } + } + + val values = nodes.map { integrand.function(it) } + val polynomials = interpolator.interpolatePolynomials(nodes, values) + val res = polynomials.integrate(DoubleField, range) + return integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + nodes.size) + } +} + +@UnstableKMathAPI +public inline val DoubleField.splineIntegrator: UnivariateIntegrator + get() = DoubleSplineIntegrator \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt index 3fc5b4599..e265f54e8 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt @@ -5,61 +5,107 @@ package space.kscience.kmath.integration -import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.UnstableKMathAPI -import kotlin.jvm.JvmInline +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.DoubleBuffer +import kotlin.reflect.KClass - -public class UnivariateIntegrand internal constructor( - override val features: FeatureSet, +public class UnivariateIntegrand internal constructor( + private val featureMap: Map, IntegrandFeature>, public val function: (Double) -> T, -) : Integrand +) : Integrand { -public fun UnivariateIntegrand.with(vararg newFeatures: IntegrandFeature): UnivariateIntegrand = - UnivariateIntegrand(features.with(*newFeatures), function) + override val features: Set get() = featureMap.values.toSet() + + @Suppress("UNCHECKED_CAST") + override fun getFeature(type: KClass): T? = featureMap[type] as? T + + public operator fun plus(pair: Pair, F>): UnivariateIntegrand = + UnivariateIntegrand(featureMap + pair, function) + + public operator fun plus(feature: F): UnivariateIntegrand = + plus(feature::class to feature) +} @Suppress("FunctionName") public fun UnivariateIntegrand( function: (Double) -> T, vararg features: IntegrandFeature, -): UnivariateIntegrand = UnivariateIntegrand(FeatureSet.of(*features), function) +): UnivariateIntegrand = UnivariateIntegrand(features.associateBy { it::class }, function) public typealias UnivariateIntegrator = Integrator> -@JvmInline -public value class IntegrationRange(public val range: ClosedRange) : IntegrandFeature +public class IntegrationRange(public val range: ClosedRange) : IntegrandFeature { + override fun toString(): String = "Range(${range.start}..${range.endInclusive})" +} -public val UnivariateIntegrand.value: T? get() = getFeature>()?.value +/** + * Set of univariate integration ranges. First components correspond to ranges themselves, second components to number of + * integration nodes per range + */ +public class UnivariateIntegrandRanges(public val ranges: List, Int>>) : IntegrandFeature { + public constructor(vararg pairs: Pair, Int>) : this(pairs.toList()) + + override fun toString(): String { + val rangesString = ranges.joinToString(separator = ",") { (range, points) -> + "${range.start}..${range.endInclusive} : $points" + } + return "UnivariateRanges($rangesString)" + } +} + +public class UnivariateIntegrationNodes(public val nodes: Buffer) : IntegrandFeature { + public constructor(vararg nodes: Double) : this(DoubleBuffer(nodes)) + + override fun toString(): String = "UnivariateNodes($nodes)" +} + + +/** + * Value of the integrand if it is present or null + */ +public val UnivariateIntegrand.valueOrNull: T? get() = getFeature>()?.value + +/** + * Value of the integrand or error + */ +public val UnivariateIntegrand.value: T get() = valueOrNull ?: error("No value in the integrand") + +/** + * A shortcut method to integrate a [function] with additional [features]. Range must be provided in features. + * The [function] is placed in the end position to allow passing a lambda. + */ +@UnstableKMathAPI +public fun UnivariateIntegrator.integrate( + vararg features: IntegrandFeature, + function: (Double) -> T, +): UnivariateIntegrand = integrate(UnivariateIntegrand(function, *features)) /** * A shortcut method to integrate a [function] in [range] with additional [features]. * The [function] is placed in the end position to allow passing a lambda. */ @UnstableKMathAPI -public fun UnivariateIntegrator.integrate( +public fun UnivariateIntegrator.integrate( range: ClosedRange, vararg features: IntegrandFeature, - function: (Double) -> Double, -): Double = process( - UnivariateIntegrand(function, IntegrationRange(range), *features) -).value ?: error("Unexpected: no value after integration.") + function: (Double) -> T, +): UnivariateIntegrand = integrate(UnivariateIntegrand(function, IntegrationRange(range), *features)) /** * A shortcut method to integrate a [function] in [range] with additional [features]. * The [function] is placed in the end position to allow passing a lambda. */ @UnstableKMathAPI -public fun UnivariateIntegrator.integrate( +public fun UnivariateIntegrator.integrate( range: ClosedRange, - function: (Double) -> Double, - featureBuilder: (MutableList.() -> Unit) = {}, -): Double { + featureBuilder: MutableList.() -> Unit = {}, + function: (Double) -> T, +): UnivariateIntegrand { //TODO use dedicated feature builder class instead or add extensions to MutableList val features = buildList { featureBuilder() add(IntegrationRange(range)) } - return process( - UnivariateIntegrand(function, *features.toTypedArray()) - ).value ?: error("Unexpected: no value after integration.") + return integrate(UnivariateIntegrand(function, *features.toTypedArray())) } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt index 08090a522..c9ec0d527 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt @@ -15,10 +15,16 @@ import space.kscience.kmath.operations.Ring import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.asBuffer +/** + * And interpolator for data with x column type [X], y column type [Y]. + */ public fun interface Interpolator { public fun interpolate(points: XYColumnarData): (X) -> Y } +/** + * And interpolator returning [PiecewisePolynomial] function + */ public interface PolynomialInterpolator> : Interpolator { public val algebra: Ring diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/LinearInterpolator.kt index 3fbf6157e..24c049647 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/LinearInterpolator.kt @@ -6,7 +6,6 @@ package space.kscience.kmath.interpolation import space.kscience.kmath.data.XYColumnarData -import space.kscience.kmath.functions.OrderedPiecewisePolynomial import space.kscience.kmath.functions.PiecewisePolynomial import space.kscience.kmath.functions.Polynomial import space.kscience.kmath.misc.UnstableKMathAPI @@ -28,7 +27,7 @@ public class LinearInterpolator>(public override val algebra: require(points.size > 0) { "Point array should not be empty" } insureSorted(points) - OrderedPiecewisePolynomial(points.x[0]).apply { + PiecewisePolynomial(points.x[0]) { for (i in 0 until points.size - 1) { val slope = (points.y[i + 1] - points.y[i]) / (points.x[i + 1] - points.x[i]) val const = points.y[i] - slope * points.x[i] diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/SplineInterpolator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/SplineInterpolator.kt index f00b66a4c..bf291c315 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/SplineInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/SplineInterpolator.kt @@ -6,12 +6,13 @@ package space.kscience.kmath.interpolation import space.kscience.kmath.data.XYColumnarData -import space.kscience.kmath.functions.OrderedPiecewisePolynomial import space.kscience.kmath.functions.PiecewisePolynomial import space.kscience.kmath.functions.Polynomial import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.Field import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.MutableBufferFactory /** @@ -34,22 +35,21 @@ public class SplineInterpolator>( // Number of intervals. The number of data points is n + 1. val n = points.size - 1 // Differences between knot points - val h = bufferFactory(points.size) { i -> points.x[i + 1] - points.x[i] } - val mu = bufferFactory(points.size - 1) { zero } - val z = bufferFactory(points.size) { zero } + val h = bufferFactory(n) { i -> points.x[i + 1] - points.x[i] } + val mu = bufferFactory(n) { zero } + val z = bufferFactory(n + 1) { zero } for (i in 1 until n) { val g = 2.0 * (points.x[i + 1] - points.x[i - 1]) - h[i - 1] * mu[i - 1] mu[i] = h[i] / g - z[i] = - (3.0 * (points.y[i + 1] * h[i - 1] - points.x[i] * (points.x[i + 1] - points.x[i - 1]) + points.y[i - 1] * h[i]) / (h[i - 1] * h[i]) - - h[i - 1] * z[i - 1]) / g + ((points.y[i + 1] * h[i - 1] - points.y[i] * (points.x[i + 1] - points.x[i - 1]) + points.y[i - 1] * h[i]) * 3.0 / + (h[i - 1] * h[i]) - h[i - 1] * z[i - 1]) / g } // cubic spline coefficients -- b is linear, c quadratic, d is cubic (original y's are constants) - OrderedPiecewisePolynomial(points.x[points.size - 1]).apply { + PiecewisePolynomial(points.x[points.size - 1]) { var cOld = zero for (j in n - 1 downTo 0) { @@ -57,10 +57,23 @@ public class SplineInterpolator>( val a = points.y[j] val b = (points.y[j + 1] - points.y[j]) / h[j] - h[j] * (cOld + 2.0 * c) / 3.0 val d = (cOld - c) / (3.0 * h[j]) - val polynomial = Polynomial(a, b, c, d) + val x0 = points.x[j] + val x02 = x0 * x0 + val x03 = x02 * x0 + //Shift coefficients to represent absolute polynomial instead of one with an offset + val polynomial = Polynomial( + a - b * x0 + c * x02 - d * x03, + b - 2*c*x0 + 3*d*x02, + c - 3*d*x0, + d + ) cOld = c - putLeft(points.x[j], polynomial) + putLeft(x0, polynomial) } } } + + public companion object { + public val double: SplineInterpolator = SplineInterpolator(DoubleField, ::DoubleBuffer) + } } diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt new file mode 100644 index 000000000..05c16d17e --- /dev/null +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/functions/PolynomialTest.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.functions + +import kotlin.test.Test +import kotlin.test.assertEquals + +class PolynomialTest { + @Test + fun testIntegration() { + val polynomial = Polynomial(1.0, -2.0, 1.0) + assertEquals(0.0, polynomial.value(1.0), 0.001) + } +} \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt index d1e452454..9f48a15ea 100644 --- a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt @@ -16,22 +16,22 @@ import kotlin.test.assertEquals class GaussIntegralTest { @Test fun gaussSin() { - val res = DoubleField.process(0.0..2 * PI) { x -> + val res = DoubleField.gaussIntegrator.integrate(0.0..2 * PI) { x -> sin(x) } - assertEquals(0.0, res.value!!, 1e-2) + assertEquals(0.0, res.value, 1e-2) } @Test fun gaussUniform() { - val res = DoubleField.process(0.0..100.0) { x -> + val res = DoubleField.gaussIntegrator.integrate(35.0..100.0) { x -> if(x in 30.0..50.0){ 1.0 } else { 0.0 } } - assertEquals(20.0, res.value!!, 0.5) + assertEquals(15.0, res.value, 0.5) } diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt new file mode 100644 index 000000000..9f2d71554 --- /dev/null +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.integration + +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import kotlin.math.PI +import kotlin.math.sin +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(UnstableKMathAPI::class) +class SimpsonIntegralTest { + @Test + fun gaussSin() { + val res = DoubleField.simpsonIntegrator.integrate(0.0..2 * PI, IntegrandMaxCalls(5)) { x -> + sin(x) + } + assertEquals(0.0, res.value, 1e-2) + } + + @Test + fun gaussUniform() { + val res = DoubleField.simpsonIntegrator.integrate(35.0..100.0, IntegrandMaxCalls(20)) { x -> + if (x in 30.0..50.0) { + 1.0 + } else { + 0.0 + } + } + assertEquals(15.0, res.value, 0.5) + } +} \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SplineIntegralTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SplineIntegralTest.kt new file mode 100644 index 000000000..afeba0be4 --- /dev/null +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SplineIntegralTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.integration + +import space.kscience.kmath.functions.Polynomial +import space.kscience.kmath.functions.integrate +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import kotlin.math.PI +import kotlin.math.sin +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(UnstableKMathAPI::class) +class SplineIntegralTest { + + @Test + fun integratePolynomial(){ + val polynomial = Polynomial(1.0, 2.0, 3.0) + val integral = polynomial.integrate(DoubleField,1.0..2.0) + assertEquals(11.0, integral, 0.001) + } + + @Test + fun gaussSin() { + val res = DoubleField.splineIntegrator.integrate(0.0..2 * PI, IntegrandMaxCalls(5)) { x -> + sin(x) + } + assertEquals(0.0, res.value, 1e-2) + } + + @Test + fun gaussUniform() { + val res = DoubleField.splineIntegrator.integrate(35.0..100.0, IntegrandMaxCalls(20)) { x -> + if(x in 30.0..50.0){ + 1.0 + } else { + 0.0 + } + } + assertEquals(15.0, res.value, 0.5) + } + + +} \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/interpolation/SplineInterpolatorTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/interpolation/SplineInterpolatorTest.kt new file mode 100644 index 000000000..3adaab2d1 --- /dev/null +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/interpolation/SplineInterpolatorTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.interpolation + +import space.kscience.kmath.functions.PiecewisePolynomial +import space.kscience.kmath.functions.asFunction +import space.kscience.kmath.operations.DoubleField +import kotlin.math.PI +import kotlin.math.sin +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class SplineInterpolatorTest { + @Test + fun testInterpolation() { + val data = (0..10).map { + val x = it.toDouble() / 5 * PI + x to sin(x) + } + + val polynomial: PiecewisePolynomial = SplineInterpolator.double.interpolatePolynomials(data) + + val function = polynomial.asFunction(DoubleField, Double.NaN) + assertEquals(Double.NaN, function(-1.0)) + assertEquals(sin(0.5), function(0.5), 0.1) + assertEquals(sin(1.5), function(1.5), 0.1) + assertEquals(sin(2.0), function(2.0), 0.1) + } +} diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean2DSpace.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean2DSpace.kt index 3604fda31..2a4837ee0 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean2DSpace.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean2DSpace.kt @@ -7,16 +7,14 @@ package space.kscience.kmath.geometry import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.GroupElement import space.kscience.kmath.operations.ScaleOperations import space.kscience.kmath.operations.invoke import kotlin.math.sqrt @OptIn(UnstableKMathAPI::class) -public interface Vector2D : Point, Vector, GroupElement { +public interface Vector2D : Point, Vector{ public val x: Double public val y: Double - public override val context: Euclidean2DSpace get() = Euclidean2DSpace public override val size: Int get() = 2 public override operator fun get(index: Int): Double = when (index) { diff --git a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt index 8643b2dbb..37e7d2cb2 100644 --- a/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt +++ b/kmath-geometry/src/commonMain/kotlin/space/kscience/kmath/geometry/Euclidean3DSpace.kt @@ -7,17 +7,15 @@ package space.kscience.kmath.geometry import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.GroupElement import space.kscience.kmath.operations.ScaleOperations import space.kscience.kmath.operations.invoke import kotlin.math.sqrt @OptIn(UnstableKMathAPI::class) -public interface Vector3D : Point, Vector, GroupElement { +public interface Vector3D : Point, Vector { public val x: Double public val y: Double public val z: Double - public override val context: Euclidean3DSpace get() = Euclidean3DSpace public override val size: Int get() = 3 public override operator fun get(index: Int): Double = when (index) { diff --git a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt index 818d81ab0..e5f6830c5 100644 --- a/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt +++ b/kmath-histograms/src/commonMain/kotlin/space/kscience/kmath/histogram/IndexedHistogramSpace.kt @@ -12,7 +12,6 @@ import space.kscience.kmath.nd.FieldND import space.kscience.kmath.nd.Strides import space.kscience.kmath.nd.StructureND import space.kscience.kmath.operations.Group -import space.kscience.kmath.operations.GroupElement import space.kscience.kmath.operations.ScaleOperations import space.kscience.kmath.operations.invoke @@ -26,9 +25,9 @@ public data class DomainBin>( @OptIn(UnstableKMathAPI::class) public class IndexedHistogram, V : Any>( - override val context: IndexedHistogramSpace, + public val context: IndexedHistogramSpace, public val values: StructureND, -) : Histogram>, GroupElement, IndexedHistogramSpace> { +) : Histogram> { override fun get(point: Point): Bin? { val index = context.getIndex(point) ?: return null diff --git a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt index 5a217f6c2..6ae8b5ee3 100644 --- a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt +++ b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/TreeHistogramSpace.kt @@ -28,7 +28,6 @@ private fun > TreeMap.getBin(val @UnstableKMathAPI public class TreeHistogram( - override val context: TreeHistogramSpace, private val binMap: TreeMap, ) : UnivariateHistogram { override fun get(value: Double): UnivariateBin? = binMap.getBin(value) @@ -79,15 +78,15 @@ public class TreeHistogramSpace( val count = binCounter.counter.value resBins[key] = UnivariateBin(binCounter.domain, count, sqrt(count)) } - return TreeHistogram(this, resBins) + return TreeHistogram(resBins) } override fun add( a: UnivariateHistogram, b: UnivariateHistogram, ): UnivariateHistogram { - require(a.context == this) { "Histogram $a does not belong to this context" } - require(b.context == this) { "Histogram $b does not belong to this context" } +// require(a.context == this) { "Histogram $a does not belong to this context" } +// require(b.context == this) { "Histogram $b does not belong to this context" } val bins = TreeMap().apply { (a.bins.map { it.domain } union b.bins.map { it.domain }).forEach { def -> put(def.center, @@ -100,7 +99,7 @@ public class TreeHistogramSpace( ) } } - return TreeHistogram(this, bins) + return TreeHistogram(bins) } override fun scale(a: UnivariateHistogram, value: Double): UnivariateHistogram { @@ -116,7 +115,7 @@ public class TreeHistogramSpace( } } - return TreeHistogram(this, bins) + return TreeHistogram(bins) } override fun UnivariateHistogram.unaryMinus(): UnivariateHistogram = this * (-1) diff --git a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt index 645116ade..70125e22e 100644 --- a/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/jvmMain/kotlin/space/kscience/kmath/histogram/UnivariateHistogram.kt @@ -7,8 +7,6 @@ package space.kscience.kmath.histogram import space.kscience.kmath.domains.UnivariateDomain import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.Group -import space.kscience.kmath.operations.GroupElement import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.asSequence @@ -35,8 +33,7 @@ public class UnivariateBin( } @OptIn(UnstableKMathAPI::class) -public interface UnivariateHistogram : Histogram, - GroupElement> { +public interface UnivariateHistogram : Histogram{ public operator fun get(value: Double): UnivariateBin? public override operator fun get(point: Buffer): UnivariateBin? = get(point[0]) diff --git a/kmath-jupyter/build.gradle.kts b/kmath-jupyter/build.gradle.kts new file mode 100644 index 000000000..83a6a771a --- /dev/null +++ b/kmath-jupyter/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("ru.mipt.npm.gradle.jvm") + kotlin("jupyter.api") +} + +dependencies { + api(project(":kmath-ast")) + api(project(":kmath-complex")) + api(project(":kmath-for-real")) +} + +kscience{ + useHtml() +} + +readme { + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE +} + +kotlin.sourceSets.all { + languageSettings.useExperimentalAnnotation("space.kscience.kmath.misc.UnstableKMathAPI") +} diff --git a/kmath-jupyter/src/main/kotlin/space/kscience/kmath/jupyter/KMathJupyter.kt b/kmath-jupyter/src/main/kotlin/space/kscience/kmath/jupyter/KMathJupyter.kt new file mode 100644 index 000000000..e3767e13c --- /dev/null +++ b/kmath-jupyter/src/main/kotlin/space/kscience/kmath/jupyter/KMathJupyter.kt @@ -0,0 +1,139 @@ +package space.kscience.kmath.jupyter + +import kotlinx.html.Unsafe +import kotlinx.html.div +import kotlinx.html.stream.createHTML +import kotlinx.html.unsafe +import org.jetbrains.kotlinx.jupyter.api.DisplayResult +import org.jetbrains.kotlinx.jupyter.api.HTML +import org.jetbrains.kotlinx.jupyter.api.annotations.JupyterLibrary +import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration +import space.kscience.kmath.ast.rendering.FeaturedMathRendererWithPostProcess +import space.kscience.kmath.ast.rendering.MathMLSyntaxRenderer +import space.kscience.kmath.ast.rendering.renderWithStringBuilder +import space.kscience.kmath.complex.Complex +import space.kscience.kmath.complex.Quaternion +import space.kscience.kmath.expressions.MST +import space.kscience.kmath.expressions.MstRing +import space.kscience.kmath.misc.PerformancePitfall +import space.kscience.kmath.nd.Structure2D +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.asSequence + +/** + * A function for conversion of number to MST for pretty print + */ +public fun Number.toMst(): MST.Numeric = MST.Numeric(this) + +@JupyterLibrary +internal class KMathJupyter : JupyterIntegration() { + private val mathRender = FeaturedMathRendererWithPostProcess.Default + private val syntaxRender = MathMLSyntaxRenderer + + private fun MST.toDisplayResult(): DisplayResult = HTML(createHTML().div { + unsafe { + +syntaxRender.renderWithStringBuilder(mathRender.render(this@toDisplayResult)) + } + }) + + private fun Unsafe.appendCellValue(it: Any?) { + when (it) { + is Number -> { + val s = StringBuilder() + syntaxRender.renderPart(mathRender.render(MST.Numeric(it)), s) + +s.toString() + } + is MST -> { + val s = StringBuilder() + syntaxRender.renderPart(mathRender.render(it), s) + +s.toString() + } + else -> { + +"" + +it.toString() + +"" + } + } + } + + @OptIn(PerformancePitfall::class) + override fun Builder.onLoaded() { + import( + "space.kscience.kmath.ast.*", + "space.kscience.kmath.ast.rendering.*", + "space.kscience.kmath.structures.*", + "space.kscience.kmath.operations.*", + "space.kscience.kmath.expressions.*", + "space.kscience.kmath.nd.*", + "space.kscience.kmath.misc.*", + "space.kscience.kmath.real.*", + ) + + import("space.kscience.kmath.jupyter.toMst") + + render { it.toDisplayResult() } + //render { MST.Numeric(it).toDisplayResult() } + + render> { structure -> + HTML(createHTML().div { + unsafe { + +"" + +"" + +"" + +"" + structure.rows.forEach { row -> + +"" + row.asSequence().forEach { + +"" + appendCellValue(it) + +"" + } + +"" + } + +"" + +"" + +"" + +"" + } + }) + } + + render> { buffer -> + HTML(createHTML().div { + unsafe { + +"" + +"" + +"" + +"" + buffer.asSequence().forEach { + +"" + +"" + appendCellValue(it) + +"" + +"" + } + +"" + +"" + +"" + +"" + } + }) + } + + render { + MstRing { + number(it.re) + number(it.im) * bindSymbol("i") + }.toDisplayResult() + } + + render { + MstRing { + number(it.w) + + number(it.x) * bindSymbol("i") + + number(it.x) * bindSymbol("j") + + number(it.x) * bindSymbol("k") + }.toDisplayResult() + } + } +} diff --git a/kmath-kotlingrad/README.md b/kmath-kotlingrad/README.md new file mode 100644 index 000000000..4393cbf8c --- /dev/null +++ b/kmath-kotlingrad/README.md @@ -0,0 +1,34 @@ +# Module kmath-kotlingrad + +[Kotlin∇](https://www.htmlsymbols.xyz/unicode/U+2207) integration module. + + - [differentiable-mst-expression](src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt) : MST based DifferentiableExpression. + - [differentiable-mst-expression](src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt) : Conversions between Kotlin∇'s SFun and MST + + +## Artifact: + +The Maven coordinates of this project are `space.kscience:kmath-kotlingrad:0.3.0-dev-11`. + +**Gradle:** +```gradle +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:kmath-kotlingrad:0.3.0-dev-11' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:kmath-kotlingrad:0.3.0-dev-11") +} +``` diff --git a/kmath-kotlingrad/build.gradle.kts b/kmath-kotlingrad/build.gradle.kts index f627beec9..01b42d7ba 100644 --- a/kmath-kotlingrad/build.gradle.kts +++ b/kmath-kotlingrad/build.gradle.kts @@ -3,12 +3,30 @@ plugins { id("ru.mipt.npm.gradle.common") } +description = "Kotlin∇ integration module" + dependencies { - implementation("com.github.breandan:kaliningraph:0.1.4") - implementation("com.github.breandan:kotlingrad:0.4.0") - api(project(":kmath-ast")) + api("com.github.breandan:kaliningraph:0.1.6") + api("com.github.breandan:kotlingrad:0.4.5") + api(project(":kmath-core")) + testImplementation(project(":kmath-ast")) } readme { - maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE + maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL + propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) + + feature( + "differentiable-mst-expression", + "src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt", + ) { + "MST based DifferentiableExpression." + } + + feature( + "differentiable-mst-expression", + "src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt", + ) { + "Conversions between Kotlin∇'s SFun and MST" + } } diff --git a/kmath-kotlingrad/docs/README-TEMPLATE.md b/kmath-kotlingrad/docs/README-TEMPLATE.md new file mode 100644 index 000000000..ac38c849b --- /dev/null +++ b/kmath-kotlingrad/docs/README-TEMPLATE.md @@ -0,0 +1,7 @@ +# Module kmath-kotlingrad + +[Kotlin∇](https://www.htmlsymbols.xyz/unicode/U+2207) integration module. + +${features} + +${artifact} diff --git a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt deleted file mode 100644 index 222145d2d..000000000 --- a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/DifferentiableMstExpression.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.kotlingrad - -import edu.umontreal.kotlingrad.api.SFun -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.MST -import space.kscience.kmath.expressions.MstAlgebra -import space.kscience.kmath.expressions.interpret -import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.operations.NumericAlgebra - -/** - * Represents wrapper of [MstExpression] implementing [DifferentiableExpression]. - * - * The principle of this API is converting the [mst] to an [SFun], differentiating it with Kotlin∇, then converting - * [SFun] back to [MST]. - * - * @param T the type of number. - * @param A the [NumericAlgebra] of [T]. - * @property expr the underlying [MstExpression]. - */ -public class DifferentiableMstExpression>( - public val algebra: A, - public val mst: MST, -) : DifferentiableExpression> { - - public override fun invoke(arguments: Map): T = mst.interpret(algebra, arguments) - - public override fun derivativeOrNull(symbols: List): DifferentiableMstExpression = - DifferentiableMstExpression( - algebra, - symbols.map(Symbol::identity) - .map(MstAlgebra::bindSymbol) - .map { it.toSVar>() } - .fold(mst.toSFun(), SFun>::d) - .toMst(), - ) -} - -/** - * Wraps this [MST] into [DifferentiableMstExpression]. - */ -public fun > MST.toDiffExpression(algebra: A): DifferentiableMstExpression = - DifferentiableMstExpression(algebra, this) diff --git a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KMathNumber.kt b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KMathNumber.kt index 0c3768dcc..9c9d07b81 100644 --- a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KMathNumber.kt +++ b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KMathNumber.kt @@ -5,19 +5,26 @@ package space.kscience.kmath.kotlingrad -import edu.umontreal.kotlingrad.api.RealNumber import edu.umontreal.kotlingrad.api.SConst import space.kscience.kmath.operations.NumericAlgebra /** - * Implements [RealNumber] by delegating its functionality to [NumericAlgebra]. + * Implements [SConst] by delegating its functionality to [NumericAlgebra]. * - * @param T the type of number. - * @param A the [NumericAlgebra] of [T]. - * @property algebra the algebra. - * @param value the value of this number. + * @param T The type of number. + * @param A The [NumericAlgebra] over [T]. + * @property algebra The algebra. + * @property value The value of this number. */ -public class KMathNumber(public val algebra: A, value: T) : - RealNumber, T>(value) where T : Number, A : NumericAlgebra { - public override fun wrap(number: Number): SConst> = SConst(algebra.number(number)) +public class KMathNumber(public val algebra: A, public override val value: T) : + SConst>(value) where T : Number, A : NumericAlgebra { + /** + * Returns a string representation of the [value]. + */ + public override fun toString(): String = value.toString() + + /** + * Wraps [Number] to [KMathNumber]. + */ + public override fun wrap(number: Number): KMathNumber = KMathNumber(algebra, algebra.number(number)) } diff --git a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt new file mode 100644 index 000000000..72ecee4f1 --- /dev/null +++ b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.kotlingrad + +import edu.umontreal.kotlingrad.api.SFun +import edu.umontreal.kotlingrad.api.SVar +import space.kscience.kmath.expressions.* +import space.kscience.kmath.operations.NumericAlgebra + +/** + * Represents [MST] based [DifferentiableExpression] relying on [Kotlin∇](https://github.com/breandan/kotlingrad). + * + * The principle of this API is converting the [mst] to an [SFun], differentiating it with Kotlin∇, then converting + * [SFun] back to [MST]. + * + * @param T The type of number. + * @param A The [NumericAlgebra] of [T]. + * @property algebra The [A] instance. + * @property mst The [MST] node. + */ +public class KotlingradExpression>( + public val algebra: A, + public val mst: MST, +) : DifferentiableExpression> { + public override fun invoke(arguments: Map): T = mst.interpret(algebra, arguments) + + public override fun derivativeOrNull(symbols: List): KotlingradExpression = + KotlingradExpression( + algebra, + symbols.map(Symbol::identity) + .map(MstNumericAlgebra::bindSymbol) + .map>>(Symbol::toSVar) + .fold(mst.toSFun(), SFun>::d) + .toMst(), + ) +} + +/** + * Wraps this [MST] into [KotlingradExpression]. + */ +public fun > MST.toDiffExpression(algebra: A): KotlingradExpression = + KotlingradExpression(algebra, this) diff --git a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/ScalarsAdapters.kt b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/scalarsAdapters.kt similarity index 72% rename from kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/ScalarsAdapters.kt rename to kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/scalarsAdapters.kt index f65d2948d..6c0b98c59 100644 --- a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/ScalarsAdapters.kt +++ b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/scalarsAdapters.kt @@ -7,26 +7,27 @@ package space.kscience.kmath.kotlingrad import edu.umontreal.kotlingrad.api.* import space.kscience.kmath.expressions.MST -import space.kscience.kmath.expressions.MstAlgebra import space.kscience.kmath.expressions.MstExtendedField import space.kscience.kmath.expressions.MstExtendedField.unaryMinus +import space.kscience.kmath.expressions.MstNumericAlgebra +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.operations.* /** - * Maps [SVar] to [MST.Symbolic] directly. + * Maps [SVar] to [Symbol] directly. * - * @receiver the variable. - * @return a node. + * @receiver The variable. + * @returnAa node. */ -public fun > SVar.toMst(): MST.Symbolic = MstAlgebra.bindSymbol(name) +public fun > SVar.toMst(): Symbol = MstNumericAlgebra.bindSymbol(name) /** * Maps [SVar] to [MST.Numeric] directly. * - * @receiver the constant. - * @return a node. + * @receiver The constant. + * @return A node. */ -public fun > SConst.toMst(): MST.Numeric = MstAlgebra.number(doubleValue) +public fun > SConst.toMst(): MST.Numeric = MstNumericAlgebra.number(doubleValue) /** * Maps [SFun] objects to [MST]. Some unsupported operations like [Derivative] are bound and converted then. @@ -49,8 +50,8 @@ public fun > SConst.toMst(): MST.Numeric = MstAlgebra.number(doub * - [VSumAll] is requested to be evaluated; * - [Derivative] is requested to be evaluated. * - * @receiver the scalar function. - * @return a node. + * @receiver The scalar function. + * @return A node. */ public fun > SFun.toMst(): MST = MstExtendedField { when (this@toMst) { @@ -74,19 +75,18 @@ public fun > SFun.toMst(): MST = MstExtendedField { /** * Maps [MST.Numeric] to [SConst] directly. * - * @receiver the node. - * @return a new constant. + * @receiver The node. + * @return A new constant. */ public fun > MST.Numeric.toSConst(): SConst = SConst(value) /** - * Maps [MST.Symbolic] to [SVar] directly. + * Maps [Symbol] to [SVar] directly. * - * @receiver the node. - * @param proto the prototype instance. - * @return a new variable. + * @receiver The node. + * @return A new variable. */ -internal fun > MST.Symbolic.toSVar(): SVar = SVar(value) +internal fun > Symbol.toSVar(): SVar = SVar(identity) /** * Maps [MST] objects to [SFun]. Unsupported operations throw [IllegalStateException]. @@ -94,17 +94,16 @@ internal fun > MST.Symbolic.toSVar(): SVar = SVar(value) * Detailed mapping is: * * - [MST.Numeric] -> [SConst]; - * - [MST.Symbolic] -> [SVar]; + * - [Symbol] -> [SVar]; * - [MST.Unary] -> [Negative], [Sine], [Cosine], [Tangent], [Power], [Log]; * - [MST.Binary] -> [Sum], [Prod], [Power]. * - * @receiver the node. - * @param proto the prototype instance. - * @return a scalar function. + * @receiver The node. + * @return A scalar function. */ public fun > MST.toSFun(): SFun = when (this) { is MST.Numeric -> toSConst() - is MST.Symbolic -> toSVar() + is Symbol -> toSVar() is MST.Unary -> when (operation) { GroupOperations.PLUS_OPERATION -> +value.toSFun() @@ -115,6 +114,12 @@ public fun > MST.toSFun(): SFun = when (this) { PowerOperations.SQRT_OPERATION -> sqrt(value.toSFun()) ExponentialOperations.EXP_OPERATION -> exp(value.toSFun()) ExponentialOperations.LN_OPERATION -> value.toSFun().ln() + ExponentialOperations.SINH_OPERATION -> MstExtendedField { (exp(value) - exp(-value)) / 2.0 }.toSFun() + ExponentialOperations.COSH_OPERATION -> MstExtendedField { (exp(value) + exp(-value)) / 2.0 }.toSFun() + ExponentialOperations.TANH_OPERATION -> MstExtendedField { (exp(value) - exp(-value)) / (exp(-value) + exp(value)) }.toSFun() + ExponentialOperations.ASINH_OPERATION -> MstExtendedField { ln(sqrt(value * value + one) + value) }.toSFun() + ExponentialOperations.ACOSH_OPERATION -> MstExtendedField { ln(value + sqrt((value - one) * (value + one))) }.toSFun() + ExponentialOperations.ATANH_OPERATION -> MstExtendedField { (ln(value + one) - ln(one - value)) / 2.0 }.toSFun() else -> error("Unary operation $operation not defined in $this") } diff --git a/kmath-kotlingrad/src/test/kotlin/space/kscience/kmath/kotlingrad/AdaptingTests.kt b/kmath-kotlingrad/src/test/kotlin/space/kscience/kmath/kotlingrad/AdaptingTests.kt index 3ed73451d..57fe2411c 100644 --- a/kmath-kotlingrad/src/test/kotlin/space/kscience/kmath/kotlingrad/AdaptingTests.kt +++ b/kmath-kotlingrad/src/test/kotlin/space/kscience/kmath/kotlingrad/AdaptingTests.kt @@ -8,9 +8,9 @@ package space.kscience.kmath.kotlingrad import edu.umontreal.kotlingrad.api.* import space.kscience.kmath.asm.compileToExpression import space.kscience.kmath.ast.parseMath -import space.kscience.kmath.expressions.MstAlgebra +import space.kscience.kmath.expressions.MstNumericAlgebra +import space.kscience.kmath.expressions.Symbol.Companion.x import space.kscience.kmath.expressions.invoke -import space.kscience.kmath.misc.symbol import space.kscience.kmath.operations.DoubleField import kotlin.test.Test import kotlin.test.assertEquals @@ -20,7 +20,7 @@ import kotlin.test.fail internal class AdaptingTests { @Test fun symbol() { - val c1 = MstAlgebra.bindSymbol(x.identity) + val c1 = MstNumericAlgebra.bindSymbol(x.identity) assertEquals(x.identity, c1.toSVar>().name) val c2 = "kitten".parseMath().toSFun>() if (c2 is SVar) assertTrue(c2.name == "kitten") else fail() @@ -28,7 +28,7 @@ internal class AdaptingTests { @Test fun number() { - val c1 = MstAlgebra.number(12354324) + val c1 = MstNumericAlgebra.number(12354324) assertTrue(c1.toSConst().doubleValue == 12354324.0) val c2 = "0.234".parseMath().toSFun>() if (c2 is SConst) assertTrue(c2.doubleValue == 0.234) else fail() @@ -46,7 +46,7 @@ internal class AdaptingTests { @Test fun simpleFunctionDerivative() { - val xSVar = MstAlgebra.bindSymbol(x.identity).toSVar>() + val xSVar = MstNumericAlgebra.bindSymbol(x.identity).toSVar>() val quadratic = "x^2-4*x-44".parseMath().toSFun>() val actualDerivative = quadratic.d(xSVar).toMst().compileToExpression(DoubleField) val expectedDerivative = "2*x-4".parseMath().compileToExpression(DoubleField) @@ -55,7 +55,7 @@ internal class AdaptingTests { @Test fun moreComplexDerivative() { - val xSVar = MstAlgebra.bindSymbol(x.identity).toSVar>() + val xSVar = MstNumericAlgebra.bindSymbol(x.identity).toSVar>() val composition = "-sqrt(sin(x^2)-cos(x)^2-16*x)".parseMath().toSFun>() val actualDerivative = composition.d(xSVar).toMst().compileToExpression(DoubleField) @@ -65,8 +65,4 @@ internal class AdaptingTests { assertEquals(actualDerivative(x to 0.1), expectedDerivative(x to 0.1)) } - - private companion object { - private val x by symbol - } } diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts index 288c61a51..4478e5b80 100644 --- a/kmath-memory/build.gradle.kts +++ b/kmath-memory/build.gradle.kts @@ -7,6 +7,6 @@ plugins { readme { maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT description = """ - An API and basic implementation for arranging objects in a continous memory block. + An API and basic implementation for arranging objects in a continuous memory block. """.trimIndent() } diff --git a/kmath-nd4j/README.md b/kmath-nd4j/README.md index 66e0483a4..6408be13b 100644 --- a/kmath-nd4j/README.md +++ b/kmath-nd4j/README.md @@ -9,30 +9,28 @@ ND4J based implementations of KMath abstractions. ## Artifact: -The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-7`. +The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-11`. **Gradle:** ```gradle repositories { maven { url 'https://repo.kotlin.link' } - maven { url 'https://dl.bintray.com/hotkeytlt/maven' } - maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap + mavenCentral() } dependencies { - implementation 'space.kscience:kmath-nd4j:0.3.0-dev-7' + implementation 'space.kscience:kmath-nd4j:0.3.0-dev-11' } ``` **Gradle Kotlin DSL:** ```kotlin repositories { maven("https://repo.kotlin.link") - maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap - maven("https://dl.bintray.com/hotkeytlt/maven") // required for a + mavenCentral() } dependencies { - implementation("space.kscience:kmath-nd4j:0.3.0-dev-7") + implementation("space.kscience:kmath-nd4j:0.3.0-dev-11") } ``` diff --git a/kmath-nd4j/build.gradle.kts b/kmath-nd4j/build.gradle.kts index bc61060db..abcc02962 100644 --- a/kmath-nd4j/build.gradle.kts +++ b/kmath-nd4j/build.gradle.kts @@ -3,8 +3,10 @@ plugins { id("ru.mipt.npm.gradle.common") } +description = "ND4J NDStructure implementation and according NDAlgebra classes" + dependencies { - api(project(":kmath-core")) + api(project(":kmath-tensors")) api("org.nd4j:nd4j-api:1.0.0-beta7") testImplementation("org.nd4j:nd4j-native:1.0.0-beta7") testImplementation("org.nd4j:nd4j-native-platform:1.0.0-beta7") @@ -12,22 +14,9 @@ dependencies { } readme { - description = "ND4J NDStructure implementation and according NDAlgebra classes" maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) - - feature( - id = "nd4jarraystructure", - description = "NDStructure wrapper for INDArray" - ) - - feature( - id = "nd4jarrayrings", - description = "Rings over Nd4jArrayStructure of Int and Long" - ) - - feature( - id = "nd4jarrayfields", - description = "Fields over Nd4jArrayStructure of Float and Double" - ) + feature(id = "nd4jarraystructure") { "NDStructure wrapper for INDArray" } + feature(id = "nd4jarrayrings") { "Rings over Nd4jArrayStructure of Int and Long" } + feature(id = "nd4jarrayfields") { "Fields over Nd4jArrayStructure of Float and Double" } } diff --git a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebra.kt b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebra.kt index 0205f1c87..e94bda12a 100644 --- a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebra.kt +++ b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebra.kt @@ -6,14 +6,14 @@ package space.kscience.kmath.nd4j import org.nd4j.linalg.api.ndarray.INDArray -import org.nd4j.linalg.api.ops.impl.scalar.Pow -import org.nd4j.linalg.api.ops.impl.transforms.strict.* +import org.nd4j.linalg.api.ops.impl.transforms.strict.ACosh +import org.nd4j.linalg.api.ops.impl.transforms.strict.ASinh import org.nd4j.linalg.factory.Nd4j import org.nd4j.linalg.ops.transforms.Transforms +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.* import space.kscience.kmath.operations.* -import space.kscience.kmath.structures.* internal fun AlgebraND<*, *>.checkShape(array: INDArray): INDArray { val arrayShape = array.shape().toIntArray() @@ -28,23 +28,16 @@ internal fun AlgebraND<*, *>.checkShape(array: INDArray): INDArray { * @param T the type of ND-structure element. * @param C the type of the element context. */ -public interface Nd4jArrayAlgebra> : AlgebraND { +public sealed interface Nd4jArrayAlgebra> : AlgebraND { /** - * Wraps [INDArray] to [N]. + * Wraps [INDArray] to [Nd4jArrayStructure]. */ public fun INDArray.wrap(): Nd4jArrayStructure + /** + * Unwraps to or acquires [INDArray] from [StructureND]. + */ public val StructureND.ndArray: INDArray - get() = when { - !shape.contentEquals(this@Nd4jArrayAlgebra.shape) -> throw ShapeMismatchException( - this@Nd4jArrayAlgebra.shape, - shape - ) - this is Nd4jArrayStructure -> ndArray //TODO check strides - else -> { - TODO() - } - } public override fun produce(initializer: C.(IntArray) -> T): Nd4jArrayStructure { val struct = Nd4j.create(*shape)!!.wrap() @@ -52,6 +45,7 @@ public interface Nd4jArrayAlgebra> : AlgebraND { return struct } + @PerformancePitfall public override fun StructureND.map(transform: C.(T) -> T): Nd4jArrayStructure { val newStruct = ndArray.dup().wrap() newStruct.elements().forEach { (idx, value) -> newStruct[idx] = elementContext.transform(value) } @@ -83,7 +77,7 @@ public interface Nd4jArrayAlgebra> : AlgebraND { * @param T the type of the element contained in ND structure. * @param S the type of space of structure elements. */ -public interface Nd4JArrayGroup> : GroupND, Nd4jArrayAlgebra { +public sealed interface Nd4jArrayGroup> : GroupND, Nd4jArrayAlgebra { public override val zero: Nd4jArrayStructure get() = Nd4j.zeros(*shape).wrap() @@ -108,7 +102,7 @@ public interface Nd4JArrayGroup> : GroupND, Nd4jArrayAlgebr * @param R the type of ring of structure elements. */ @OptIn(UnstableKMathAPI::class) -public interface Nd4jArrayRing> : RingND, Nd4JArrayGroup { +public sealed interface Nd4jArrayRing> : RingND, Nd4jArrayGroup { public override val one: Nd4jArrayStructure get() = Nd4j.ones(*shape).wrap() @@ -133,10 +127,7 @@ public interface Nd4jArrayRing> : RingND, Nd4JArrayGroup> = - ThreadLocal.withInitial { hashMapOf() } - - private val longNd4jArrayRingCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } + ThreadLocal.withInitial(::HashMap) /** * Creates an [RingND] for [Int] values or pull it from cache if it was created previously. @@ -144,20 +135,13 @@ public interface Nd4jArrayRing> : RingND, Nd4JArrayGroup = intNd4jArrayRingCache.get().getOrPut(shape) { IntNd4jArrayRing(shape) } - /** - * Creates an [RingND] for [Long] values or pull it from cache if it was created previously. - */ - public fun long(vararg shape: Int): Nd4jArrayRing = - longNd4jArrayRingCache.get().getOrPut(shape) { LongNd4jArrayRing(shape) } - /** * Creates a most suitable implementation of [RingND] using reified class. */ @Suppress("UNCHECKED_CAST") - public inline fun auto(vararg shape: Int): Nd4jArrayRing> = when { - T::class == Int::class -> int(*shape) as Nd4jArrayRing> - T::class == Long::class -> long(*shape) as Nd4jArrayRing> - else -> throw UnsupportedOperationException("This factory method only supports Int and Long types.") + public inline fun auto(vararg shape: Int): Nd4jArrayRing> = when { + T::class == Int::class -> int(*shape) as Nd4jArrayRing> + else -> throw UnsupportedOperationException("This factory method only supports Long type.") } } } @@ -166,11 +150,9 @@ public interface Nd4jArrayRing> : RingND, Nd4JArrayGroup> : FieldND, Nd4jArrayRing { - +public sealed interface Nd4jArrayField> : FieldND, Nd4jArrayRing { public override fun divide(a: StructureND, b: StructureND): Nd4jArrayStructure = a.ndArray.div(b.ndArray).wrap() @@ -178,10 +160,10 @@ public interface Nd4jArrayField> : FieldND, Nd4jArrayRing< public companion object { private val floatNd4jArrayFieldCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } + ThreadLocal.withInitial(::HashMap) private val doubleNd4JArrayFieldCache: ThreadLocal> = - ThreadLocal.withInitial { hashMapOf() } + ThreadLocal.withInitial(::HashMap) /** * Creates an [FieldND] for [Float] values or pull it from cache if it was created previously. @@ -196,26 +178,64 @@ public interface Nd4jArrayField> : FieldND, Nd4jArrayRing< doubleNd4JArrayFieldCache.get().getOrPut(shape) { DoubleNd4jArrayField(shape) } /** - * Creates a most suitable implementation of [RingND] using reified class. + * Creates a most suitable implementation of [FieldND] using reified class. */ @Suppress("UNCHECKED_CAST") - public inline fun auto(vararg shape: Int): Nd4jArrayField> = when { - T::class == Float::class -> float(*shape) as Nd4jArrayField> - T::class == Double::class -> real(*shape) as Nd4jArrayField> + public inline fun auto(vararg shape: Int): Nd4jArrayField> = when { + T::class == Float::class -> float(*shape) as Nd4jArrayField> + T::class == Double::class -> real(*shape) as Nd4jArrayField> else -> throw UnsupportedOperationException("This factory method only supports Float and Double types.") } } } +/** + * Represents intersection of [ExtendedField] and [Field] over [Nd4jArrayStructure]. + */ +public sealed interface Nd4jArrayExtendedField> : ExtendedField>, + Nd4jArrayField { + public override fun sin(arg: StructureND): StructureND = Transforms.sin(arg.ndArray).wrap() + public override fun cos(arg: StructureND): StructureND = Transforms.cos(arg.ndArray).wrap() + public override fun asin(arg: StructureND): StructureND = Transforms.asin(arg.ndArray).wrap() + public override fun acos(arg: StructureND): StructureND = Transforms.acos(arg.ndArray).wrap() + public override fun atan(arg: StructureND): StructureND = Transforms.atan(arg.ndArray).wrap() + + public override fun power(arg: StructureND, pow: Number): StructureND = + Transforms.pow(arg.ndArray, pow).wrap() + + public override fun exp(arg: StructureND): StructureND = Transforms.exp(arg.ndArray).wrap() + public override fun ln(arg: StructureND): StructureND = Transforms.log(arg.ndArray).wrap() + public override fun sqrt(arg: StructureND): StructureND = Transforms.sqrt(arg.ndArray).wrap() + public override fun sinh(arg: StructureND): StructureND = Transforms.sinh(arg.ndArray).wrap() + public override fun cosh(arg: StructureND): StructureND = Transforms.cosh(arg.ndArray).wrap() + public override fun tanh(arg: StructureND): StructureND = Transforms.tanh(arg.ndArray).wrap() + + public override fun asinh(arg: StructureND): StructureND = + Nd4j.getExecutioner().exec(ASinh(arg.ndArray, arg.ndArray.ulike())).wrap() + + public override fun acosh(arg: StructureND): StructureND = + Nd4j.getExecutioner().exec(ACosh(arg.ndArray, arg.ndArray.ulike())).wrap() + + public override fun atanh(arg: StructureND): StructureND = Transforms.atanh(arg.ndArray).wrap() +} + /** * Represents [FieldND] over [Nd4jArrayDoubleStructure]. */ -public class DoubleNd4jArrayField(public override val shape: IntArray) : Nd4jArrayField, - ExtendedField> { +public class DoubleNd4jArrayField(public override val shape: IntArray) : Nd4jArrayExtendedField { public override val elementContext: DoubleField get() = DoubleField public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asDoubleStructure() + @OptIn(PerformancePitfall::class) + override val StructureND.ndArray: INDArray + get() = when (this) { + is Nd4jArrayStructure -> checkShape(ndArray) + else -> Nd4j.zeros(*shape).also { + elements().forEach { (idx, value) -> it.putScalar(idx, value) } + } + } + override fun scale(a: StructureND, value: Double): Nd4jArrayStructure { return a.ndArray.mul(value).wrap() } @@ -243,34 +263,25 @@ public class DoubleNd4jArrayField(public override val shape: IntArray) : Nd4jArr public override operator fun Double.minus(arg: StructureND): Nd4jArrayStructure { return arg.ndArray.rsub(this).wrap() } - - override fun sin(arg: StructureND): StructureND = Transforms.sin(arg.ndArray).wrap() - - override fun cos(arg: StructureND): StructureND = Transforms.cos(arg.ndArray).wrap() - - override fun asin(arg: StructureND): StructureND = Transforms.asin(arg.ndArray).wrap() - - override fun acos(arg: StructureND): StructureND = Transforms.acos(arg.ndArray).wrap() - - override fun atan(arg: StructureND): StructureND = Transforms.atan(arg.ndArray).wrap() - - override fun power(arg: StructureND, pow: Number): StructureND = - Transforms.pow(arg.ndArray,pow).wrap() - - override fun exp(arg: StructureND): StructureND = Transforms.exp(arg.ndArray).wrap() - - override fun ln(arg: StructureND): StructureND = Transforms.log(arg.ndArray).wrap() } /** * Represents [FieldND] over [Nd4jArrayStructure] of [Float]. */ -public class FloatNd4jArrayField(public override val shape: IntArray) : Nd4jArrayField, - ExtendedField> { +public class FloatNd4jArrayField(public override val shape: IntArray) : Nd4jArrayExtendedField { public override val elementContext: FloatField get() = FloatField public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asFloatStructure() + @OptIn(PerformancePitfall::class) + public override val StructureND.ndArray: INDArray + get() = when (this) { + is Nd4jArrayStructure -> checkShape(ndArray) + else -> Nd4j.zeros(*shape).also { + elements().forEach { (idx, value) -> it.putScalar(idx, value) } + } + } + override fun scale(a: StructureND, value: Double): StructureND = a.ndArray.mul(value).wrap() @@ -291,23 +302,6 @@ public class FloatNd4jArrayField(public override val shape: IntArray) : Nd4jArra public override operator fun Float.minus(arg: StructureND): Nd4jArrayStructure = arg.ndArray.rsub(this).wrap() - - override fun sin(arg: StructureND): StructureND = Sin(arg.ndArray).z().wrap() - - override fun cos(arg: StructureND): StructureND = Cos(arg.ndArray).z().wrap() - - override fun asin(arg: StructureND): StructureND = ASin(arg.ndArray).z().wrap() - - override fun acos(arg: StructureND): StructureND = ACos(arg.ndArray).z().wrap() - - override fun atan(arg: StructureND): StructureND = ATan(arg.ndArray).z().wrap() - - override fun power(arg: StructureND, pow: Number): StructureND = - Pow(arg.ndArray, pow.toDouble()).z().wrap() - - override fun exp(arg: StructureND): StructureND = Exp(arg.ndArray).z().wrap() - - override fun ln(arg: StructureND): StructureND = Log(arg.ndArray).z().wrap() } /** @@ -319,6 +313,15 @@ public class IntNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRi public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asIntStructure() + @OptIn(PerformancePitfall::class) + public override val StructureND.ndArray: INDArray + get() = when (this) { + is Nd4jArrayStructure -> checkShape(ndArray) + else -> Nd4j.zeros(*shape).also { + elements().forEach { (idx, value) -> it.putScalar(idx, value) } + } + } + public override operator fun StructureND.plus(arg: Int): Nd4jArrayStructure = ndArray.add(arg).wrap() @@ -331,25 +334,3 @@ public class IntNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRi public override operator fun Int.minus(arg: StructureND): Nd4jArrayStructure = arg.ndArray.rsub(this).wrap() } - -/** - * Represents [RingND] over [Nd4jArrayStructure] of [Long]. - */ -public class LongNd4jArrayRing(public override val shape: IntArray) : Nd4jArrayRing { - public override val elementContext: LongRing - get() = LongRing - - public override fun INDArray.wrap(): Nd4jArrayStructure = checkShape(this).asLongStructure() - - public override operator fun StructureND.plus(arg: Long): Nd4jArrayStructure = - ndArray.add(arg).wrap() - - public override operator fun StructureND.minus(arg: Long): Nd4jArrayStructure = - ndArray.sub(arg).wrap() - - public override operator fun StructureND.times(arg: Long): Nd4jArrayStructure = - ndArray.mul(arg).wrap() - - public override operator fun Long.minus(arg: StructureND): Nd4jArrayStructure = - arg.ndArray.rsub(this).wrap() -} diff --git a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayIterator.kt b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayIterator.kt index 71887ca3c..140a212f8 100644 --- a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayIterator.kt +++ b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayIterator.kt @@ -48,12 +48,6 @@ private class Nd4jArrayDoubleIterator(iterateOver: INDArray) : Nd4jArrayIterator internal fun INDArray.realIterator(): Iterator> = Nd4jArrayDoubleIterator(this) -private class Nd4jArrayLongIterator(iterateOver: INDArray) : Nd4jArrayIteratorBase(iterateOver) { - override fun getSingle(indices: LongArray) = iterateOver.getLong(*indices) -} - -internal fun INDArray.longIterator(): Iterator> = Nd4jArrayLongIterator(this) - private class Nd4jArrayIntIterator(iterateOver: INDArray) : Nd4jArrayIteratorBase(iterateOver) { override fun getSingle(indices: LongArray) = iterateOver.getInt(*indices.toIntArray()) } diff --git a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructure.kt b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructure.kt index 79db86fcc..ffddcef90 100644 --- a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructure.kt +++ b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructure.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.nd4j import org.nd4j.linalg.api.ndarray.INDArray +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.nd.MutableStructureND import space.kscience.kmath.nd.StructureND @@ -16,7 +17,8 @@ import space.kscience.kmath.nd.StructureND */ public sealed class Nd4jArrayStructure : MutableStructureND { /** - * The wrapped [INDArray]. + * The wrapped [INDArray]. Since KMath uses [Int] indexes, assuming that the size of [INDArray] is less or equal to + * [Int.MAX_VALUE]. */ public abstract val ndArray: INDArray @@ -24,6 +26,8 @@ public sealed class Nd4jArrayStructure : MutableStructureND { internal abstract fun elementsIterator(): Iterator> internal fun indicesIterator(): Iterator = ndArray.indicesIterator() + + @PerformancePitfall public override fun elements(): Sequence> = Sequence(::elementsIterator) } @@ -38,17 +42,6 @@ private data class Nd4jArrayIntStructure(override val ndArray: INDArray) : Nd4jA */ public fun INDArray.asIntStructure(): Nd4jArrayStructure = Nd4jArrayIntStructure(this) -private data class Nd4jArrayLongStructure(override val ndArray: INDArray) : Nd4jArrayStructure() { - override fun elementsIterator(): Iterator> = ndArray.longIterator() - override fun get(index: IntArray): Long = ndArray.getLong(*index.toLongArray()) - override fun set(index: IntArray, value: Long): Unit = run { ndArray.putScalar(index, value.toDouble()) } -} - -/** - * Wraps this [INDArray] to [Nd4jArrayStructure]. - */ -public fun INDArray.asLongStructure(): Nd4jArrayStructure = Nd4jArrayLongStructure(this) - private data class Nd4jArrayDoubleStructure(override val ndArray: INDArray) : Nd4jArrayStructure() { override fun elementsIterator(): Iterator> = ndArray.realIterator() override fun get(index: IntArray): Double = ndArray.getDouble(*index) diff --git a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt new file mode 100644 index 000000000..456f7c2a9 --- /dev/null +++ b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/Nd4jTensorAlgebra.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.nd4j + +import org.nd4j.linalg.api.ndarray.INDArray +import org.nd4j.linalg.api.ops.impl.summarystats.Variance +import org.nd4j.linalg.api.ops.impl.transforms.strict.ACosh +import org.nd4j.linalg.api.ops.impl.transforms.strict.ASinh +import org.nd4j.linalg.factory.Nd4j +import org.nd4j.linalg.factory.ops.NDBase +import org.nd4j.linalg.ops.transforms.Transforms +import space.kscience.kmath.misc.PerformancePitfall +import space.kscience.kmath.nd.StructureND +import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.api.TensorAlgebra +import space.kscience.kmath.tensors.core.DoubleTensorAlgebra + +/** + * ND4J based [TensorAlgebra] implementation. + */ +public sealed interface Nd4jTensorAlgebra : AnalyticTensorAlgebra { + /** + * Wraps [INDArray] to [Nd4jArrayStructure]. + */ + public fun INDArray.wrap(): Nd4jArrayStructure + + /** + * Unwraps to or acquires [INDArray] from [StructureND]. + */ + public val StructureND.ndArray: INDArray + + public override fun T.plus(other: Tensor): Tensor = other.ndArray.add(this).wrap() + public override fun Tensor.plus(value: T): Tensor = ndArray.add(value).wrap() + + public override fun Tensor.plus(other: Tensor): Tensor = ndArray.add(other.ndArray).wrap() + + public override fun Tensor.plusAssign(value: T) { + ndArray.addi(value) + } + + public override fun Tensor.plusAssign(other: Tensor) { + ndArray.addi(other.ndArray) + } + + public override fun T.minus(other: Tensor): Tensor = other.ndArray.rsub(this).wrap() + public override fun Tensor.minus(value: T): Tensor = ndArray.sub(value).wrap() + public override fun Tensor.minus(other: Tensor): Tensor = ndArray.sub(other.ndArray).wrap() + + public override fun Tensor.minusAssign(value: T) { + ndArray.rsubi(value) + } + + public override fun Tensor.minusAssign(other: Tensor) { + ndArray.subi(other.ndArray) + } + + public override fun T.times(other: Tensor): Tensor = other.ndArray.mul(this).wrap() + + public override fun Tensor.times(value: T): Tensor = + ndArray.mul(value).wrap() + + public override fun Tensor.times(other: Tensor): Tensor = ndArray.mul(other.ndArray).wrap() + + public override fun Tensor.timesAssign(value: T) { + ndArray.muli(value) + } + + public override fun Tensor.timesAssign(other: Tensor) { + ndArray.mmuli(other.ndArray) + } + + public override fun Tensor.unaryMinus(): Tensor = ndArray.neg().wrap() + public override fun Tensor.get(i: Int): Tensor = ndArray.slice(i.toLong()).wrap() + public override fun Tensor.transpose(i: Int, j: Int): Tensor = ndArray.swapAxes(i, j).wrap() + public override fun Tensor.dot(other: Tensor): Tensor = ndArray.mmul(other.ndArray).wrap() + + public override fun Tensor.min(dim: Int, keepDim: Boolean): Tensor = + ndArray.min(keepDim, dim).wrap() + + public override fun Tensor.sum(dim: Int, keepDim: Boolean): Tensor = + ndArray.sum(keepDim, dim).wrap() + + public override fun Tensor.max(dim: Int, keepDim: Boolean): Tensor = + ndArray.max(keepDim, dim).wrap() + + public override fun Tensor.view(shape: IntArray): Tensor = ndArray.reshape(shape).wrap() + public override fun Tensor.viewAs(other: Tensor): Tensor = view(other.shape) + + public override fun Tensor.argMax(dim: Int, keepDim: Boolean): Tensor = + ndBase.get().argmax(ndArray, keepDim, dim).wrap() + + public override fun Tensor.mean(dim: Int, keepDim: Boolean): Tensor = ndArray.mean(keepDim, dim).wrap() + + public override fun Tensor.exp(): Tensor = Transforms.exp(ndArray).wrap() + public override fun Tensor.ln(): Tensor = Transforms.log(ndArray).wrap() + public override fun Tensor.sqrt(): Tensor = Transforms.sqrt(ndArray).wrap() + public override fun Tensor.cos(): Tensor = Transforms.cos(ndArray).wrap() + public override fun Tensor.acos(): Tensor = Transforms.acos(ndArray).wrap() + public override fun Tensor.cosh(): Tensor = Transforms.cosh(ndArray).wrap() + + public override fun Tensor.acosh(): Tensor = + Nd4j.getExecutioner().exec(ACosh(ndArray, ndArray.ulike())).wrap() + + public override fun Tensor.sin(): Tensor = Transforms.sin(ndArray).wrap() + public override fun Tensor.asin(): Tensor = Transforms.asin(ndArray).wrap() + public override fun Tensor.sinh(): Tensor = Transforms.sinh(ndArray).wrap() + + public override fun Tensor.asinh(): Tensor = + Nd4j.getExecutioner().exec(ASinh(ndArray, ndArray.ulike())).wrap() + + public override fun Tensor.tan(): Tensor = Transforms.tan(ndArray).wrap() + public override fun Tensor.atan(): Tensor = Transforms.atan(ndArray).wrap() + public override fun Tensor.tanh(): Tensor = Transforms.tanh(ndArray).wrap() + public override fun Tensor.atanh(): Tensor = Transforms.atanh(ndArray).wrap() + public override fun Tensor.ceil(): Tensor = Transforms.ceil(ndArray).wrap() + public override fun Tensor.floor(): Tensor = Transforms.floor(ndArray).wrap() + public override fun Tensor.std(dim: Int, keepDim: Boolean): Tensor = ndArray.std(true, keepDim, dim).wrap() + public override fun T.div(other: Tensor): Tensor = other.ndArray.rdiv(this).wrap() + public override fun Tensor.div(value: T): Tensor = ndArray.div(value).wrap() + public override fun Tensor.div(other: Tensor): Tensor = ndArray.div(other.ndArray).wrap() + + public override fun Tensor.divAssign(value: T) { + ndArray.divi(value) + } + + public override fun Tensor.divAssign(other: Tensor) { + ndArray.divi(other.ndArray) + } + + public override fun Tensor.variance(dim: Int, keepDim: Boolean): Tensor = + Nd4j.getExecutioner().exec(Variance(ndArray, true, true, dim)).wrap() + + private companion object { + private val ndBase: ThreadLocal = ThreadLocal.withInitial(::NDBase) + } +} + +/** + * [Double] specialization of [Nd4jTensorAlgebra]. + */ +public object DoubleNd4jTensorAlgebra : Nd4jTensorAlgebra { + public override fun INDArray.wrap(): Nd4jArrayStructure = asDoubleStructure() + + @OptIn(PerformancePitfall::class) + public override val StructureND.ndArray: INDArray + get() = when (this) { + is Nd4jArrayStructure -> ndArray + else -> Nd4j.zeros(*shape).also { + elements().forEach { (idx, value) -> it.putScalar(idx, value) } + } + } + + public override fun Tensor.valueOrNull(): Double? = + if (shape contentEquals intArrayOf(1)) ndArray.getDouble(0) else null + + // TODO rewrite + @PerformancePitfall + public override fun diagonalEmbedding( + diagonalEntries: Tensor, + offset: Int, + dim1: Int, + dim2: Int, + ): Tensor = DoubleTensorAlgebra.diagonalEmbedding(diagonalEntries, offset, dim1, dim2) + + public override fun Tensor.sum(): Double = ndArray.sumNumber().toDouble() + public override fun Tensor.min(): Double = ndArray.minNumber().toDouble() + public override fun Tensor.max(): Double = ndArray.maxNumber().toDouble() + public override fun Tensor.mean(): Double = ndArray.meanNumber().toDouble() + public override fun Tensor.std(): Double = ndArray.stdNumber().toDouble() + public override fun Tensor.variance(): Double = ndArray.varNumber().toDouble() +} diff --git a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/arrays.kt b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/arrays.kt index 6c414cc13..75a334ca7 100644 --- a/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/arrays.kt +++ b/kmath-nd4j/src/main/kotlin/space/kscience/kmath/nd4j/arrays.kt @@ -5,5 +5,4 @@ package space.kscience.kmath.nd4j -internal fun IntArray.toLongArray(): LongArray = LongArray(size) { this[it].toLong() } internal fun LongArray.toIntArray(): IntArray = IntArray(size) { this[it].toInt() } diff --git a/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt b/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt index b6c524cc6..40da22763 100644 --- a/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt +++ b/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayAlgebraTest.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.nd4j import org.nd4j.linalg.factory.Nd4j +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.nd.StructureND import space.kscience.kmath.operations.invoke import kotlin.math.PI @@ -14,6 +15,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail +@OptIn(PerformancePitfall::class) internal class Nd4jArrayAlgebraTest { @Test fun testProduce() { @@ -28,7 +30,7 @@ internal class Nd4jArrayAlgebraTest { @Test fun testMap() { - val res = with(IntNd4jArrayRing(intArrayOf(2, 2))) { one.map() { it + it * 2 } } + val res = with(IntNd4jArrayRing(intArrayOf(2, 2))) { one.map { it + it * 2 } } val expected = (Nd4j.create(2, 2) ?: fail()).asIntStructure() expected[intArrayOf(0, 0)] = 3 expected[intArrayOf(0, 1)] = 3 @@ -50,7 +52,7 @@ internal class Nd4jArrayAlgebraTest { @Test fun testSin() = DoubleNd4jArrayField(intArrayOf(2, 2)).invoke { - val initial = produce { (i, j) -> if (i == j) PI/2 else 0.0 } + val initial = produce { (i, j) -> if (i == j) PI / 2 else 0.0 } val transformed = sin(initial) val expected = produce { (i, j) -> if (i == j) 1.0 else 0.0 } diff --git a/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt b/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt index d59e04194..30d01338f 100644 --- a/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt +++ b/kmath-nd4j/src/test/kotlin/space/kscience/kmath/nd4j/Nd4jArrayStructureTest.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.nd4j import org.nd4j.linalg.factory.Nd4j +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.nd.get import kotlin.test.Test import kotlin.test.assertEquals @@ -13,6 +14,7 @@ import kotlin.test.assertNotEquals import kotlin.test.fail internal class Nd4jArrayStructureTest { + @OptIn(PerformancePitfall::class) @Test fun testElements() { val nd = Nd4j.create(doubleArrayOf(1.0, 2.0, 3.0))!! @@ -70,7 +72,7 @@ internal class Nd4jArrayStructureTest { @Test fun testSet() { val nd = Nd4j.rand(17, 12, 4, 8)!! - val struct = nd.asLongStructure() + val struct = nd.asIntStructure() struct[intArrayOf(1, 2, 3, 4)] = 777 assertEquals(777, struct[1, 2, 3, 4]) } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index f1b6ef38d..637746a27 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -6,11 +6,7 @@ package space.kscience.kmath.optimization import space.kscience.kmath.data.ColumnarData -import space.kscience.kmath.expressions.AutoDiffProcessor -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.expressions.ExpressionAlgebra -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.* import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.Field diff --git a/kmath-stat/src/jvmTest/kotlin/space/kscience/kmath/stat/CommonsDistributionsTest.kt b/kmath-stat/src/jvmTest/kotlin/space/kscience/kmath/stat/CommonsDistributionsTest.kt index 066fd69e6..19c01e099 100644 --- a/kmath-stat/src/jvmTest/kotlin/space/kscience/kmath/stat/CommonsDistributionsTest.kt +++ b/kmath-stat/src/jvmTest/kotlin/space/kscience/kmath/stat/CommonsDistributionsTest.kt @@ -5,15 +5,14 @@ package space.kscience.kmath.stat -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import space.kscience.kmath.samplers.GaussianSampler internal class CommonsDistributionsTest { @Test - fun testNormalDistributionSuspend() = GlobalScope.launch { + fun testNormalDistributionSuspend() = runBlocking { val distribution = GaussianSampler(7.0, 2.0) val generator = RandomGenerator.default(1) val sample = distribution.sample(generator).nextBuffer(1000) diff --git a/kmath-tensors/README.md b/kmath-tensors/README.md new file mode 100644 index 000000000..75de2bf35 --- /dev/null +++ b/kmath-tensors/README.md @@ -0,0 +1,35 @@ +# Module kmath-tensors + +Common linear algebra operations on tensors. + + - [tensor algebra](src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt) : Basic linear algebra operations on tensors (plus, dot, etc.) + - [tensor algebra with broadcasting](src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/BroadcastDoubleTensorAlgebra.kt) : Basic linear algebra operations implemented with broadcasting. + - [linear algebra operations](src/commonMain/kotlin/space/kscience/kmath/tensors/api/LinearOpsTensorAlgebra.kt) : Advanced linear algebra operations like LU decomposition, SVD, etc. + + +## Artifact: + +The Maven coordinates of this project are `space.kscience:kmath-tensors:0.3.0-dev-11`. + +**Gradle:** +```gradle +repositories { + maven { url 'https://repo.kotlin.link' } + mavenCentral() +} + +dependencies { + implementation 'space.kscience:kmath-tensors:0.3.0-dev-11' +} +``` +**Gradle Kotlin DSL:** +```kotlin +repositories { + maven("https://repo.kotlin.link") + mavenCentral() +} + +dependencies { + implementation("space.kscience:kmath-tensors:0.3.0-dev-11") +} +``` diff --git a/kmath-tensors/build.gradle.kts b/kmath-tensors/build.gradle.kts new file mode 100644 index 000000000..b7f24dc6a --- /dev/null +++ b/kmath-tensors/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + id("ru.mipt.npm.gradle.mpp") +} + +kotlin.sourceSets { + all { + languageSettings.useExperimentalAnnotation("space.kscience.kmath.misc.UnstableKMathAPI") + } + commonMain { + dependencies { + api(project(":kmath-core")) + api(project(":kmath-stat")) + } + } +} + +tasks.dokkaHtml { + dependsOn(tasks.build) +} + +readme { + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE + propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md")) + + feature( + id = "tensor algebra", + description = "Basic linear algebra operations on tensors (plus, dot, etc.)", + ref = "src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt" + ) + + feature( + id = "tensor algebra with broadcasting", + description = "Basic linear algebra operations implemented with broadcasting.", + ref = "src/commonMain/kotlin/space/kscience/kmath/tensors/core/algebras/BroadcastDoubleTensorAlgebra.kt" + ) + + feature( + id = "linear algebra operations", + description = "Advanced linear algebra operations like LU decomposition, SVD, etc.", + ref = "src/commonMain/kotlin/space/kscience/kmath/tensors/api/LinearOpsTensorAlgebra.kt" + ) + +} \ No newline at end of file diff --git a/kmath-tensors/docs/README-TEMPLATE.md b/kmath-tensors/docs/README-TEMPLATE.md new file mode 100644 index 000000000..5fd968afd --- /dev/null +++ b/kmath-tensors/docs/README-TEMPLATE.md @@ -0,0 +1,7 @@ +# Module kmath-tensors + +Common linear algebra operations on tensors. + +${features} + +${artifact} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt new file mode 100644 index 000000000..e58af14db --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/AnalyticTensorAlgebra.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.api + + +/** + * Analytic operations on [Tensor]. + * + * @param T the type of items closed under analytic functions in the tensors. + */ +public interface AnalyticTensorAlgebra : TensorPartialDivisionAlgebra { + + /** + * @return the mean of all elements in the input tensor. + */ + public fun Tensor.mean(): T + + /** + * Returns the mean of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the mean of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.mean(dim: Int, keepDim: Boolean): Tensor + + /** + * @return the standard deviation of all elements in the input tensor. + */ + public fun Tensor.std(): T + + /** + * Returns the standard deviation of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the standard deviation of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.std(dim: Int, keepDim: Boolean): Tensor + + /** + * @return the variance of all elements in the input tensor. + */ + public fun Tensor.variance(): T + + /** + * Returns the variance of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the variance of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.variance(dim: Int, keepDim: Boolean): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.exp.html + public fun Tensor.exp(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.log.html + public fun Tensor.ln(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.sqrt.html + public fun Tensor.sqrt(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.acos.html#torch.cos + public fun Tensor.cos(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.acos.html#torch.acos + public fun Tensor.acos(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.acosh.html#torch.cosh + public fun Tensor.cosh(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.acosh.html#torch.acosh + public fun Tensor.acosh(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.asin.html#torch.sin + public fun Tensor.sin(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.asin.html#torch.asin + public fun Tensor.asin(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.asin.html#torch.sinh + public fun Tensor.sinh(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.asin.html#torch.asinh + public fun Tensor.asinh(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.atan.html#torch.tan + public fun Tensor.tan(): Tensor + + //https://pytorch.org/docs/stable/generated/torch.atan.html#torch.atan + public fun Tensor.atan(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.atanh.html#torch.tanh + public fun Tensor.tanh(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.atanh.html#torch.atanh + public fun Tensor.atanh(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.ceil.html#torch.ceil + public fun Tensor.ceil(): Tensor + + //For information: https://pytorch.org/docs/stable/generated/torch.floor.html#torch.floor + public fun Tensor.floor(): Tensor + +} \ No newline at end of file diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/LinearOpsTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/LinearOpsTensorAlgebra.kt new file mode 100644 index 000000000..6bdecfa85 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/LinearOpsTensorAlgebra.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.api + +/** + * Common linear algebra operations. Operates on [Tensor]. + * + * @param T the type of items closed under division in the tensors. + */ +public interface LinearOpsTensorAlgebra : TensorPartialDivisionAlgebra { + + /** + * Computes the determinant of a square matrix input, or of each square matrix in a batched input. + * For more information: https://pytorch.org/docs/stable/linalg.html#torch.linalg.det + * + * @return the determinant. + */ + public fun Tensor.det(): Tensor + + /** + * Computes the multiplicative inverse matrix of a square matrix input, or of each square matrix in a batched input. + * Given a square matrix `A`, return the matrix `AInv` satisfying + * `A dot AInv = AInv dot A = eye(a.shape[0])`. + * For more information: https://pytorch.org/docs/stable/linalg.html#torch.linalg.inv + * + * @return the multiplicative inverse of a matrix. + */ + public fun Tensor.inv(): Tensor + + /** + * Cholesky decomposition. + * + * Computes the Cholesky decomposition of a Hermitian (or symmetric for real-valued matrices) + * positive-definite matrix or the Cholesky decompositions for a batch of such matrices. + * Each decomposition has the form: + * Given a tensor `input`, return the tensor `L` satisfying `input = L dot L.H`, + * where L is a lower-triangular matrix and L.H is the conjugate transpose of L, + * which is just a transpose for the case of real-valued input matrices. + * For more information: https://pytorch.org/docs/stable/linalg.html#torch.linalg.cholesky + * + * @return the batch of L matrices. + */ + public fun Tensor.cholesky(): Tensor + + /** + * QR decomposition. + * + * Computes the QR decomposition of a matrix or a batch of matrices, and returns a pair `(Q, R)` of tensors. + * Given a tensor `input`, return tensors (Q, R) satisfying ``input = Q dot R``, + * with `Q` being an orthogonal matrix or batch of orthogonal matrices + * and `R` being an upper triangular matrix or batch of upper triangular matrices. + * For more information: https://pytorch.org/docs/stable/linalg.html#torch.linalg.qr + * + * @return pair of Q and R tensors. + */ + public fun Tensor.qr(): Pair, Tensor> + + /** + * LUP decomposition + * + * Computes the LUP decomposition of a matrix or a batch of matrices. + * Given a tensor `input`, return tensors (P, L, U) satisfying `P dot input = L dot U`, + * with `P` being a permutation matrix or batch of matrices, + * `L` being a lower triangular matrix or batch of matrices, + * `U` being an upper triangular matrix or batch of matrices. + * + * * @return triple of P, L and U tensors + */ + public fun Tensor.lu(): Triple, Tensor, Tensor> + + /** + * Singular Value Decomposition. + * + * Computes the singular value decomposition of either a matrix or batch of matrices `input`. + * The singular value decomposition is represented as a triple `(U, S, V)`, + * such that `input = U dot diagonalEmbedding(S) dot V.H`, + * where V.H is the conjugate transpose of V. + * If input is a batch of tensors, then U, S, and Vh are also batched with the same batch dimensions as input. + * For more information: https://pytorch.org/docs/stable/linalg.html#torch.linalg.svd + * + * @return triple `(U, S, V)`. + */ + public fun Tensor.svd(): Triple, Tensor, Tensor> + + /** + * Returns eigenvalues and eigenvectors of a real symmetric matrix input or a batch of real symmetric matrices, + * represented by a pair (eigenvalues, eigenvectors). + * For more information: https://pytorch.org/docs/stable/generated/torch.symeig.html + * + * @return a pair (eigenvalues, eigenvectors) + */ + public fun Tensor.symEig(): Pair, Tensor> + +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/Tensor.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/Tensor.kt new file mode 100644 index 000000000..179787684 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/Tensor.kt @@ -0,0 +1,5 @@ +package space.kscience.kmath.tensors.api + +import space.kscience.kmath.nd.MutableStructureND + +public typealias Tensor = MutableStructureND diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt new file mode 100644 index 000000000..62b8ef046 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorAlgebra.kt @@ -0,0 +1,327 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.api + +import space.kscience.kmath.operations.Algebra + +/** + * Algebra over a ring on [Tensor]. + * For more information: https://proofwiki.org/wiki/Definition:Algebra_over_Ring + * + * @param T the type of items in the tensors. + */ +public interface TensorAlgebra : Algebra> { + + /** + * Returns a single tensor value of unit dimension if tensor shape equals to [1]. + * + * @return a nullable value of a potentially scalar tensor. + */ + public fun Tensor.valueOrNull(): T? + + /** + * Returns a single tensor value of unit dimension. The tensor shape must be equal to [1]. + * + * @return the value of a scalar tensor. + */ + public fun Tensor.value(): T = + valueOrNull() ?: throw IllegalArgumentException("Inconsistent value for tensor of with $shape shape") + + /** + * Each element of the tensor [other] is added to this value. + * The resulting tensor is returned. + * + * @param other tensor to be added. + * @return the sum of this value and tensor [other]. + */ + public operator fun T.plus(other: Tensor): Tensor + + /** + * Adds the scalar [value] to each element of this tensor and returns a new resulting tensor. + * + * @param value the number to be added to each element of this tensor. + * @return the sum of this tensor and [value]. + */ + public operator fun Tensor.plus(value: T): Tensor + + /** + * Each element of the tensor [other] is added to each element of this tensor. + * The resulting tensor is returned. + * + * @param other tensor to be added. + * @return the sum of this tensor and [other]. + */ + public operator fun Tensor.plus(other: Tensor): Tensor + + /** + * Adds the scalar [value] to each element of this tensor. + * + * @param value the number to be added to each element of this tensor. + */ + public operator fun Tensor.plusAssign(value: T) + + /** + * Each element of the tensor [other] is added to each element of this tensor. + * + * @param other tensor to be added. + */ + public operator fun Tensor.plusAssign(other: Tensor) + + /** + * Each element of the tensor [other] is subtracted from this value. + * The resulting tensor is returned. + * + * @param other tensor to be subtracted. + * @return the difference between this value and tensor [other]. + */ + public operator fun T.minus(other: Tensor): Tensor + + /** + * Subtracts the scalar [value] from each element of this tensor and returns a new resulting tensor. + * + * @param value the number to be subtracted from each element of this tensor. + * @return the difference between this tensor and [value]. + */ + public operator fun Tensor.minus(value: T): Tensor + + /** + * Each element of the tensor [other] is subtracted from each element of this tensor. + * The resulting tensor is returned. + * + * @param other tensor to be subtracted. + * @return the difference between this tensor and [other]. + */ + public operator fun Tensor.minus(other: Tensor): Tensor + + /** + * Subtracts the scalar [value] from each element of this tensor. + * + * @param value the number to be subtracted from each element of this tensor. + */ + public operator fun Tensor.minusAssign(value: T) + + /** + * Each element of the tensor [other] is subtracted from each element of this tensor. + * + * @param other tensor to be subtracted. + */ + public operator fun Tensor.minusAssign(other: Tensor) + + + /** + * Each element of the tensor [other] is multiplied by this value. + * The resulting tensor is returned. + * + * @param other tensor to be multiplied. + * @return the product of this value and tensor [other]. + */ + public operator fun T.times(other: Tensor): Tensor + + /** + * Multiplies the scalar [value] by each element of this tensor and returns a new resulting tensor. + * + * @param value the number to be multiplied by each element of this tensor. + * @return the product of this tensor and [value]. + */ + public operator fun Tensor.times(value: T): Tensor + + /** + * Each element of the tensor [other] is multiplied by each element of this tensor. + * The resulting tensor is returned. + * + * @param other tensor to be multiplied. + * @return the product of this tensor and [other]. + */ + public operator fun Tensor.times(other: Tensor): Tensor + + /** + * Multiplies the scalar [value] by each element of this tensor. + * + * @param value the number to be multiplied by each element of this tensor. + */ + public operator fun Tensor.timesAssign(value: T) + + /** + * Each element of the tensor [other] is multiplied by each element of this tensor. + * + * @param other tensor to be multiplied. + */ + public operator fun Tensor.timesAssign(other: Tensor) + + /** + * Numerical negative, element-wise. + * + * @return tensor negation of the original tensor. + */ + public operator fun Tensor.unaryMinus(): Tensor + + /** + * Returns the tensor at index i + * For more information: https://pytorch.org/cppdocs/notes/tensor_indexing.html + * + * @param i index of the extractable tensor + * @return subtensor of the original tensor with index [i] + */ + public operator fun Tensor.get(i: Int): Tensor + + /** + * Returns a tensor that is a transposed version of this tensor. The given dimensions [i] and [j] are swapped. + * For more information: https://pytorch.org/docs/stable/generated/torch.transpose.html + * + * @param i the first dimension to be transposed + * @param j the second dimension to be transposed + * @return transposed tensor + */ + public fun Tensor.transpose(i: Int = -2, j: Int = -1): Tensor + + /** + * Returns a new tensor with the same data as the self tensor but of a different shape. + * The returned tensor shares the same data and must have the same number of elements, but may have a different size + * For more information: https://pytorch.org/docs/stable/tensor_view.html + * + * @param shape the desired size + * @return tensor with new shape + */ + public fun Tensor.view(shape: IntArray): Tensor + + /** + * View this tensor as the same size as [other]. + * ``this.viewAs(other) is equivalent to this.view(other.shape)``. + * For more information: https://pytorch.org/cppdocs/notes/tensor_indexing.html + * + * @param other the result tensor has the same size as other. + * @return the result tensor with the same size as other. + */ + public fun Tensor.viewAs(other: Tensor): Tensor + + /** + * Matrix product of two tensors. + * + * The behavior depends on the dimensionality of the tensors as follows: + * 1. If both tensors are 1-dimensional, the dot product (scalar) is returned. + * + * 2. If both arguments are 2-dimensional, the matrix-matrix product is returned. + * + * 3. If the first argument is 1-dimensional and the second argument is 2-dimensional, + * a 1 is prepended to its dimension for the purpose of the matrix multiply. + * After the matrix multiply, the prepended dimension is removed. + * + * 4. If the first argument is 2-dimensional and the second argument is 1-dimensional, + * the matrix-vector product is returned. + * + * 5. If both arguments are at least 1-dimensional and at least one argument is N-dimensional (where N > 2), + * then a batched matrix multiply is returned. If the first argument is 1-dimensional, + * a 1 is prepended to its dimension for the purpose of the batched matrix multiply and removed after. + * If the second argument is 1-dimensional, a 1 is appended to its dimension for the purpose of the batched matrix + * multiple and removed after. + * The non-matrix (i.e. batch) dimensions are broadcast (and thus must be broadcastable). + * For example, if `input` is a (j × 1 × n × n) tensor and `other` is a + * (k × n × n) tensor, out will be a (j × k × n × n) tensor. + * + * For more information: https://pytorch.org/docs/stable/generated/torch.matmul.html + * + * @param other tensor to be multiplied + * @return mathematical product of two tensors + */ + public infix fun Tensor.dot(other: Tensor): Tensor + + /** + * Creates a tensor whose diagonals of certain 2D planes (specified by [dim1] and [dim2]) + * are filled by [diagonalEntries]. + * To facilitate creating batched diagonal matrices, + * the 2D planes formed by the last two dimensions of the returned tensor are chosen by default. + * + * The argument [offset] controls which diagonal to consider: + * 1. If [offset] = 0, it is the main diagonal. + * 1. If [offset] > 0, it is above the main diagonal. + * 1. If [offset] < 0, it is below the main diagonal. + * + * The size of the new matrix will be calculated + * to make the specified diagonal of the size of the last input dimension. + * For more information: https://pytorch.org/docs/stable/generated/torch.diag_embed.html + * + * @param diagonalEntries the input tensor. Must be at least 1-dimensional. + * @param offset which diagonal to consider. Default: 0 (main diagonal). + * @param dim1 first dimension with respect to which to take diagonal. Default: -2. + * @param dim2 second dimension with respect to which to take diagonal. Default: -1. + * + * @return tensor whose diagonals of certain 2D planes (specified by [dim1] and [dim2]) + * are filled by [diagonalEntries] + */ + public fun diagonalEmbedding( + diagonalEntries: Tensor, + offset: Int = 0, + dim1: Int = -2, + dim2: Int = -1, + ): Tensor + + /** + * @return the sum of all elements in the input tensor. + */ + public fun Tensor.sum(): T + + /** + * Returns the sum of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the sum of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.sum(dim: Int, keepDim: Boolean): Tensor + + /** + * @return the minimum value of all elements in the input tensor. + */ + public fun Tensor.min(): T + + /** + * Returns the minimum value of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the minimum value of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.min(dim: Int, keepDim: Boolean): Tensor + + /** + * Returns the maximum value of all elements in the input tensor. + */ + public fun Tensor.max(): T + + /** + * Returns the maximum value of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the maximum value of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.max(dim: Int, keepDim: Boolean): Tensor + + /** + * Returns the index of maximum value of each row of the input tensor in the given dimension [dim]. + * + * If [keepDim] is true, the output tensor is of the same size as + * input except in the dimension [dim] where it is of size 1. + * Otherwise, [dim] is squeezed, resulting in the output tensor having 1 fewer dimension. + * + * @param dim the dimension to reduce. + * @param keepDim whether the output tensor has [dim] retained or not. + * @return the the index of maximum value of each row of the input tensor in the given dimension [dim]. + */ + public fun Tensor.argMax(dim: Int, keepDim: Boolean): Tensor +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorPartialDivisionAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorPartialDivisionAlgebra.kt new file mode 100644 index 000000000..02bf5415d --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/api/TensorPartialDivisionAlgebra.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.api + +/** + * Algebra over a field with partial division on [Tensor]. + * For more information: https://proofwiki.org/wiki/Definition:Division_Algebra + * + * @param T the type of items closed under division in the tensors. + */ +public interface TensorPartialDivisionAlgebra : TensorAlgebra { + + /** + * Each element of the tensor [other] is divided by this value. + * The resulting tensor is returned. + * + * @param other tensor to divide by. + * @return the division of this value by the tensor [other]. + */ + public operator fun T.div(other: Tensor): Tensor + + /** + * Divide by the scalar [value] each element of this tensor returns a new resulting tensor. + * + * @param value the number to divide by each element of this tensor. + * @return the division of this tensor by the [value]. + */ + public operator fun Tensor.div(value: T): Tensor + + /** + * Each element of the tensor [other] is divided by each element of this tensor. + * The resulting tensor is returned. + * + * @param other tensor to be divided by. + * @return the division of this tensor by [other]. + */ + public operator fun Tensor.div(other: Tensor): Tensor + + /** + * Divides by the scalar [value] each element of this tensor. + * + * @param value the number to divide by each element of this tensor. + */ + public operator fun Tensor.divAssign(value: T) + + /** + * Each element of this tensor is divided by each element of the [other] tensor. + * + * @param other tensor to be divide by. + */ + public operator fun Tensor.divAssign(other: Tensor) +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt new file mode 100644 index 000000000..b8530f637 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BroadcastDoubleTensorAlgebra.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.core.internal.array +import space.kscience.kmath.tensors.core.internal.broadcastTensors +import space.kscience.kmath.tensors.core.internal.broadcastTo +import space.kscience.kmath.tensors.core.internal.tensor + +/** + * Basic linear algebra operations implemented with broadcasting. + * For more information: https://pytorch.org/docs/stable/notes/broadcasting.html + */ +public object BroadcastDoubleTensorAlgebra : DoubleTensorAlgebra() { + + override fun Tensor.plus(other: Tensor): DoubleTensor { + val broadcast = broadcastTensors(tensor, other.tensor) + val newThis = broadcast[0] + val newOther = broadcast[1] + val resBuffer = DoubleArray(newThis.linearStructure.linearSize) { i -> + newThis.mutableBuffer.array()[i] + newOther.mutableBuffer.array()[i] + } + return DoubleTensor(newThis.shape, resBuffer) + } + + override fun Tensor.plusAssign(other: Tensor) { + val newOther = broadcastTo(other.tensor, tensor.shape) + for (i in 0 until tensor.linearStructure.linearSize) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] += + newOther.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Tensor.minus(other: Tensor): DoubleTensor { + val broadcast = broadcastTensors(tensor, other.tensor) + val newThis = broadcast[0] + val newOther = broadcast[1] + val resBuffer = DoubleArray(newThis.linearStructure.linearSize) { i -> + newThis.mutableBuffer.array()[i] - newOther.mutableBuffer.array()[i] + } + return DoubleTensor(newThis.shape, resBuffer) + } + + override fun Tensor.minusAssign(other: Tensor) { + val newOther = broadcastTo(other.tensor, tensor.shape) + for (i in 0 until tensor.linearStructure.linearSize) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] -= + newOther.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Tensor.times(other: Tensor): DoubleTensor { + val broadcast = broadcastTensors(tensor, other.tensor) + val newThis = broadcast[0] + val newOther = broadcast[1] + val resBuffer = DoubleArray(newThis.linearStructure.linearSize) { i -> + newThis.mutableBuffer.array()[newThis.bufferStart + i] * + newOther.mutableBuffer.array()[newOther.bufferStart + i] + } + return DoubleTensor(newThis.shape, resBuffer) + } + + override fun Tensor.timesAssign(other: Tensor) { + val newOther = broadcastTo(other.tensor, tensor.shape) + for (i in 0 until tensor.linearStructure.linearSize) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] *= + newOther.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Tensor.div(other: Tensor): DoubleTensor { + val broadcast = broadcastTensors(tensor, other.tensor) + val newThis = broadcast[0] + val newOther = broadcast[1] + val resBuffer = DoubleArray(newThis.linearStructure.linearSize) { i -> + newThis.mutableBuffer.array()[newOther.bufferStart + i] / + newOther.mutableBuffer.array()[newOther.bufferStart + i] + } + return DoubleTensor(newThis.shape, resBuffer) + } + + override fun Tensor.divAssign(other: Tensor) { + val newOther = broadcastTo(other.tensor, tensor.shape) + for (i in 0 until tensor.linearStructure.linearSize) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] /= + newOther.mutableBuffer.array()[tensor.bufferStart + i] + } + } +} \ No newline at end of file diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BufferedTensor.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BufferedTensor.kt new file mode 100644 index 000000000..315dc4505 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/BufferedTensor.kt @@ -0,0 +1,40 @@ +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.misc.PerformancePitfall +import space.kscience.kmath.nd.Strides +import space.kscience.kmath.structures.MutableBuffer +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.core.internal.TensorLinearStructure + +/** + * Represents [Tensor] over a [MutableBuffer] intended to be used through [DoubleTensor] and [IntTensor] + */ +public open class BufferedTensor internal constructor( + override val shape: IntArray, + internal val mutableBuffer: MutableBuffer, + internal val bufferStart: Int +) : Tensor { + + /** + * Buffer strides based on [TensorLinearStructure] implementation + */ + public val linearStructure: Strides + get() = TensorLinearStructure(shape) + + /** + * Number of elements in tensor + */ + public val numElements: Int + get() = linearStructure.linearSize + + override fun get(index: IntArray): T = mutableBuffer[bufferStart + linearStructure.offset(index)] + + override fun set(index: IntArray, value: T) { + mutableBuffer[bufferStart + linearStructure.offset(index)] = value + } + + @PerformancePitfall + override fun elements(): Sequence> = linearStructure.indices().map { + it to get(it) + } +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensor.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensor.kt new file mode 100644 index 000000000..41df50cba --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensor.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.tensors.core.internal.toPrettyString + +/** + * Default [BufferedTensor] implementation for [Double] values + */ +public class DoubleTensor internal constructor( + shape: IntArray, + buffer: DoubleArray, + offset: Int = 0 +) : BufferedTensor(shape, DoubleBuffer(buffer), offset) { + override fun toString(): String = toPrettyString() +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt new file mode 100644 index 000000000..1fd46bd57 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt @@ -0,0 +1,934 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.nd.as1D +import space.kscience.kmath.nd.as2D +import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra +import space.kscience.kmath.tensors.api.LinearOpsTensorAlgebra +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.api.TensorPartialDivisionAlgebra +import space.kscience.kmath.tensors.core.internal.* +import kotlin.math.* + +/** + * Implementation of basic operations over double tensors and basic algebra operations on them. + */ +public open class DoubleTensorAlgebra : + TensorPartialDivisionAlgebra, + AnalyticTensorAlgebra, + LinearOpsTensorAlgebra { + + public companion object : DoubleTensorAlgebra() + + override fun Tensor.valueOrNull(): Double? = if (tensor.shape contentEquals intArrayOf(1)) + tensor.mutableBuffer.array()[tensor.bufferStart] else null + + override fun Tensor.value(): Double = valueOrNull() + ?: throw IllegalArgumentException("The tensor shape is $shape, but value method is allowed only for shape [1]") + + /** + * Constructs a tensor with the specified shape and data. + * + * @param shape the desired shape for the tensor. + * @param buffer one-dimensional data array. + * @return tensor with the [shape] shape and [buffer] data. + */ + public fun fromArray(shape: IntArray, buffer: DoubleArray): DoubleTensor { + checkEmptyShape(shape) + checkEmptyDoubleBuffer(buffer) + checkBufferShapeConsistency(shape, buffer) + return DoubleTensor(shape, buffer, 0) + } + + /** + * Constructs a tensor with the specified shape and initializer. + * + * @param shape the desired shape for the tensor. + * @param initializer mapping tensor indices to values. + * @return tensor with the [shape] shape and data generated by the [initializer]. + */ + public fun produce(shape: IntArray, initializer: (IntArray) -> Double): DoubleTensor = + fromArray( + shape, + TensorLinearStructure(shape).indices().map(initializer).toMutableList().toDoubleArray() + ) + + override operator fun Tensor.get(i: Int): DoubleTensor { + val lastShape = tensor.shape.drop(1).toIntArray() + val newShape = if (lastShape.isNotEmpty()) lastShape else intArrayOf(1) + val newStart = newShape.reduce(Int::times) * i + tensor.bufferStart + return DoubleTensor(newShape, tensor.mutableBuffer.array(), newStart) + } + + /** + * Creates a tensor of a given shape and fills all elements with a given value. + * + * @param value the value to fill the output tensor with. + * @param shape array of integers defining the shape of the output tensor. + * @return tensor with the [shape] shape and filled with [value]. + */ + public fun full(value: Double, shape: IntArray): DoubleTensor { + checkEmptyShape(shape) + val buffer = DoubleArray(shape.reduce(Int::times)) { value } + return DoubleTensor(shape, buffer) + } + + /** + * Returns a tensor with the same shape as `input` filled with [value]. + * + * @param value the value to fill the output tensor with. + * @return tensor with the `input` tensor shape and filled with [value]. + */ + public fun Tensor.fullLike(value: Double): DoubleTensor { + val shape = tensor.shape + val buffer = DoubleArray(tensor.numElements) { value } + return DoubleTensor(shape, buffer) + } + + /** + * Returns a tensor filled with the scalar value 0.0, with the shape defined by the variable argument [shape]. + * + * @param shape array of integers defining the shape of the output tensor. + * @return tensor filled with the scalar value 0.0, with the [shape] shape. + */ + public fun zeros(shape: IntArray): DoubleTensor = full(0.0, shape) + + /** + * Returns a tensor filled with the scalar value 0.0, with the same shape as a given array. + * + * @return tensor filled with the scalar value 0.0, with the same shape as `input` tensor. + */ + public fun Tensor.zeroesLike(): DoubleTensor = tensor.fullLike(0.0) + + /** + * Returns a tensor filled with the scalar value 1.0, with the shape defined by the variable argument [shape]. + * + * @param shape array of integers defining the shape of the output tensor. + * @return tensor filled with the scalar value 1.0, with the [shape] shape. + */ + public fun ones(shape: IntArray): DoubleTensor = full(1.0, shape) + + /** + * Returns a tensor filled with the scalar value 1.0, with the same shape as a given array. + * + * @return tensor filled with the scalar value 1.0, with the same shape as `input` tensor. + */ + public fun Tensor.onesLike(): DoubleTensor = tensor.fullLike(1.0) + + /** + * Returns a 2-D tensor with shape ([n], [n]), with ones on the diagonal and zeros elsewhere. + * + * @param n the number of rows and columns + * @return a 2-D tensor with ones on the diagonal and zeros elsewhere. + */ + public fun eye(n: Int): DoubleTensor { + val shape = intArrayOf(n, n) + val buffer = DoubleArray(n * n) { 0.0 } + val res = DoubleTensor(shape, buffer) + for (i in 0 until n) { + res[intArrayOf(i, i)] = 1.0 + } + return res + } + + /** + * Return a copy of the tensor. + * + * @return a copy of the `input` tensor with a copied buffer. + */ + public fun Tensor.copy(): DoubleTensor { + return DoubleTensor(tensor.shape, tensor.mutableBuffer.array().copyOf(), tensor.bufferStart) + } + + override fun Double.plus(other: Tensor): DoubleTensor { + val resBuffer = DoubleArray(other.tensor.numElements) { i -> + other.tensor.mutableBuffer.array()[other.tensor.bufferStart + i] + this + } + return DoubleTensor(other.shape, resBuffer) + } + + override fun Tensor.plus(value: Double): DoubleTensor = value + tensor + + override fun Tensor.plus(other: Tensor): DoubleTensor { + checkShapesCompatible(tensor, other.tensor) + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[i] + other.tensor.mutableBuffer.array()[i] + } + return DoubleTensor(tensor.shape, resBuffer) + } + + override fun Tensor.plusAssign(value: Double) { + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] += value + } + } + + override fun Tensor.plusAssign(other: Tensor) { + checkShapesCompatible(tensor, other.tensor) + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] += + other.tensor.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Double.minus(other: Tensor): DoubleTensor { + val resBuffer = DoubleArray(other.tensor.numElements) { i -> + this - other.tensor.mutableBuffer.array()[other.tensor.bufferStart + i] + } + return DoubleTensor(other.shape, resBuffer) + } + + override fun Tensor.minus(value: Double): DoubleTensor { + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[tensor.bufferStart + i] - value + } + return DoubleTensor(tensor.shape, resBuffer) + } + + override fun Tensor.minus(other: Tensor): DoubleTensor { + checkShapesCompatible(tensor, other) + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[i] - other.tensor.mutableBuffer.array()[i] + } + return DoubleTensor(tensor.shape, resBuffer) + } + + override fun Tensor.minusAssign(value: Double) { + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] -= value + } + } + + override fun Tensor.minusAssign(other: Tensor) { + checkShapesCompatible(tensor, other) + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] -= + other.tensor.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Double.times(other: Tensor): DoubleTensor { + val resBuffer = DoubleArray(other.tensor.numElements) { i -> + other.tensor.mutableBuffer.array()[other.tensor.bufferStart + i] * this + } + return DoubleTensor(other.shape, resBuffer) + } + + override fun Tensor.times(value: Double): DoubleTensor = value * tensor + + override fun Tensor.times(other: Tensor): DoubleTensor { + checkShapesCompatible(tensor, other) + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[tensor.bufferStart + i] * + other.tensor.mutableBuffer.array()[other.tensor.bufferStart + i] + } + return DoubleTensor(tensor.shape, resBuffer) + } + + override fun Tensor.timesAssign(value: Double) { + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] *= value + } + } + + override fun Tensor.timesAssign(other: Tensor) { + checkShapesCompatible(tensor, other) + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] *= + other.tensor.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Double.div(other: Tensor): DoubleTensor { + val resBuffer = DoubleArray(other.tensor.numElements) { i -> + this / other.tensor.mutableBuffer.array()[other.tensor.bufferStart + i] + } + return DoubleTensor(other.shape, resBuffer) + } + + override fun Tensor.div(value: Double): DoubleTensor { + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[tensor.bufferStart + i] / value + } + return DoubleTensor(shape, resBuffer) + } + + override fun Tensor.div(other: Tensor): DoubleTensor { + checkShapesCompatible(tensor, other) + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[other.tensor.bufferStart + i] / + other.tensor.mutableBuffer.array()[other.tensor.bufferStart + i] + } + return DoubleTensor(tensor.shape, resBuffer) + } + + override fun Tensor.divAssign(value: Double) { + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] /= value + } + } + + override fun Tensor.divAssign(other: Tensor) { + checkShapesCompatible(tensor, other) + for (i in 0 until tensor.numElements) { + tensor.mutableBuffer.array()[tensor.bufferStart + i] /= + other.tensor.mutableBuffer.array()[tensor.bufferStart + i] + } + } + + override fun Tensor.unaryMinus(): DoubleTensor { + val resBuffer = DoubleArray(tensor.numElements) { i -> + tensor.mutableBuffer.array()[tensor.bufferStart + i].unaryMinus() + } + return DoubleTensor(tensor.shape, resBuffer) + } + + override fun Tensor.transpose(i: Int, j: Int): DoubleTensor { + val ii = tensor.minusIndex(i) + val jj = tensor.minusIndex(j) + checkTranspose(tensor.dimension, ii, jj) + val n = tensor.numElements + val resBuffer = DoubleArray(n) + + val resShape = tensor.shape.copyOf() + resShape[ii] = resShape[jj].also { resShape[jj] = resShape[ii] } + + val resTensor = DoubleTensor(resShape, resBuffer) + + for (offset in 0 until n) { + val oldMultiIndex = tensor.linearStructure.index(offset) + val newMultiIndex = oldMultiIndex.copyOf() + newMultiIndex[ii] = newMultiIndex[jj].also { newMultiIndex[jj] = newMultiIndex[ii] } + + val linearIndex = resTensor.linearStructure.offset(newMultiIndex) + resTensor.mutableBuffer.array()[linearIndex] = + tensor.mutableBuffer.array()[tensor.bufferStart + offset] + } + return resTensor + } + + + override fun Tensor.view(shape: IntArray): DoubleTensor { + checkView(tensor, shape) + return DoubleTensor(shape, tensor.mutableBuffer.array(), tensor.bufferStart) + } + + override fun Tensor.viewAs(other: Tensor): DoubleTensor = + tensor.view(other.shape) + + override infix fun Tensor.dot(other: Tensor): DoubleTensor { + if (tensor.shape.size == 1 && other.shape.size == 1) { + return DoubleTensor(intArrayOf(1), doubleArrayOf(tensor.times(other).tensor.mutableBuffer.array().sum())) + } + + var newThis = tensor.copy() + var newOther = other.copy() + + var penultimateDim = false + var lastDim = false + if (tensor.shape.size == 1) { + penultimateDim = true + newThis = tensor.view(intArrayOf(1) + tensor.shape) + } + if (other.shape.size == 1) { + lastDim = true + newOther = other.tensor.view(other.shape + intArrayOf(1)) + } + + val broadcastTensors = broadcastOuterTensors(newThis.tensor, newOther.tensor) + newThis = broadcastTensors[0] + newOther = broadcastTensors[1] + + val l = newThis.shape[newThis.shape.size - 2] + val m1 = newThis.shape[newThis.shape.size - 1] + val m2 = newOther.shape[newOther.shape.size - 2] + val n = newOther.shape[newOther.shape.size - 1] + check(m1 == m2) { + "Tensors dot operation dimension mismatch: ($l, $m1) x ($m2, $n)" + } + + val resShape = newThis.shape.sliceArray(0..(newThis.shape.size - 2)) + intArrayOf(newOther.shape.last()) + val resSize = resShape.reduce { acc, i -> acc * i } + val resTensor = DoubleTensor(resShape, DoubleArray(resSize)) + + for ((res, ab) in resTensor.matrixSequence().zip(newThis.matrixSequence().zip(newOther.matrixSequence()))) { + val (a, b) = ab + dotHelper(a.as2D(), b.as2D(), res.as2D(), l, m1, n) + } + + if (penultimateDim) { + return resTensor.view( + resTensor.shape.dropLast(2).toIntArray() + + intArrayOf(resTensor.shape.last()) + ) + } + if (lastDim) { + return resTensor.view(resTensor.shape.dropLast(1).toIntArray()) + } + return resTensor + } + + override fun diagonalEmbedding(diagonalEntries: Tensor, offset: Int, dim1: Int, dim2: Int): + DoubleTensor { + val n = diagonalEntries.shape.size + val d1 = minusIndexFrom(n + 1, dim1) + val d2 = minusIndexFrom(n + 1, dim2) + + check(d1 != d2) { + "Diagonal dimensions cannot be identical $d1, $d2" + } + check(d1 <= n && d2 <= n) { + "Dimension out of range" + } + + var lessDim = d1 + var greaterDim = d2 + var realOffset = offset + if (lessDim > greaterDim) { + realOffset *= -1 + lessDim = greaterDim.also { greaterDim = lessDim } + } + + val resShape = diagonalEntries.shape.slice(0 until lessDim).toIntArray() + + intArrayOf(diagonalEntries.shape[n - 1] + abs(realOffset)) + + diagonalEntries.shape.slice(lessDim until greaterDim - 1).toIntArray() + + intArrayOf(diagonalEntries.shape[n - 1] + abs(realOffset)) + + diagonalEntries.shape.slice(greaterDim - 1 until n - 1).toIntArray() + val resTensor = zeros(resShape) + + for (i in 0 until diagonalEntries.tensor.numElements) { + val multiIndex = diagonalEntries.tensor.linearStructure.index(i) + + var offset1 = 0 + var offset2 = abs(realOffset) + if (realOffset < 0) { + offset1 = offset2.also { offset2 = offset1 } + } + val diagonalMultiIndex = multiIndex.slice(0 until lessDim).toIntArray() + + intArrayOf(multiIndex[n - 1] + offset1) + + multiIndex.slice(lessDim until greaterDim - 1).toIntArray() + + intArrayOf(multiIndex[n - 1] + offset2) + + multiIndex.slice(greaterDim - 1 until n - 1).toIntArray() + + resTensor[diagonalMultiIndex] = diagonalEntries[multiIndex] + } + + return resTensor.tensor + } + + /** + * Applies the [transform] function to each element of the tensor and returns the resulting modified tensor. + * + * @param transform the function to be applied to each element of the tensor. + * @return the resulting tensor after applying the function. + */ + public fun Tensor.map(transform: (Double) -> Double): DoubleTensor { + return DoubleTensor( + tensor.shape, + tensor.mutableBuffer.array().map { transform(it) }.toDoubleArray(), + tensor.bufferStart + ) + } + + /** + * Compares element-wise two tensors with a specified precision. + * + * @param other the tensor to compare with `input` tensor. + * @param epsilon permissible error when comparing two Double values. + * @return true if two tensors have the same shape and elements, false otherwise. + */ + public fun Tensor.eq(other: Tensor, epsilon: Double): Boolean = + tensor.eq(other) { x, y -> abs(x - y) < epsilon } + + /** + * Compares element-wise two tensors. + * Comparison of two Double values occurs with 1e-5 precision. + * + * @param other the tensor to compare with `input` tensor. + * @return true if two tensors have the same shape and elements, false otherwise. + */ + public infix fun Tensor.eq(other: Tensor): Boolean = tensor.eq(other, 1e-5) + + private fun Tensor.eq( + other: Tensor, + eqFunction: (Double, Double) -> Boolean, + ): Boolean { + checkShapesCompatible(tensor, other) + val n = tensor.numElements + if (n != other.tensor.numElements) { + return false + } + for (i in 0 until n) { + if (!eqFunction( + tensor.mutableBuffer[tensor.bufferStart + i], + other.tensor.mutableBuffer[other.tensor.bufferStart + i] + ) + ) { + return false + } + } + return true + } + + /** + * Returns a tensor of random numbers drawn from normal distributions with 0.0 mean and 1.0 standard deviation. + * + * @param shape the desired shape for the output tensor. + * @param seed the random seed of the pseudo-random number generator. + * @return tensor of a given shape filled with numbers from the normal distribution + * with 0.0 mean and 1.0 standard deviation. + */ + public fun randomNormal(shape: IntArray, seed: Long = 0): DoubleTensor = + DoubleTensor(shape, getRandomNormals(shape.reduce(Int::times), seed)) + + /** + * Returns a tensor with the same shape as `input` of random numbers drawn from normal distributions + * with 0.0 mean and 1.0 standard deviation. + * + * @param seed the random seed of the pseudo-random number generator. + * @return tensor with the same shape as `input` filled with numbers from the normal distribution + * with 0.0 mean and 1.0 standard deviation. + */ + public fun Tensor.randomNormalLike(seed: Long = 0): DoubleTensor = + DoubleTensor(tensor.shape, getRandomNormals(tensor.shape.reduce(Int::times), seed)) + + /** + * Concatenates a sequence of tensors with equal shapes along the first dimension. + * + * @param tensors the [List] of tensors with same shapes to concatenate + * @return tensor with concatenation result + */ + public fun stack(tensors: List>): DoubleTensor { + check(tensors.isNotEmpty()) { "List must have at least 1 element" } + val shape = tensors[0].shape + check(tensors.all { it.shape contentEquals shape }) { "Tensors must have same shapes" } + val resShape = intArrayOf(tensors.size) + shape + val resBuffer = tensors.flatMap { + it.tensor.mutableBuffer.array().drop(it.tensor.bufferStart).take(it.tensor.numElements) + }.toDoubleArray() + return DoubleTensor(resShape, resBuffer, 0) + } + + /** + * Builds tensor from rows of input tensor + * + * @param indices the [IntArray] of 1-dimensional indices + * @return tensor with rows corresponding to rows by [indices] + */ + public fun Tensor.rowsByIndices(indices: IntArray): DoubleTensor { + return stack(indices.map { this[it] }) + } + + internal fun Tensor.fold(foldFunction: (DoubleArray) -> Double): Double = + foldFunction(tensor.toDoubleArray()) + + internal fun Tensor.foldDim( + foldFunction: (DoubleArray) -> Double, + dim: Int, + keepDim: Boolean, + ): DoubleTensor { + check(dim < dimension) { "Dimension $dim out of range $dimension" } + val resShape = if (keepDim) { + shape.take(dim).toIntArray() + intArrayOf(1) + shape.takeLast(dimension - dim - 1).toIntArray() + } else { + shape.take(dim).toIntArray() + shape.takeLast(dimension - dim - 1).toIntArray() + } + val resNumElements = resShape.reduce(Int::times) + val resTensor = DoubleTensor(resShape, DoubleArray(resNumElements) { 0.0 }, 0) + for (index in resTensor.linearStructure.indices()) { + val prefix = index.take(dim).toIntArray() + val suffix = index.takeLast(dimension - dim - 1).toIntArray() + resTensor[index] = foldFunction(DoubleArray(shape[dim]) { i -> + tensor[prefix + intArrayOf(i) + suffix] + }) + } + + return resTensor + } + + override fun Tensor.sum(): Double = tensor.fold { it.sum() } + + override fun Tensor.sum(dim: Int, keepDim: Boolean): DoubleTensor = + foldDim({ x -> x.sum() }, dim, keepDim) + + override fun Tensor.min(): Double = this.fold { it.minOrNull()!! } + + override fun Tensor.min(dim: Int, keepDim: Boolean): DoubleTensor = + foldDim({ x -> x.minOrNull()!! }, dim, keepDim) + + override fun Tensor.max(): Double = this.fold { it.maxOrNull()!! } + + override fun Tensor.max(dim: Int, keepDim: Boolean): DoubleTensor = + foldDim({ x -> x.maxOrNull()!! }, dim, keepDim) + + override fun Tensor.argMax(dim: Int, keepDim: Boolean): DoubleTensor = + foldDim({ x -> + x.withIndex().maxByOrNull { it.value }?.index!!.toDouble() + }, dim, keepDim) + + + override fun Tensor.mean(): Double = this.fold { it.sum() / tensor.numElements } + + override fun Tensor.mean(dim: Int, keepDim: Boolean): DoubleTensor = + foldDim( + { arr -> + check(dim < dimension) { "Dimension $dim out of range $dimension" } + arr.sum() / shape[dim] + }, + dim, + keepDim + ) + + override fun Tensor.std(): Double = this.fold { arr -> + val mean = arr.sum() / tensor.numElements + sqrt(arr.sumOf { (it - mean) * (it - mean) } / (tensor.numElements - 1)) + } + + override fun Tensor.std(dim: Int, keepDim: Boolean): DoubleTensor = foldDim( + { arr -> + check(dim < dimension) { "Dimension $dim out of range $dimension" } + val mean = arr.sum() / shape[dim] + sqrt(arr.sumOf { (it - mean) * (it - mean) } / (shape[dim] - 1)) + }, + dim, + keepDim + ) + + override fun Tensor.variance(): Double = this.fold { arr -> + val mean = arr.sum() / tensor.numElements + arr.sumOf { (it - mean) * (it - mean) } / (tensor.numElements - 1) + } + + override fun Tensor.variance(dim: Int, keepDim: Boolean): DoubleTensor = foldDim( + { arr -> + check(dim < dimension) { "Dimension $dim out of range $dimension" } + val mean = arr.sum() / shape[dim] + arr.sumOf { (it - mean) * (it - mean) } / (shape[dim] - 1) + }, + dim, + keepDim + ) + + private fun cov(x: DoubleTensor, y: DoubleTensor): Double { + val n = x.shape[0] + return ((x - x.mean()) * (y - y.mean())).mean() * n / (n - 1) + } + + /** + * Returns the covariance matrix M of given vectors. + * + * M[i, j] contains covariance of i-th and j-th given vectors + * + * @param tensors the [List] of 1-dimensional tensors with same shape + * @return the covariance matrix + */ + public fun cov(tensors: List>): DoubleTensor { + check(tensors.isNotEmpty()) { "List must have at least 1 element" } + val n = tensors.size + val m = tensors[0].shape[0] + check(tensors.all { it.shape contentEquals intArrayOf(m) }) { "Tensors must have same shapes" } + val resTensor = DoubleTensor( + intArrayOf(n, n), + DoubleArray(n * n) { 0.0 } + ) + for (i in 0 until n) { + for (j in 0 until n) { + resTensor[intArrayOf(i, j)] = cov(tensors[i].tensor, tensors[j].tensor) + } + } + return resTensor + } + + override fun Tensor.exp(): DoubleTensor = tensor.map(::exp) + + override fun Tensor.ln(): DoubleTensor = tensor.map(::ln) + + override fun Tensor.sqrt(): DoubleTensor = tensor.map(::sqrt) + + override fun Tensor.cos(): DoubleTensor = tensor.map(::cos) + + override fun Tensor.acos(): DoubleTensor = tensor.map(::acos) + + override fun Tensor.cosh(): DoubleTensor = tensor.map(::cosh) + + override fun Tensor.acosh(): DoubleTensor = tensor.map(::acosh) + + override fun Tensor.sin(): DoubleTensor = tensor.map(::sin) + + override fun Tensor.asin(): DoubleTensor = tensor.map(::asin) + + override fun Tensor.sinh(): DoubleTensor = tensor.map(::sinh) + + override fun Tensor.asinh(): DoubleTensor = tensor.map(::asinh) + + override fun Tensor.tan(): DoubleTensor = tensor.map(::tan) + + override fun Tensor.atan(): DoubleTensor = tensor.map(::atan) + + override fun Tensor.tanh(): DoubleTensor = tensor.map(::tanh) + + override fun Tensor.atanh(): DoubleTensor = tensor.map(::atanh) + + override fun Tensor.ceil(): DoubleTensor = tensor.map(::ceil) + + override fun Tensor.floor(): DoubleTensor = tensor.map(::floor) + + override fun Tensor.inv(): DoubleTensor = invLU(1e-9) + + override fun Tensor.det(): DoubleTensor = detLU(1e-9) + + /** + * Computes the LU factorization of a matrix or batches of matrices `input`. + * Returns a tuple containing the LU factorization and pivots of `input`. + * + * @param epsilon permissible error when comparing the determinant of a matrix with zero + * @return pair of `factorization` and `pivots`. + * The `factorization` has the shape ``(*, m, n)``, where``(*, m, n)`` is the shape of the `input` tensor. + * The `pivots` has the shape ``(∗, min(m, n))``. `pivots` stores all the intermediate transpositions of rows. + */ + public fun Tensor.luFactor(epsilon: Double): Pair = + computeLU(tensor, epsilon) + ?: throw IllegalArgumentException("Tensor contains matrices which are singular at precision $epsilon") + + /** + * Computes the LU factorization of a matrix or batches of matrices `input`. + * Returns a tuple containing the LU factorization and pivots of `input`. + * Uses an error of ``1e-9`` when calculating whether a matrix is degenerate. + * + * @return pair of `factorization` and `pivots`. + * The `factorization` has the shape ``(*, m, n)``, where``(*, m, n)`` is the shape of the `input` tensor. + * The `pivots` has the shape ``(∗, min(m, n))``. `pivots` stores all the intermediate transpositions of rows. + */ + public fun Tensor.luFactor(): Pair = luFactor(1e-9) + + /** + * Unpacks the data and pivots from a LU factorization of a tensor. + * Given a tensor [luTensor], return tensors (P, L, U) satisfying ``P * luTensor = L * U``, + * with `P` being a permutation matrix or batch of matrices, + * `L` being a lower triangular matrix or batch of matrices, + * `U` being an upper triangular matrix or batch of matrices. + * + * @param luTensor the packed LU factorization data + * @param pivotsTensor the packed LU factorization pivots + * @return triple of P, L and U tensors + */ + public fun luPivot( + luTensor: Tensor, + pivotsTensor: Tensor, + ): Triple { + checkSquareMatrix(luTensor.shape) + check( + luTensor.shape.dropLast(2).toIntArray() contentEquals pivotsTensor.shape.dropLast(1).toIntArray() || + luTensor.shape.last() == pivotsTensor.shape.last() - 1 + ) { "Inappropriate shapes of input tensors" } + + val n = luTensor.shape.last() + val pTensor = luTensor.zeroesLike() + pTensor + .matrixSequence() + .zip(pivotsTensor.tensor.vectorSequence()) + .forEach { (p, pivot) -> pivInit(p.as2D(), pivot.as1D(), n) } + + val lTensor = luTensor.zeroesLike() + val uTensor = luTensor.zeroesLike() + + lTensor.matrixSequence() + .zip(uTensor.matrixSequence()) + .zip(luTensor.tensor.matrixSequence()) + .forEach { (pairLU, lu) -> + val (l, u) = pairLU + luPivotHelper(l.as2D(), u.as2D(), lu.as2D(), n) + } + + return Triple(pTensor, lTensor, uTensor) + } + + /** + * QR decomposition. + * + * Computes the QR decomposition of a matrix or a batch of matrices, and returns a pair `(Q, R)` of tensors. + * Given a tensor `input`, return tensors (Q, R) satisfying ``input = Q * R``, + * with `Q` being an orthogonal matrix or batch of orthogonal matrices + * and `R` being an upper triangular matrix or batch of upper triangular matrices. + * + * @param epsilon permissible error when comparing tensors for equality. + * Used when checking the positive definiteness of the input matrix or matrices. + * @return pair of Q and R tensors. + */ + public fun Tensor.cholesky(epsilon: Double): DoubleTensor { + checkSquareMatrix(shape) + checkPositiveDefinite(tensor, epsilon) + + val n = shape.last() + val lTensor = zeroesLike() + + for ((a, l) in tensor.matrixSequence().zip(lTensor.matrixSequence())) + for (i in 0 until n) choleskyHelper(a.as2D(), l.as2D(), n) + + return lTensor + } + + override fun Tensor.cholesky(): DoubleTensor = cholesky(1e-6) + + override fun Tensor.qr(): Pair { + checkSquareMatrix(shape) + val qTensor = zeroesLike() + val rTensor = zeroesLike() + tensor.matrixSequence() + .zip( + (qTensor.matrixSequence() + .zip(rTensor.matrixSequence())) + ).forEach { (matrix, qr) -> + val (q, r) = qr + qrHelper(matrix.asTensor(), q.asTensor(), r.as2D()) + } + + return qTensor to rTensor + } + + override fun Tensor.svd(): Triple = + svd(epsilon = 1e-10) + + /** + * Singular Value Decomposition. + * + * Computes the singular value decomposition of either a matrix or batch of matrices `input`. + * The singular value decomposition is represented as a triple `(U, S, V)`, + * such that ``input = U.dot(diagonalEmbedding(S).dot(V.T))``. + * If input is a batch of tensors, then U, S, and Vh are also batched with the same batch dimensions as input. + * + * @param epsilon permissible error when calculating the dot product of vectors, + * i.e. the precision with which the cosine approaches 1 in an iterative algorithm. + * @return triple `(U, S, V)`. + */ + public fun Tensor.svd(epsilon: Double): Triple { + val size = tensor.dimension + val commonShape = tensor.shape.sliceArray(0 until size - 2) + val (n, m) = tensor.shape.sliceArray(size - 2 until size) + val uTensor = zeros(commonShape + intArrayOf(min(n, m), n)) + val sTensor = zeros(commonShape + intArrayOf(min(n, m))) + val vTensor = zeros(commonShape + intArrayOf(min(n, m), m)) + + tensor.matrixSequence() + .zip( + uTensor.matrixSequence() + .zip( + sTensor.vectorSequence() + .zip(vTensor.matrixSequence()) + ) + ).forEach { (matrix, USV) -> + val matrixSize = matrix.shape.reduce { acc, i -> acc * i } + val curMatrix = DoubleTensor( + matrix.shape, + matrix.mutableBuffer.array().slice(matrix.bufferStart until matrix.bufferStart + matrixSize) + .toDoubleArray() + ) + svdHelper(curMatrix, USV, m, n, epsilon) + } + + return Triple(uTensor.transpose(), sTensor, vTensor.transpose()) + } + + override fun Tensor.symEig(): Pair = + symEig(epsilon = 1e-15) + + /** + * Returns eigenvalues and eigenvectors of a real symmetric matrix input or a batch of real symmetric matrices, + * represented by a pair (eigenvalues, eigenvectors). + * + * @param epsilon permissible error when comparing tensors for equality + * and when the cosine approaches 1 in the SVD algorithm. + * @return a pair (eigenvalues, eigenvectors) + */ + public fun Tensor.symEig(epsilon: Double): Pair { + checkSymmetric(tensor, epsilon) + val (u, s, v) = tensor.svd(epsilon) + val shp = s.shape + intArrayOf(1) + val utv = u.transpose() dot v + val n = s.shape.last() + for (matrix in utv.matrixSequence()) + cleanSymHelper(matrix.as2D(), n) + + val eig = (utv dot s.view(shp)).view(s.shape) + return eig to v + } + + /** + * Computes the determinant of a square matrix input, or of each square matrix in a batched input + * using LU factorization algorithm. + * + * @param epsilon error in the LU algorithm - permissible error when comparing the determinant of a matrix with zero + * @return the determinant. + */ + public fun Tensor.detLU(epsilon: Double = 1e-9): DoubleTensor { + + checkSquareMatrix(tensor.shape) + val luTensor = tensor.copy() + val pivotsTensor = tensor.setUpPivots() + + val n = shape.size + + val detTensorShape = IntArray(n - 1) { i -> shape[i] } + detTensorShape[n - 2] = 1 + val resBuffer = DoubleArray(detTensorShape.reduce(Int::times)) { 0.0 } + + val detTensor = DoubleTensor( + detTensorShape, + resBuffer + ) + + luTensor.matrixSequence().zip(pivotsTensor.vectorSequence()).forEachIndexed { index, (lu, pivots) -> + resBuffer[index] = if (luHelper(lu.as2D(), pivots.as1D(), epsilon)) + 0.0 else luMatrixDet(lu.as2D(), pivots.as1D()) + } + + return detTensor + } + + /** + * Computes the multiplicative inverse matrix of a square matrix input, or of each square matrix in a batched input + * using LU factorization algorithm. + * Given a square matrix `a`, return the matrix `aInv` satisfying + * ``a.dot(aInv) = aInv.dot(a) = eye(a.shape[0])``. + * + * @param epsilon error in the LU algorithm - permissible error when comparing the determinant of a matrix with zero + * @return the multiplicative inverse of a matrix. + */ + public fun Tensor.invLU(epsilon: Double = 1e-9): DoubleTensor { + val (luTensor, pivotsTensor) = luFactor(epsilon) + val invTensor = luTensor.zeroesLike() + + val seq = luTensor.matrixSequence().zip(pivotsTensor.vectorSequence()).zip(invTensor.matrixSequence()) + for ((luP, invMatrix) in seq) { + val (lu, pivots) = luP + luMatrixInv(lu.as2D(), pivots.as1D(), invMatrix.as2D()) + } + + return invTensor + } + + /** + * LUP decomposition + * + * Computes the LUP decomposition of a matrix or a batch of matrices. + * Given a tensor `input`, return tensors (P, L, U) satisfying ``P * input = L * U``, + * with `P` being a permutation matrix or batch of matrices, + * `L` being a lower triangular matrix or batch of matrices, + * `U` being an upper triangular matrix or batch of matrices. + * + * @param epsilon permissible error when comparing the determinant of a matrix with zero + * @return triple of P, L and U tensors + */ + public fun Tensor.lu(epsilon: Double = 1e-9): Triple { + val (lu, pivots) = tensor.luFactor(epsilon) + return luPivot(lu, pivots) + } + + override fun Tensor.lu(): Triple = lu(1e-9) +} + + diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/IntTensor.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/IntTensor.kt new file mode 100644 index 000000000..ae1e6c8c8 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/IntTensor.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.structures.IntBuffer + +/** + * Default [BufferedTensor] implementation for [Int] values + */ +public class IntTensor internal constructor( + shape: IntArray, + buffer: IntArray, + offset: Int = 0 +) : BufferedTensor(shape, IntBuffer(buffer), offset) diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/TensorLinearStructure.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/TensorLinearStructure.kt new file mode 100644 index 000000000..4bdd987db --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/TensorLinearStructure.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core.internal + +import space.kscience.kmath.nd.Strides +import kotlin.math.max + + +internal fun stridesFromShape(shape: IntArray): IntArray { + val nDim = shape.size + val res = IntArray(nDim) + if (nDim == 0) + return res + + var current = nDim - 1 + res[current] = 1 + + while (current > 0) { + res[current - 1] = max(1, shape[current]) * res[current] + current-- + } + return res +} + +internal fun indexFromOffset(offset: Int, strides: IntArray, nDim: Int): IntArray { + val res = IntArray(nDim) + var current = offset + var strideIndex = 0 + + while (strideIndex < nDim) { + res[strideIndex] = (current / strides[strideIndex]) + current %= strides[strideIndex] + strideIndex++ + } + return res +} + +/** + * This [Strides] implementation follows the last dimension first convention + * For more information: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.strides.html + * + * @param shape the shape of the tensor. + */ +internal class TensorLinearStructure(override val shape: IntArray) : Strides { + override val strides: IntArray + get() = stridesFromShape(shape) + + override fun index(offset: Int): IntArray = + indexFromOffset(offset, strides, shape.size) + + override val linearSize: Int + get() = shape.reduce(Int::times) + +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/broadcastUtils.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/broadcastUtils.kt new file mode 100644 index 000000000..6324dc242 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/broadcastUtils.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core.internal + +import space.kscience.kmath.tensors.core.DoubleTensor +import kotlin.math.max + +internal fun multiIndexBroadCasting(tensor: DoubleTensor, resTensor: DoubleTensor, linearSize: Int) { + for (linearIndex in 0 until linearSize) { + val totalMultiIndex = resTensor.linearStructure.index(linearIndex) + val curMultiIndex = tensor.shape.copyOf() + + val offset = totalMultiIndex.size - curMultiIndex.size + + for (i in curMultiIndex.indices) { + if (curMultiIndex[i] != 1) { + curMultiIndex[i] = totalMultiIndex[i + offset] + } else { + curMultiIndex[i] = 0 + } + } + + val curLinearIndex = tensor.linearStructure.offset(curMultiIndex) + resTensor.mutableBuffer.array()[linearIndex] = + tensor.mutableBuffer.array()[tensor.bufferStart + curLinearIndex] + } +} + +internal fun broadcastShapes(vararg shapes: IntArray): IntArray { + var totalDim = 0 + for (shape in shapes) { + totalDim = max(totalDim, shape.size) + } + + val totalShape = IntArray(totalDim) { 0 } + for (shape in shapes) { + for (i in shape.indices) { + val curDim = shape[i] + val offset = totalDim - shape.size + totalShape[i + offset] = max(totalShape[i + offset], curDim) + } + } + + for (shape in shapes) { + for (i in shape.indices) { + val curDim = shape[i] + val offset = totalDim - shape.size + check(curDim == 1 || totalShape[i + offset] == curDim) { + "Shapes are not compatible and cannot be broadcast" + } + } + } + + return totalShape +} + +internal fun broadcastTo(tensor: DoubleTensor, newShape: IntArray): DoubleTensor { + require(tensor.shape.size <= newShape.size) { + "Tensor is not compatible with the new shape" + } + + val n = newShape.reduce { acc, i -> acc * i } + val resTensor = DoubleTensor(newShape, DoubleArray(n)) + + for (i in tensor.shape.indices) { + val curDim = tensor.shape[i] + val offset = newShape.size - tensor.shape.size + check(curDim == 1 || newShape[i + offset] == curDim) { + "Tensor is not compatible with the new shape and cannot be broadcast" + } + } + + multiIndexBroadCasting(tensor, resTensor, n) + return resTensor +} + +internal fun broadcastTensors(vararg tensors: DoubleTensor): List { + val totalShape = broadcastShapes(*(tensors.map { it.shape }).toTypedArray()) + val n = totalShape.reduce { acc, i -> acc * i } + + return tensors.map { tensor -> + val resTensor = DoubleTensor(totalShape, DoubleArray(n)) + multiIndexBroadCasting(tensor, resTensor, n) + resTensor + } +} + +internal fun broadcastOuterTensors(vararg tensors: DoubleTensor): List { + val onlyTwoDims = tensors.asSequence().onEach { + require(it.shape.size >= 2) { + "Tensors must have at least 2 dimensions" + } + }.any { it.shape.size != 2 } + + if (!onlyTwoDims) { + return tensors.asList() + } + + val totalShape = broadcastShapes(*(tensors.map { it.shape.sliceArray(0..it.shape.size - 3) }).toTypedArray()) + val n = totalShape.reduce { acc, i -> acc * i } + + return buildList { + for (tensor in tensors) { + val matrixShape = tensor.shape.sliceArray(tensor.shape.size - 2 until tensor.shape.size).copyOf() + val matrixSize = matrixShape[0] * matrixShape[1] + val matrix = DoubleTensor(matrixShape, DoubleArray(matrixSize)) + + val outerTensor = DoubleTensor(totalShape, DoubleArray(n)) + val resTensor = DoubleTensor(totalShape + matrixShape, DoubleArray(n * matrixSize)) + + for (linearIndex in 0 until n) { + val totalMultiIndex = outerTensor.linearStructure.index(linearIndex) + var curMultiIndex = tensor.shape.sliceArray(0..tensor.shape.size - 3).copyOf() + curMultiIndex = IntArray(totalMultiIndex.size - curMultiIndex.size) { 1 } + curMultiIndex + + val newTensor = DoubleTensor(curMultiIndex + matrixShape, tensor.mutableBuffer.array()) + + for (i in curMultiIndex.indices) { + if (curMultiIndex[i] != 1) { + curMultiIndex[i] = totalMultiIndex[i] + } else { + curMultiIndex[i] = 0 + } + } + + for (i in 0 until matrixSize) { + val curLinearIndex = newTensor.linearStructure.offset( + curMultiIndex + + matrix.linearStructure.index(i) + ) + val newLinearIndex = resTensor.linearStructure.offset( + totalMultiIndex + + matrix.linearStructure.index(i) + ) + + resTensor.mutableBuffer.array()[resTensor.bufferStart + newLinearIndex] = + newTensor.mutableBuffer.array()[newTensor.bufferStart + curLinearIndex] + } + } + add(resTensor) + } + } +} \ No newline at end of file diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/checks.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/checks.kt new file mode 100644 index 000000000..f1c158770 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/checks.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core.internal + +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.core.DoubleTensor +import space.kscience.kmath.tensors.core.DoubleTensorAlgebra + + +internal fun checkEmptyShape(shape: IntArray) = + check(shape.isNotEmpty()) { + "Illegal empty shape provided" + } + +internal fun checkEmptyDoubleBuffer(buffer: DoubleArray) = + check(buffer.isNotEmpty()) { + "Illegal empty buffer provided" + } + +internal fun checkBufferShapeConsistency(shape: IntArray, buffer: DoubleArray) = + check(buffer.size == shape.reduce(Int::times)) { + "Inconsistent shape ${shape.toList()} for buffer of size ${buffer.size} provided" + } + +internal fun checkShapesCompatible(a: Tensor, b: Tensor) = + check(a.shape contentEquals b.shape) { + "Incompatible shapes ${a.shape.toList()} and ${b.shape.toList()} " + } + +internal fun checkTranspose(dim: Int, i: Int, j: Int) = + check((i < dim) and (j < dim)) { + "Cannot transpose $i to $j for a tensor of dim $dim" + } + +internal fun checkView(a: Tensor, shape: IntArray) = + check(a.shape.reduce(Int::times) == shape.reduce(Int::times)) + +internal fun checkSquareMatrix(shape: IntArray) { + val n = shape.size + check(n >= 2) { + "Expected tensor with 2 or more dimensions, got size $n instead" + } + check(shape[n - 1] == shape[n - 2]) { + "Tensor must be batches of square matrices, but they are ${shape[n - 1]} by ${shape[n - 1]} matrices" + } +} + +internal fun DoubleTensorAlgebra.checkSymmetric( + tensor: Tensor, epsilon: Double = 1e-6 +) = + check(tensor.eq(tensor.transpose(), epsilon)) { + "Tensor is not symmetric about the last 2 dimensions at precision $epsilon" + } + +internal fun DoubleTensorAlgebra.checkPositiveDefinite(tensor: DoubleTensor, epsilon: Double = 1e-6) { + checkSymmetric(tensor, epsilon) + for (mat in tensor.matrixSequence()) + check(mat.asTensor().detLU().value() > 0.0) { + "Tensor contains matrices which are not positive definite ${mat.asTensor().detLU().value()}" + } +} \ No newline at end of file diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt new file mode 100644 index 000000000..7d3617547 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt @@ -0,0 +1,342 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core.internal + +import space.kscience.kmath.nd.MutableStructure1D +import space.kscience.kmath.nd.MutableStructure2D +import space.kscience.kmath.nd.as1D +import space.kscience.kmath.nd.as2D +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.* +import space.kscience.kmath.tensors.core.DoubleTensorAlgebra +import space.kscience.kmath.tensors.core.DoubleTensorAlgebra.Companion.valueOrNull +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.sign +import kotlin.math.sqrt + + +internal fun BufferedTensor.vectorSequence(): Sequence> = sequence { + val n = shape.size + val vectorOffset = shape[n - 1] + val vectorShape = intArrayOf(shape.last()) + for (offset in 0 until numElements step vectorOffset) { + val vector = BufferedTensor(vectorShape, mutableBuffer, bufferStart + offset) + yield(vector) + } +} + +internal fun BufferedTensor.matrixSequence(): Sequence> = sequence { + val n = shape.size + check(n >= 2) { "Expected tensor with 2 or more dimensions, got size $n" } + val matrixOffset = shape[n - 1] * shape[n - 2] + val matrixShape = intArrayOf(shape[n - 2], shape[n - 1]) + for (offset in 0 until numElements step matrixOffset) { + val matrix = BufferedTensor(matrixShape, mutableBuffer, bufferStart + offset) + yield(matrix) + } +} + +internal fun dotHelper( + a: MutableStructure2D, + b: MutableStructure2D, + res: MutableStructure2D, + l: Int, m: Int, n: Int +) { + for (i in 0 until l) { + for (j in 0 until n) { + var curr = 0.0 + for (k in 0 until m) { + curr += a[i, k] * b[k, j] + } + res[i, j] = curr + } + } +} + +internal fun luHelper( + lu: MutableStructure2D, + pivots: MutableStructure1D, + epsilon: Double +): Boolean { + + val m = lu.rowNum + + for (row in 0..m) pivots[row] = row + + for (i in 0 until m) { + var maxVal = 0.0 + var maxInd = i + + for (k in i until m) { + val absA = abs(lu[k, i]) + if (absA > maxVal) { + maxVal = absA + maxInd = k + } + } + + if (abs(maxVal) < epsilon) + return true // matrix is singular + + if (maxInd != i) { + + val j = pivots[i] + pivots[i] = pivots[maxInd] + pivots[maxInd] = j + + for (k in 0 until m) { + val tmp = lu[i, k] + lu[i, k] = lu[maxInd, k] + lu[maxInd, k] = tmp + } + + pivots[m] += 1 + + } + + for (j in i + 1 until m) { + lu[j, i] /= lu[i, i] + for (k in i + 1 until m) { + lu[j, k] -= lu[j, i] * lu[i, k] + } + } + } + return false +} + +internal fun BufferedTensor.setUpPivots(): IntTensor { + val n = this.shape.size + val m = this.shape.last() + val pivotsShape = IntArray(n - 1) { i -> this.shape[i] } + pivotsShape[n - 2] = m + 1 + + return IntTensor( + pivotsShape, + IntArray(pivotsShape.reduce(Int::times)) { 0 } + ) +} + +internal fun DoubleTensorAlgebra.computeLU( + tensor: DoubleTensor, + epsilon: Double +): Pair? { + + checkSquareMatrix(tensor.shape) + val luTensor = tensor.copy() + val pivotsTensor = tensor.setUpPivots() + + for ((lu, pivots) in luTensor.matrixSequence().zip(pivotsTensor.vectorSequence())) + if (luHelper(lu.as2D(), pivots.as1D(), epsilon)) + return null + + return Pair(luTensor, pivotsTensor) +} + +internal fun pivInit( + p: MutableStructure2D, + pivot: MutableStructure1D, + n: Int +) { + for (i in 0 until n) { + p[i, pivot[i]] = 1.0 + } +} + +internal fun luPivotHelper( + l: MutableStructure2D, + u: MutableStructure2D, + lu: MutableStructure2D, + n: Int +) { + for (i in 0 until n) { + for (j in 0 until n) { + if (i == j) { + l[i, j] = 1.0 + } + if (j < i) { + l[i, j] = lu[i, j] + } + if (j >= i) { + u[i, j] = lu[i, j] + } + } + } +} + +internal fun choleskyHelper( + a: MutableStructure2D, + l: MutableStructure2D, + n: Int +) { + for (i in 0 until n) { + for (j in 0 until i) { + var h = a[i, j] + for (k in 0 until j) { + h -= l[i, k] * l[j, k] + } + l[i, j] = h / l[j, j] + } + var h = a[i, i] + for (j in 0 until i) { + h -= l[i, j] * l[i, j] + } + l[i, i] = sqrt(h) + } +} + +internal fun luMatrixDet(lu: MutableStructure2D, pivots: MutableStructure1D): Double { + if (lu[0, 0] == 0.0) { + return 0.0 + } + val m = lu.shape[0] + val sign = if ((pivots[m] - m) % 2 == 0) 1.0 else -1.0 + return (0 until m).asSequence().map { lu[it, it] }.fold(sign) { left, right -> left * right } +} + +internal fun luMatrixInv( + lu: MutableStructure2D, + pivots: MutableStructure1D, + invMatrix: MutableStructure2D +) { + val m = lu.shape[0] + + for (j in 0 until m) { + for (i in 0 until m) { + if (pivots[i] == j) { + invMatrix[i, j] = 1.0 + } + + for (k in 0 until i) { + invMatrix[i, j] -= lu[i, k] * invMatrix[k, j] + } + } + + for (i in m - 1 downTo 0) { + for (k in i + 1 until m) { + invMatrix[i, j] -= lu[i, k] * invMatrix[k, j] + } + invMatrix[i, j] /= lu[i, i] + } + } +} + +internal fun DoubleTensorAlgebra.qrHelper( + matrix: DoubleTensor, + q: DoubleTensor, + r: MutableStructure2D +) { + checkSquareMatrix(matrix.shape) + val n = matrix.shape[0] + val qM = q.as2D() + val matrixT = matrix.transpose(0, 1) + val qT = q.transpose(0, 1) + + for (j in 0 until n) { + val v = matrixT[j] + val vv = v.as1D() + if (j > 0) { + for (i in 0 until j) { + r[i, j] = (qT[i] dot matrixT[j]).value() + for (k in 0 until n) { + val qTi = qT[i].as1D() + vv[k] = vv[k] - r[i, j] * qTi[k] + } + } + } + r[j, j] = DoubleTensorAlgebra { (v dot v).sqrt().value() } + for (i in 0 until n) { + qM[i, j] = vv[i] / r[j, j] + } + } +} + +internal fun DoubleTensorAlgebra.svd1d(a: DoubleTensor, epsilon: Double = 1e-10): DoubleTensor { + val (n, m) = a.shape + var v: DoubleTensor + val b: DoubleTensor + if (n > m) { + b = a.transpose(0, 1).dot(a) + v = DoubleTensor(intArrayOf(m), getRandomUnitVector(m, 0)) + } else { + b = a.dot(a.transpose(0, 1)) + v = DoubleTensor(intArrayOf(n), getRandomUnitVector(n, 0)) + } + + var lastV: DoubleTensor + while (true) { + lastV = v + v = b.dot(lastV) + val norm = DoubleTensorAlgebra { (v dot v).sqrt().value() } + v = v.times(1.0 / norm) + if (abs(v.dot(lastV).value()) > 1 - epsilon) { + return v + } + } +} + +internal fun DoubleTensorAlgebra.svdHelper( + matrix: DoubleTensor, + USV: Pair, Pair, BufferedTensor>>, + m: Int, n: Int, epsilon: Double +) { + val res = ArrayList>(0) + val (matrixU, SV) = USV + val (matrixS, matrixV) = SV + + for (k in 0 until min(n, m)) { + var a = matrix.copy() + for ((singularValue, u, v) in res.slice(0 until k)) { + val outerProduct = DoubleArray(u.shape[0] * v.shape[0]) + for (i in 0 until u.shape[0]) { + for (j in 0 until v.shape[0]) { + outerProduct[i * v.shape[0] + j] = u[i].value() * v[j].value() + } + } + a = a - singularValue.times(DoubleTensor(intArrayOf(u.shape[0], v.shape[0]), outerProduct)) + } + var v: DoubleTensor + var u: DoubleTensor + var norm: Double + if (n > m) { + v = svd1d(a, epsilon) + u = matrix.dot(v) + norm = DoubleTensorAlgebra { (u dot u).sqrt().value() } + u = u.times(1.0 / norm) + } else { + u = svd1d(a, epsilon) + v = matrix.transpose(0, 1).dot(u) + norm = DoubleTensorAlgebra { (v dot v).sqrt().value() } + v = v.times(1.0 / norm) + } + + res.add(Triple(norm, u, v)) + } + + val s = res.map { it.first }.toDoubleArray() + val uBuffer = res.map { it.second }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray() + val vBuffer = res.map { it.third }.flatMap { it.mutableBuffer.array().toList() }.toDoubleArray() + for (i in uBuffer.indices) { + matrixU.mutableBuffer.array()[matrixU.bufferStart + i] = uBuffer[i] + } + for (i in s.indices) { + matrixS.mutableBuffer.array()[matrixS.bufferStart + i] = s[i] + } + for (i in vBuffer.indices) { + matrixV.mutableBuffer.array()[matrixV.bufferStart + i] = vBuffer[i] + } +} + +internal fun cleanSymHelper(matrix: MutableStructure2D, n: Int) { + for (i in 0 until n) + for (j in 0 until n) { + if (i == j) { + matrix[i, j] = sign(matrix[i, j]) + } else { + matrix[i, j] = 0.0 + } + } +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/tensorCastsUtils.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/tensorCastsUtils.kt new file mode 100644 index 000000000..d965b6bcd --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/tensorCastsUtils.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core.internal + +import space.kscience.kmath.nd.MutableBufferND +import space.kscience.kmath.structures.asMutableBuffer +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.core.BufferedTensor +import space.kscience.kmath.tensors.core.DoubleTensor +import space.kscience.kmath.tensors.core.IntTensor + +internal fun BufferedTensor.asTensor(): IntTensor = + IntTensor(this.shape, this.mutableBuffer.array(), this.bufferStart) + +internal fun BufferedTensor.asTensor(): DoubleTensor = + DoubleTensor(this.shape, this.mutableBuffer.array(), this.bufferStart) + +internal fun Tensor.copyToBufferedTensor(): BufferedTensor = + BufferedTensor( + this.shape, + TensorLinearStructure(this.shape).indices().map(this::get).toMutableList().asMutableBuffer(), 0 + ) + +internal fun Tensor.toBufferedTensor(): BufferedTensor = when (this) { + is BufferedTensor -> this + is MutableBufferND -> if (this.strides.strides contentEquals TensorLinearStructure(this.shape).strides) + BufferedTensor(this.shape, this.mutableBuffer, 0) else this.copyToBufferedTensor() + else -> this.copyToBufferedTensor() +} + +internal val Tensor.tensor: DoubleTensor + get() = when (this) { + is DoubleTensor -> this + else -> this.toBufferedTensor().asTensor() + } + +internal val Tensor.tensor: IntTensor + get() = when (this) { + is IntTensor -> this + else -> this.toBufferedTensor().asTensor() + } diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/utils.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/utils.kt new file mode 100644 index 000000000..0ffaf39e7 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/utils.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core.internal + +import space.kscience.kmath.nd.as1D +import space.kscience.kmath.samplers.GaussianSampler +import space.kscience.kmath.stat.RandomGenerator +import space.kscience.kmath.structures.* +import space.kscience.kmath.tensors.core.BufferedTensor +import space.kscience.kmath.tensors.core.DoubleTensor +import kotlin.math.* + +/** + * Returns a reference to [IntArray] containing all of the elements of this [Buffer] or copy the data. + */ +internal fun Buffer.array(): IntArray = when (this) { + is IntBuffer -> array + else -> this.toIntArray() +} + +/** + * Returns a reference to [DoubleArray] containing all of the elements of this [Buffer] or copy the data. + */ +internal fun Buffer.array(): DoubleArray = when (this) { + is DoubleBuffer -> array + else -> this.toDoubleArray() +} + +internal fun getRandomNormals(n: Int, seed: Long): DoubleArray { + val distribution = GaussianSampler(0.0, 1.0) + val generator = RandomGenerator.default(seed) + return distribution.sample(generator).nextBufferBlocking(n).toDoubleArray() +} + +internal fun getRandomUnitVector(n: Int, seed: Long): DoubleArray { + val unnorm = getRandomNormals(n, seed) + val norm = sqrt(unnorm.sumOf { it * it }) + return unnorm.map { it / norm }.toDoubleArray() +} + +internal fun minusIndexFrom(n: Int, i: Int): Int = if (i >= 0) i else { + val ii = n + i + check(ii >= 0) { + "Out of bound index $i for tensor of dim $n" + } + ii +} + +internal fun BufferedTensor.minusIndex(i: Int): Int = minusIndexFrom(this.dimension, i) + +internal fun format(value: Double, digits: Int = 4): String = buildString { + val res = buildString { + val ten = 10.0 + val approxOrder = if (value == 0.0) 0 else ceil(log10(abs(value))).toInt() + val order = if ( + ((value % ten) == 0.0) || + (value == 1.0) || + ((1 / value) % ten == 0.0) + ) approxOrder else approxOrder - 1 + val lead = value / ten.pow(order) + if (value >= 0.0) append(' ') + append(round(lead * ten.pow(digits)) / ten.pow(digits)) + when { + order == 0 -> Unit + order > 0 -> { + append("e+") + append(order) + } + else -> { + append('e') + append(order) + } + } + } + val fLength = digits + 6 + append(res) + repeat(fLength - res.length) { append(' ') } +} + +internal fun DoubleTensor.toPrettyString(): String = buildString { + var offset = 0 + val shape = this@toPrettyString.shape + val linearStructure = this@toPrettyString.linearStructure + val vectorSize = shape.last() + append("DoubleTensor(\n") + var charOffset = 3 + for (vector in vectorSequence()) { + repeat(charOffset) { append(' ') } + val index = linearStructure.index(offset) + for (ind in index.reversed()) { + if (ind != 0) { + break + } + append('[') + charOffset += 1 + } + + val values = vector.as1D().toMutableList().map(::format) + + values.joinTo(this, separator = ", ") + + append(']') + charOffset -= 1 + + index.reversed().zip(shape.reversed()).drop(1).forEach { (ind, maxInd) -> + if (ind != maxInd - 1) { + return@forEach + } + append(']') + charOffset -= 1 + } + + offset += vectorSize + if (this@toPrettyString.numElements == offset) { + break + } + + append(",\n") + } + append("\n)") +} diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/tensorCasts.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/tensorCasts.kt new file mode 100644 index 000000000..142cb2156 --- /dev/null +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/tensorCasts.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.tensors.api.Tensor +import space.kscience.kmath.tensors.core.internal.tensor + +/** + * Casts [Tensor] of [Double] to [DoubleTensor] + */ +public fun Tensor.toDoubleTensor(): DoubleTensor = this.tensor + +/** + * Casts [Tensor] of [Int] to [IntTensor] + */ +public fun Tensor.toIntTensor(): IntTensor = this.tensor + +/** + * Returns [DoubleArray] of tensor elements + */ +public fun DoubleTensor.toDoubleArray(): DoubleArray { + return DoubleArray(numElements) { i -> + mutableBuffer[bufferStart + i] + } +} + +/** + * Returns [IntArray] of tensor elements + */ +public fun IntTensor.toIntArray(): IntArray { + return IntArray(numElements) { i -> + mutableBuffer[bufferStart + i] + } +} diff --git a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt new file mode 100644 index 000000000..35e605fd9 --- /dev/null +++ b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestBroadcasting.kt @@ -0,0 +1,105 @@ +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.internal.* +import kotlin.test.Test +import kotlin.test.assertTrue + +internal class TestBroadcasting { + + @Test + fun testBroadcastShapes() = DoubleTensorAlgebra { + assertTrue( + broadcastShapes( + intArrayOf(2, 3), intArrayOf(1, 3), intArrayOf(1, 1, 1) + ) contentEquals intArrayOf(1, 2, 3) + ) + + assertTrue( + broadcastShapes( + intArrayOf(6, 7), intArrayOf(5, 6, 1), intArrayOf(7), intArrayOf(5, 1, 7) + ) contentEquals intArrayOf(5, 6, 7) + ) + } + + @Test + fun testBroadcastTo() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(1, 3), doubleArrayOf(10.0, 20.0, 30.0)) + + val res = broadcastTo(tensor2, tensor1.shape) + assertTrue(res.shape contentEquals intArrayOf(2, 3)) + assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0)) + } + + @Test + fun testBroadcastTensors() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(1, 3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor3 = fromArray(intArrayOf(1, 1, 1), doubleArrayOf(500.0)) + + val res = broadcastTensors(tensor1, tensor2, tensor3) + + assertTrue(res[0].shape contentEquals intArrayOf(1, 2, 3)) + assertTrue(res[1].shape contentEquals intArrayOf(1, 2, 3)) + assertTrue(res[2].shape contentEquals intArrayOf(1, 2, 3)) + + assertTrue(res[0].mutableBuffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + assertTrue(res[1].mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0, 10.0, 20.0, 30.0)) + assertTrue(res[2].mutableBuffer.array() contentEquals doubleArrayOf(500.0, 500.0, 500.0, 500.0, 500.0, 500.0)) + } + + @Test + fun testBroadcastOuterTensors() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(1, 3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor3 = fromArray(intArrayOf(1, 1, 1), doubleArrayOf(500.0)) + + val res = broadcastOuterTensors(tensor1, tensor2, tensor3) + + assertTrue(res[0].shape contentEquals intArrayOf(1, 2, 3)) + assertTrue(res[1].shape contentEquals intArrayOf(1, 1, 3)) + assertTrue(res[2].shape contentEquals intArrayOf(1, 1, 1)) + + assertTrue(res[0].mutableBuffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + assertTrue(res[1].mutableBuffer.array() contentEquals doubleArrayOf(10.0, 20.0, 30.0)) + assertTrue(res[2].mutableBuffer.array() contentEquals doubleArrayOf(500.0)) + } + + @Test + fun testBroadcastOuterTensorsShapes() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 1, 3, 2, 3), DoubleArray(2 * 1 * 3 * 2 * 3) {0.0}) + val tensor2 = fromArray(intArrayOf(4, 2, 5, 1, 3, 3), DoubleArray(4 * 2 * 5 * 1 * 3 * 3) {0.0}) + val tensor3 = fromArray(intArrayOf(1, 1), doubleArrayOf(500.0)) + + val res = broadcastOuterTensors(tensor1, tensor2, tensor3) + + assertTrue(res[0].shape contentEquals intArrayOf(4, 2, 5, 3, 2, 3)) + assertTrue(res[1].shape contentEquals intArrayOf(4, 2, 5, 3, 3, 3)) + assertTrue(res[2].shape contentEquals intArrayOf(4, 2, 5, 3, 1, 1)) + } + + @Test + fun testMinusTensor() = BroadcastDoubleTensorAlgebra.invoke { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(1, 3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor3 = fromArray(intArrayOf(1, 1, 1), doubleArrayOf(500.0)) + + val tensor21 = tensor2 - tensor1 + val tensor31 = tensor3 - tensor1 + val tensor32 = tensor3 - tensor2 + + assertTrue(tensor21.shape contentEquals intArrayOf(2, 3)) + assertTrue(tensor21.mutableBuffer.array() contentEquals doubleArrayOf(9.0, 18.0, 27.0, 6.0, 15.0, 24.0)) + + assertTrue(tensor31.shape contentEquals intArrayOf(1, 2, 3)) + assertTrue( + tensor31.mutableBuffer.array() + contentEquals doubleArrayOf(499.0, 498.0, 497.0, 496.0, 495.0, 494.0) + ) + + assertTrue(tensor32.shape contentEquals intArrayOf(1, 1, 3)) + assertTrue(tensor32.mutableBuffer.array() contentEquals doubleArrayOf(490.0, 480.0, 470.0)) + } + +} diff --git a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt new file mode 100644 index 000000000..3b4c615b4 --- /dev/null +++ b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleAnalyticTensorAlgebra.kt @@ -0,0 +1,158 @@ +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.operations.invoke +import kotlin.math.* +import kotlin.test.Test +import kotlin.test.assertTrue + +internal class TestDoubleAnalyticTensorAlgebra { + + val shape = intArrayOf(2, 1, 3, 2) + val buffer = doubleArrayOf( + 27.1, 20.0, 19.84, + 23.123, 3.0, 2.0, + + 3.23, 133.7, 25.3, + 100.3, 11.0, 12.012 + ) + val tensor = DoubleTensor(shape, buffer) + + fun DoubleArray.fmap(transform: (Double) -> Double): DoubleArray { + return this.map(transform).toDoubleArray() + } + + fun expectedTensor(transform: (Double) -> Double): DoubleTensor { + return DoubleTensor(shape, buffer.fmap(transform)) + } + + @Test + fun testExp() = DoubleTensorAlgebra { + assertTrue { tensor.exp() eq expectedTensor(::exp) } + } + + @Test + fun testLog() = DoubleTensorAlgebra { + assertTrue { tensor.ln() eq expectedTensor(::ln) } + } + + @Test + fun testSqrt() = DoubleTensorAlgebra { + assertTrue { tensor.sqrt() eq expectedTensor(::sqrt) } + } + + @Test + fun testCos() = DoubleTensorAlgebra { + assertTrue { tensor.cos() eq expectedTensor(::cos) } + } + + + @Test + fun testCosh() = DoubleTensorAlgebra { + assertTrue { tensor.cosh() eq expectedTensor(::cosh) } + } + + @Test + fun testAcosh() = DoubleTensorAlgebra { + assertTrue { tensor.acosh() eq expectedTensor(::acosh) } + } + + @Test + fun testSin() = DoubleTensorAlgebra { + assertTrue { tensor.sin() eq expectedTensor(::sin) } + } + + @Test + fun testSinh() = DoubleTensorAlgebra { + assertTrue { tensor.sinh() eq expectedTensor(::sinh) } + } + + @Test + fun testAsinh() = DoubleTensorAlgebra { + assertTrue { tensor.asinh() eq expectedTensor(::asinh) } + } + + @Test + fun testTan() = DoubleTensorAlgebra { + assertTrue { tensor.tan() eq expectedTensor(::tan) } + } + + @Test + fun testAtan() = DoubleTensorAlgebra { + assertTrue { tensor.atan() eq expectedTensor(::atan) } + } + + @Test + fun testTanh() = DoubleTensorAlgebra { + assertTrue { tensor.tanh() eq expectedTensor(::tanh) } + } + + @Test + fun testCeil() = DoubleTensorAlgebra { + assertTrue { tensor.ceil() eq expectedTensor(::ceil) } + } + + @Test + fun testFloor() = DoubleTensorAlgebra { + assertTrue { tensor.floor() eq expectedTensor(::floor) } + } + + val shape2 = intArrayOf(2, 2) + val buffer2 = doubleArrayOf( + 1.0, 2.0, + -3.0, 4.0 + ) + val tensor2 = DoubleTensor(shape2, buffer2) + + @Test + fun testMin() = DoubleTensorAlgebra { + assertTrue { tensor2.min() == -3.0 } + assertTrue { tensor2.min(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(-3.0, 2.0) + )} + assertTrue { tensor2.min(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(1.0, -3.0) + )} + } + + @Test + fun testMax() = DoubleTensorAlgebra { + assertTrue { tensor2.max() == 4.0 } + assertTrue { tensor2.max(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(1.0, 4.0) + )} + assertTrue { tensor2.max(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(2.0, 4.0) + )} + } + + @Test + fun testSum() = DoubleTensorAlgebra { + assertTrue { tensor2.sum() == 4.0 } + assertTrue { tensor2.sum(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(-2.0, 6.0) + )} + assertTrue { tensor2.sum(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(3.0, 1.0) + )} + } + + @Test + fun testMean() = DoubleTensorAlgebra { + assertTrue { tensor2.mean() == 1.0 } + assertTrue { tensor2.mean(0, true) eq fromArray( + intArrayOf(1, 2), + doubleArrayOf(-1.0, 3.0) + )} + assertTrue { tensor2.mean(1, false) eq fromArray( + intArrayOf(2), + doubleArrayOf(1.5, 0.5) + )} + } + +} diff --git a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleLinearOpsAlgebra.kt b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleLinearOpsAlgebra.kt new file mode 100644 index 000000000..347bb683f --- /dev/null +++ b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleLinearOpsAlgebra.kt @@ -0,0 +1,196 @@ +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.internal.array +import space.kscience.kmath.tensors.core.internal.svd1d +import kotlin.math.abs +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +internal class TestDoubleLinearOpsTensorAlgebra { + + @Test + fun testDetLU() = DoubleTensorAlgebra { + val tensor = fromArray( + intArrayOf(2, 2, 2), + doubleArrayOf( + 1.0, 3.0, + 1.0, 2.0, + 1.5, 1.0, + 10.0, 2.0 + ) + ) + + val expectedTensor = fromArray( + intArrayOf(2, 1), + doubleArrayOf( + -1.0, + -7.0 + ) + ) + val detTensor = tensor.detLU() + + assertTrue(detTensor.eq(expectedTensor)) + + } + + @Test + fun testDet() = DoubleTensorAlgebra { + val expectedValue = 0.019827417 + val m = fromArray( + intArrayOf(3, 3), doubleArrayOf( + 2.1843, 1.4391, -0.4845, + 1.4391, 1.7772, 0.4055, + -0.4845, 0.4055, 0.7519 + ) + ) + + assertTrue { abs(m.det().value() - expectedValue) < 1e-5 } + } + + @Test + fun testDetSingle() = DoubleTensorAlgebra { + val expectedValue = 48.151623 + val m = fromArray( + intArrayOf(1, 1), doubleArrayOf( + expectedValue + ) + ) + + assertTrue { abs(m.det().value() - expectedValue) < 1e-5 } + } + + @Test + fun testInvLU() = DoubleTensorAlgebra { + val tensor = fromArray( + intArrayOf(2, 2, 2), + doubleArrayOf( + 1.0, 0.0, + 0.0, 2.0, + 1.0, 1.0, + 1.0, 0.0 + ) + ) + + val expectedTensor = fromArray( + intArrayOf(2, 2, 2), doubleArrayOf( + 1.0, 0.0, + 0.0, 0.5, + 0.0, 1.0, + 1.0, -1.0 + ) + ) + + val invTensor = tensor.invLU() + assertTrue(invTensor.eq(expectedTensor)) + } + + @Test + fun testScalarProduct() = DoubleTensorAlgebra { + val a = fromArray(intArrayOf(3), doubleArrayOf(1.8, 2.5, 6.8)) + val b = fromArray(intArrayOf(3), doubleArrayOf(5.5, 2.6, 6.4)) + assertEquals(a.dot(b).value(), 59.92) + } + + @Test + fun testQR() = DoubleTensorAlgebra { + val shape = intArrayOf(2, 2, 2) + val buffer = doubleArrayOf( + 1.0, 3.0, + 1.0, 2.0, + 1.5, 1.0, + 10.0, 2.0 + ) + + val tensor = fromArray(shape, buffer) + + val (q, r) = tensor.qr() + + assertTrue { q.shape contentEquals shape } + assertTrue { r.shape contentEquals shape } + + assertTrue((q dot r).eq(tensor)) + + } + + @Test + fun testLU() = DoubleTensorAlgebra { + val shape = intArrayOf(2, 2, 2) + val buffer = doubleArrayOf( + 1.0, 3.0, + 1.0, 2.0, + 1.5, 1.0, + 10.0, 2.0 + ) + val tensor = fromArray(shape, buffer) + + val (p, l, u) = tensor.lu() + + assertTrue { p.shape contentEquals shape } + assertTrue { l.shape contentEquals shape } + assertTrue { u.shape contentEquals shape } + + assertTrue((p dot tensor).eq(l dot u)) + } + + @Test + fun testCholesky() = DoubleTensorAlgebra { + val tensor = randomNormal(intArrayOf(2, 5, 5), 0) + val sigma = (tensor dot tensor.transpose()) + diagonalEmbedding( + fromArray(intArrayOf(2, 5), DoubleArray(10) { 0.1 }) + ) + val low = sigma.cholesky() + val sigmChol = low dot low.transpose() + assertTrue(sigma.eq(sigmChol)) + } + + @Test + fun testSVD1D() = DoubleTensorAlgebra { + val tensor2 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + + val res = svd1d(tensor2) + + assertTrue(res.shape contentEquals intArrayOf(2)) + assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart]) - 0.386) < 0.01 } + assertTrue { abs(abs(res.mutableBuffer.array()[res.bufferStart + 1]) - 0.922) < 0.01 } + } + + @Test + fun testSVD() = DoubleTensorAlgebra{ + testSVDFor(fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0))) + testSVDFor(fromArray(intArrayOf(2, 2), doubleArrayOf(-1.0, 0.0, 239.0, 238.0))) + } + + @Test + fun testBatchedSVD() = DoubleTensorAlgebra { + val tensor = randomNormal(intArrayOf(2, 5, 3), 0) + val (tensorU, tensorS, tensorV) = tensor.svd() + val tensorSVD = tensorU dot (diagonalEmbedding(tensorS) dot tensorV.transpose()) + assertTrue(tensor.eq(tensorSVD)) + } + + @Test + fun testBatchedSymEig() = DoubleTensorAlgebra { + val tensor = randomNormal(shape = intArrayOf(2, 3, 3), 0) + val tensorSigma = tensor + tensor.transpose() + val (tensorS, tensorV) = tensorSigma.symEig() + val tensorSigmaCalc = tensorV dot (diagonalEmbedding(tensorS) dot tensorV.transpose()) + assertTrue(tensorSigma.eq(tensorSigmaCalc)) + } + + +} + + +private fun DoubleTensorAlgebra.testSVDFor(tensor: DoubleTensor, epsilon: Double = 1e-10): Unit { + val svd = tensor.svd() + + val tensorSVD = svd.first + .dot( + diagonalEmbedding(svd.second) + .dot(svd.third.transpose()) + ) + + assertTrue(tensor.eq(tensorSVD, epsilon)) +} diff --git a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensor.kt b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensor.kt new file mode 100644 index 000000000..a176abdd4 --- /dev/null +++ b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensor.kt @@ -0,0 +1,91 @@ +package space.kscience.kmath.tensors.core + +import space.kscience.kmath.misc.PerformancePitfall +import space.kscience.kmath.nd.DefaultStrides +import space.kscience.kmath.nd.MutableBufferND +import space.kscience.kmath.nd.as1D +import space.kscience.kmath.nd.as2D +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.toDoubleArray +import space.kscience.kmath.tensors.core.internal.array +import space.kscience.kmath.tensors.core.internal.asTensor +import space.kscience.kmath.tensors.core.internal.matrixSequence +import space.kscience.kmath.tensors.core.internal.toBufferedTensor +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +internal class TestDoubleTensor { + + @Test + fun testValue() = DoubleTensorAlgebra { + val value = 12.5 + val tensor = fromArray(intArrayOf(1), doubleArrayOf(value)) + assertEquals(tensor.value(), value) + } + + @OptIn(PerformancePitfall::class) + @Test + fun testStrides() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(2, 2), doubleArrayOf(3.5, 5.8, 58.4, 2.4)) + assertEquals(tensor[intArrayOf(0, 1)], 5.8) + assertTrue( + tensor.elements().map { it.second }.toList().toDoubleArray() contentEquals tensor.mutableBuffer.toDoubleArray() + ) + } + + @Test + fun testGet() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(1, 2, 2), doubleArrayOf(3.5, 5.8, 58.4, 2.4)) + val matrix = tensor[0].as2D() + assertEquals(matrix[0, 1], 5.8) + + val vector = tensor[0][1].as1D() + assertEquals(vector[0], 58.4) + + matrix[0, 1] = 77.89 + assertEquals(tensor[intArrayOf(0, 0, 1)], 77.89) + + vector[0] = 109.56 + assertEquals(tensor[intArrayOf(0, 1, 0)], 109.56) + + tensor.matrixSequence().forEach { + val a = it.asTensor() + val secondRow = a[1].as1D() + val secondColumn = a.transpose(0, 1)[1].as1D() + assertEquals(secondColumn[0], 77.89) + assertEquals(secondRow[1], secondColumn[1]) + } + } + + @Test + fun testNoBufferProtocol() { + + // create buffer + val doubleArray = DoubleBuffer(doubleArrayOf(1.0, 2.0, 3.0)) + + // create ND buffers, no data is copied + val ndArray = MutableBufferND(DefaultStrides(intArrayOf(3)), doubleArray) + + // map to tensors + val bufferedTensorArray = ndArray.toBufferedTensor() // strides are flipped so data copied + val tensorArray = bufferedTensorArray.asTensor() // data not contiguous so copied again + + val tensorArrayPublic = ndArray.toDoubleTensor() // public API, data copied twice + val sharedTensorArray = tensorArrayPublic.toDoubleTensor() // no data copied by matching type + + assertTrue(tensorArray.mutableBuffer.array() contentEquals sharedTensorArray.mutableBuffer.array()) + + tensorArray[intArrayOf(0)] = 55.9 + assertEquals(tensorArrayPublic[intArrayOf(0)], 1.0) + + tensorArrayPublic[intArrayOf(0)] = 55.9 + assertEquals(sharedTensorArray[intArrayOf(0)], 55.9) + assertEquals(bufferedTensorArray[intArrayOf(0)], 1.0) + + bufferedTensorArray[intArrayOf(0)] = 55.9 + assertEquals(ndArray[intArrayOf(0)], 1.0) + + } +} diff --git a/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt new file mode 100644 index 000000000..e7e898008 --- /dev/null +++ b/kmath-tensors/src/commonTest/kotlin/space/kscience/kmath/tensors/core/TestDoubleTensorAlgebra.kt @@ -0,0 +1,167 @@ +package space.kscience.kmath.tensors.core + + +import space.kscience.kmath.operations.invoke +import space.kscience.kmath.tensors.core.internal.array +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +internal class TestDoubleTensorAlgebra { + + @Test + fun testDoublePlus() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(2), doubleArrayOf(1.0, 2.0)) + val res = 10.0 + tensor + assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(11.0, 12.0)) + } + + @Test + fun TestDoubleDiv() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(2), doubleArrayOf(2.0, 4.0)) + val res = 2.0/tensor + assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 0.5)) + } + + @Test + fun testDivDouble() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(2), doubleArrayOf(10.0, 5.0)) + val res = tensor / 2.5 + assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(4.0, 2.0)) + } + + @Test + fun testTranspose1x1() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(1), doubleArrayOf(0.0)) + val res = tensor.transpose(0, 0) + + assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(0.0)) + assertTrue(res.shape contentEquals intArrayOf(1)) + } + + @Test + fun testTranspose3x2() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(3, 2), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val res = tensor.transpose(1, 0) + + assertTrue(res.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 3.0, 5.0, 2.0, 4.0, 6.0)) + assertTrue(res.shape contentEquals intArrayOf(2, 3)) + } + + @Test + fun testTranspose1x2x3() = DoubleTensorAlgebra { + val tensor = fromArray(intArrayOf(1, 2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val res01 = tensor.transpose(0, 1) + val res02 = tensor.transpose(-3, 2) + val res12 = tensor.transpose() + + assertTrue(res01.shape contentEquals intArrayOf(2, 1, 3)) + assertTrue(res02.shape contentEquals intArrayOf(3, 2, 1)) + assertTrue(res12.shape contentEquals intArrayOf(1, 3, 2)) + + assertTrue(res01.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + assertTrue(res02.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 4.0, 2.0, 5.0, 3.0, 6.0)) + assertTrue(res12.mutableBuffer.array() contentEquals doubleArrayOf(1.0, 4.0, 2.0, 5.0, 3.0, 6.0)) + } + + @Test + fun testLinearStructure() = DoubleTensorAlgebra { + val shape = intArrayOf(3) + val tensorA = full(value = -4.5, shape = shape) + val tensorB = full(value = 10.9, shape = shape) + val tensorC = full(value = 789.3, shape = shape) + val tensorD = full(value = -72.9, shape = shape) + val tensorE = full(value = 553.1, shape = shape) + val result = 15.8 * tensorA - 1.5 * tensorB * (-tensorD) + 0.02 * tensorC / tensorE - 39.4 + + val expected = fromArray( + shape, + (1..3).map { + 15.8 * (-4.5) - 1.5 * 10.9 * 72.9 + 0.02 * 789.3 / 553.1 - 39.4 + }.toDoubleArray() + ) + + val assignResult = zeros(shape) + tensorA *= 15.8 + tensorB *= 1.5 + tensorB *= -tensorD + tensorC *= 0.02 + tensorC /= tensorE + assignResult += tensorA + assignResult -= tensorB + assignResult += tensorC + assignResult += -39.4 + + assertTrue(expected.mutableBuffer.array() contentEquals result.mutableBuffer.array()) + assertTrue(expected.mutableBuffer.array() contentEquals assignResult.mutableBuffer.array()) + } + + @Test + fun testDot() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor11 = fromArray(intArrayOf(3, 2), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor3 = fromArray(intArrayOf(1, 1, 3), doubleArrayOf(-1.0, -2.0, -3.0)) + + val res12 = tensor1.dot(tensor2) + assertTrue(res12.mutableBuffer.array() contentEquals doubleArrayOf(140.0, 320.0)) + assertTrue(res12.shape contentEquals intArrayOf(2)) + + val res32 = tensor3.dot(tensor2) + assertTrue(res32.mutableBuffer.array() contentEquals doubleArrayOf(-140.0)) + assertTrue(res32.shape contentEquals intArrayOf(1, 1)) + + val res22 = tensor2.dot(tensor2) + assertTrue(res22.mutableBuffer.array() contentEquals doubleArrayOf(1400.0)) + assertTrue(res22.shape contentEquals intArrayOf(1)) + + val res11 = tensor1.dot(tensor11) + assertTrue(res11.mutableBuffer.array() contentEquals doubleArrayOf(22.0, 28.0, 49.0, 64.0)) + assertTrue(res11.shape contentEquals intArrayOf(2, 2)) + } + + @Test + fun testDiagonalEmbedding() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(3), doubleArrayOf(10.0, 20.0, 30.0)) + val tensor2 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor3 = zeros(intArrayOf(2, 3, 4, 5)) + + assertTrue(diagonalEmbedding(tensor3, 0, 3, 4).shape contentEquals + intArrayOf(2, 3, 4, 5, 5)) + assertTrue(diagonalEmbedding(tensor3, 1, 3, 4).shape contentEquals + intArrayOf(2, 3, 4, 6, 6)) + assertTrue(diagonalEmbedding(tensor3, 2, 0, 3).shape contentEquals + intArrayOf(7, 2, 3, 7, 4)) + + val diagonal1 = diagonalEmbedding(tensor1, 0, 1, 0) + assertTrue(diagonal1.shape contentEquals intArrayOf(3, 3)) + assertTrue(diagonal1.mutableBuffer.array() contentEquals + doubleArrayOf(10.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 30.0)) + + val diagonal1Offset = diagonalEmbedding(tensor1, 1, 1, 0) + assertTrue(diagonal1Offset.shape contentEquals intArrayOf(4, 4)) + assertTrue(diagonal1Offset.mutableBuffer.array() contentEquals + doubleArrayOf(0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0, 0.0, 0.0, 30.0, 0.0)) + + val diagonal2 = diagonalEmbedding(tensor2, 1, 0, 2) + assertTrue(diagonal2.shape contentEquals intArrayOf(4, 2, 4)) + assertTrue(diagonal2.mutableBuffer.array() contentEquals + doubleArrayOf( + 0.0, 1.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, + 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 5.0, 0.0, + 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 6.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) + } + + @Test + fun testEq() = DoubleTensorAlgebra { + val tensor1 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor2 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)) + val tensor3 = fromArray(intArrayOf(2, 3), doubleArrayOf(1.0, 2.0, 3.0, 4.0, 5.0, 5.0)) + + assertTrue(tensor1 eq tensor1) + assertTrue(tensor1 eq tensor2) + assertFalse(tensor1.eq(tensor3)) + + } +} diff --git a/kmath-viktor/build.gradle.kts b/kmath-viktor/build.gradle.kts index 232bd1388..2e932b441 100644 --- a/kmath-viktor/build.gradle.kts +++ b/kmath-viktor/build.gradle.kts @@ -7,9 +7,9 @@ description = "Binding for https://github.com/JetBrains-Research/viktor" dependencies { api(project(":kmath-core")) - api("org.jetbrains.bio:viktor:1.0.1") + api("org.jetbrains.bio:viktor:1.1.0") } readme { maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT -} \ No newline at end of file +} diff --git a/kmath-viktor/src/main/kotlin/space/kscience/kmath/viktor/ViktorStructureND.kt b/kmath-viktor/src/main/kotlin/space/kscience/kmath/viktor/ViktorStructureND.kt index dc1b45f5d..b7abf4304 100644 --- a/kmath-viktor/src/main/kotlin/space/kscience/kmath/viktor/ViktorStructureND.kt +++ b/kmath-viktor/src/main/kotlin/space/kscience/kmath/viktor/ViktorStructureND.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.viktor import org.jetbrains.bio.viktor.F64Array +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.* import space.kscience.kmath.operations.DoubleField @@ -23,6 +24,7 @@ public class ViktorStructureND(public val f64Buffer: F64Array) : MutableStructur f64Buffer.set(*index, value = value) } + @PerformancePitfall public override fun elements(): Sequence> = DefaultStrides(shape).indices().map { it to get(it) } } diff --git a/license/COPYRIGHT.txt b/license/COPYRIGHT.txt deleted file mode 100644 index 7bf2faffd..000000000 --- a/license/COPYRIGHT.txt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * - * 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. - */ \ No newline at end of file diff --git a/license/COPYRIGHT_HEADER.txt b/license/COPYRIGHT_HEADER.txt deleted file mode 100644 index 3e7d28489..000000000 --- a/license/COPYRIGHT_HEADER.txt +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ \ No newline at end of file diff --git a/license/LICENSE.txt b/license/LICENSE.txt deleted file mode 100644 index 84b106a07..000000000 --- a/license/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2018-2021 KMath contributors. - - 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. diff --git a/license/README.md b/license/README.md deleted file mode 100644 index cfef3de79..000000000 --- a/license/README.md +++ /dev/null @@ -1,63 +0,0 @@ -The Apache 2 license (given in full in LICENSE.txt) applies to all code in this repository, which is copyright by the -contributors of KMath. The following sections of the repository contain third-party code, to which different licenses -may apply: - -## KMath Libraries - -The following modules contain third-party code and are incorporated into the KMath Libraries: - -- Path: kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/SplineInterpolator.kt - - License: Apache 2 ([cm](third_party/cm_license.txt)) - - Origin: Derived from Apache Commons Math, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/LinearInterpolator.kt - - License: Apache 2 ([cm](third_party/cm_license.txt)) - - Origin: Derived from Apache Commons Math, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/LoessInterpolator.kt - - License: Apache 2 ([cm](third_party/cm_license.txt)) - - Origin: Derived from Apache Commons Math, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt - - License: Apache 2 ([cm](third_party/cm_license.txt)) - - Origin: Derived from Apache Commons Math, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt - - License: Apache 2 ([numky](third_party/numky_license.txt)) - - Origin: Initial implementation was taken from Numky -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/AhrensDieterExponentialSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/AhrensDieterMarsagliaTsangGammaSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/AliasMethodDiscreteSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/BoxMullerSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/GaussianSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/KempSmallMeanPoissonSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/MarsagliaNormalizedGaussianSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/NormalizedGaussianSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/PoissonSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: kmath-stat/src/commonMain/kotlin/space/kscience/kmath/samplers/ZigguratNormalizedGaussianSampler.kt - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation -- Path: - - License: Apache 2 ([cm](third_party/crng_license.txt)) - - Origin: Derived from Apache Commons RNG, (c) 2001-2020 The Apache Software Foundation - \ No newline at end of file diff --git a/license/third_party/cm_license.txt b/license/third_party/cm_license.txt deleted file mode 100644 index 6172c3fb2..000000000 --- a/license/third_party/cm_license.txt +++ /dev/null @@ -1,457 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. - - -Apache Commons Math includes the following code provided to the ASF under the -Apache License 2.0: - - - The inverse error function implementation in the Erf class is based on CUDA - code developed by Mike Giles, Oxford-Man Institute of Quantitative Finance, - and published in GPU Computing Gems, volume 2, 2010 (grant received on - March 23th 2013) - - The LinearConstraint, LinearObjectiveFunction, LinearOptimizer, - RelationShip, SimplexSolver and SimplexTableau classes in package - org.apache.commons.math3.optimization.linear include software developed by - Benjamin McCann (http://www.benmccann.com) and distributed with - the following copyright: Copyright 2009 Google Inc. (grant received on - March 16th 2009) - - The class "org.apache.commons.math3.exception.util.LocalizedFormatsTest" which - is an adapted version of "OrekitMessagesTest" test class for the Orekit library - - The "org.apache.commons.math3.analysis.interpolation.HermiteInterpolator" - has been imported from the Orekit space flight dynamics library. - -=============================================================================== - - - -APACHE COMMONS MATH DERIVATIVE WORKS: - -The Apache commons-math library includes a number of subcomponents -whose implementation is derived from original sources written -in C or Fortran. License terms of the original sources -are reproduced below. - -=============================================================================== -For the lmder, lmpar and qrsolv Fortran routine from minpack and translated in -the LevenbergMarquardtOptimizer class in package -org.apache.commons.math3.optimization.general -Original source copyright and license statement: - -Minpack Copyright Notice (1999) University of Chicago. All rights reserved - -Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the -following conditions are met: - -1. Redistributions of source code must retain the above -copyright notice, this list of conditions and the following -disclaimer. - -2. Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following -disclaimer in the documentation and/or other materials -provided with the distribution. - -3. The end-user documentation included with the -redistribution, if any, must include the following -acknowledgment: - - "This product includes software developed by the - University of Chicago, as Operator of Argonne National - Laboratory. - -Alternately, this acknowledgment may appear in the software -itself, if and wherever such third-party acknowledgments -normally appear. - -4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" -WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE -UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND -THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE -OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY -OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR -USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF -THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) -DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION -UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL -BE CORRECTED. - -5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT -HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF -ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, -INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF -ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF -PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER -SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT -(INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, -EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE -POSSIBILITY OF SUCH LOSS OR DAMAGES. -=============================================================================== - -Copyright and license statement for the odex Fortran routine developed by -E. Hairer and G. Wanner and translated in GraggBulirschStoerIntegrator class -in package org.apache.commons.math3.ode.nonstiff: - - -Copyright (c) 2004, Ernst Hairer - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -- Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -=============================================================================== - -Copyright and license statement for the original Mersenne twister C -routines translated in MersenneTwister class in package -org.apache.commons.math3.random: - - Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=============================================================================== - -The initial code for shuffling an array (originally in class -"org.apache.commons.math3.random.RandomDataGenerator", now replaced by -a method in class "org.apache.commons.math3.util.MathArrays") was -inspired from the algorithm description provided in -"Algorithms", by Ian Craw and John Pulham (University of Aberdeen 1999). -The textbook (containing a proof that the shuffle is uniformly random) is -available here: - http://citeseerx.ist.psu.edu/viewdoc/download;?doi=10.1.1.173.1898&rep=rep1&type=pdf - -=============================================================================== -License statement for the direction numbers in the resource files for Sobol sequences. - ------------------------------------------------------------------------------ -Licence pertaining to sobol.cc and the accompanying sets of direction numbers - ------------------------------------------------------------------------------ -Copyright (c) 2008, Frances Y. Kuo and Stephen Joe -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the names of the copyright holders nor the names of the - University of New South Wales and the University of Waikato - and its contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -=============================================================================== - -The initial commit of package "org.apache.commons.math3.ml.neuralnet" is -an adapted version of code developed in the context of the Data Processing -and Analysis Consortium (DPAC) of the "Gaia" project of the European Space -Agency (ESA). -=============================================================================== - -The initial commit of the class "org.apache.commons.math3.special.BesselJ" is -an adapted version of code translated from the netlib Fortran program, rjbesl -http://www.netlib.org/specfun/rjbesl by R.J. Cody at Argonne National -Laboratory (USA). There is no license or copyright statement included with the -original Fortran sources. -=============================================================================== - - -The BracketFinder (package org.apache.commons.math3.optimization.univariate) -and PowellOptimizer (package org.apache.commons.math3.optimization.general) -classes are based on the Python code in module "optimize.py" (version 0.5) -developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/) -Copyright © 2003-2009 SciPy Developers. - -SciPy license -Copyright © 2001, 2002 Enthought, Inc. -All rights reserved. - -Copyright © 2003-2013 SciPy Developers. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Enthought nor the names of the SciPy Developers may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -=============================================================================== diff --git a/license/third_party/crng_license.txt b/license/third_party/crng_license.txt deleted file mode 100644 index dec0e2a5c..000000000 --- a/license/third_party/crng_license.txt +++ /dev/null @@ -1,275 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. - -================================================================================ - -Class "org.apache.commons.rng.core.source64.MersenneTwister64" contains -Java code partly ported from the reference implementation in C. -That source file contained the following notice: - - Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -================================================================================ - -Class "org.apache.commons.rng.core.source32.MersenneTwister" contains -Java code partly ported from the reference implementation in C. -That source file contained the following notice: - - Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -================================================================================ \ No newline at end of file diff --git a/license/third_party/numky_license.txt b/license/third_party/numky_license.txt deleted file mode 100644 index f49a4e16e..000000000 --- a/license/third_party/numky_license.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ca36168e1..065dd3ac4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,22 +1,23 @@ pluginManagement { repositories { - mavenLocal() + maven("https://repo.kotlin.link") mavenCentral() gradlePluginPortal() - maven("https://repo.kotlin.link") } - val toolsVersion = "0.9.5-dev-2" - val kotlinVersion = "1.5.0-RC" + val toolsVersion = "0.9.9" + val kotlinVersion = "1.5.0" plugins { - kotlin("multiplatform") version kotlinVersion - kotlin("jvm") version kotlinVersion - kotlin("plugin.allopen") version kotlinVersion - id("org.jetbrains.kotlinx.benchmark") version "0.3.0" id("ru.mipt.npm.gradle.project") version toolsVersion id("ru.mipt.npm.gradle.mpp") version toolsVersion id("ru.mipt.npm.gradle.jvm") version toolsVersion + kotlin("multiplatform") version kotlinVersion + kotlin("jvm") version kotlinVersion + kotlin("plugin.allopen") version kotlinVersion + id("org.jetbrains.kotlinx.benchmark") version "0.3.1" + kotlin("jupyter.api") version "0.10.0-25" + } } @@ -39,6 +40,8 @@ include( ":kmath-ast", ":kmath-ejml", ":kmath-kotlingrad", + ":kmath-tensors", + ":kmath-jupyter", ":examples", ":benchmarks" )