Merge pull request #16 from altavir/dev
Update from dev
This commit is contained in:
commit
33cf7769b2
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@
|
|||||||
# Cache of project
|
# Cache of project
|
||||||
.gradletasknamecache
|
.gradletasknamecache
|
||||||
|
|
||||||
|
gradle.properties
|
45
README.md
45
README.md
@ -9,13 +9,27 @@ and `scipy` it is modular and has a lightweight core.
|
|||||||
* Basic linear algebra operations (summs products, etc) backed by `Space` API.
|
* Basic linear algebra operations (summs products, etc) backed by `Space` API.
|
||||||
* Complex numbers backed by `Field` API (meaning that they will be useable in any structures like vectors and NDArrays).
|
* Complex numbers backed by `Field` API (meaning that they will be useable in any structures like vectors and NDArrays).
|
||||||
* [In progress] advanced linear algebra operations like matrix inversions.
|
* [In progress] advanced linear algebra operations like matrix inversions.
|
||||||
* **Array-like structures** Full support of numpy-like ndarray including mixed ariphmetic operations and function operations
|
* **Array-like structures** Full support of numpy-like ndarray including mixed arithmetic operations and function operations
|
||||||
on arrays and numbers just like it works in python (with benefit of static type checking).
|
on arrays and numbers just like it works in python (with benefit of static type checking).
|
||||||
|
|
||||||
|
* **Expressions** Expressions are one of the ultimate goals of kmath. It is planned to be able to write some mathematical
|
||||||
|
expression once an then apply it to different types of objects by providing different context. Exception could be used
|
||||||
|
for a wide variety of purposes from high performance calculations to code generation.
|
||||||
|
|
||||||
|
## Planned features
|
||||||
|
|
||||||
|
* **Common mathematics** It is planned to gradually wrap most parts of [Apache commons-math](http://commons.apache.org/proper/commons-math/)
|
||||||
|
library in kotlin code and maybe rewrite some parts to better suite kotlin programming paradigm. There is no fixed priority list for that. Feel free
|
||||||
|
to submit a future request if you want something to be done first.
|
||||||
|
|
||||||
|
* **Messaging** A mathematical notation to support multi-language and multi-node communication for mathematical tasks.
|
||||||
|
|
||||||
## Multi-platform support
|
## Multi-platform support
|
||||||
KMath is developed as a multi-platform library, which means that most of interfaces are declared in common module.
|
KMath is developed as a multi-platform library, which means that most of interfaces are declared in common module.
|
||||||
Implementation is also done in common module wherever it is possible. In some cases features are delegated to
|
Implementation is also done in common module wherever it is possible. In some cases features are delegated to
|
||||||
platform even if they could be done in common module because of platform performance optimization.
|
platform even if they could be done in common module because of platform performance optimization.
|
||||||
|
Currently the main focus of development is the JVM platform, contribution of implementations for Kotlin - Native and
|
||||||
|
Kotlin - JS is welcome.
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
The calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve
|
The calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve
|
||||||
@ -25,8 +39,33 @@ but worse than optimized native/scipy (mostly due to boxing operations on primit
|
|||||||
of optimized parts should be better than scipy.
|
of optimized parts should be better than scipy.
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
The project is currently in pre-release stage. Work builds could be obtained with
|
|
||||||
[![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath).
|
The project is currently in pre-release stage. Nightly builds could be used by adding additional repository to (groovy) gradle config:
|
||||||
|
```groovy
|
||||||
|
repositories {
|
||||||
|
maven { url = "http://npm.mipt.ru:8081/artifactory/gradle-dev" }
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
or for kotlin gradle dsl:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
repositories {
|
||||||
|
maven { setUrl("http://npm.mipt.ru:8081/artifactory/gradle-dev") }
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use regular dependency like
|
||||||
|
```groovy
|
||||||
|
compile(group: 'scientifik', name: 'kmath-core-jvm', version: '0.0.1-SNAPSHOT')
|
||||||
|
```
|
||||||
|
or in kotlin
|
||||||
|
```kotlin
|
||||||
|
compile(group = "scientifik", name = "kmath-core-jvm", version = "0.0.1-SNAPSHOT")
|
||||||
|
```
|
||||||
|
|
||||||
|
Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
The project requires a lot of additional work. Please fill free to contribute in any way and propose new features.
|
The project requires a lot of additional work. Please fill free to contribute in any way and propose new features.
|
||||||
|
41
build.gradle
41
build.gradle
@ -1,14 +1,49 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.2.60'
|
ext.kotlin_version = '1.3.0-rc-146'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url = "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
allprojects {
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
apply plugin: "com.jfrog.artifactory"
|
||||||
|
|
||||||
group = 'scientifik'
|
group = 'scientifik'
|
||||||
version = '0.1 - SNAPSHOT'
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
artifactory {
|
||||||
|
contextUrl = "${artifactory_contextUrl}" //The base Artifactory URL if not overridden by the publisher/resolver
|
||||||
|
publish {
|
||||||
|
repository {
|
||||||
|
repoKey = 'gradle-dev-local'
|
||||||
|
username = "${artifactory_user}"
|
||||||
|
password = "${artifactory_password}"
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults {
|
||||||
|
publications('jvm', 'js', 'kotlinMultiplatform', 'metadata')
|
||||||
|
publishBuildInfo = false
|
||||||
|
publishArtifacts = true
|
||||||
|
publishPom = true
|
||||||
|
publishIvy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve {
|
||||||
|
repository {
|
||||||
|
repoKey = 'gradle-dev'
|
||||||
|
username = "${artifactory_user}"
|
||||||
|
password = "${artifactory_password}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
description = "Platform-independent interfaces for kotlin maths"
|
|
||||||
|
|
||||||
apply plugin: 'kotlin-platform-common'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
|
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version"
|
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
|
|
||||||
}
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
|||||||
package scientifik.kmath.structures
|
|
||||||
|
|
||||||
import scientifik.kmath.operations.DoubleField
|
|
||||||
import scientifik.kmath.operations.Field
|
|
||||||
import scientifik.kmath.operations.Space
|
|
||||||
import scientifik.kmath.operations.SpaceElement
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The space for linear elements. Supports scalar product alongside with standard linear operations.
|
|
||||||
* @param T type of individual element of the vector or matrix
|
|
||||||
* @param V the type of vector space element
|
|
||||||
*/
|
|
||||||
abstract class LinearSpace<T : Any, V : LinearStructure<out T>>(val rows: Int, val columns: Int, val field: Field<T>) : Space<V> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produce the element of this space
|
|
||||||
*/
|
|
||||||
abstract fun produce(initializer: (Int, Int) -> T): V
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produce new linear space with given dimensions
|
|
||||||
*/
|
|
||||||
abstract fun produceSpace(rows: Int, columns: Int): LinearSpace<T, V>
|
|
||||||
|
|
||||||
override val zero: V by lazy {
|
|
||||||
produce { _, _ -> field.zero }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun add(a: V, b: V): V {
|
|
||||||
return produce { i, j -> with(field) { a[i, j] + b[i, j] } }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun multiply(a: V, k: Double): V {
|
|
||||||
//TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser
|
|
||||||
return produce { i, j -> with(field) { a[i, j] * k } }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dot product
|
|
||||||
*/
|
|
||||||
fun multiply(a: V, b: V): V {
|
|
||||||
if (a.rows != b.columns) {
|
|
||||||
//TODO replace by specific exception
|
|
||||||
error("Dimension mismatch in vector dot product")
|
|
||||||
}
|
|
||||||
return produceSpace(a.rows, b.columns).produce { i, j ->
|
|
||||||
(0..a.columns).asSequence().map { k -> field.multiply(a[i, k], b[k, j]) }.reduce { first, second -> field.add(first, second) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infix fun V.dot(b: V): V = multiply(this, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A matrix-like structure that is not dependent on specific space implementation
|
|
||||||
*/
|
|
||||||
interface LinearStructure<T : Any> {
|
|
||||||
val rows: Int
|
|
||||||
val columns: Int
|
|
||||||
|
|
||||||
operator fun get(i: Int, j: Int): T
|
|
||||||
|
|
||||||
fun transpose(): LinearStructure<T> {
|
|
||||||
return object : LinearStructure<T> {
|
|
||||||
override val rows: Int = this@LinearStructure.columns
|
|
||||||
override val columns: Int = this@LinearStructure.rows
|
|
||||||
override fun get(i: Int, j: Int): T = this@LinearStructure.get(j, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Vector<T : Any> : LinearStructure<T> {
|
|
||||||
override val columns: Int
|
|
||||||
get() = 1
|
|
||||||
|
|
||||||
operator fun get(i: Int) = get(i, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DoubleArray-based implementation of vector space
|
|
||||||
*/
|
|
||||||
class ArraySpace<T : Any>(rows: Int, columns: Int, field: Field<T>) : LinearSpace<T, LinearStructure<out T>>(rows, columns, field) {
|
|
||||||
|
|
||||||
override fun produce(initializer: (Int, Int) -> T): LinearStructure<T> = ArrayMatrix<T>(this, initializer)
|
|
||||||
|
|
||||||
|
|
||||||
override fun produceSpace(rows: Int, columns: Int): LinearSpace<T, LinearStructure<out T>> {
|
|
||||||
return ArraySpace(rows, columns, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Member of [ArraySpace] which wraps 2-D array
|
|
||||||
*/
|
|
||||||
class ArrayMatrix<T : Any>(override val context: ArraySpace<T>, initializer: (Int, Int) -> T) : LinearStructure<T>, SpaceElement<LinearStructure<out T>, ArraySpace<T>> {
|
|
||||||
|
|
||||||
val list: List<List<T>> = (0 until rows).map { i -> (0 until columns).map { j -> initializer(i, j) } }
|
|
||||||
|
|
||||||
override val rows: Int get() = context.rows
|
|
||||||
|
|
||||||
override val columns: Int get() = context.columns
|
|
||||||
|
|
||||||
override fun get(i: Int, j: Int): T {
|
|
||||||
return list[i][j]
|
|
||||||
}
|
|
||||||
|
|
||||||
override val self: ArrayMatrix<T> get() = this
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ArrayVector<T : Any>(override val context: ArraySpace<T>, initializer: (Int) -> T) : Vector<T>, SpaceElement<LinearStructure<out T>, ArraySpace<T>> {
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (context.columns != 1) {
|
|
||||||
error("Vector must have single column")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val list: List<T> = (0 until context.rows).map(initializer)
|
|
||||||
|
|
||||||
|
|
||||||
override val rows: Int get() = context.rows
|
|
||||||
|
|
||||||
override val columns: Int = 1
|
|
||||||
|
|
||||||
override fun get(i: Int, j: Int): T {
|
|
||||||
return list[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
override val self: ArrayVector<T> get() = this
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any> vector(size: Int, field: Field<T>, initializer: (Int) -> T) = ArrayVector(ArraySpace(size, 1, field), initializer)
|
|
||||||
//TODO replace by primitive array version
|
|
||||||
fun realVector(size: Int, initializer: (Int) -> Double) = vector(size, DoubleField, initializer)
|
|
||||||
|
|
||||||
fun <T : Any> Array<T>.asVector(field: Field<T>) = vector(size, field) { this[it] }
|
|
||||||
//TODO add inferred field from field element
|
|
||||||
fun DoubleArray.asVector() = realVector(this.size) { this[it] }
|
|
||||||
|
|
||||||
fun <T : Any> matrix(rows: Int, columns: Int, field: Field<T>, initializer: (Int, Int) -> T) = ArrayMatrix<T>(ArraySpace(rows, columns, field), initializer)
|
|
||||||
fun realMatrix(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = matrix(rows, columns, DoubleField, initializer)
|
|
58
kmath-core/build.gradle
Normal file
58
kmath-core/build.gradle
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
plugins {
|
||||||
|
id 'kotlin-multiplatform'// version '1.3.0-rc-116'
|
||||||
|
id "me.champeau.gradle.jmh" version "0.4.5"
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
targets {
|
||||||
|
fromPreset(presets.jvm, 'jvm')
|
||||||
|
fromPreset(presets.js, 'js')
|
||||||
|
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||||
|
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||||
|
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||||
|
//fromPreset(presets.mingwX64, 'mingw')
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-common'
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test'
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-junit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsMain {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// mingwMain {
|
||||||
|
// }
|
||||||
|
// mingwTest {
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package scientifik.kmath.expressions
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.Field
|
||||||
|
import scientifik.kmath.operations.Space
|
||||||
|
|
||||||
|
|
||||||
|
interface Expression<T> {
|
||||||
|
operator fun invoke(arguments: Map<String, T>): T
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun <T> Expression<T>.invoke(vararg pairs: Pair<String, T>): T = invoke(mapOf(*pairs))
|
||||||
|
|
||||||
|
interface ExpressionContext<T> {
|
||||||
|
fun variable(name: String, default: T? = null): Expression<T>
|
||||||
|
|
||||||
|
fun const(value: T): Expression<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class VariableExpression<T>(val name: String, val default: T? = null) : Expression<T> {
|
||||||
|
override fun invoke(arguments: Map<String, T>): T {
|
||||||
|
return arguments[name] ?: default ?: error("The parameter not found: $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ConstantExpression<T>(val value: T) : Expression<T> {
|
||||||
|
override fun invoke(arguments: Map<String, T>): T = value
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SumExpression<T>(val context: Space<T>, val first: Expression<T>, val second: Expression<T>) : Expression<T> {
|
||||||
|
override fun invoke(arguments: Map<String, T>): T = context.add(first.invoke(arguments), second.invoke(arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ProductExpression<T>(val context: Field<T>, val first: Expression<T>, val second: Expression<T>) : Expression<T> {
|
||||||
|
override fun invoke(arguments: Map<String, T>): T = context.multiply(first.invoke(arguments), second.invoke(arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ConstProductExpession<T>(val context: Field<T>, val expr: Expression<T>, val const: Double) : Expression<T> {
|
||||||
|
override fun invoke(arguments: Map<String, T>): T = context.multiply(expr.invoke(arguments), const)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DivExpession<T>(val context: Field<T>, val expr: Expression<T>, val second: Expression<T>) : Expression<T> {
|
||||||
|
override fun invoke(arguments: Map<String, T>): T = context.divide(expr.invoke(arguments), second.invoke(arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
class FieldExpressionContext<T>(val field: Field<T>) : Field<Expression<T>>, ExpressionContext<T> {
|
||||||
|
|
||||||
|
override val zero: Expression<T> = ConstantExpression(field.zero)
|
||||||
|
|
||||||
|
override val one: Expression<T> = ConstantExpression(field.one)
|
||||||
|
|
||||||
|
override fun const(value: T): Expression<T> = ConstantExpression(value)
|
||||||
|
|
||||||
|
override fun variable(name: String, default: T?): Expression<T> = VariableExpression(name, default)
|
||||||
|
|
||||||
|
override fun add(a: Expression<T>, b: Expression<T>): Expression<T> = SumExpression(field, a, b)
|
||||||
|
|
||||||
|
override fun multiply(a: Expression<T>, k: Double): Expression<T> = ConstProductExpession(field, a, k)
|
||||||
|
|
||||||
|
override fun multiply(a: Expression<T>, b: Expression<T>): Expression<T> = ProductExpression(field, a, b)
|
||||||
|
|
||||||
|
override fun divide(a: Expression<T>, b: Expression<T>): Expression<T> = DivExpession(field, a, b)
|
||||||
|
}
|
@ -0,0 +1,294 @@
|
|||||||
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import scientifik.kmath.structures.MutableNDArray
|
||||||
|
import scientifik.kmath.structures.NDArray
|
||||||
|
import scientifik.kmath.structures.NDArrays
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation copier from Apache common-maths
|
||||||
|
*/
|
||||||
|
abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
|
||||||
|
|
||||||
|
private val field get() = matrix.context.field
|
||||||
|
/** Entries of LU decomposition. */
|
||||||
|
internal val lu: NDArray<T>
|
||||||
|
/** Pivot permutation associated with LU decomposition. */
|
||||||
|
internal val pivot: IntArray
|
||||||
|
/** Parity of the permutation associated with the LU decomposition. */
|
||||||
|
private var even: Boolean = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
val pair = calculateLU()
|
||||||
|
lu = pair.first
|
||||||
|
pivot = pair.second
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the matrix L of the decomposition.
|
||||||
|
*
|
||||||
|
* L is a lower-triangular matrix
|
||||||
|
* @return the L matrix (or null if decomposed matrix is singular)
|
||||||
|
*/
|
||||||
|
val l: Matrix<out T> by lazy {
|
||||||
|
matrix.context.produce { i, j ->
|
||||||
|
when {
|
||||||
|
j < i -> lu[i, j]
|
||||||
|
j == i -> matrix.context.field.one
|
||||||
|
else -> matrix.context.field.zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the matrix U of the decomposition.
|
||||||
|
*
|
||||||
|
* U is an upper-triangular matrix
|
||||||
|
* @return the U matrix (or null if decomposed matrix is singular)
|
||||||
|
*/
|
||||||
|
val u: Matrix<out T> by lazy {
|
||||||
|
matrix.context.produce { i, j ->
|
||||||
|
if (j >= i) lu[i, j] else field.zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the P rows permutation matrix.
|
||||||
|
*
|
||||||
|
* P is a sparse matrix with exactly one element set to 1.0 in
|
||||||
|
* each row and each column, all other elements being set to 0.0.
|
||||||
|
*
|
||||||
|
* The positions of the 1 elements are given by the [ pivot permutation vector][.getPivot].
|
||||||
|
* @return the P rows permutation matrix (or null if decomposed matrix is singular)
|
||||||
|
* @see .getPivot
|
||||||
|
*/
|
||||||
|
val p: Matrix<out T> by lazy {
|
||||||
|
matrix.context.produce { i, j ->
|
||||||
|
//TODO ineffective. Need sparse matrix for that
|
||||||
|
if (j == pivot[i]) field.one else field.zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the determinant of the matrix
|
||||||
|
* @return determinant of the matrix
|
||||||
|
*/
|
||||||
|
val determinant: T
|
||||||
|
get() {
|
||||||
|
with(matrix.context.field) {
|
||||||
|
var determinant = if (even) one else -one
|
||||||
|
for (i in 0 until matrix.rows) {
|
||||||
|
determinant *= lu[i, i]
|
||||||
|
}
|
||||||
|
return determinant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Get a solver for finding the A X = B solution in exact linear
|
||||||
|
// * sense.
|
||||||
|
// * @return a solver
|
||||||
|
// */
|
||||||
|
// val solver: DecompositionSolver
|
||||||
|
// get() = Solver(lu, pivot, singular)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-place transformation for [MutableNDArray], using given transformation for each element
|
||||||
|
*/
|
||||||
|
operator fun <T> MutableNDArray<T>.set(i: Int, j: Int, value: T) {
|
||||||
|
this[listOf(i, j)] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun isSingular(value: T): Boolean
|
||||||
|
|
||||||
|
private fun abs(value: T) = if (value > matrix.context.field.zero) value else with(matrix.context.field) { -value }
|
||||||
|
|
||||||
|
private fun calculateLU(): Pair<NDArray<T>, IntArray> {
|
||||||
|
if (matrix.rows != matrix.columns) {
|
||||||
|
error("LU decomposition supports only square matrices")
|
||||||
|
}
|
||||||
|
|
||||||
|
val m = matrix.columns
|
||||||
|
val pivot = IntArray(matrix.rows)
|
||||||
|
//TODO fix performance
|
||||||
|
val lu: MutableNDArray<T> = NDArrays.createMutable(matrix.context.field, listOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] }
|
||||||
|
|
||||||
|
|
||||||
|
with(matrix.context.field) {
|
||||||
|
// Initialize permutation array and parity
|
||||||
|
for (row in 0 until m) {
|
||||||
|
pivot[row] = row
|
||||||
|
}
|
||||||
|
even = true
|
||||||
|
|
||||||
|
// Loop over columns
|
||||||
|
for (col in 0 until m) {
|
||||||
|
|
||||||
|
// upper
|
||||||
|
for (row in 0 until col) {
|
||||||
|
var sum = lu[row, col]
|
||||||
|
for (i in 0 until row) {
|
||||||
|
sum -= lu[row, i] * lu[i, col]
|
||||||
|
}
|
||||||
|
lu[row, col] = sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// lower
|
||||||
|
val max = (col until m).maxBy { row ->
|
||||||
|
var sum = lu[row, col]
|
||||||
|
for (i in 0 until col) {
|
||||||
|
sum -= lu[row, i] * lu[i, col]
|
||||||
|
}
|
||||||
|
//luRow[col] = sum
|
||||||
|
lu[row, col] = sum
|
||||||
|
|
||||||
|
abs(sum)
|
||||||
|
} ?: col
|
||||||
|
|
||||||
|
// Singularity check
|
||||||
|
if (isSingular(lu[max, col])) {
|
||||||
|
error("Singular matrix")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pivot if necessary
|
||||||
|
if (max != col) {
|
||||||
|
//var tmp = zero
|
||||||
|
//val luMax = lu[max]
|
||||||
|
//val luCol = lu[col]
|
||||||
|
for (i in 0 until m) {
|
||||||
|
lu[max, i] = lu[col, i]
|
||||||
|
lu[col, i] = lu[max, i]
|
||||||
|
}
|
||||||
|
val temp = pivot[max]
|
||||||
|
pivot[max] = pivot[col]
|
||||||
|
pivot[col] = temp
|
||||||
|
even = !even
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide the lower elements by the "winning" diagonal elt.
|
||||||
|
val luDiag = lu[col, col]
|
||||||
|
for (row in col + 1 until m) {
|
||||||
|
lu[row, col] = lu[row, col] / luDiag
|
||||||
|
// lu[row, col] /= luDiag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(lu, pivot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pivot permutation vector.
|
||||||
|
* @return the pivot permutation vector
|
||||||
|
* @see .getP
|
||||||
|
*/
|
||||||
|
fun getPivot(): IntArray {
|
||||||
|
return pivot.copyOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class RealLUDecomposition(matrix: Matrix<Double>, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : LUDecomposition<Double>(matrix) {
|
||||||
|
override fun isSingular(value: Double): Boolean {
|
||||||
|
return value.absoluteValue < singularityThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Default bound to determine effective singularity in LU decomposition. */
|
||||||
|
private const val DEFAULT_TOO_SMALL = 1e-11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Specialized solver. */
|
||||||
|
object RealLUSolver : LinearSolver<Double> {
|
||||||
|
|
||||||
|
//
|
||||||
|
// /** {@inheritDoc} */
|
||||||
|
// override fun solve(b: RealVector): RealVector {
|
||||||
|
// val m = pivot.size
|
||||||
|
// if (b.getDimension() != m) {
|
||||||
|
// throw DimensionMismatchException(b.getDimension(), m)
|
||||||
|
// }
|
||||||
|
// if (singular) {
|
||||||
|
// throw SingularMatrixException()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val bp = DoubleArray(m)
|
||||||
|
//
|
||||||
|
// // Apply permutations to b
|
||||||
|
// for (row in 0 until m) {
|
||||||
|
// bp[row] = b.getEntry(pivot[row])
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Solve LY = b
|
||||||
|
// for (col in 0 until m) {
|
||||||
|
// val bpCol = bp[col]
|
||||||
|
// for (i in col + 1 until m) {
|
||||||
|
// bp[i] -= bpCol * lu[i][col]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Solve UX = Y
|
||||||
|
// for (col in m - 1 downTo 0) {
|
||||||
|
// bp[col] /= lu[col][col]
|
||||||
|
// val bpCol = bp[col]
|
||||||
|
// for (i in 0 until col) {
|
||||||
|
// bp[i] -= bpCol * lu[i][col]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return ArrayRealVector(bp, false)
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
fun decompose(mat: Matrix<Double>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold)
|
||||||
|
|
||||||
|
override fun solve(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> {
|
||||||
|
val decomposition = decompose(a, a.context.field.zero)
|
||||||
|
|
||||||
|
if (b.rows != a.rows) {
|
||||||
|
error("Matrix dimension mismatch expected ${a.rows}, but got ${b.rows}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply permutations to b
|
||||||
|
val bp = Array(a.rows) { DoubleArray(b.columns) }
|
||||||
|
for (row in 0 until a.rows) {
|
||||||
|
val bpRow = bp[row]
|
||||||
|
val pRow = decomposition.pivot[row]
|
||||||
|
for (col in 0 until b.columns) {
|
||||||
|
bpRow[col] = b[pRow, col]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve LY = b
|
||||||
|
for (col in 0 until a.rows) {
|
||||||
|
val bpCol = bp[col]
|
||||||
|
for (i in col + 1 until a.rows) {
|
||||||
|
val bpI = bp[i]
|
||||||
|
val luICol = decomposition.lu[i, col]
|
||||||
|
for (j in 0 until b.columns) {
|
||||||
|
bpI[j] -= bpCol[j] * luICol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve UX = Y
|
||||||
|
for (col in a.rows - 1 downTo 0) {
|
||||||
|
val bpCol = bp[col]
|
||||||
|
val luDiag = decomposition.lu[col, col]
|
||||||
|
for (j in 0 until b.columns) {
|
||||||
|
bpCol[j] /= luDiag
|
||||||
|
}
|
||||||
|
for (i in 0 until col) {
|
||||||
|
val bpI = bp[i]
|
||||||
|
val luICol = decomposition.lu[i, col]
|
||||||
|
for (j in 0 until b.columns) {
|
||||||
|
bpI[j] -= bpCol[j] * luICol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.context.produce { i, j -> bp[i][j] }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,311 @@
|
|||||||
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import scientifik.kmath.operations.Field
|
||||||
|
import scientifik.kmath.operations.Space
|
||||||
|
import scientifik.kmath.operations.SpaceElement
|
||||||
|
import scientifik.kmath.structures.NDArray
|
||||||
|
import scientifik.kmath.structures.NDArrays.createFactory
|
||||||
|
import scientifik.kmath.structures.NDFieldFactory
|
||||||
|
import scientifik.kmath.structures.realNDFieldFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The space for linear elements. Supports scalar product alongside with standard linear operations.
|
||||||
|
* @param T type of individual element of the vector or matrix
|
||||||
|
* @param V the type of vector space element
|
||||||
|
*/
|
||||||
|
abstract class MatrixSpace<T : Any>(val rows: Int, val columns: Int, val field: Field<T>) : Space<Matrix<T>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the element of this space
|
||||||
|
*/
|
||||||
|
abstract fun produce(initializer: (Int, Int) -> T): Matrix<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce new matrix space with given dimensions. The space produced could be raised from cache since [MatrixSpace] does not have mutable elements
|
||||||
|
*/
|
||||||
|
abstract fun produceSpace(rows: Int, columns: Int): MatrixSpace<T>
|
||||||
|
|
||||||
|
override val zero: Matrix<T> by lazy {
|
||||||
|
produce { _, _ -> field.zero }
|
||||||
|
}
|
||||||
|
|
||||||
|
// val one: Matrix<T> by lazy {
|
||||||
|
// produce { i, j -> if (i == j) field.one else field.zero }
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun add(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
|
||||||
|
return produce { i, j -> with(field) { a[i, j] + b[i, j] } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun multiply(a: Matrix<T>, k: Double): Matrix<T> {
|
||||||
|
//TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser
|
||||||
|
return produce { i, j -> with(field) { a[i, j] * k } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dot product. Throws exception on dimension mismatch
|
||||||
|
*/
|
||||||
|
fun multiply(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
|
||||||
|
if (a.rows != b.columns) {
|
||||||
|
//TODO replace by specific exception
|
||||||
|
error("Dimension mismatch in linear structure dot product: [${a.rows},${a.columns}]*[${b.rows},${b.columns}]")
|
||||||
|
}
|
||||||
|
return produceSpace(a.rows, b.columns).produce { i, j ->
|
||||||
|
(0 until a.columns).asSequence().map { k -> field.multiply(a[i, k], b[k, j]) }.reduce { first, second -> field.add(first, second) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is MatrixSpace<*>) return false
|
||||||
|
|
||||||
|
if (rows != other.rows) return false
|
||||||
|
if (columns != other.columns) return false
|
||||||
|
if (field != other.field) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = rows
|
||||||
|
result = 31 * result + columns
|
||||||
|
result = 31 * result + field.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
infix fun <T : Any> Matrix<T>.dot(b: Matrix<T>): Matrix<T> = this.context.multiply(this, b)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A matrix-like structure
|
||||||
|
*/
|
||||||
|
interface Matrix<T : Any> : SpaceElement<Matrix<T>, MatrixSpace<T>> {
|
||||||
|
/**
|
||||||
|
* Number of rows
|
||||||
|
*/
|
||||||
|
val rows: Int
|
||||||
|
/**
|
||||||
|
* Number of columns
|
||||||
|
*/
|
||||||
|
val columns: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element in row [i] and column [j]. Throws error in case of call ounside structure dimensions
|
||||||
|
*/
|
||||||
|
operator fun get(i: Int, j: Int): T
|
||||||
|
|
||||||
|
override val self: Matrix<T>
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
fun transpose(): Matrix<T> {
|
||||||
|
return object : Matrix<T> {
|
||||||
|
override val context: MatrixSpace<T> = this@Matrix.context
|
||||||
|
override val rows: Int = this@Matrix.columns
|
||||||
|
override val columns: Int = this@Matrix.rows
|
||||||
|
override fun get(i: Int, j: Int): T = this@Matrix[j, i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create [ArrayMatrix] with custom field
|
||||||
|
*/
|
||||||
|
fun <T : Any> of(rows: Int, columns: Int, field: Field<T>, initializer: (Int, Int) -> T) =
|
||||||
|
ArrayMatrix(ArrayMatrixSpace(rows, columns, field), initializer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create [ArrayMatrix] of doubles. The implementation in general should be faster than generic one due to boxing.
|
||||||
|
*/
|
||||||
|
fun ofReal(rows: Int, columns: Int, initializer: (Int, Int) -> Double) =
|
||||||
|
ArrayMatrix(ArrayMatrixSpace(rows, columns, DoubleField, realNDFieldFactory), initializer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a diagonal value matrix. By default value equals [Field.one].
|
||||||
|
*/
|
||||||
|
fun <T : Any> diagonal(rows: Int, columns: Int, field: Field<T>, values: (Int) -> T = { field.one }): Matrix<T> {
|
||||||
|
return of(rows, columns, field) { i, j -> if (i == j) values(i) else field.zero }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equality check on two generic matrices
|
||||||
|
*/
|
||||||
|
fun equals(mat1: Matrix<*>, mat2: Matrix<*>): Boolean {
|
||||||
|
if (mat1 === mat2) return true
|
||||||
|
if (mat1.context != mat2.context) return false
|
||||||
|
for (i in 0 until mat1.rows) {
|
||||||
|
for (j in 0 until mat2.columns) {
|
||||||
|
if (mat1[i, j] != mat2[i, j]) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A linear space for vectors
|
||||||
|
*/
|
||||||
|
abstract class VectorSpace<T : Any>(val size: Int, val field: Field<T>) : Space<Vector<T>> {
|
||||||
|
|
||||||
|
abstract fun produce(initializer: (Int) -> T): Vector<T>
|
||||||
|
|
||||||
|
override val zero: Vector<T> by lazy { produce { field.zero } }
|
||||||
|
|
||||||
|
override fun add(a: Vector<T>, b: Vector<T>): Vector<T> = produce { with(field) { a[it] + b[it] } }
|
||||||
|
|
||||||
|
override fun multiply(a: Vector<T>, k: Double): Vector<T> = produce { with(field) { a[it] * k } }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface Vector<T : Any> : SpaceElement<Vector<T>, VectorSpace<T>> {
|
||||||
|
val size: Int
|
||||||
|
get() = context.size
|
||||||
|
|
||||||
|
operator fun get(i: Int): T
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Create vector with custom field
|
||||||
|
*/
|
||||||
|
fun <T : Any> of(size: Int, field: Field<T>, initializer: (Int) -> T) =
|
||||||
|
ArrayVector(ArrayVectorSpace(size, field), initializer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create vector of [Double]
|
||||||
|
*/
|
||||||
|
fun ofReal(size: Int, initializer: (Int) -> Double) =
|
||||||
|
ArrayVector(ArrayVectorSpace(size, DoubleField, realNDFieldFactory), initializer)
|
||||||
|
|
||||||
|
|
||||||
|
fun equals(v1: Vector<*>, v2: Vector<*>): Boolean {
|
||||||
|
if (v1 === v2) return true
|
||||||
|
if (v1.context != v2.context) return false
|
||||||
|
for (i in 0 until v2.size) {
|
||||||
|
if (v1[i] != v2[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NDArray-based implementation of vector space. By default uses slow [SimpleNDField], but could be overridden with custom [NDField] factory.
|
||||||
|
*/
|
||||||
|
class ArrayMatrixSpace<T : Any>(
|
||||||
|
rows: Int,
|
||||||
|
columns: Int,
|
||||||
|
field: Field<T>,
|
||||||
|
val ndFactory: NDFieldFactory<T> = createFactory(field)
|
||||||
|
) : MatrixSpace<T>(rows, columns, field) {
|
||||||
|
|
||||||
|
val ndField by lazy {
|
||||||
|
ndFactory(listOf(rows, columns))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun produce(initializer: (Int, Int) -> T): Matrix<T> = ArrayMatrix(this, initializer)
|
||||||
|
|
||||||
|
override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace<T> {
|
||||||
|
return ArrayMatrixSpace(rows, columns, field, ndFactory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayVectorSpace<T : Any>(
|
||||||
|
size: Int,
|
||||||
|
field: Field<T>,
|
||||||
|
val ndFactory: NDFieldFactory<T> = createFactory(field)
|
||||||
|
) : VectorSpace<T>(size, field) {
|
||||||
|
val ndField by lazy {
|
||||||
|
ndFactory(listOf(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun produce(initializer: (Int) -> T): Vector<T> = ArrayVector(this, initializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Member of [ArrayMatrixSpace] which wraps 2-D array
|
||||||
|
*/
|
||||||
|
class ArrayMatrix<T : Any> internal constructor(override val context: ArrayMatrixSpace<T>, val array: NDArray<T>) : Matrix<T> {
|
||||||
|
|
||||||
|
constructor(context: ArrayMatrixSpace<T>, initializer: (Int, Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0], list[1]) })
|
||||||
|
|
||||||
|
override val rows: Int get() = context.rows
|
||||||
|
|
||||||
|
override val columns: Int get() = context.columns
|
||||||
|
|
||||||
|
override fun get(i: Int, j: Int): T {
|
||||||
|
return array[i, j]
|
||||||
|
}
|
||||||
|
|
||||||
|
override val self: ArrayMatrix<T> get() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ArrayVector<T : Any> internal constructor(override val context: ArrayVectorSpace<T>, val array: NDArray<T>) : Vector<T> {
|
||||||
|
|
||||||
|
constructor(context: ArrayVectorSpace<T>, initializer: (Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0]) })
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (context.size != array.shape[0]) {
|
||||||
|
error("Array dimension mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(i: Int): T {
|
||||||
|
return array[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
override val self: ArrayVector<T> get() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors
|
||||||
|
*/
|
||||||
|
interface LinearSolver<T : Any> {
|
||||||
|
fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T>
|
||||||
|
fun solve(a: Matrix<T>, b: Vector<T>): Vector<T> = solve(a, b.toMatrix()).toVector()
|
||||||
|
fun inverse(a: Matrix<T>): Matrix<T> = solve(a, Matrix.diagonal(a.rows, a.columns, a.context.field))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert vector to array (copying content of array)
|
||||||
|
*/
|
||||||
|
fun <T : Any> Array<T>.toVector(field: Field<T>) = Vector.of(size, field) { this[it] }
|
||||||
|
|
||||||
|
fun DoubleArray.toVector() = Vector.ofReal(this.size) { this[it] }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert matrix to vector if it is possible
|
||||||
|
*/
|
||||||
|
fun <T : Any> Matrix<T>.toVector(): Vector<T> {
|
||||||
|
return when {
|
||||||
|
this.columns == 1 -> {
|
||||||
|
// if (this is ArrayMatrix) {
|
||||||
|
// //Reuse existing underlying array
|
||||||
|
// ArrayVector(ArrayVectorSpace(rows, context.field, context.ndFactory), array)
|
||||||
|
// } else {
|
||||||
|
// //Generic vector
|
||||||
|
// vector(rows, context.field) { get(it, 0) }
|
||||||
|
// }
|
||||||
|
Vector.of(rows, context.field) { get(it, 0) }
|
||||||
|
}
|
||||||
|
else -> error("Can't convert matrix with more than one column to vector")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Any> Vector<T>.toMatrix(): Matrix<T> {
|
||||||
|
// return if (this is ArrayVector) {
|
||||||
|
// //Reuse existing underlying array
|
||||||
|
// ArrayMatrix(ArrayMatrixSpace(size, 1, context.field, context.ndFactory), array)
|
||||||
|
// } else {
|
||||||
|
// //Generic vector
|
||||||
|
// matrix(size, 1, context.field) { i, j -> get(i) }
|
||||||
|
// }
|
||||||
|
return Matrix.of(size, 1, context.field) { i, j -> get(i) }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
|||||||
|
package scientifik.kmath.misc
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic cumulative operation on iterator
|
||||||
|
* @param T type of initial iterable
|
||||||
|
* @param R type of resulting iterable
|
||||||
|
* @param initial lazy evaluated
|
||||||
|
*/
|
||||||
|
fun <T, R> Iterator<T>.cumulative(initial: R, operation: (T, R) -> R): Iterator<R> = object : Iterator<R> {
|
||||||
|
var state: R = initial
|
||||||
|
override fun hasNext(): Boolean = this@cumulative.hasNext()
|
||||||
|
|
||||||
|
override fun next(): R {
|
||||||
|
state = operation.invoke(this@cumulative.next(), state)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, R> Iterable<T>.cumulative(initial: R, operation: (T, R) -> R): Iterable<R> = object : Iterable<R> {
|
||||||
|
override fun iterator(): Iterator<R> = this@cumulative.iterator().cumulative(initial, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, R> Sequence<T>.cumulative(initial: R, operation: (T, R) -> R): Sequence<R> = object : Sequence<R> {
|
||||||
|
override fun iterator(): Iterator<R> = this@cumulative.iterator().cumulative(initial, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, R> List<T>.cumulative(initial: R, operation: (T, R) -> R): List<R> = this.iterator().cumulative(initial, operation).asSequence().toList()
|
||||||
|
|
||||||
|
//Cumulative sum
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfDouble")
|
||||||
|
fun Iterable<Double>.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfInt")
|
||||||
|
fun Iterable<Int>.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfLong")
|
||||||
|
fun Iterable<Long>.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfDouble")
|
||||||
|
fun Sequence<Double>.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfInt")
|
||||||
|
fun Sequence<Int>.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfLong")
|
||||||
|
fun Sequence<Long>.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfDouble")
|
||||||
|
fun List<Double>.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfInt")
|
||||||
|
fun List<Int>.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element}
|
||||||
|
|
||||||
|
@JvmName("cumulativeSumOfLong")
|
||||||
|
fun List<Long>.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element}
|
@ -0,0 +1,37 @@
|
|||||||
|
package scientifik.kmath.misc
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert double range to sequence.
|
||||||
|
*
|
||||||
|
* If the step is positive, than the sequence starts with the lower boundary and increments by [step] until current value is lower than upper boundary.
|
||||||
|
* The boundary itself is not necessary included.
|
||||||
|
*
|
||||||
|
* If step is negative, the same goes from upper boundary downwards
|
||||||
|
*/
|
||||||
|
fun ClosedFloatingPointRange<Double>.toSequence(step: Double): Sequence<Double> {
|
||||||
|
return when {
|
||||||
|
step == 0.0 -> error("Zero step in double progression")
|
||||||
|
step > 0 -> sequence {
|
||||||
|
var current = start
|
||||||
|
while (current <= endInclusive) {
|
||||||
|
yield(current)
|
||||||
|
current += step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> sequence {
|
||||||
|
var current = endInclusive
|
||||||
|
while (current >= start) {
|
||||||
|
yield(current)
|
||||||
|
current += step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert double range to array of evenly spaced doubles, where the size of array equals [numPoints]
|
||||||
|
*/
|
||||||
|
fun ClosedFloatingPointRange<Double>.toGrid(numPoints: Int): DoubleArray {
|
||||||
|
if (numPoints < 2) error("Can't create grid with less than two points")
|
||||||
|
return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i }
|
||||||
|
}
|
@ -3,8 +3,15 @@ package scientifik.kmath.operations
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The generic mathematics elements which is able to store its context
|
* The generic mathematics elements which is able to store its context
|
||||||
|
* @param T the self type of the element
|
||||||
|
* @param S the type of mathematical context for this element
|
||||||
*/
|
*/
|
||||||
interface MathElement<T, S> {
|
interface MathElement<T, S> {
|
||||||
|
/**
|
||||||
|
* Self value. Needed for static type checking.
|
||||||
|
*/
|
||||||
|
val self: T
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context this element belongs to
|
* The context this element belongs to
|
||||||
*/
|
*/
|
||||||
@ -52,12 +59,6 @@ interface Space<T> {
|
|||||||
* @param S the type of space
|
* @param S the type of space
|
||||||
*/
|
*/
|
||||||
interface SpaceElement<T, S : Space<T>> : MathElement<T, S> {
|
interface SpaceElement<T, S : Space<T>> : MathElement<T, S> {
|
||||||
|
|
||||||
/**
|
|
||||||
* Self value. Needed for static type checking. Needed to avoid type erasure on JVM.
|
|
||||||
*/
|
|
||||||
val self: T
|
|
||||||
|
|
||||||
operator fun plus(b: T): T = context.add(self, b)
|
operator fun plus(b: T): T = context.add(self, b)
|
||||||
operator fun minus(b: T): T = context.add(self, context.multiply(b, -1.0))
|
operator fun minus(b: T): T = context.add(self, context.multiply(b, -1.0))
|
||||||
operator fun times(k: Number): T = context.multiply(self, k.toDouble())
|
operator fun times(k: Number): T = context.multiply(self, k.toDouble())
|
||||||
@ -99,6 +100,12 @@ interface Field<T> : Ring<T> {
|
|||||||
|
|
||||||
operator fun T.div(b: T): T = divide(this, b)
|
operator fun T.div(b: T): T = divide(this, b)
|
||||||
operator fun Number.div(b: T) = this * divide(one, b)
|
operator fun Number.div(b: T) = this * divide(one, b)
|
||||||
|
|
||||||
|
operator fun T.plus(b: Number) = this.plus(b * one)
|
||||||
|
operator fun Number.plus(b: T) = b + this
|
||||||
|
|
||||||
|
operator fun T.minus(b: Number) = this.minus(b * one)
|
||||||
|
operator fun Number.minus(b: T) = -b + this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -0,0 +1,41 @@
|
|||||||
|
package scientifik.kmath.operations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A field for complex numbers
|
||||||
|
*/
|
||||||
|
object ComplexField : Field<Complex> {
|
||||||
|
override val zero: Complex = Complex(0.0, 0.0)
|
||||||
|
|
||||||
|
override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im)
|
||||||
|
|
||||||
|
override fun multiply(a: Complex, k: Double): Complex = Complex(a.re * k, a.im * k)
|
||||||
|
|
||||||
|
override val one: Complex = Complex(1.0, 0.0)
|
||||||
|
|
||||||
|
override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re)
|
||||||
|
|
||||||
|
override fun divide(a: Complex, b: Complex): Complex = Complex(a.re * b.re + a.im * b.im, a.re * b.im - a.im * b.re) / b.square
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complex number class
|
||||||
|
*/
|
||||||
|
data class Complex(val re: Double, val im: Double) : FieldElement<Complex, ComplexField> {
|
||||||
|
override val self: Complex get() = this
|
||||||
|
override val context: ComplexField
|
||||||
|
get() = ComplexField
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A complex conjugate
|
||||||
|
*/
|
||||||
|
val conjugate: Complex
|
||||||
|
get() = Complex(re, -im)
|
||||||
|
|
||||||
|
val square: Double
|
||||||
|
get() = re * re + im * im
|
||||||
|
|
||||||
|
val abs: Double
|
||||||
|
get() = kotlin.math.sqrt(square)
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package scientifik.kmath.operations
|
package scientifik.kmath.operations
|
||||||
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field for real values
|
* Field for real values
|
||||||
@ -29,7 +28,7 @@ object RealField : Field<Real>, TrigonometricOperations<Real>, PowerOperations<R
|
|||||||
*
|
*
|
||||||
* TODO could be replaced by inline class in kotlin 1.3 if it would allow to avoid boxing
|
* TODO could be replaced by inline class in kotlin 1.3 if it would allow to avoid boxing
|
||||||
*/
|
*/
|
||||||
class Real(val value: Double) : Number(), FieldElement<Real, RealField> {
|
data class Real(val value: Double) : Number(), FieldElement<Real, RealField> {
|
||||||
/*
|
/*
|
||||||
* The class uses composition instead of inheritance since Double is final
|
* The class uses composition instead of inheritance since Double is final
|
||||||
*/
|
*/
|
||||||
@ -51,53 +50,22 @@ class Real(val value: Double) : Number(), FieldElement<Real, RealField> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A field for complex numbers
|
|
||||||
*/
|
|
||||||
object ComplexField : Field<Complex> {
|
|
||||||
override val zero: Complex = Complex(0.0, 0.0)
|
|
||||||
|
|
||||||
override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im)
|
|
||||||
|
|
||||||
override fun multiply(a: Complex, k: Double): Complex = Complex(a.re * k, a.im * k)
|
|
||||||
|
|
||||||
override val one: Complex = Complex(1.0, 0.0)
|
|
||||||
|
|
||||||
override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re)
|
|
||||||
|
|
||||||
override fun divide(a: Complex, b: Complex): Complex = Complex(a.re * b.re + a.im * b.im, a.re * b.im - a.im * b.re) / b.square
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complex number class
|
|
||||||
*/
|
|
||||||
data class Complex(val re: Double, val im: Double) : FieldElement<Complex, ComplexField> {
|
|
||||||
override val self: Complex get() = this
|
|
||||||
override val context: ComplexField
|
|
||||||
get() = ComplexField
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A complex conjugate
|
|
||||||
*/
|
|
||||||
val conjugate: Complex
|
|
||||||
get() = Complex(re, -im)
|
|
||||||
|
|
||||||
val square: Double
|
|
||||||
get() = re * re + im * im
|
|
||||||
|
|
||||||
val module: Double
|
|
||||||
get() = sqrt(square)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A field for double without boxing. Does not produce appropriate field element
|
* A field for double without boxing. Does not produce appropriate field element
|
||||||
*/
|
*/
|
||||||
object DoubleField : Field<Double> {
|
object DoubleField : Field<Double>, TrigonometricOperations<Double>, PowerOperations<Double>, ExponentialOperations<Double> {
|
||||||
override val zero: Double = 0.0
|
override val zero: Double = 0.0
|
||||||
override fun add(a: Double, b: Double): Double = a + b
|
override fun add(a: Double, b: Double): Double = a + b
|
||||||
override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b
|
override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b
|
||||||
override val one: Double = 1.0
|
override val one: Double = 1.0
|
||||||
override fun divide(a: Double, b: Double): Double = a / b
|
override fun divide(a: Double, b: Double): Double = a / b
|
||||||
|
|
||||||
|
override fun sin(arg: Double): Double = kotlin.math.sin(arg)
|
||||||
|
override fun cos(arg: Double): Double = kotlin.math.cos(arg)
|
||||||
|
|
||||||
|
override fun power(arg: Double, pow: Double): Double = arg.pow(pow)
|
||||||
|
|
||||||
|
override fun exp(arg: Double): Double =kotlin.math.exp(arg)
|
||||||
|
|
||||||
|
override fun ln(arg: Double): Double = kotlin.math.ln(arg)
|
||||||
}
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.Field
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic buffer for both primitives and objects
|
||||||
|
*/
|
||||||
|
interface Buffer<T> {
|
||||||
|
operator fun get(index: Int): T
|
||||||
|
operator fun set(index: Int, value: T)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shallow copy of the buffer
|
||||||
|
*/
|
||||||
|
fun copy(): Buffer<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic implementation of NDField based on continuous buffer
|
||||||
|
*/
|
||||||
|
abstract class BufferNDField<T>(shape: List<Int>, field: Field<T>) : NDField<T>(shape, field) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strides for memory access
|
||||||
|
*/
|
||||||
|
private val strides: List<Int> by lazy {
|
||||||
|
ArrayList<Int>(shape.size).apply {
|
||||||
|
var current = 1
|
||||||
|
add(1)
|
||||||
|
shape.forEach {
|
||||||
|
current *= it
|
||||||
|
add(current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun offset(index: List<Int>): Int {
|
||||||
|
return index.mapIndexed { i, value ->
|
||||||
|
if (value < 0 || value >= shape[i]) {
|
||||||
|
throw RuntimeException("Index out of shape bounds: ($i,$value)")
|
||||||
|
}
|
||||||
|
value * strides[i]
|
||||||
|
}.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO introduce a fast way to calculate index of the next element?
|
||||||
|
protected fun index(offset: Int): List<Int> {
|
||||||
|
return sequence {
|
||||||
|
var current = offset
|
||||||
|
var strideIndex = strides.size - 2
|
||||||
|
while (strideIndex >= 0) {
|
||||||
|
yield(current / strides[strideIndex])
|
||||||
|
current %= strides[strideIndex]
|
||||||
|
strideIndex--
|
||||||
|
}
|
||||||
|
}.toList().reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val capacity: Int
|
||||||
|
get() = strides[shape.size]
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer<T>
|
||||||
|
|
||||||
|
override fun produce(initializer: (List<Int>) -> T): NDArray<T> {
|
||||||
|
val buffer = createBuffer(capacity) { initializer(index(it)) }
|
||||||
|
return BufferNDArray(this, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce mutable NDArray instance
|
||||||
|
*/
|
||||||
|
fun produceMutable(initializer: (List<Int>) -> T): MutableNDArray<T> {
|
||||||
|
val buffer = createBuffer(capacity) { initializer(index(it)) }
|
||||||
|
return MutableBufferedNDArray(this, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private open class BufferNDArray<T>(override val context: BufferNDField<T>, val data: Buffer<T>) : NDArray<T> {
|
||||||
|
|
||||||
|
override fun get(vararg index: Int): T {
|
||||||
|
return data[context.offset(index.asList())]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is BufferNDArray<*>) return false
|
||||||
|
|
||||||
|
if (context != other.context) return false
|
||||||
|
if (data != other.data) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = context.hashCode()
|
||||||
|
result = 31 * result + data.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override val self: NDArray<T> get() = this
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MutableBufferedNDArray<T>(context: BufferNDField<T>, data: Buffer<T>): BufferNDArray<T>(context,data), MutableNDArray<T>{
|
||||||
|
override operator fun set(index: List<Int>, value: T){
|
||||||
|
data[context.offset(index)] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +73,9 @@ abstract class NDField<T>(val shape: List<Int>, val field: Field<T>) : Field<NDA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Many-dimensional array
|
||||||
|
*/
|
||||||
interface NDArray<T> : FieldElement<NDArray<T>, NDField<T>> {
|
interface NDArray<T> : FieldElement<NDArray<T>, NDField<T>> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,6 +127,22 @@ interface NDArray<T> : FieldElement<NDArray<T>, NDField<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-place mutable [NDArray]
|
||||||
|
*/
|
||||||
|
interface MutableNDArray<T> : NDArray<T> {
|
||||||
|
operator fun set(index: List<Int>, value: T)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-place transformation for [MutableNDArray], using given transformation for each element
|
||||||
|
*/
|
||||||
|
fun <T> MutableNDArray<T>.transformInPlace(action: (List<Int>, T) -> T) {
|
||||||
|
for ((index, oldValue) in this) {
|
||||||
|
this[index] = action(index, oldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||||
*/
|
*/
|
||||||
@ -198,15 +216,3 @@ operator fun <T> T.div(arg: NDArray<T>): NDArray<T> = arg.transform { _, value -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a platform-specific NDArray of doubles
|
|
||||||
*/
|
|
||||||
expect fun realNDArray(shape: List<Int>, initializer: (List<Int>) -> Double = { 0.0 }): NDArray<Double>
|
|
||||||
|
|
||||||
fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray<Double> {
|
|
||||||
return realNDArray(listOf(dim1, dim2)) { initializer(it[0], it[1]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDArray<Double> {
|
|
||||||
return realNDArray(listOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
|
|
||||||
}
|
|
@ -0,0 +1,72 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.Field
|
||||||
|
|
||||||
|
typealias NDFieldFactory<T> = (shape: List<Int>) -> NDField<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The factory class for fast platform-dependent implementation of NDField of doubles
|
||||||
|
*/
|
||||||
|
expect val realNDFieldFactory: NDFieldFactory<Double>
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleNDField<T : Any>(field: Field<T>, shape: List<Int>) : BufferNDField<T>(shape, field) {
|
||||||
|
override fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer<T> {
|
||||||
|
val array = ArrayList<T>(capacity)
|
||||||
|
(0 until capacity).forEach {
|
||||||
|
array.add(initializer(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
return BufferOfObjects(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BufferOfObjects<T>(val array: ArrayList<T>) : Buffer<T> {
|
||||||
|
override fun get(index: Int): T = array[index]
|
||||||
|
|
||||||
|
override fun set(index: Int, value: T) {
|
||||||
|
array[index] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copy(): Buffer<T> = BufferOfObjects(ArrayList(array))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object NDArrays {
|
||||||
|
/**
|
||||||
|
* Create a platform-optimized NDArray of doubles
|
||||||
|
*/
|
||||||
|
fun realNDArray(shape: List<Int>, initializer: (List<Int>) -> Double = { 0.0 }): NDArray<Double> {
|
||||||
|
return realNDFieldFactory(shape).produce(initializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray<Double> {
|
||||||
|
return realNDArray(listOf(dim)) { initializer(it[0]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray<Double> {
|
||||||
|
return realNDArray(listOf(dim1, dim2)) { initializer(it[0], it[1]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDArray<Double> {
|
||||||
|
return realNDArray(listOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple boxing NDField
|
||||||
|
*/
|
||||||
|
fun <T : Any> createFactory(field: Field<T>): NDFieldFactory<T> = { shape -> SimpleNDField(field, shape) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple boxing NDArray
|
||||||
|
*/
|
||||||
|
fun <T : Any> create(field: Field<T>, shape: List<Int>, initializer: (List<Int>) -> T): NDArray<T> {
|
||||||
|
return SimpleNDField(field, shape).produce { initializer(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable boxing NDArray
|
||||||
|
*/
|
||||||
|
fun <T : Any> createMutable(field: Field<T>, shape: List<Int>, initializer: (List<Int>) -> T): MutableNDArray<T> {
|
||||||
|
return SimpleNDField(field, shape).produceMutable { initializer(it) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package scientifik.kmath.expressions
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.Complex
|
||||||
|
import scientifik.kmath.operations.ComplexField
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class FieldExpressionContextTest {
|
||||||
|
@Test
|
||||||
|
fun testExpression() {
|
||||||
|
val context = FieldExpressionContext(DoubleField)
|
||||||
|
val expression = with(context) {
|
||||||
|
val x = variable("x", 2.0)
|
||||||
|
x * x + 2 * x + 1.0
|
||||||
|
}
|
||||||
|
assertEquals(expression("x" to 1.0), 4.0)
|
||||||
|
assertEquals(expression(), 9.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testComplex() {
|
||||||
|
val context = FieldExpressionContext(ComplexField)
|
||||||
|
val expression = with(context) {
|
||||||
|
val x = variable("x", Complex(2.0, 0.0))
|
||||||
|
x * x + 2 * x + 1.0
|
||||||
|
}
|
||||||
|
assertEquals(expression("x" to Complex(1.0, 0.0)), Complex(4.0, 0.0))
|
||||||
|
assertEquals(expression(), Complex(9.0, 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun separateContext() {
|
||||||
|
fun <T> FieldExpressionContext<T>.expression(): Expression<T>{
|
||||||
|
val x = variable("x")
|
||||||
|
return x * x + 2 * x + 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
val expression = FieldExpressionContext(DoubleField).expression()
|
||||||
|
assertEquals(expression("x" to 1.0), 4.0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class ArrayMatrixTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSum() {
|
||||||
|
val vector1 = realVector(5) { it.toDouble() }
|
||||||
|
val vector2 = realVector(5) { 5 - it.toDouble() }
|
||||||
|
val sum = vector1 + vector2
|
||||||
|
assertEquals(5.0, sum[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testVectorToMatrix() {
|
||||||
|
val vector = realVector(5) { it.toDouble() }
|
||||||
|
val matrix = vector.toMatrix()
|
||||||
|
assertEquals(4.0, matrix[4, 0])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDot() {
|
||||||
|
val vector1 = realVector(5) { it.toDouble() }
|
||||||
|
val vector2 = realVector(5) { 5 - it.toDouble() }
|
||||||
|
val product = vector1.toMatrix() dot (vector2.toMatrix().transpose())
|
||||||
|
|
||||||
|
|
||||||
|
assertEquals(5.0, product[1, 0])
|
||||||
|
assertEquals(6.0, product[2, 2])
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class RealLUSolverTest {
|
||||||
|
@Test
|
||||||
|
fun testInvertOne() {
|
||||||
|
val matrix = Matrix.diagonal(2, 2, DoubleField)
|
||||||
|
val inverted = RealLUSolver.inverse(matrix)
|
||||||
|
assertTrue { Matrix.equals(matrix,inverted) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun testInvert() {
|
||||||
|
// val matrix = realMatrix(2,2){}
|
||||||
|
// val inverted = RealLUSolver.inverse(matrix)
|
||||||
|
// assertTrue { Matrix.equals(matrix,inverted) }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package scientifik.kmath.misc
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class CumulativeKtTest {
|
||||||
|
@Test
|
||||||
|
fun testCumulativeSum() {
|
||||||
|
val initial = listOf(-1.0, 2.0, 1.0, 1.0)
|
||||||
|
val cumulative = initial.cumulativeSum()
|
||||||
|
assertEquals(listOf(-1.0, 1.0, 2.0, 3.0), cumulative)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
package scientifik.kmath.structures
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
import scientifik.kmath.structures.NDArrays.real2DArray
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class RealNDFieldTest {
|
class RealNDFieldTest {
|
||||||
val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() }
|
val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() }
|
||||||
@ -11,13 +12,13 @@ class RealNDFieldTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSum() {
|
fun testSum() {
|
||||||
val sum = array1 + array2
|
val sum = array1 + array2
|
||||||
assertEquals(4.0, sum[2, 2], 0.1)
|
assertEquals(4.0, sum[2, 2])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testProduct() {
|
fun testProduct() {
|
||||||
val product = array1 * array2
|
val product = array1 * array2
|
||||||
assertEquals(0.0, product[2, 2], 0.1)
|
assertEquals(0.0, product[2, 2])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -28,7 +29,7 @@ class RealNDFieldTest {
|
|||||||
for (i in 0..2) {
|
for (i in 0..2) {
|
||||||
for (j in 0..2) {
|
for (j in 0..2) {
|
||||||
val expected = (i * 10 + j).toDouble()
|
val expected = (i * 10 + j).toDouble()
|
||||||
assertEquals("Error at index [$i, $j]", expected, array[i, j], 0.1)
|
assertEquals(expected, array[i, j],"Error at index [$i, $j]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,6 +38,6 @@ class RealNDFieldTest {
|
|||||||
fun testExternalFunction() {
|
fun testExternalFunction() {
|
||||||
val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 }
|
val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 }
|
||||||
val result = function(array1) + 1.0
|
val result = function(array1) + 1.0
|
||||||
assertEquals(10.0, result[1,1],0.01)
|
assertEquals(10.0, result[1,1])
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import scientifik.kmath.structures.NDArrays.create
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleNDFieldTest{
|
||||||
|
@Test
|
||||||
|
fun testStrides(){
|
||||||
|
val ndArray = create(DoubleField, listOf(10,10)){(it[0]+it[1]).toDouble()}
|
||||||
|
assertEquals(ndArray[5,5], 10.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package scietifik.kmath.structures
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.*
|
||||||
|
import java.nio.IntBuffer
|
||||||
|
|
||||||
|
|
||||||
|
@Fork(1)
|
||||||
|
@Warmup(iterations = 2)
|
||||||
|
@Measurement(iterations = 50)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
open class ArrayBenchmark {
|
||||||
|
|
||||||
|
lateinit var array: IntArray
|
||||||
|
lateinit var arrayBuffer: IntBuffer
|
||||||
|
lateinit var nativeBuffer: IntBuffer
|
||||||
|
|
||||||
|
@Setup
|
||||||
|
fun setup() {
|
||||||
|
array = IntArray(10000) { it }
|
||||||
|
arrayBuffer = IntBuffer.wrap(array)
|
||||||
|
nativeBuffer = IntBuffer.allocate(10000)
|
||||||
|
for (i in 0 until 10000) {
|
||||||
|
nativeBuffer.put(i,i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
fun benchmarkArrayRead() {
|
||||||
|
var res = 0
|
||||||
|
for (i in 1..10000) {
|
||||||
|
res += array[10000 - i]
|
||||||
|
}
|
||||||
|
print(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
fun benchmarkBufferRead() {
|
||||||
|
var res = 0
|
||||||
|
for (i in 1..10000) {
|
||||||
|
res += arrayBuffer.get(10000 - i)
|
||||||
|
}
|
||||||
|
print(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
fun nativeBufferRead() {
|
||||||
|
var res = 0
|
||||||
|
for (i in 1..10000) {
|
||||||
|
res += nativeBuffer.get(10000 - i)
|
||||||
|
}
|
||||||
|
print(res)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using boxing implementation for js
|
||||||
|
*/
|
||||||
|
actual val realNDFieldFactory: NDFieldFactory<Double> = NDArrays.createFactory(DoubleField)
|
@ -0,0 +1,26 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import java.nio.DoubleBuffer
|
||||||
|
|
||||||
|
private class RealNDField(shape: List<Int>) : BufferNDField<Double>(shape, DoubleField) {
|
||||||
|
override fun createBuffer(capacity: Int, initializer: (Int) -> Double): Buffer<Double> {
|
||||||
|
val array = DoubleArray(capacity, initializer)
|
||||||
|
val buffer = DoubleBuffer.wrap(array)
|
||||||
|
return BufferOfDoubles(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BufferOfDoubles(val buffer: DoubleBuffer): Buffer<Double>{
|
||||||
|
override fun get(index: Int): Double = buffer.get(index)
|
||||||
|
|
||||||
|
override fun set(index: Int, value: Double) {
|
||||||
|
buffer.put(index, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copy(): Buffer<Double> {
|
||||||
|
return BufferOfDoubles(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual val realNDFieldFactory: NDFieldFactory<Double> = { shape -> RealNDField(shape) }
|
@ -1,21 +0,0 @@
|
|||||||
apply plugin: 'kotlin-platform-jvm'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
expectedBy project(":kmath-common")
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
|
||||||
testCompile "junit:junit:4.12"
|
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
|
||||||
}
|
|
||||||
|
|
||||||
compileKotlin {
|
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
compileTestKotlin {
|
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
sourceCompatibility = "1.8"
|
|
@ -1,80 +0,0 @@
|
|||||||
package scientifik.kmath.structures
|
|
||||||
|
|
||||||
import scientifik.kmath.operations.DoubleField
|
|
||||||
import java.nio.DoubleBuffer
|
|
||||||
|
|
||||||
private class RealNDField(shape: List<Int>) : NDField<Double>(shape, DoubleField) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strides for memory access
|
|
||||||
*/
|
|
||||||
private val strides: List<Int> by lazy {
|
|
||||||
ArrayList<Int>(shape.size).apply {
|
|
||||||
var current = 1
|
|
||||||
add(1)
|
|
||||||
shape.forEach {
|
|
||||||
current *= it
|
|
||||||
add(current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun offset(index: List<Int>): Int {
|
|
||||||
return index.mapIndexed { i, value ->
|
|
||||||
if (value < 0 || value >= shape[i]) {
|
|
||||||
throw RuntimeException("Index out of shape bounds: ($i,$value)")
|
|
||||||
}
|
|
||||||
value * strides[i]
|
|
||||||
}.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
val capacity: Int
|
|
||||||
get() = strides[shape.size]
|
|
||||||
|
|
||||||
|
|
||||||
override fun produce(initializer: (List<Int>) -> Double): NDArray<Double> {
|
|
||||||
//TODO use sparse arrays for large capacities
|
|
||||||
val buffer = DoubleBuffer.allocate(capacity)
|
|
||||||
//FIXME there could be performance degradation due to iteration procedure. Replace by straight iteration
|
|
||||||
NDArray.iterateIndexes(shape).forEach {
|
|
||||||
buffer.put(offset(it), initializer(it))
|
|
||||||
}
|
|
||||||
return RealNDArray(this, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
class RealNDArray(override val context: RealNDField, val data: DoubleBuffer) : NDArray<Double> {
|
|
||||||
|
|
||||||
override fun get(vararg index: Int): Double {
|
|
||||||
return data.get(context.offset(index.asList()))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as RealNDArray
|
|
||||||
|
|
||||||
if (context.shape != other.context.shape) return false
|
|
||||||
if (data != other.data) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = context.shape.hashCode()
|
|
||||||
result = 31 * result + data.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO generate fixed hash code for quick comparison?
|
|
||||||
|
|
||||||
|
|
||||||
override val self: NDArray<Double> get() = this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
actual fun realNDArray(shape: List<Int>, initializer: (List<Int>) -> Double): NDArray<Double> {
|
|
||||||
//TODO cache fields?
|
|
||||||
return RealNDField(shape).produce { initializer(it) }
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package scientifik.kmath.structures
|
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class ArrayMatrixTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSum() {
|
|
||||||
val vector1 = realVector(5) { it.toDouble() }
|
|
||||||
val vector2 = realVector(5) { 5 - it.toDouble() }
|
|
||||||
val sum = vector1 + vector2
|
|
||||||
assertEquals(5.0, sum[2, 0], 0.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDot() {
|
|
||||||
val vector1 = realVector(5) { it.toDouble() }
|
|
||||||
val vector2 = realVector(5) { 5 - it.toDouble() }
|
|
||||||
val product = with(vector1.context) {
|
|
||||||
vector1 dot (vector2.transpose())
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(10.0, product[1, 0], 0.1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,13 @@
|
|||||||
rootProject.name = 'kmath'
|
pluginManagement {
|
||||||
include 'kmath-common'
|
repositories {
|
||||||
include 'kmath-jvm'
|
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
||||||
|
mavenCentral()
|
||||||
|
maven { url = 'https://plugins.gradle.org/m2/' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableFeaturePreview('GRADLE_METADATA')
|
||||||
|
|
||||||
|
rootProject.name = 'kmath'
|
||||||
|
include ':kmath-core'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user