Rewrite EJML module by dropping ejml-simple abstraction level #294

Merged
CommanderTvis merged 1 commits from commandertvis/rework-ejml into dev 2021-04-30 15:51:45 +03:00
23 changed files with 281 additions and 249 deletions
Showing only changes of commit 598b2e1587 - Show all commits

View File

@ -30,9 +30,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Build - name: Build
run: | run: ./gradlew dokkaHtmlMultiModule --no-daemon --no-parallel --stacktrace
./gradlew dokkaHtmlMultiModule --no-daemon --no-parallel --stacktrace
mv build/dokka/htmlMultiModule/-modules.html build/dokka/htmlMultiModule/index.html
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@4.1.0 uses: JamesIves/github-pages-deploy-action@4.1.0
with: with:

View File

@ -10,7 +10,7 @@
- Blocking chains and Statistics - Blocking chains and Statistics
- Multiplatform integration - Multiplatform integration
- Integration for any Field element - Integration for any Field element
- Extendend operations for ND4J fields - Extended operations for ND4J fields
### Changed ### Changed
- Exponential operations merged with hyperbolic functions - Exponential operations merged with hyperbolic functions
@ -24,6 +24,7 @@
- Redesign MST. Remove MSTExpression. - Redesign MST. Remove MSTExpression.
- Move MST to core - Move MST to core
- Separated benchmarks and examples - Separated benchmarks and examples
- Rewritten EJML module without ejml-simple
### Deprecated ### Deprecated

View File

@ -9,14 +9,10 @@ sourceSets.register("benchmarks")
repositories { repositories {
mavenCentral() mavenCentral()
jcenter()
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://clojars.org/repo") 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("https://jitpack.io")
maven { maven("http://logicrunch.research.it.uu.se/maven") {
setUrl("http://logicrunch.research.it.uu.se/maven/")
isAllowInsecureProtocol = true isAllowInsecureProtocol = true
} }
} }

View File

@ -10,7 +10,7 @@ import kotlinx.benchmark.Blackhole
import kotlinx.benchmark.Scope import kotlinx.benchmark.Scope
import kotlinx.benchmark.State import kotlinx.benchmark.State
import space.kscience.kmath.commons.linear.CMLinearSpace 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.LinearSpace
import space.kscience.kmath.linear.invoke import space.kscience.kmath.linear.invoke
import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.DoubleField
@ -29,8 +29,8 @@ internal class DotBenchmark {
val cmMatrix1 = CMLinearSpace { matrix1.toCM() } val cmMatrix1 = CMLinearSpace { matrix1.toCM() }
val cmMatrix2 = CMLinearSpace { matrix2.toCM() } val cmMatrix2 = CMLinearSpace { matrix2.toCM() }
val ejmlMatrix1 = EjmlLinearSpace { matrix1.toEjml() } val ejmlMatrix1 = EjmlLinearSpaceDDRM { matrix1.toEjml() }
val ejmlMatrix2 = EjmlLinearSpace { matrix2.toEjml() } val ejmlMatrix2 = EjmlLinearSpaceDDRM { matrix2.toEjml() }
} }
@Benchmark @Benchmark
@ -42,14 +42,14 @@ internal class DotBenchmark {
@Benchmark @Benchmark
fun ejmlDot(blackhole: Blackhole) { fun ejmlDot(blackhole: Blackhole) {
EjmlLinearSpace { EjmlLinearSpaceDDRM {
blackhole.consume(ejmlMatrix1 dot ejmlMatrix2) blackhole.consume(ejmlMatrix1 dot ejmlMatrix2)
} }
} }
@Benchmark @Benchmark
fun ejmlDotWithConversion(blackhole: Blackhole) { fun ejmlDotWithConversion(blackhole: Blackhole) {
EjmlLinearSpace { EjmlLinearSpaceDDRM {
blackhole.consume(matrix1 dot matrix2) blackhole.consume(matrix1 dot matrix2)
} }
} }

View File

@ -11,25 +11,26 @@ import kotlinx.benchmark.Scope
import kotlinx.benchmark.State import kotlinx.benchmark.State
import space.kscience.kmath.commons.linear.CMLinearSpace import space.kscience.kmath.commons.linear.CMLinearSpace
import space.kscience.kmath.commons.linear.inverse import space.kscience.kmath.commons.linear.inverse
import space.kscience.kmath.ejml.EjmlLinearSpace import space.kscience.kmath.ejml.EjmlLinearSpaceDDRM
import space.kscience.kmath.ejml.inverse import space.kscience.kmath.linear.InverseMatrixFeature
import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.LinearSpace
import space.kscience.kmath.linear.inverseWithLup import space.kscience.kmath.linear.inverseWithLup
import space.kscience.kmath.linear.invoke import space.kscience.kmath.linear.invoke
import space.kscience.kmath.nd.getFeature
import kotlin.random.Random import kotlin.random.Random
@State(Scope.Benchmark) @State(Scope.Benchmark)
internal class MatrixInverseBenchmark { internal class MatrixInverseBenchmark {
companion object { private companion object {
val random = Random(1224) private val random = Random(1224)
const val dim = 100 private const val dim = 100
private val space = LinearSpace.real private val space = LinearSpace.real
//creating invertible matrix //creating invertible matrix
val u = space.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } private 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 } private val l = space.buildMatrix(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 }
val matrix = space { l dot u } private val matrix = space { l dot u }
} }
@Benchmark @Benchmark
@ -46,8 +47,8 @@ internal class MatrixInverseBenchmark {
@Benchmark @Benchmark
fun ejmlInverse(blackhole: Blackhole) { fun ejmlInverse(blackhole: Blackhole) {
with(EjmlLinearSpace) { with(EjmlLinearSpaceDDRM) {
blackhole.consume(inverse(matrix)) blackhole.consume(matrix.getFeature<InverseMatrixFeature<Double>>()?.inverse)
} }
} }
} }

View File

@ -4,14 +4,12 @@ plugins {
allprojects { allprojects {
repositories { repositories {
jcenter()
maven("https://clojars.org/repo") 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("https://jitpack.io")
maven("http://logicrunch.research.it.uu.se/maven/") { maven("http://logicrunch.research.it.uu.se/maven") {
isAllowInsecureProtocol = true isAllowInsecureProtocol = true
} }
maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
mavenCentral() mavenCentral()
} }
@ -23,22 +21,16 @@ subprojects {
if (name.startsWith("kmath")) apply<MavenPublishPlugin>() if (name.startsWith("kmath")) apply<MavenPublishPlugin>()
afterEvaluate { afterEvaluate {
tasks.withType<org.jetbrains.dokka.gradle.DokkaTask> { tasks.withType<org.jetbrains.dokka.gradle.DokkaTaskPartial> {
dokkaSourceSets.all { dependsOn(tasks.getByName("assemble"))
val readmeFile = File(this@subprojects.projectDir, "./README.md")
if (readmeFile.exists())
includes.setFrom(includes + readmeFile.absolutePath)
arrayOf( dokkaSourceSets.all {
"http://ejml.org/javadoc/", val readmeFile = File(this@subprojects.projectDir, "README.md")
"https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/", if (readmeFile.exists()) includes.setFrom(includes + readmeFile.absolutePath)
"https://deeplearning4j.org/api/latest/" externalDocumentationLink("http://ejml.org/javadoc/")
).map { java.net.URL("${it}package-list") to java.net.URL(it) }.forEach { (a, b) -> externalDocumentationLink("https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/")
externalDocumentationLink { externalDocumentationLink("https://deeplearning4j.org/api/latest/")
packageListUrl.set(a) externalDocumentationLink("https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/")
url.set(b)
}
}
} }
} }
} }

View File

@ -6,8 +6,7 @@ The Maven coordinates of this project are `${group}:${name}:${version}`.
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } maven { url 'https://repo.kotlin.link' }
maven { url 'https://dl.bintray.com/hotkeytlt/maven' } mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
dependencies { dependencies {
@ -18,8 +17,7 @@ dependencies {
```kotlin ```kotlin
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap mavenCentral()
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -4,14 +4,11 @@ plugins {
repositories { repositories {
mavenCentral() mavenCentral()
jcenter()
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://clojars.org/repo") 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("https://jitpack.io")
maven{ maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers")
setUrl("http://logicrunch.research.it.uu.se/maven/") maven("http://logicrunch.research.it.uu.se/maven") {
isAllowInsecureProtocol = true isAllowInsecureProtocol = true
} }
} }

View File

@ -16,8 +16,7 @@ The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-7`
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } maven { url 'https://repo.kotlin.link' }
maven { url 'https://dl.bintray.com/hotkeytlt/maven' } mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
dependencies { dependencies {
@ -28,8 +27,7 @@ dependencies {
```kotlin ```kotlin
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap mavenCentral()
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -14,8 +14,7 @@ The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-de
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } maven { url 'https://repo.kotlin.link' }
maven { url 'https://dl.bintray.com/hotkeytlt/maven' } mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
dependencies { dependencies {
@ -26,8 +25,7 @@ dependencies {
```kotlin ```kotlin
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap mavenCentral()
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -21,8 +21,7 @@ The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-7
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } maven { url 'https://repo.kotlin.link' }
maven { url 'https://dl.bintray.com/hotkeytlt/maven' } mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
dependencies { dependencies {
@ -33,8 +32,7 @@ dependencies {
```kotlin ```kotlin
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap mavenCentral()
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -2,9 +2,9 @@
EJML based linear algebra implementation. EJML based linear algebra implementation.
- [ejml-vector](src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt) : The Point 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) : The Matrix implementation using SimpleMatrix. - [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) : The LinearSpace implementation using SimpleMatrix. - [ejml-linear-space](src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt) : LinearSpace implementations.
## Artifact: ## Artifact:
@ -15,8 +15,7 @@ The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-7
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } maven { url 'https://repo.kotlin.link' }
maven { url 'https://dl.bintray.com/hotkeytlt/maven' } mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
dependencies { dependencies {
@ -27,8 +26,7 @@ dependencies {
```kotlin ```kotlin
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap mavenCentral()
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -4,7 +4,7 @@ plugins {
} }
dependencies { dependencies {
api("org.ejml:ejml-simple:0.40") api("org.ejml:ejml-ddense:0.40")
api(project(":kmath-core")) api(project(":kmath-core"))
} }
@ -14,19 +14,19 @@ readme {
feature( feature(
id = "ejml-vector", id = "ejml-vector",
description = "The Point implementation using SimpleMatrix.", description = "Point implementations.",
ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt" ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlVector.kt"
) )
feature( feature(
id = "ejml-matrix", id = "ejml-matrix",
description = "The Matrix implementation using SimpleMatrix.", description = "Matrix implementation.",
ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt" ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlMatrix.kt"
) )
feature( feature(
id = "ejml-linear-space", id = "ejml-linear-space",
description = "The LinearSpace implementation using SimpleMatrix.", description = "LinearSpace implementations.",
ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt" ref = "src/main/kotlin/space/kscience/kmath/ejml/EjmlLinearSpace.kt"
) )
} }

View File

@ -5,45 +5,71 @@
package space.kscience.kmath.ejml package space.kscience.kmath.ejml
import org.ejml.data.DMatrix
import org.ejml.data.DMatrixD1
import org.ejml.data.DMatrixRMaj
import org.ejml.dense.row.CommonOps_DDRM
import org.ejml.dense.row.factory.DecompositionFactory_DDRM import org.ejml.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix
import space.kscience.kmath.linear.* import space.kscience.kmath.linear.*
import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureFeature import space.kscience.kmath.nd.StructureFeature
import space.kscience.kmath.nd.getFeature
import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.Ring
import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.DoubleBuffer
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.cast import kotlin.reflect.cast
/** /**
* 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
*/
public abstract class EjmlLinearSpace<T : Any, out A : Ring<T>, M : org.ejml.data.Matrix> : LinearSpace<T, A> {
/**
* Converts this matrix to EJML one.
*/
public abstract fun Matrix<T>.toEjml(): EjmlMatrix<T, M>
/**
* Converts this vector to EJML one.
*/
public abstract fun Point<T>.toEjml(): EjmlVector<T, M>
public abstract override fun buildMatrix(
rows: Int,
columns: Int,
initializer: A.(i: Int, j: Int) -> T,
): EjmlMatrix<T, M>
public abstract override fun buildVector(size: Int, initializer: A.(Int) -> T): EjmlVector<T, M>
}
/**
* [EjmlLinearSpace] implementation based on [CommonOps_DDRM], [DecompositionFactory_DDRM] operations and
* [DMatrixRMaj] matrices.
* *
* @author Iaroslav Postovalov * @author Iaroslav Postovalov
* @author Alexander Nozik
*/ */
public object EjmlLinearSpace : LinearSpace<Double, DoubleField> { public object EjmlLinearSpaceDDRM : EjmlLinearSpace<Double, DoubleField, DMatrixRMaj>() {
/** /**
* The [DoubleField] reference. * The [DoubleField] reference.
*/ */
public override val elementAlgebra: DoubleField get() = DoubleField public override val elementAlgebra: DoubleField get() = DoubleField
/** @Suppress("UNCHECKED_CAST")
* Converts this matrix to EJML one. public override fun Matrix<Double>.toEjml(): EjmlDoubleMatrix<DMatrixRMaj> = when {
*/ this is EjmlDoubleMatrix<*> && origin is DMatrixRMaj -> this as EjmlDoubleMatrix<DMatrixRMaj>
@OptIn(UnstableKMathAPI::class)
public fun Matrix<Double>.toEjml(): EjmlMatrix = when (val matrix = origin) {
is EjmlMatrix -> matrix
else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) }
} }
/** @Suppress("UNCHECKED_CAST")
* Converts this vector to EJML one. public override fun Point<Double>.toEjml(): EjmlDoubleVector<DMatrixRMaj> = when {
*/ this is EjmlDoubleVector<*> && origin is DMatrixRMaj -> this as EjmlDoubleVector<DMatrixRMaj>
public fun Point<Double>.toEjml(): EjmlVector = when (this) { else -> EjmlDoubleVector(DMatrixRMaj(size, 1).also {
is EjmlVector -> this (0 until it.numRows).forEach { row -> it[row, 0] = get(row) }
else -> EjmlVector(SimpleMatrix(size, 1).also {
(0 until it.numRows()).forEach { row -> it[row, 0] = get(row) }
}) })
} }
@ -51,159 +77,178 @@ public object EjmlLinearSpace : LinearSpace<Double, DoubleField> {
rows: Int, rows: Int,
columns: Int, columns: Int,
initializer: DoubleField.(i: Int, j: Int) -> Double, initializer: DoubleField.(i: Int, j: Int) -> Double,
): EjmlMatrix = EjmlMatrix(SimpleMatrix(rows, columns).also { ): EjmlDoubleMatrix<DMatrixRMaj> = EjmlDoubleMatrix(DMatrixRMaj(rows, columns).also {
(0 until rows).forEach { row -> (0 until rows).forEach { row ->
(0 until columns).forEach { col -> it[row, col] = DoubleField.initializer(row, col) } (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) }
} }
}) })
public override fun buildVector(size: Int, initializer: DoubleField.(Int) -> Double): Point<Double> = public override fun buildVector(
EjmlVector(SimpleMatrix(size, 1).also { size: Int,
(0 until it.numRows()).forEach { row -> it[row, 0] = DoubleField.initializer(row) } initializer: DoubleField.(Int) -> Double,
}) ): EjmlDoubleVector<DMatrixRMaj> = EjmlDoubleVector(DMatrixRMaj(size, 1).also {
(0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) }
})
private fun SimpleMatrix.wrapMatrix() = EjmlMatrix(this) private fun <T : DMatrix> T.wrapMatrix() = EjmlDoubleMatrix(this)
private fun SimpleMatrix.wrapVector() = EjmlVector(this) private fun <T : DMatrixD1> T.wrapVector() = EjmlDoubleVector(this)
public override fun Matrix<Double>.unaryMinus(): Matrix<Double> = this * (-1.0) public override fun Matrix<Double>.unaryMinus(): Matrix<Double> = this * (-1.0)
public override fun Matrix<Double>.dot(other: Matrix<Double>): EjmlMatrix = public override fun Matrix<Double>.dot(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
EjmlMatrix(toEjml().origin.mult(other.toEjml().origin)) val out = DMatrixRMaj(1, 1)
CommonOps_DDRM.mult(toEjml().origin, other.toEjml().origin, out)
return out.wrapMatrix()
}
public override fun Matrix<Double>.dot(vector: Point<Double>): EjmlVector = public override fun Matrix<Double>.dot(vector: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
EjmlVector(toEjml().origin.mult(vector.toEjml().origin)) val out = DMatrixRMaj(1, 1)
CommonOps_DDRM.mult(toEjml().origin, vector.toEjml().origin, out)
return out.wrapVector()
}
public override operator fun Matrix<Double>.minus(other: Matrix<Double>): EjmlMatrix = public override operator fun Matrix<Double>.minus(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
(toEjml().origin - other.toEjml().origin).wrapMatrix() val out = DMatrixRMaj(1, 1)
CommonOps_DDRM.subtract(toEjml().origin, other.toEjml().origin, out)
return out.wrapMatrix()
}
public override operator fun Matrix<Double>.times(value: Double): EjmlMatrix = public override operator fun Matrix<Double>.times(value: Double): EjmlDoubleMatrix<DMatrixRMaj> {
toEjml().origin.scale(value).wrapMatrix() val res = this.toEjml().origin.copy()
CommonOps_DDRM.scale(value, res)
return res.wrapMatrix()
}
public override fun Point<Double>.unaryMinus(): EjmlVector = public override fun Point<Double>.unaryMinus(): EjmlDoubleVector<DMatrixRMaj> {
toEjml().origin.negative().wrapVector() val out = toEjml().origin.copy()
CommonOps_DDRM.changeSign(out)
return out.wrapVector()
}
public override fun Matrix<Double>.plus(other: Matrix<Double>): EjmlMatrix = public override fun Matrix<Double>.plus(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
(toEjml().origin + other.toEjml().origin).wrapMatrix() val out = DMatrixRMaj(1, 1)
CommonOps_DDRM.add(toEjml().origin, other.toEjml().origin, out)
return out.wrapMatrix()
}
public override fun Point<Double>.plus(other: Point<Double>): EjmlVector = public override fun Point<Double>.plus(other: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
(toEjml().origin + other.toEjml().origin).wrapVector() val out = DMatrixRMaj(1, 1)
CommonOps_DDRM.add(toEjml().origin, other.toEjml().origin, out)
return out.wrapVector()
}
public override fun Point<Double>.minus(other: Point<Double>): EjmlVector = public override fun Point<Double>.minus(other: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
(toEjml().origin - other.toEjml().origin).wrapVector() val out = DMatrixRMaj(1, 1)
CommonOps_DDRM.subtract(toEjml().origin, other.toEjml().origin, out)
return out.wrapVector()
}
public override fun Double.times(m: Matrix<Double>): EjmlMatrix = public override fun Double.times(m: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> = m * this
m.toEjml().origin.scale(this).wrapMatrix()
public override fun Point<Double>.times(value: Double): EjmlVector = public override fun Point<Double>.times(value: Double): EjmlDoubleVector<DMatrixRMaj> {
toEjml().origin.scale(value).wrapVector() val res = this.toEjml().origin.copy()
CommonOps_DDRM.scale(value, res)
return res.wrapVector()
}
public override fun Double.times(v: Point<Double>): EjmlVector = public override fun Double.times(v: Point<Double>): EjmlDoubleVector<DMatrixRMaj> = v * this
v.toEjml().origin.scale(this).wrapVector()
@UnstableKMathAPI @UnstableKMathAPI
public override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? { public override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? {
//Return the feature if it is intrinsic to the structure // Return the feature if it is intrinsic to the structure
structure.getFeature(type)?.let { return it } structure.getFeature(type)?.let { return it }
val origin = structure.toEjml().origin val origin = structure.toEjml().origin
return when (type) { return when (type) {
InverseMatrixFeature::class -> object : InverseMatrixFeature<Double> { InverseMatrixFeature::class -> object : InverseMatrixFeature<Double> {
override val inverse: Matrix<Double> by lazy { EjmlMatrix(origin.invert()) } override val inverse: Matrix<Double> by lazy {
val res = origin.copy()
CommonOps_DDRM.invert(res)
EjmlDoubleMatrix(res)
}
} }
DeterminantFeature::class -> object : DeterminantFeature<Double> { DeterminantFeature::class -> object : DeterminantFeature<Double> {
override val determinant: Double by lazy(origin::determinant) override val determinant: Double by lazy { CommonOps_DDRM.det(DMatrixRMaj(origin)) }
} }
SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<Double> { SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<Double> {
private val svd by lazy { private val svd by lazy {
DecompositionFactory_DDRM.svd(origin.numRows(), origin.numCols(), true, true, false) DecompositionFactory_DDRM.svd(origin.numRows, origin.numCols, true, true, false)
.apply { decompose(origin.ddrm.copy()) } .apply { decompose(origin.copy()) }
} }
override val u: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getU(null, false))) } override val u: Matrix<Double> by lazy { EjmlDoubleMatrix(svd.getU(null, false)) }
override val s: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getW(null))) } override val s: Matrix<Double> by lazy { EjmlDoubleMatrix(svd.getW(null)) }
override val v: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getV(null, false))) } override val v: Matrix<Double> by lazy { EjmlDoubleMatrix(svd.getV(null, false)) }
override val singularValues: Point<Double> by lazy { DoubleBuffer(svd.singularValues) } override val singularValues: Point<Double> by lazy { DoubleBuffer(svd.singularValues) }
} }
QRDecompositionFeature::class -> object : QRDecompositionFeature<Double> { QRDecompositionFeature::class -> object : QRDecompositionFeature<Double> {
private val qr by lazy { private val qr by lazy {
DecompositionFactory_DDRM.qr().apply { decompose(origin.ddrm.copy()) } DecompositionFactory_DDRM.qr().apply { decompose(origin.copy()) }
} }
override val q: Matrix<Double> by lazy { override val q: Matrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(qr.getQ(null, false))) + OrthogonalFeature EjmlDoubleMatrix(qr.getQ(null, false)) + OrthogonalFeature
} }
override val r: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(qr.getR(null, false))) + UFeature } override val r: Matrix<Double> by lazy { EjmlDoubleMatrix(qr.getR(null, false)) + UFeature }
} }
CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Double> { CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Double> {
override val l: Matrix<Double> by lazy { override val l: Matrix<Double> by lazy {
val cholesky = val cholesky =
DecompositionFactory_DDRM.chol(structure.rowNum, true).apply { decompose(origin.ddrm.copy()) } DecompositionFactory_DDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) }
EjmlMatrix(SimpleMatrix(cholesky.getT(null))) + LFeature EjmlDoubleMatrix(cholesky.getT(null)) + LFeature
} }
} }
LupDecompositionFeature::class -> object : LupDecompositionFeature<Double> { LupDecompositionFeature::class -> object : LupDecompositionFeature<Double> {
private val lup by lazy { private val lup by lazy {
DecompositionFactory_DDRM.lu(origin.numRows(), origin.numCols()) DecompositionFactory_DDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) }
.apply { decompose(origin.ddrm.copy()) }
} }
override val l: Matrix<Double> by lazy { override val l: Matrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(lup.getLower(null))) + LFeature EjmlDoubleMatrix(lup.getLower(null)) + LFeature
} }
override val u: Matrix<Double> by lazy { override val u: Matrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(lup.getUpper(null))) + UFeature EjmlDoubleMatrix(lup.getUpper(null)) + UFeature
} }
override val p: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(lup.getRowPivot(null))) } override val p: Matrix<Double> by lazy { EjmlDoubleMatrix(lup.getRowPivot(null)) }
} }
else -> null else -> null
}?.let(type::cast) }?.let(type::cast)
} }
/**
* Solves for *x* in the following equation: *x = [a] <sup>-1</sup> &middot; [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<Double>, b: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
val res = DMatrixRMaj(1, 1)
CommonOps_DDRM.solve(DMatrixRMaj(a.toEjml().origin), DMatrixRMaj(b.toEjml().origin), res)
return EjmlDoubleMatrix(res)
}
/**
* Solves for *x* in the following equation: *x = [a] <sup>-1</sup> &middot; [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<Double>, b: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
val res = DMatrixRMaj(1, 1)
CommonOps_DDRM.solve(DMatrixRMaj(a.toEjml().origin), DMatrixRMaj(b.toEjml().origin), res)
return EjmlDoubleVector(res)
}
} }
/**
* Solves for *x* in the following equation: *x = [a] <sup>-1</sup> &middot; [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<Double>, b: Matrix<Double>): EjmlMatrix =
EjmlMatrix(a.toEjml().origin.solve(b.toEjml().origin))
/**
* Solves for *x* in the following equation: *x = [a] <sup>-1</sup> &middot; [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<Double>, b: Point<Double>): EjmlVector =
EjmlVector(a.toEjml().origin.solve(b.toEjml().origin))
/**
* Inverts this matrix.
*
* @author Alexander Nozik
*/
@OptIn(UnstableKMathAPI::class)
public fun EjmlMatrix.inverted(): EjmlMatrix = getFeature<InverseMatrixFeature<Double>>()!!.inverse as EjmlMatrix
/**
* Inverts the given matrix.
*
* @author Alexander Nozik
*/
public fun EjmlLinearSpace.inverse(matrix: Matrix<Double>): Matrix<Double> = matrix.toEjml().inverted()

View File

@ -5,18 +5,28 @@
package space.kscience.kmath.ejml package space.kscience.kmath.ejml
import org.ejml.simple.SimpleMatrix import org.ejml.data.DMatrix
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 * @author Iaroslav Postovalov
*/ */
public class EjmlMatrix(public val origin: SimpleMatrix) : Matrix<Double> { public abstract class EjmlMatrix<T, out M : Matrix>(public open val origin: M) : Structure2D<T> {
public override val rowNum: Int get() = origin.numRows() public override val rowNum: Int get() = origin.numRows
public override val colNum: Int get() = origin.numCols() public override val colNum: Int get() = origin.numCols
}
/**
* [EjmlMatrix] specialization for [Double].
*
* @author Iaroslav Postovalov
*/
public class EjmlDoubleMatrix<out M : DMatrix>(public override val origin: M) : EjmlMatrix<Double, M>(origin) {
public override operator fun get(i: Int, j: Int): Double = origin[i, j] public override operator fun get(i: Int, j: Int): Double = origin[i, j]
} }

View File

@ -5,35 +5,41 @@
package space.kscience.kmath.ejml package space.kscience.kmath.ejml
import org.ejml.simple.SimpleMatrix import org.ejml.data.DMatrixD1
import org.ejml.data.Matrix
import space.kscience.kmath.linear.Point 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.
* @author Iaroslav Postovalov * @author Iaroslav Postovalov
*/ */
public class EjmlVector internal constructor(public val origin: SimpleMatrix) : Point<Double> { public abstract class EjmlVector<out T, out M : Matrix>(public open val origin: M) : Point<T> {
public override val size: Int public override val size: Int
get() = origin.numRows() get() = origin.numRows
init { public override operator fun iterator(): Iterator<T> = object : Iterator<T> {
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<Double> = object : Iterator<Double> {
private var cursor: Int = 0 private var cursor: Int = 0
override fun next(): Double { override fun next(): T {
cursor += 1 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)" public override fun toString(): String = "EjmlVector(origin=$origin)"
} }
/**
* [EjmlVector] specialization for [Double].
*
* @author Iaroslav Postovalov
*/
public class EjmlDoubleVector<out M : DMatrixD1>(public override val origin: M) : EjmlVector<Double, M>(origin) {
public override operator fun get(index: Int): Double = origin[index]
}

View File

@ -5,12 +5,15 @@
package space.kscience.kmath.ejml 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.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix import space.kscience.kmath.linear.DeterminantFeature
import space.kscience.kmath.linear.* import space.kscience.kmath.linear.LupDecompositionFeature
import space.kscience.kmath.linear.getFeature
import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.StructureND
import space.kscience.kmath.nd.getFeature
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.asJavaRandom import kotlin.random.asJavaRandom
import kotlin.test.* import kotlin.test.*
@ -22,65 +25,59 @@ fun <T : Any> assertMatrixEquals(expected: StructureND<T>, actual: StructureND<T
internal class EjmlMatrixTest { internal class EjmlMatrixTest {
private val random = Random(0) private val random = Random(0)
private val randomMatrix: SimpleMatrix private val randomMatrix: DMatrixRMaj
get() { get() {
val s = random.nextInt(2, 100) val s = random.nextInt(2, 100)
return SimpleMatrix.random_DDRM(s, s, 0.0, 10.0, random.asJavaRandom()) val d = DMatrixRMaj(s, s)
RandomMatrices_DDRM.fillUniform(d, random.asJavaRandom())
return d
} }
@Test @Test
fun rowNum() { fun rowNum() {
val m = randomMatrix val m = randomMatrix
assertEquals(m.numRows(), EjmlMatrix(m).rowNum) assertEquals(m.numRows, EjmlDoubleMatrix(m).rowNum)
} }
@Test @Test
fun colNum() { fun colNum() {
val m = randomMatrix val m = randomMatrix
assertEquals(m.numCols(), EjmlMatrix(m).rowNum) assertEquals(m.numCols, EjmlDoubleMatrix(m).rowNum)
} }
@Test @Test
fun shape() { fun shape() {
val m = randomMatrix val m = randomMatrix
val w = EjmlMatrix(m) val w = EjmlDoubleMatrix(m)
assertEquals(listOf(m.numRows(), m.numCols()), w.shape.toList()) assertContentEquals(intArrayOf(m.numRows, m.numCols), w.shape)
} }
@OptIn(UnstableKMathAPI::class) @OptIn(UnstableKMathAPI::class)
@Test @Test
fun features() { fun features() {
val m = randomMatrix val m = randomMatrix
val w = EjmlMatrix(m) val w = EjmlDoubleMatrix(m)
val det: DeterminantFeature<Double> = EjmlLinearSpace.getFeature(w) ?: fail() val det: DeterminantFeature<Double> = EjmlLinearSpaceDDRM.getFeature(w) ?: fail()
assertEquals(m.determinant(), det.determinant) assertEquals(CommonOps_DDRM.det(m), det.determinant)
val lup: LupDecompositionFeature<Double> = EjmlLinearSpace.getFeature(w) ?: fail() val lup: LupDecompositionFeature<Double> = EjmlLinearSpaceDDRM.getFeature(w) ?: fail()
val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows(), m.numCols()) val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows, m.numCols)
.also { it.decompose(m.ddrm.copy()) } .also { it.decompose(m.copy()) }
assertMatrixEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getLower(null))), lup.l) assertMatrixEquals(EjmlDoubleMatrix(ludecompositionF64.getLower(null)), lup.l)
assertMatrixEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getUpper(null))), lup.u) assertMatrixEquals(EjmlDoubleMatrix(ludecompositionF64.getUpper(null)), lup.u)
assertMatrixEquals(EjmlMatrix(SimpleMatrix(ludecompositionF64.getRowPivot(null))), lup.p) assertMatrixEquals(EjmlDoubleMatrix(ludecompositionF64.getRowPivot(null)), lup.p)
}
private object SomeFeature : MatrixFeature {}
@OptIn(UnstableKMathAPI::class)
@Test
fun suggestFeature() {
assertNotNull((EjmlMatrix(randomMatrix) + SomeFeature).getFeature<SomeFeature>())
} }
@Test @Test
fun get() { fun get() {
val m = randomMatrix val m = randomMatrix
assertEquals(m[0, 0], EjmlMatrix(m)[0, 0]) assertEquals(m[0, 0], EjmlDoubleMatrix(m)[0, 0])
} }
@Test @Test
fun origin() { fun origin() {
val m = randomMatrix val m = randomMatrix
assertSame(m, EjmlMatrix(m).origin) assertSame(m, EjmlDoubleMatrix(m).origin)
} }
} }

View File

@ -5,7 +5,8 @@
package space.kscience.kmath.ejml 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.Random
import kotlin.random.asJavaRandom import kotlin.random.asJavaRandom
import kotlin.test.Test import kotlin.test.Test
@ -15,30 +16,34 @@ import kotlin.test.assertSame
internal class EjmlVectorTest { internal class EjmlVectorTest {
private val random = Random(0) private val random = Random(0)
private val randomMatrix: SimpleMatrix private val randomMatrix: DMatrixRMaj
get() = SimpleMatrix.random_DDRM(random.nextInt(2, 100), 1, 0.0, 10.0, random.asJavaRandom()) get() {
val d = DMatrixRMaj(random.nextInt(2, 100), 1)
RandomMatrices_DDRM.fillUniform(d, random.asJavaRandom())
return d
}
@Test @Test
fun size() { fun size() {
val m = randomMatrix val m = randomMatrix
val w = EjmlVector(m) val w = EjmlDoubleVector(m)
assertEquals(m.numRows(), w.size) assertEquals(m.numRows, w.size)
} }
@Test @Test
fun get() { fun get() {
val m = randomMatrix val m = randomMatrix
val w = EjmlVector(m) val w = EjmlDoubleVector(m)
assertEquals(m[0, 0], w[0]) assertEquals(m[0, 0], w[0])
} }
@Test @Test
fun iterator() { fun iterator() {
val m = randomMatrix val m = randomMatrix
val w = EjmlVector(m) val w = EjmlDoubleVector(m)
assertEquals( assertEquals(
m.iterator(true, 0, 0, m.numRows() - 1, 0).asSequence().toList(), m.iterator(true, 0, 0, m.numRows - 1, 0).asSequence().toList(),
w.iterator().asSequence().toList() w.iterator().asSequence().toList()
) )
} }
@ -46,7 +51,7 @@ internal class EjmlVectorTest {
@Test @Test
fun origin() { fun origin() {
val m = randomMatrix val m = randomMatrix
val w = EjmlVector(m) val w = EjmlDoubleVector(m)
assertSame(m, w.origin) assertSame(m, w.origin)
} }
} }

View File

@ -15,7 +15,6 @@ The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-d
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } 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 maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
@ -28,7 +27,6 @@ dependencies {
repositories { repositories {
maven("https://repo.kotlin.link") 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/kotlin/kotlin-eap") // include for builds based on kotlin-eap
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -17,7 +17,6 @@ The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } 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 maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
@ -30,7 +29,6 @@ dependencies {
repositories { repositories {
maven("https://repo.kotlin.link") 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/kotlin/kotlin-eap") // include for builds based on kotlin-eap
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -4,8 +4,8 @@ plugins {
} }
dependencies { dependencies {
implementation("com.github.breandan:kaliningraph:0.1.4") api("com.github.breandan:kaliningraph:0.1.4")
implementation("com.github.breandan:kotlingrad:0.4.0") api("com.github.breandan:kotlingrad:0.4.5")
api(project(":kmath-ast")) api(project(":kmath-ast"))
} }

View File

@ -15,8 +15,7 @@ The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-7
```gradle ```gradle
repositories { repositories {
maven { url 'https://repo.kotlin.link' } maven { url 'https://repo.kotlin.link' }
maven { url 'https://dl.bintray.com/hotkeytlt/maven' } mavenCentral()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } // include for builds based on kotlin-eap
} }
dependencies { dependencies {
@ -27,8 +26,7 @@ dependencies {
```kotlin ```kotlin
repositories { repositories {
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
maven("https://dl.bintray.com/kotlin/kotlin-eap") // include for builds based on kotlin-eap mavenCentral()
maven("https://dl.bintray.com/hotkeytlt/maven") // required for a
} }
dependencies { dependencies {

View File

@ -7,7 +7,7 @@ description = "Binding for https://github.com/JetBrains-Research/viktor"
dependencies { dependencies {
api(project(":kmath-core")) api(project(":kmath-core"))
api("org.jetbrains.bio:viktor:1.0.1") api("org.jetbrains.bio:viktor:1.1.0")
} }
readme { readme {