pre-0.0.3 #46
61
README.md
61
README.md
@ -1,53 +1,54 @@
|
||||
# KMath
|
||||
Kotlin MATHematics library is intended as a kotlin based analog of numpy python library. Contrary to `numpy`
|
||||
and `scipy` it is modular and has a lightweight core.
|
||||
The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's `numpy` library. In contrast to `numpy` and `scipy` it is modular and has a lightweight core.
|
||||
|
||||
## Features
|
||||
|
||||
* **Algebra**
|
||||
* Mathematical operation entities like rings, spaces and fields with (**TODO** add example to wiki)
|
||||
* 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).
|
||||
* [In progress] advanced linear algebra operations like matrix inversions.
|
||||
* **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).
|
||||
* Algebraic structures like rings, spaces and field (**TODO** add example to wiki)
|
||||
* Basic linear algebra operations (sums, products, etc.), backed by the `Space` API.
|
||||
* Complex numbers backed by the `Field` API (meaning that they will be usable in any structure like vectors and N-dimensional arrays).
|
||||
* [In progress] advanced linear algebra operations like matrix inversion and LU decomposition.
|
||||
* **Array-like structures** Full support of [numpy-like ndarrays](https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.ndarray.html) including mixed arithmetic operations and function operations over arrays and numbers just like in Python (with the added 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. Expressions could be used
|
||||
for a wide variety of purposes from high performance calculations to code generation.
|
||||
* **Expressions** Expressions are one of the ultimate goals of KMath. By writing a single mathematical expression
|
||||
once, users will be able to apply different types of objects to the expression by providing a context. Exceptions
|
||||
can 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.
|
||||
library in Kotlin code and maybe rewrite some parts to better suit the Kotlin programming paradigm, however there is no fixed roadmap for that. Feel free
|
||||
to submit a feature 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
|
||||
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
|
||||
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.
|
||||
|
||||
KMath is developed as a multi-platform library, which means that most of interfaces are declared in the [common module](kmath-core/src/commonMain).
|
||||
Implementation is also done in the common module wherever possible. In some cases, features are delegated to
|
||||
platform-specific implementations even if they could be done in the common module for performance reasons.
|
||||
Currently, the JVM is the main focus of development, however Kotlin/Native and Kotlin/JS contributions are also welcome.
|
||||
|
||||
## Performance
|
||||
The calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve
|
||||
both performance and flexibility. We expect to firstly focus on creating convenient universal API and then work on
|
||||
increasing performance for specific cases. We expect the worst KMath performance still be better than natural python,
|
||||
but worse than optimized native/scipy (mostly due to boxing operations on primitive numbers). The best performance
|
||||
of optimized parts should be better than scipy.
|
||||
|
||||
Calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve
|
||||
both performance and flexibility. We expect to focus on creating convenient universal API first and then work on
|
||||
increasing performance for specific cases. We expect the worst KMath benchmarks will perform better than native Python,
|
||||
but worse than optimized native/SciPy (mostly due to boxing operations on primitive numbers). The best performance
|
||||
of optimized parts should be better than SciPy.
|
||||
|
||||
## Releases
|
||||
|
||||
The project is currently in pre-release stage. Nightly builds could be used by adding additional repository to (groovy) gradle config:
|
||||
The project is currently in pre-release stage. Nightly builds can be used by adding an additional repository to the Gradle config like so:
|
||||
|
||||
```groovy
|
||||
repositories {
|
||||
maven { url = "http://npm.mipt.ru:8081/artifactory/gradle-dev" }
|
||||
mavenCentral()
|
||||
}
|
||||
```
|
||||
or for kotlin gradle dsl:
|
||||
|
||||
or for the Gradle Kotlin DSL:
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
@ -56,16 +57,20 @@ repositories {
|
||||
}
|
||||
```
|
||||
|
||||
Then use regular dependency like
|
||||
Then use a regular dependency like so:
|
||||
|
||||
```groovy
|
||||
compile(group: 'scientifik', name: 'kmath-core', version: '0.0.1-SNAPSHOT')
|
||||
```
|
||||
or in kotlin
|
||||
|
||||
or in the Gradle Kotlin DSL:
|
||||
|
||||
```kotlin
|
||||
compile(group = "scientifik", name = "kmath-core", version = "0.0.1-SNAPSHOT")
|
||||
```
|
||||
|
||||
Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath).
|
||||
Working builds can be obtained here: [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath).
|
||||
|
||||
## Contributing
|
||||
|
||||
The project requires a lot of additional work. Please fill free to contribute in any way and propose new features.
|
39
benchmarks/build.gradle
Normal file
39
benchmarks/build.gradle
Normal file
@ -0,0 +1,39 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "me.champeau.gradle.jmh" version "0.4.8"
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
|
||||
maven{ url "http://dl.bintray.com/kyonifer/maven"}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":kmath-core")
|
||||
implementation project(":kmath-coroutines")
|
||||
implementation project(":kmath-commons")
|
||||
implementation project(":kmath-koma")
|
||||
implementation group: "com.kyonifer", name:"koma-core-ejml", version: "0.12"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-io-jvm:0.1.5"
|
||||
//compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
//jmh project(':kmath-core')
|
||||
}
|
||||
|
||||
jmh{
|
||||
warmupIterations = 1
|
||||
}
|
||||
|
||||
jmhClasses.dependsOn(compileKotlin)
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark
|
||||
import org.openjdk.jmh.annotations.Scope
|
||||
import org.openjdk.jmh.annotations.State
|
||||
import java.nio.IntBuffer
|
||||
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
open class ArrayBenchmark {
|
||||
|
||||
@Benchmark
|
||||
fun benchmarkArrayRead() {
|
||||
var res = 0
|
||||
for (i in 1..size) {
|
||||
res += array[size - i]
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun benchmarkBufferRead() {
|
||||
var res = 0
|
||||
for (i in 1..size) {
|
||||
res += arrayBuffer.get(size - i)
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun nativeBufferRead() {
|
||||
var res = 0
|
||||
for (i in 1..size) {
|
||||
res += nativeBuffer.get(size - i)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val size = 1000
|
||||
|
||||
val array = IntArray(size) { it }
|
||||
val arrayBuffer = IntBuffer.wrap(array)
|
||||
val nativeBuffer = IntBuffer.allocate(size).also {
|
||||
for (i in 0 until size) {
|
||||
it.put(i, i)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import org.openjdk.jmh.annotations.*
|
||||
import org.openjdk.jmh.annotations.Benchmark
|
||||
import org.openjdk.jmh.annotations.Scope
|
||||
import org.openjdk.jmh.annotations.State
|
||||
import scientifik.kmath.operations.Complex
|
||||
|
||||
@Warmup(iterations = 1)
|
||||
@Measurement(iterations = 5)
|
||||
@State(Scope.Benchmark)
|
||||
open class BufferBenchmark {
|
||||
|
||||
@ -22,7 +22,7 @@ open class BufferBenchmark {
|
||||
|
||||
@Benchmark
|
||||
fun complexBufferReadWrite() {
|
||||
val buffer = Complex.createBuffer(size/2)
|
||||
val buffer = MutableBuffer.complex(size / 2)
|
||||
(0 until size / 2).forEach {
|
||||
buffer[it] = Complex(it.toDouble(), -it.toDouble())
|
||||
}
|
||||
@ -33,6 +33,6 @@ open class BufferBenchmark {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val size = 1000
|
||||
const val size = 100
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark
|
||||
import scientifik.kmath.operations.RealField
|
||||
|
||||
open class NDFieldBenchmark {
|
||||
|
||||
@Benchmark
|
||||
fun autoFieldAdd() {
|
||||
bufferedField.run {
|
||||
var res: NDBuffer<Double> = one
|
||||
repeat(n) {
|
||||
res += one
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun autoElementAdd() {
|
||||
var res = genericField.one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun specializedFieldAdd() {
|
||||
specializedField.run {
|
||||
var res: NDBuffer<Double> = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
fun lazyFieldAdd() {
|
||||
lazyNDField.run {
|
||||
var res = one
|
||||
repeat(n) {
|
||||
res += one
|
||||
}
|
||||
|
||||
res.elements().sumByDouble { it.second }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
fun boxingFieldAdd() {
|
||||
genericField.run {
|
||||
var res: NDBuffer<Double> = one
|
||||
repeat(n) {
|
||||
res += one
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val dim = 1000
|
||||
val n = 100
|
||||
|
||||
val bufferedField = NDField.auto(RealField, intArrayOf(dim, dim))
|
||||
val specializedField = NDField.real(intArrayOf(dim, dim))
|
||||
val genericField = NDField.buffered(intArrayOf(dim, dim), RealField)
|
||||
val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField)
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import koma.matrix.ejml.EJMLMatrixFactory
|
||||
import kotlin.random.Random
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
fun main() {
|
||||
val random = Random(12224)
|
||||
val dim = 100
|
||||
//creating invertible matrix
|
||||
val u = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
|
||||
val l = Matrix.real(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 }
|
||||
val matrix = l dot u
|
||||
|
||||
val n = 500 // iterations
|
||||
|
||||
val solver = LUSolver.real
|
||||
|
||||
repeat(50) {
|
||||
val res = solver.inverse(matrix)
|
||||
}
|
||||
|
||||
val inverseTime = measureTimeMillis {
|
||||
repeat(n) {
|
||||
val res = solver.inverse(matrix)
|
||||
}
|
||||
}
|
||||
|
||||
println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis")
|
||||
|
||||
//commons-math
|
||||
|
||||
val cmContext = CMMatrixContext
|
||||
|
||||
val commonsTime = measureTimeMillis {
|
||||
cmContext.run {
|
||||
val cm = matrix.toCM() //avoid overhead on conversion
|
||||
repeat(n) {
|
||||
val res = inverse(cm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
println("[commons-math] Inversion of $n matrices $dim x $dim finished in $commonsTime millis")
|
||||
|
||||
//koma-ejml
|
||||
|
||||
val komaContext = KomaMatrixContext(EJMLMatrixFactory())
|
||||
|
||||
val komaTime = measureTimeMillis {
|
||||
komaContext.run {
|
||||
val km = matrix.toKoma() //avoid overhead on conversion
|
||||
repeat(n) {
|
||||
val res = inverse(km)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("[koma-ejml] Inversion of $n matrices $dim x $dim finished in $komaTime millis")
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import koma.matrix.ejml.EJMLMatrixFactory
|
||||
import kotlin.random.Random
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
fun main() {
|
||||
val random = Random(12224)
|
||||
val dim = 1000
|
||||
//creating invertible matrix
|
||||
val matrix1 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
|
||||
val matrix2 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
|
||||
|
||||
// //warmup
|
||||
// matrix1 dot matrix2
|
||||
|
||||
CMMatrixContext.run {
|
||||
val cmMatrix1 = matrix1.toCM()
|
||||
val cmMatrix2 = matrix2.toCM()
|
||||
|
||||
val cmTime = measureTimeMillis {
|
||||
cmMatrix1 dot cmMatrix2
|
||||
}
|
||||
|
||||
println("CM implementation time: $cmTime")
|
||||
}
|
||||
|
||||
|
||||
KomaMatrixContext(EJMLMatrixFactory()).run {
|
||||
val komaMatrix1 = matrix1.toKoma()
|
||||
val komaMatrix2 = matrix2.toKoma()
|
||||
|
||||
val komaTime = measureTimeMillis {
|
||||
komaMatrix1 dot komaMatrix2
|
||||
}
|
||||
|
||||
println("Koma-ejml implementation time: $komaTime")
|
||||
}
|
||||
|
||||
val genericTime = measureTimeMillis {
|
||||
val res = matrix1 dot matrix2
|
||||
}
|
||||
|
||||
println("Generic implementation time: $genericTime")
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
fun main() {
|
||||
val dim = 1000
|
||||
val n = 1000
|
||||
|
||||
val realField = NDField.real(dim, dim)
|
||||
val complexField = NDField.complex(dim, dim)
|
||||
|
||||
|
||||
val realTime = measureTimeMillis {
|
||||
realField.run {
|
||||
var res: NDBuffer<Double> = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Real addition completed in $realTime millis")
|
||||
|
||||
val complexTime = measureTimeMillis {
|
||||
complexField.run {
|
||||
var res: ComplexNDElement = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Complex addition completed in $complexTime millis")
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import scientifik.kmath.operations.RealField
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val dim = 1000
|
||||
val n = 1000
|
||||
|
||||
// automatically build context most suited for given type.
|
||||
val autoField = NDField.auto(RealField, dim, dim)
|
||||
// specialized nd-field for Double. It works as generic Double field as well
|
||||
val specializedField = NDField.real(dim, dim)
|
||||
//A generic boxing field. It should be used for objects, not primitives.
|
||||
val genericField = NDField.buffered(intArrayOf(dim, dim), RealField)
|
||||
|
||||
|
||||
val autoTime = measureTimeMillis {
|
||||
autoField.run {
|
||||
var res = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Automatic field addition completed in $autoTime millis")
|
||||
|
||||
val elementTime = measureTimeMillis {
|
||||
var res = genericField.one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
|
||||
println("Element addition completed in $elementTime millis")
|
||||
|
||||
val specializedTime = measureTimeMillis {
|
||||
specializedField.run {
|
||||
var res: NDBuffer<Double> = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Specialized addition completed in $specializedTime millis")
|
||||
|
||||
|
||||
val lazyTime = measureTimeMillis {
|
||||
val res = specializedField.one.mapAsync(GlobalScope) {
|
||||
var c = 0.0
|
||||
repeat(n) {
|
||||
c += 1.0
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
res.elements().forEach { it.second }
|
||||
}
|
||||
|
||||
println("Lazy addition completed in $lazyTime millis")
|
||||
|
||||
val genericTime = measureTimeMillis {
|
||||
//genericField.run(action)
|
||||
genericField.run {
|
||||
var res: NDBuffer<Double> = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Generic addition completed in $genericTime millis")
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val n = 6000
|
||||
|
||||
val array = DoubleArray(n * n) { 1.0 }
|
||||
val buffer = DoubleBuffer(array)
|
||||
val strides = DefaultStrides(intArrayOf(n, n))
|
||||
|
||||
val structure = BufferNDStructure(strides, buffer)
|
||||
|
||||
measureTimeMillis {
|
||||
var res: Double = 0.0
|
||||
strides.indices().forEach { res = structure[it] }
|
||||
} // warmup
|
||||
|
||||
val time1 = measureTimeMillis {
|
||||
var res: Double = 0.0
|
||||
strides.indices().forEach { res = structure[it] }
|
||||
}
|
||||
println("Structure reading finished in $time1 millis")
|
||||
|
||||
val time2 = measureTimeMillis {
|
||||
var res: Double = 0.0
|
||||
strides.indices().forEach { res = buffer[strides.offset(it)] }
|
||||
}
|
||||
println("Buffer reading finished in $time2 millis")
|
||||
|
||||
val time3 = measureTimeMillis {
|
||||
var res: Double = 0.0
|
||||
strides.indices().forEach { res = array[strides.offset(it)] }
|
||||
}
|
||||
println("Array reading finished in $time3 millis")
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
val n = 6000
|
||||
|
||||
val structure = NDStructure.build(intArrayOf(n, n), DoubleBufferFactory) { 1.0 }
|
||||
|
||||
structure.mapToBuffer { it + 1 } // warm-up
|
||||
|
||||
val time1 = measureTimeMillis {
|
||||
val res = structure.mapToBuffer { it + 1 }
|
||||
}
|
||||
println("Structure mapping finished in $time1 millis")
|
||||
|
||||
val array = DoubleArray(n * n) { 1.0 }
|
||||
|
||||
val time2 = measureTimeMillis {
|
||||
val target = DoubleArray(n * n)
|
||||
val res = array.forEachIndexed { index, value ->
|
||||
target[index] = value + 1
|
||||
}
|
||||
}
|
||||
println("Array mapping finished in $time2 millis")
|
||||
|
||||
val buffer = DoubleBuffer(DoubleArray(n * n) { 1.0 })
|
||||
|
||||
val time3 = measureTimeMillis {
|
||||
val target = DoubleBuffer(DoubleArray(n * n))
|
||||
val res = array.forEachIndexed { index, value ->
|
||||
target[index] = value + 1
|
||||
}
|
||||
}
|
||||
println("Buffer mapping finished in $time3 millis")
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
buildscript {
|
||||
extra["kotlinVersion"] = "1.3.11"
|
||||
extra["ioVersion"] = "0.1.2-dev-2"
|
||||
extra["coroutinesVersion"] = "1.0.1"
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
|
||||
val kotlinVersion: String by extra
|
||||
val ioVersion: String by extra
|
||||
val coroutinesVersion: String by extra
|
||||
buildscript {
|
||||
val kotlinVersion: String by rootProject.extra("1.3.21")
|
||||
val ioVersion: String by rootProject.extra("0.1.5")
|
||||
val coroutinesVersion: String by rootProject.extra("1.1.1")
|
||||
val atomicfuVersion: String by rootProject.extra("0.12.1")
|
||||
|
||||
repositories {
|
||||
//maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
jcenter()
|
||||
}
|
||||
|
||||
@ -18,16 +18,37 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.jfrog.artifactory") version "4.8.1" apply false
|
||||
// id("org.jetbrains.kotlin.multiplatform") apply false
|
||||
id("com.jfrog.artifactory") version "4.9.1" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
if (project.name.startsWith("kmath")) {
|
||||
apply(plugin = "maven-publish")
|
||||
apply(plugin = "com.jfrog.artifactory")
|
||||
}
|
||||
|
||||
group = "scientifik"
|
||||
version = "0.0.2-dev-1"
|
||||
version = "0.0.3-dev"
|
||||
|
||||
repositories {
|
||||
//maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
jcenter()
|
||||
}
|
||||
|
||||
extensions.findByType<KotlinMultiplatformExtension>()?.apply {
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
targets.all {
|
||||
sourceSets.all {
|
||||
languageSettings.progressiveMode = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file("artifactory.gradle").exists()) {
|
||||
|
70
doc/algebra.md
Normal file
70
doc/algebra.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Algebra and algebra elements
|
||||
|
||||
The mathematical operations in `kmath` are generally separated from mathematical objects.
|
||||
This means that in order to perform an operation, say `+`, one needs two objects of a type `T` and
|
||||
and algebra context which defines appropriate operation, say `Space<T>`. Next one needs to run actual operation
|
||||
in the context:
|
||||
|
||||
```kotlin
|
||||
val a: T
|
||||
val b: T
|
||||
val space: Space<T>
|
||||
|
||||
val c = space.run{a + b}
|
||||
```
|
||||
|
||||
From the first glance, this distinction seems to be a needless complication, but in fact one needs
|
||||
to remember that in mathematics, one could define different operations on the same objects. For example,
|
||||
one could use different types of geometry for vectors.
|
||||
|
||||
## Algebra hierarchy
|
||||
|
||||
Mathematical contexts have the following hierarchy:
|
||||
|
||||
**Space** <- **Ring** <- **Field**
|
||||
|
||||
All classes follow abstract mathematical constructs.
|
||||
[Space](http://mathworld.wolfram.com/Space.html) defines `zero` element, addition operation and multiplication by constant,
|
||||
[Ring](http://mathworld.wolfram.com/Ring.html) adds multiplication and unit `one` element,
|
||||
[Field](http://mathworld.wolfram.com/Field.html) adds division operation.
|
||||
|
||||
Typical case of `Field` is the `RealField` which works on doubles. And typical case of `Space` is a `VectorSpace`.
|
||||
|
||||
In some cases algebra context could hold additional operation like `exp` or `sin`, in this case it inherits appropriate
|
||||
interface. Also a context could have an operation which produces an element outside of its context. For example
|
||||
`Matrix` `dot` operation produces a matrix with new dimensions which could not be compatible with initial matrix in
|
||||
terms of linear operations.
|
||||
|
||||
## Algebra element
|
||||
|
||||
In order to achieve more familiar behavior (where you apply operations directly to mathematica objects), without involving contexts
|
||||
`kmath` introduces special type objects called `MathElement`. A `MathElement` is basically some object coupled to
|
||||
a mathematical context. For example `Complex` is the pair of real numbers representing real and imaginary parts,
|
||||
but it also holds reference to the `ComplexField` singleton which allows to perform direct operations on `Complex`
|
||||
numbers without explicit involving the context like:
|
||||
|
||||
```kotlin
|
||||
val c1 = Complex(1.0, 1.0)
|
||||
val c2 = Complex(1.0, -1.0)
|
||||
val c3 = c1 + c2 + 3.0.toComplex()
|
||||
//or with field notation:
|
||||
val c4 = ComplexField.run{c1 + i - 2.0}
|
||||
```
|
||||
|
||||
Both notations have their pros and cons.
|
||||
|
||||
The hierarchy for algebra elements follows the hierarchy for the corresponding algebra.
|
||||
|
||||
**MathElement** <- **SpaceElement** <- **RingElement** <- **FieldElement**
|
||||
|
||||
**MathElement** is the generic common ancestor of the class with context.
|
||||
|
||||
One important distinction between algebra elements and algebra contexts is that algebra element has three type parameters:
|
||||
|
||||
1. The type of elements, field operates on.
|
||||
2. The self-type of the element returned from operation (must be algebra element).
|
||||
3. The type of the algebra over first type-parameter.
|
||||
|
||||
The middle type is needed in case algebra members do not store context. For example, it is not possible to add
|
||||
a context to regular `Double`. The element performs automatic conversions from context types and back.
|
||||
One should used context operations in all important places. The performance of element operations is not guaranteed.
|
1
doc/buffers.md
Normal file
1
doc/buffers.md
Normal file
@ -0,0 +1 @@
|
||||
**TODO**
|
73
doc/contexts.md
Normal file
73
doc/contexts.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Context-oriented mathematics
|
||||
|
||||
## The problem
|
||||
|
||||
A known problem for implementing mathematics in statically-typed languages (but not only in them) is that different
|
||||
sets of mathematical operators can be defined on the same mathematical objects. Sometimes there is no single way to
|
||||
treat some operations, including basic arithmetic operations, on a Java/Kotlin `Number`. Sometimes there are different ways to
|
||||
define the same structure, such as Euclidean and elliptic geometry vector spaces over real vectors. Another problem arises when
|
||||
one wants to add some kind of behavior to an existing entity. In dynamic languages those problems are usually solved
|
||||
by adding dynamic context-specific behaviors at runtime, but this solution has a lot of drawbacks.
|
||||
|
||||
## Context-oriented approach
|
||||
|
||||
One possible solution to these problems is to divorce numerical representations from behaviors.
|
||||
For example in Kotlin one can define a separate class which represents some entity without any operations,
|
||||
ex. a complex number:
|
||||
|
||||
```kotlin
|
||||
data class Complex(val re: Double, val im: Double)
|
||||
```
|
||||
|
||||
And then to define a separate class or singleton, representing an operation on those complex numbers:
|
||||
|
||||
```kotlin
|
||||
object ComplexOperations {
|
||||
operator fun Complex.plus(other: Complex) = Complex(re + other.re, im + other.im)
|
||||
operator fun Complex.minus(other: Complex) = Complex(re - other.re, im - other.im)
|
||||
}
|
||||
```
|
||||
|
||||
In Java, applying such external operations could be very cumbersome, but Kotlin has a unique feature which allows us
|
||||
implement this naturally: [extensions with receivers](https://kotlinlang.org/docs/reference/extensions.html#extension-functions).
|
||||
In Kotlin, an operation on complex number could be implemented as:
|
||||
|
||||
```kotlin
|
||||
with(ComplexOperations) { c1 + c2 - c3 }
|
||||
```
|
||||
|
||||
Kotlin also allows the creation of functions with receivers:
|
||||
|
||||
```kotlin
|
||||
fun ComplexOperations.doSomethingWithComplex(c1: Complex, c2: Complex, c3: Complex) = c1 + c2 - c3
|
||||
|
||||
ComplexOperations.doComethingWithComplex(c1, c2, c3)
|
||||
```
|
||||
|
||||
In fact, whole parts of a program may be run within a mathematical context or even multiple nested contexts.
|
||||
|
||||
In KMath, contexts are not only responsible for operations, but also for raw object creation and advanced features.
|
||||
|
||||
## Other possibilities
|
||||
|
||||
### Type classes
|
||||
|
||||
An obvious candidate to get more or less the same functionality is the type class, which allows one to bind a behavior to
|
||||
a specific type without modifying the type itself. On the plus side, type classes do not require explicit context
|
||||
declaration, so the code looks cleaner. On the minus side, if there are different sets of behaviors for the same types,
|
||||
it is impossible to combine them into one module. Also, unlike type classes, context can have parameters or even
|
||||
state. For example in KMath, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize
|
||||
performance in case of a large amount of structures.
|
||||
|
||||
### Wildcard imports and importing-on-demand
|
||||
|
||||
Sometimes, one may wish to use a single context throughout a file. In this case, is possible to import all members
|
||||
from a package or file, via `import context.complex.*`. Effectively, this is the same as enclosing an entire file
|
||||
with a single context. However when using multiple contexts, this technique can introduce operator ambiguity, due to
|
||||
namespace pollution. If there are multiple scoped contexts which define the same operation, it is still possible to
|
||||
to import specific operations as needed, without using an explicit context with extension functions, for example:
|
||||
|
||||
```
|
||||
import context.complex.op1
|
||||
import context.quaternion.op2
|
||||
```
|
26
doc/expressions.md
Normal file
26
doc/expressions.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Expressions
|
||||
|
||||
**Experimental: this API is in early stage and could change any time**
|
||||
|
||||
Expressions is an experimental feature which allows to construct lazily or immediately calculated parametric mathematical
|
||||
expressions.
|
||||
|
||||
The potential use-cases for it (so far) are following:
|
||||
|
||||
* Lazy evaluation (in general simple lambda is better, but there are some border cases)
|
||||
|
||||
* Automatic differentiation in single-dimension and in multiple dimensions
|
||||
|
||||
* Generation of mathematical syntax trees with subsequent code generation for other languages
|
||||
|
||||
* Maybe symbolic computations (needs additional research)
|
||||
|
||||
The workhorse of this API is `Expression` interface which exposes single `operator fun invoke(arguments: Map<String, T>): T`
|
||||
method. `ExpressionContext` is used to generate expressions and introduce variables.
|
||||
|
||||
Currently there are two implementations:
|
||||
|
||||
* Generic `ExpressionField` in `kmath-core` which allows construction of custom lazy expressions
|
||||
|
||||
* Auto-differentiation expression in `kmath-commons` module allows to use full power of `DerivativeStructure`
|
||||
from commons-math. **TODO: add example**
|
0
doc/features.md
Normal file
0
doc/features.md
Normal file
1
doc/histograms.md
Normal file
1
doc/histograms.md
Normal file
@ -0,0 +1 @@
|
||||
**TODO**
|
1
doc/linear.md
Normal file
1
doc/linear.md
Normal file
@ -0,0 +1 @@
|
||||
**TODO**
|
127
doc/nd-performance.md
Normal file
127
doc/nd-performance.md
Normal file
@ -0,0 +1,127 @@
|
||||
# Performance for n-dimensional structures operations
|
||||
|
||||
One of the most sought after features of mathematical libraries is the high-performance operations on n-dimensional
|
||||
structures. In `kmath` performance depends on which particular context was used for operation.
|
||||
|
||||
Let us consider following contexts:
|
||||
```kotlin
|
||||
// specialized nd-field for Double. It works as generic Double field as well
|
||||
val specializedField = NDField.real(intArrayOf(dim, dim))
|
||||
|
||||
// automatically build context most suited for given type.
|
||||
val autoField = NDField.auto(intArrayOf(dim, dim), RealField)
|
||||
|
||||
//A field implementing lazy computations. All elements are computed on-demand
|
||||
val lazyField = NDField.lazy(intArrayOf(dim, dim), RealField)
|
||||
|
||||
//A generic boxing field. It should be used for objects, not primitives.
|
||||
val genericField = NDField.buffered(intArrayOf(dim, dim), RealField)
|
||||
```
|
||||
Now let us perform several tests and see which implementation is best suited for each case:
|
||||
|
||||
## Test case
|
||||
|
||||
In order to test performance we will take 2d-structures with `dim = 1000` and add a structure filled with `1.0`
|
||||
to it `n = 1000` times.
|
||||
|
||||
## Specialized
|
||||
The code to run this looks like:
|
||||
```kotlin
|
||||
specializedField.run {
|
||||
var res = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
The performance of this code is the best of all tests since it inlines all operations and is specialized for operation
|
||||
with doubles. We will measure everything else relative to this one, so time for this test will be `1x` (real time
|
||||
on my computer is about 4.5 seconds). The only problem with this approach is that it requires to specify type
|
||||
from the beginning. Everyone do so anyway, so it is the recommended approach.
|
||||
|
||||
## Automatic
|
||||
Let's do the same with automatic field inference:
|
||||
```kotlin
|
||||
autoField.run {
|
||||
var res = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
Ths speed of this operation is approximately the same as for specialized case since `NDField.auto` just
|
||||
returns the same `RealNDField` in this case. Of course it is usually better to use specialized method to be sure.
|
||||
|
||||
## Lazy
|
||||
Lazy field does not produce a structure when asked, instead it generates an empty structure and fills it on-demand
|
||||
using coroutines to parallelize computations.
|
||||
When one calls
|
||||
```kotlin
|
||||
lazyField.run {
|
||||
var res = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
The result will be calculated almost immediately but the result will be empty. In order to get the full result
|
||||
structure one needs to call all its elements. In this case computation overhead will be huge. So this field never
|
||||
should be used if one expects to use the full result structure. Though if one wants only small fraction, it could
|
||||
save a lot of time.
|
||||
|
||||
This field still could be used with reasonable performance if call code is changed:
|
||||
```kotlin
|
||||
lazyField.run {
|
||||
val res = one.map {
|
||||
var c = 0.0
|
||||
repeat(n) {
|
||||
c += 1.0
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
res.elements().forEach { it.second }
|
||||
}
|
||||
```
|
||||
In this case it completes in about `4x-5x` time due to boxing.
|
||||
|
||||
## Boxing
|
||||
The boxing field produced by
|
||||
```kotlin
|
||||
genericField.run {
|
||||
var res = one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
obviously is the slowest one, because it requires to box and unbox the `double` on each operation. It takes about
|
||||
`15x` time (**TODO: there seems to be a problem here, it should be slow, but not that slow**). This field should
|
||||
never be used for primitives.
|
||||
|
||||
## Element operation
|
||||
Let us also check the speed for direct operations on elements:
|
||||
```kotlin
|
||||
var res = genericField.one
|
||||
repeat(n) {
|
||||
res += 1.0
|
||||
}
|
||||
```
|
||||
One would expect to be at least as slow as field operation, but in fact, this one takes only `2x` time to complete.
|
||||
It happens, because in this particular case it does not use actual `NDField` but instead calculated directly
|
||||
via extension function.
|
||||
|
||||
## What about python?
|
||||
|
||||
Usually it is bad idea to compare the direct numerical operation performance in different languages, but it hard to
|
||||
work completely without frame of reference. In this case, simple numpy code:
|
||||
```python
|
||||
res = np.ones((1000,1000))
|
||||
for i in range(1000):
|
||||
res = res + 1.0
|
||||
```
|
||||
gives the completion time of about `1.1x`, which means that specialized kotlin code in fact is working faster (I think it is
|
||||
because better memory management). Of course if one writes `res += 1.0`, the performance will be different,
|
||||
but it would be differenc case, because numpy overrides `+=` with in-place operations. In-place operations are
|
||||
available in `kmath` with `MutableNDStructure` but there is no field for it (one can still work with mapping
|
||||
functions).
|
40
doc/operations.md
Normal file
40
doc/operations.md
Normal file
@ -0,0 +1,40 @@
|
||||
## Spaces and fields
|
||||
|
||||
An obvious first choice of mathematical objects to implement in a context-oriented style are algebraic elements like spaces,
|
||||
rings and fields. Those are located in the `scientifik.kmath.operations.Algebra.kt` file. Alongside common contexts, the file includes definitions for algebra elements like `FieldElement`. A `FieldElement` object
|
||||
stores a reference to the `Field` which contains additive and multiplicative operations, meaning
|
||||
it has one fixed context attached and does not require explicit external context. So those `MathElements` can be operated without context:
|
||||
|
||||
```kotlin
|
||||
val c1 = Complex(1.0, 2.0)
|
||||
val c2 = ComplexField.i
|
||||
val c3 = c1 + c2
|
||||
```
|
||||
|
||||
`ComplexField` also features special operations to mix complex and real numbers, for example:
|
||||
|
||||
```kotlin
|
||||
val c1 = Complex(1.0, 2.0)
|
||||
val c2 = ComplexField.run{ c1 - 1.0} // Returns: [re:0.0, im: 2.0]
|
||||
val c3 = ComplexField.run{ c1 - i*2.0}
|
||||
```
|
||||
|
||||
**Note**: In theory it is possible to add behaviors directly to the context, but currently kotlin syntax does not support
|
||||
that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) and [KEEP-176](https://github.com/Kotlin/KEEP/pull/176) for updates.
|
||||
|
||||
## Nested fields
|
||||
|
||||
Contexts allow one to build more complex structures. For example, it is possible to create a `Matrix` from complex elements like so:
|
||||
|
||||
```kotlin
|
||||
val element = NDElement.complex(shape = intArrayOf(2,2)){ index: IntArray ->
|
||||
Complex(index[0].toDouble() - index[1].toDouble(), index[0].toDouble() + index[1].toDouble())
|
||||
}
|
||||
```
|
||||
|
||||
The `element` in this example is a member of the `Field` of 2-d structures, each element of which is a member of its own
|
||||
`ComplexField`. The important thing is one does not need to create a special n-d class to hold complex
|
||||
numbers and implement operations on it, one just needs to provide a field for its elements.
|
||||
|
||||
**Note**: Fields themselves do not solve the problem of JVM boxing, but it is possible to solve with special contexts like
|
||||
`BufferSpec`. This feature is in development phase.
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
13
kmath-commons/build.gradle.kts
Normal file
13
kmath-commons/build.gradle.kts
Normal file
@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
description = "Commons math binding for kmath"
|
||||
|
||||
dependencies {
|
||||
api(project(":kmath-core"))
|
||||
api(project(":kmath-sequential"))
|
||||
api("org.apache.commons:commons-math3:3.6.1")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package scientifik.kmath.expressions
|
||||
|
||||
import org.apache.commons.math3.analysis.differentiation.DerivativeStructure
|
||||
import scientifik.kmath.operations.ExtendedField
|
||||
import scientifik.kmath.operations.ExtendedFieldOperations
|
||||
import scientifik.kmath.operations.Field
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A field wrapping commons-math derivative structures
|
||||
*/
|
||||
class DerivativeStructureField(val order: Int, val parameters: Map<String, Double>) :
|
||||
ExtendedField<DerivativeStructure> {
|
||||
|
||||
override val zero: DerivativeStructure by lazy { DerivativeStructure(order, parameters.size) }
|
||||
|
||||
override val one: DerivativeStructure by lazy { DerivativeStructure(order, parameters.size, 1.0) }
|
||||
|
||||
private val variables: Map<String, DerivativeStructure> = parameters.mapValues { (key, value) ->
|
||||
DerivativeStructure(parameters.size, order, parameters.keys.indexOf(key), value)
|
||||
}
|
||||
|
||||
val variable = object : ReadOnlyProperty<Any?, DerivativeStructure> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): DerivativeStructure {
|
||||
return variables[property.name] ?: error("A variable with name ${property.name} does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
fun variable(name: String, default: DerivativeStructure? = null): DerivativeStructure =
|
||||
variables[name] ?: default ?: error("A variable with name $name does not exist")
|
||||
|
||||
|
||||
fun Number.const() = DerivativeStructure(order, parameters.size, toDouble())
|
||||
|
||||
fun DerivativeStructure.deriv(parName: String, order: Int = 1): Double {
|
||||
return deriv(mapOf(parName to order))
|
||||
}
|
||||
|
||||
fun DerivativeStructure.deriv(orders: Map<String, Int>): Double {
|
||||
return getPartialDerivative(*parameters.keys.map { orders[it] ?: 0 }.toIntArray())
|
||||
}
|
||||
|
||||
fun DerivativeStructure.deriv(vararg orders: Pair<String, Int>): Double = deriv(mapOf(*orders))
|
||||
|
||||
override fun add(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.add(b)
|
||||
|
||||
override fun multiply(a: DerivativeStructure, k: Number): DerivativeStructure = when (k) {
|
||||
is Double -> a.multiply(k)
|
||||
is Int -> a.multiply(k)
|
||||
else -> a.multiply(k.toDouble())
|
||||
}
|
||||
|
||||
override fun multiply(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.multiply(b)
|
||||
|
||||
override fun divide(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.divide(b)
|
||||
|
||||
override fun sin(arg: DerivativeStructure): DerivativeStructure = arg.sin()
|
||||
|
||||
override fun cos(arg: DerivativeStructure): DerivativeStructure = arg.cos()
|
||||
|
||||
override fun power(arg: DerivativeStructure, pow: Number): DerivativeStructure = when (pow) {
|
||||
is Double -> arg.pow(pow)
|
||||
is Int -> arg.pow(pow)
|
||||
else -> arg.pow(pow.toDouble())
|
||||
}
|
||||
|
||||
fun power(arg: DerivativeStructure, pow: DerivativeStructure): DerivativeStructure = arg.pow(pow)
|
||||
|
||||
override fun exp(arg: DerivativeStructure): DerivativeStructure = arg.exp()
|
||||
|
||||
override fun ln(arg: DerivativeStructure): DerivativeStructure = arg.log()
|
||||
|
||||
operator fun DerivativeStructure.plus(n: Number): DerivativeStructure = add(n.toDouble())
|
||||
operator fun DerivativeStructure.minus(n: Number): DerivativeStructure = subtract(n.toDouble())
|
||||
operator fun Number.plus(s: DerivativeStructure) = s + this
|
||||
operator fun Number.minus(s: DerivativeStructure) = s - this
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructs that creates a derivative structure with required order on-demand
|
||||
*/
|
||||
class DiffExpression(val function: DerivativeStructureField.() -> DerivativeStructure) : Expression<Double> {
|
||||
override fun invoke(arguments: Map<String, Double>): Double = DerivativeStructureField(0, arguments)
|
||||
.run(function).value
|
||||
|
||||
/**
|
||||
* Get the derivative expression with given orders
|
||||
* TODO make result [DiffExpression]
|
||||
*/
|
||||
fun derivative(orders: Map<String, Int>): Expression<Double> {
|
||||
return object : Expression<Double> {
|
||||
override fun invoke(arguments: Map<String, Double>): Double =
|
||||
DerivativeStructureField(orders.values.max() ?: 0, arguments)
|
||||
.run {
|
||||
function().deriv(orders)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO add gradient and maybe other vector operators
|
||||
}
|
||||
|
||||
fun DiffExpression.derivative(vararg orders: Pair<String, Int>) = derivative(mapOf(*orders))
|
||||
fun DiffExpression.derivative(name: String) = derivative(name to 1)
|
||||
|
||||
/**
|
||||
* A context for [DiffExpression] (not to be confused with [DerivativeStructure])
|
||||
*/
|
||||
object DiffExpressionContext : ExpressionContext<Double>, Field<DiffExpression> {
|
||||
override fun variable(name: String, default: Double?) = DiffExpression { variable(name, default?.const()) }
|
||||
|
||||
override fun const(value: Double): DiffExpression = DiffExpression { value.const() }
|
||||
|
||||
override fun add(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) + b.function(this) }
|
||||
|
||||
override val zero = DiffExpression { 0.0.const() }
|
||||
|
||||
override fun multiply(a: DiffExpression, k: Number) = DiffExpression { a.function(this) * k }
|
||||
|
||||
override val one = DiffExpression { 1.0.const() }
|
||||
|
||||
override fun multiply(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) * b.function(this) }
|
||||
|
||||
override fun divide(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) / b.function(this) }
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,93 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import org.apache.commons.math3.linear.*
|
||||
import org.apache.commons.math3.linear.RealMatrix
|
||||
import org.apache.commons.math3.linear.RealVector
|
||||
|
||||
class CMMatrix(val origin: RealMatrix, features: Set<MatrixFeature>? = null) : Matrix<Double> {
|
||||
override val rowNum: Int get() = origin.rowDimension
|
||||
override val colNum: Int get() = origin.columnDimension
|
||||
|
||||
override val features: Set<MatrixFeature> = features ?: sequence<MatrixFeature> {
|
||||
if(origin is DiagonalMatrix) yield(DiagonalFeature)
|
||||
}.toSet()
|
||||
|
||||
override fun suggestFeature(vararg features: MatrixFeature) =
|
||||
CMMatrix(origin, this.features + features)
|
||||
|
||||
override fun get(i: Int, j: Int): Double = origin.getEntry(i, j)
|
||||
}
|
||||
|
||||
fun Matrix<Double>.toCM(): CMMatrix = if (this is CMMatrix) {
|
||||
this
|
||||
} else {
|
||||
//TODO add feature analysis
|
||||
val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } }
|
||||
CMMatrix(Array2DRowRealMatrix(array))
|
||||
}
|
||||
|
||||
fun RealMatrix.toMatrix() = CMMatrix(this)
|
||||
|
||||
class CMVector(val origin: RealVector) : Point<Double> {
|
||||
override val size: Int get() = origin.dimension
|
||||
|
||||
override fun get(index: Int): Double = origin.getEntry(index)
|
||||
|
||||
override fun iterator(): Iterator<Double> = origin.toArray().iterator()
|
||||
}
|
||||
|
||||
fun Point<Double>.toCM(): CMVector = if (this is CMVector) {
|
||||
this
|
||||
} else {
|
||||
val array = DoubleArray(size) { this[it] }
|
||||
CMVector(ArrayRealVector(array))
|
||||
}
|
||||
|
||||
fun RealVector.toPoint() = CMVector(this)
|
||||
|
||||
object CMMatrixContext : MatrixContext<Double>, LinearSolver<Double> {
|
||||
|
||||
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix {
|
||||
val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } }
|
||||
return CMMatrix(Array2DRowRealMatrix(array))
|
||||
}
|
||||
|
||||
override fun solve(a: Matrix<Double>, b: Matrix<Double>): CMMatrix {
|
||||
val decomposition = LUDecomposition(a.toCM().origin)
|
||||
return decomposition.solver.solve(b.toCM().origin).toMatrix()
|
||||
}
|
||||
|
||||
override fun solve(a: Matrix<Double>, b: Point<Double>): CMVector {
|
||||
val decomposition = LUDecomposition(a.toCM().origin)
|
||||
return decomposition.solver.solve(b.toCM().origin).toPoint()
|
||||
}
|
||||
|
||||
override fun inverse(a: Matrix<Double>): CMMatrix {
|
||||
val decomposition = LUDecomposition(a.toCM().origin)
|
||||
return decomposition.solver.inverse.toMatrix()
|
||||
}
|
||||
|
||||
override fun Matrix<Double>.dot(other: Matrix<Double>) =
|
||||
CMMatrix(this.toCM().origin.multiply(other.toCM().origin))
|
||||
|
||||
override fun Matrix<Double>.dot(vector: Point<Double>): CMVector =
|
||||
CMVector(this.toCM().origin.preMultiply(vector.toCM().origin))
|
||||
|
||||
|
||||
override fun Matrix<Double>.unaryMinus(): CMMatrix =
|
||||
produce(rowNum, colNum) { i, j -> -get(i, j) }
|
||||
|
||||
override fun Matrix<Double>.plus(b: Matrix<Double>) =
|
||||
CMMatrix(this.toCM().origin.multiply(b.toCM().origin))
|
||||
|
||||
override fun Matrix<Double>.minus(b: Matrix<Double>) =
|
||||
CMMatrix(this.toCM().origin.subtract(b.toCM().origin))
|
||||
|
||||
override fun Matrix<Double>.times(value: Double) =
|
||||
CMMatrix(this.toCM().origin.scalarMultiply(value.toDouble()))
|
||||
}
|
||||
|
||||
operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin))
|
||||
operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.subtract(other.origin))
|
||||
|
||||
infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(this.origin.multiply(other.origin))
|
@ -0,0 +1,86 @@
|
||||
package scientifik.kmath.transform
|
||||
|
||||
import org.apache.commons.math3.transform.*
|
||||
import scientifik.kmath.operations.Complex
|
||||
import scientifik.kmath.sequential.Processor
|
||||
import scientifik.kmath.sequential.Producer
|
||||
import scientifik.kmath.sequential.map
|
||||
import scientifik.kmath.structures.*
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
object Transformations {
|
||||
|
||||
private fun Buffer<Complex>.toArray(): Array<org.apache.commons.math3.complex.Complex> =
|
||||
Array(size) { org.apache.commons.math3.complex.Complex(get(it).re, get(it).im) }
|
||||
|
||||
private fun Buffer<Double>.asArray() = if (this is DoubleBuffer) {
|
||||
array
|
||||
} else {
|
||||
DoubleArray(size) { i -> get(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual buffer on top of array
|
||||
*/
|
||||
private fun Array<org.apache.commons.math3.complex.Complex>.asBuffer() = VirtualBuffer<Complex>(size) {
|
||||
val value = get(it)
|
||||
Complex(value.real, value.imaginary)
|
||||
}
|
||||
|
||||
fun fourier(
|
||||
normalization: DftNormalization = DftNormalization.STANDARD,
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): BufferTransform<Complex, Complex> = {
|
||||
FastFourierTransformer(normalization).transform(it.toArray(), direction).asBuffer()
|
||||
}
|
||||
|
||||
fun realFourier(
|
||||
normalization: DftNormalization = DftNormalization.STANDARD,
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): BufferTransform<Double, Complex> = {
|
||||
FastFourierTransformer(normalization).transform(it.asArray(), direction).asBuffer()
|
||||
}
|
||||
|
||||
fun sine(
|
||||
normalization: DstNormalization = DstNormalization.STANDARD_DST_I,
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): BufferTransform<Double, Double> = {
|
||||
FastSineTransformer(normalization).transform(it.asArray(), direction).asBuffer()
|
||||
}
|
||||
|
||||
fun cosine(
|
||||
normalization: DctNormalization = DctNormalization.STANDARD_DCT_I,
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): BufferTransform<Double, Double> = {
|
||||
FastCosineTransformer(normalization).transform(it.asArray(), direction).asBuffer()
|
||||
}
|
||||
|
||||
fun hadamard(
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): BufferTransform<Double, Double> = {
|
||||
FastHadamardTransformer().transform(it.asArray(), direction).asBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process given [Producer] with commons-math fft transformation
|
||||
*/
|
||||
fun Producer<Buffer<Complex>>.FFT(
|
||||
normalization: DftNormalization = DftNormalization.STANDARD,
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): Processor<Buffer<Complex>, Buffer<Complex>> {
|
||||
val transform = Transformations.fourier(normalization, direction)
|
||||
return map { transform(it) }
|
||||
}
|
||||
|
||||
@JvmName("realFFT")
|
||||
fun Producer<Buffer<Double>>.FFT(
|
||||
normalization: DftNormalization = DftNormalization.STANDARD,
|
||||
direction: TransformType = TransformType.FORWARD
|
||||
): Processor<Buffer<Double>, Buffer<Complex>> {
|
||||
val transform = Transformations.realFourier(normalization, direction)
|
||||
return map { transform(it) }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package scientifik.kmath.expressions
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
inline fun <R> diff(order: Int, vararg parameters: Pair<String, Double>, block: DerivativeStructureField.() -> R) =
|
||||
DerivativeStructureField(order, mapOf(*parameters)).run(block)
|
||||
|
||||
class AutoDiffTest {
|
||||
@Test
|
||||
fun derivativeStructureFieldTest() {
|
||||
val res = diff(3, "x" to 1.0, "y" to 1.0) {
|
||||
val x by variable
|
||||
val y = variable("y")
|
||||
val z = x * (-sin(x * y) + y)
|
||||
z.deriv("x")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun autoDifTest() {
|
||||
val f = DiffExpression {
|
||||
val x by variable
|
||||
val y by variable
|
||||
x.pow(2) + 2 * x * y + y.pow(2) + 1
|
||||
}
|
||||
|
||||
assertEquals(10.0, f("x" to 1.0, "y" to 2.0))
|
||||
assertEquals(6.0, f.derivative("x")("x" to 1.0, "y" to 2.0))
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
fromPreset(presets.js, 'js')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.mingwX64, 'mingw')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api '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 {
|
||||
api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
}
|
||||
}
|
||||
jvmTest {
|
||||
dependencies {
|
||||
implementation 'org.jetbrains.kotlin:kotlin-test'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-test-junit'
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api 'org.jetbrains.kotlin:kotlin-stdlib-js'
|
||||
}
|
||||
}
|
||||
jsTest {
|
||||
dependencies {
|
||||
implementation 'org.jetbrains.kotlin:kotlin-test-js'
|
||||
}
|
||||
}
|
||||
// mingwMain {
|
||||
// }
|
||||
// mingwTest {
|
||||
// }
|
||||
}
|
||||
}
|
51
kmath-core/build.gradle.kts
Normal file
51
kmath-core/build.gradle.kts
Normal file
@ -0,0 +1,51 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val ioVersion: String by rootProject.extra
|
||||
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":kmath-memory"))
|
||||
api(kotlin("stdlib"))
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
api(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api(kotlin("stdlib-js"))
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
// mingwMain {
|
||||
// }
|
||||
// mingwTest {
|
||||
// }
|
||||
}
|
||||
}
|
@ -1,48 +1,64 @@
|
||||
package scientifik.kmath.expressions
|
||||
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.Ring
|
||||
import scientifik.kmath.operations.Space
|
||||
|
||||
|
||||
/**
|
||||
* An elementary function that could be invoked on a map of arguments
|
||||
*/
|
||||
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))
|
||||
|
||||
/**
|
||||
* A context for expression construction
|
||||
*/
|
||||
interface ExpressionContext<T> {
|
||||
/**
|
||||
* Introduce a variable into expression context
|
||||
*/
|
||||
fun variable(name: String, default: T? = null): Expression<T>
|
||||
|
||||
/**
|
||||
* A constant expression which does not depend on arguments
|
||||
*/
|
||||
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")
|
||||
}
|
||||
override fun invoke(arguments: Map<String, T>): T =
|
||||
arguments[name] ?: default ?: error("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> {
|
||||
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 ProductExpression<T>(val context: Ring<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> {
|
||||
internal class ConstProductExpession<T>(val context: Space<T>, val expr: Expression<T>, val const: Number) :
|
||||
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> {
|
||||
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> {
|
||||
class ExpressionField<T>(val field: Field<T>) : Field<Expression<T>>, ExpressionContext<T> {
|
||||
|
||||
override val zero: Expression<T> = ConstantExpression(field.zero)
|
||||
|
||||
@ -54,9 +70,20 @@ class FieldExpressionContext<T>(val field: Field<T>) : Field<Expression<T>>, Exp
|
||||
|
||||
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>, k: Number): 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)
|
||||
|
||||
|
||||
operator fun Expression<T>.plus(arg: T) = this + const(arg)
|
||||
operator fun Expression<T>.minus(arg: T) = this - const(arg)
|
||||
operator fun Expression<T>.times(arg: T) = this * const(arg)
|
||||
operator fun Expression<T>.div(arg: T) = this / const(arg)
|
||||
|
||||
operator fun T.plus(arg: Expression<T>) = arg + this
|
||||
operator fun T.minus(arg: Expression<T>) = arg - this
|
||||
operator fun T.times(arg: Expression<T>) = arg * this
|
||||
operator fun T.div(arg: Expression<T>) = arg / this
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
/*
|
||||
* Common representation for atomic counters
|
||||
*/
|
||||
|
||||
|
||||
expect class LongCounter(){
|
||||
fun decrement()
|
||||
fun increment()
|
||||
fun reset()
|
||||
fun sum(): Long
|
||||
fun add(l:Long)
|
||||
}
|
||||
|
||||
expect class DoubleCounter(){
|
||||
fun reset()
|
||||
fun sum(): Double
|
||||
fun add(d: Double)
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
import scientifik.kmath.structures.ArrayBuffer
|
||||
import scientifik.kmath.structures.Buffer
|
||||
import scientifik.kmath.structures.DoubleBuffer
|
||||
|
||||
typealias Point<T> = Buffer<T>
|
||||
|
||||
typealias RealPoint = Buffer<Double>
|
||||
|
||||
/**
|
||||
* A simple geometric domain
|
||||
* TODO move to geometry module
|
||||
*/
|
||||
interface Domain<T: Any> {
|
||||
operator fun contains(vector: Point<out T>): Boolean
|
||||
val dimension: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* The bin in the histogram. The histogram is by definition always done in the real space
|
||||
*/
|
||||
interface Bin<T: Any> : Domain<T> {
|
||||
/**
|
||||
* The value of this bin
|
||||
*/
|
||||
val value: Number
|
||||
val center: Point<T>
|
||||
}
|
||||
|
||||
interface Histogram<T: Any, out B : Bin<T>> : Iterable<B> {
|
||||
|
||||
/**
|
||||
* Find existing bin, corresponding to given coordinates
|
||||
*/
|
||||
operator fun get(point: Point<out T>): B?
|
||||
|
||||
/**
|
||||
* Dimension of the histogram
|
||||
*/
|
||||
val dimension: Int
|
||||
|
||||
}
|
||||
|
||||
interface MutableHistogram<T: Any, out B : Bin<T>>: Histogram<T,B>{
|
||||
|
||||
/**
|
||||
* Increment appropriate bin
|
||||
*/
|
||||
fun put(point: Point<out T>, weight: Double = 1.0)
|
||||
}
|
||||
|
||||
fun <T: Any> MutableHistogram<T,*>.put(vararg point: T) = put(ArrayBuffer(point))
|
||||
|
||||
fun MutableHistogram<Double,*>.put(vararg point: Number) = put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray()))
|
||||
fun MutableHistogram<Double,*>.put(vararg point: Double) = put(DoubleBuffer(point))
|
||||
|
||||
fun <T: Any> MutableHistogram<T,*>.fill(sequence: Iterable<Point<T>>) = sequence.forEach { put(it) }
|
||||
|
||||
/**
|
||||
* Pass a sequence builder into histogram
|
||||
*/
|
||||
fun <T: Any> MutableHistogram<T, *>.fill(buider: suspend SequenceScope<Point<T>>.() -> Unit) = fill(sequence(buider).asIterable())
|
@ -1,63 +0,0 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
import scientifik.kmath.linear.Vector
|
||||
import scientifik.kmath.operations.Space
|
||||
import scientifik.kmath.structures.NDStructure
|
||||
import scientifik.kmath.structures.asSequence
|
||||
|
||||
data class BinTemplate<T : Comparable<T>>(val center: Vector<T, *>, val sizes: Point<T>) {
|
||||
fun contains(vector: Point<out T>): Boolean {
|
||||
if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}")
|
||||
val upper = center.context.run { center + sizes / 2.0}
|
||||
val lower = center.context.run {center - sizes / 2.0}
|
||||
return vector.asSequence().mapIndexed { i, value ->
|
||||
value in lower[i]..upper[i]
|
||||
}.all { it }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A space to perform arithmetic operations on histograms
|
||||
*/
|
||||
interface HistogramSpace<T : Any, B : Bin<T>, H : Histogram<T, B>> : Space<H> {
|
||||
/**
|
||||
* Rules for performing operations on bins
|
||||
*/
|
||||
val binSpace: Space<Bin<T>>
|
||||
}
|
||||
|
||||
class PhantomBin<T : Comparable<T>>(val template: BinTemplate<T>, override val value: Number) : Bin<T> {
|
||||
|
||||
override fun contains(vector: Point<out T>): Boolean = template.contains(vector)
|
||||
|
||||
override val dimension: Int
|
||||
get() = template.center.size
|
||||
|
||||
override val center: Point<T>
|
||||
get() = template.center
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable histogram with explicit structure for content and additional external bin description.
|
||||
* Bin search is slow, but full histogram algebra is supported.
|
||||
* @param bins map a template into structure index
|
||||
*/
|
||||
class PhantomHistogram<T : Comparable<T>>(
|
||||
val bins: Map<BinTemplate<T>, IntArray>,
|
||||
val data: NDStructure<Number>
|
||||
) : Histogram<T, PhantomBin<T>> {
|
||||
|
||||
override val dimension: Int
|
||||
get() = data.dimension
|
||||
|
||||
override fun iterator(): Iterator<PhantomBin<T>> {
|
||||
return bins.asSequence().map { entry -> PhantomBin(entry.key, data[entry.value]) }.iterator()
|
||||
}
|
||||
|
||||
override fun get(point: Point<out T>): PhantomBin<T>? {
|
||||
val template = bins.keys.find { it.contains(point) }
|
||||
return template?.let { PhantomBin(it, data[bins[it]!!]) }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.operations.Ring
|
||||
import scientifik.kmath.structures.*
|
||||
|
||||
/**
|
||||
* Basic implementation of Matrix space based on [NDStructure]
|
||||
*/
|
||||
class BufferMatrixContext<T : Any, R : Ring<T>>(
|
||||
override val elementContext: R,
|
||||
private val bufferFactory: BufferFactory<T>
|
||||
) : GenericMatrixContext<T, R> {
|
||||
|
||||
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix<T> {
|
||||
val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) }
|
||||
return BufferMatrix(rows, columns, buffer)
|
||||
}
|
||||
|
||||
override fun point(size: Int, initializer: (Int) -> T): Point<T> = bufferFactory(size, initializer)
|
||||
}
|
||||
|
||||
class BufferMatrix<T : Any>(
|
||||
override val rowNum: Int,
|
||||
override val colNum: Int,
|
||||
val buffer: Buffer<out T>,
|
||||
override val features: Set<MatrixFeature> = emptySet()
|
||||
) : Matrix<T> {
|
||||
|
||||
init {
|
||||
if (buffer.size != rowNum * colNum) {
|
||||
error("Dimension mismatch for matrix structure")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val shape: IntArray get() = intArrayOf(rowNum, colNum)
|
||||
|
||||
override fun suggestFeature(vararg features: MatrixFeature) =
|
||||
BufferMatrix(rowNum, colNum, buffer, this.features + features)
|
||||
|
||||
override fun get(index: IntArray): T = get(index[0], index[1])
|
||||
|
||||
override fun get(i: Int, j: Int): T = buffer[i * colNum + j]
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> = sequence {
|
||||
for (i in 0 until rowNum) {
|
||||
for (j in 0 until colNum) {
|
||||
yield(intArrayOf(i, j) to get(i, j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
return when (other) {
|
||||
is NDStructure<*> -> return NDStructure.equals(this, other)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = buffer.hashCode()
|
||||
result = 31 * result + features.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return if (rowNum <= 5 && colNum <= 5) {
|
||||
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" +
|
||||
rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") {
|
||||
it.asSequence().joinToString(separator = "\t") { it.toString() }
|
||||
}
|
||||
} else {
|
||||
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized dot product for real matrices
|
||||
*/
|
||||
infix fun BufferMatrix<Double>.dot(other: BufferMatrix<Double>): BufferMatrix<Double> {
|
||||
if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})")
|
||||
|
||||
val array = DoubleArray(this.rowNum * other.colNum)
|
||||
|
||||
val a = this.buffer.array
|
||||
val b = other.buffer.array
|
||||
|
||||
for (i in (0 until rowNum)) {
|
||||
for (j in (0 until other.colNum)) {
|
||||
for (k in (0 until colNum)) {
|
||||
array[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val buffer = DoubleBuffer(array)
|
||||
return BufferMatrix(rowNum, other.colNum, buffer)
|
||||
}
|
@ -1,251 +0,0 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.structures.MutableNDStructure
|
||||
import scientifik.kmath.structures.NDStructure
|
||||
import scientifik.kmath.structures.genericNdStructure
|
||||
import scientifik.kmath.structures.get
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/**
|
||||
* Implementation based on Apache common-maths LU-decomposition
|
||||
*/
|
||||
abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matrix<T, F>) {
|
||||
|
||||
private val field get() = matrix.context.field
|
||||
/** Entries of LU decomposition. */
|
||||
internal val lu: NDStructure<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, F> 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, F> 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, F> 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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In-place transformation for [MutableNDArray], using given transformation for each element
|
||||
*/
|
||||
operator fun <T> MutableNDStructure<T>.set(i: Int, j: Int, value: T) {
|
||||
this[intArrayOf(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<NDStructure<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: MutableNDStructure<T> = genericNdStructure(intArrayOf(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: RealMatrix, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : LUDecomposition<Double, DoubleField>(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, DoubleField> {
|
||||
|
||||
|
||||
fun decompose(mat: Matrix<Double, DoubleField>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold)
|
||||
|
||||
override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix {
|
||||
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,205 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.Ring
|
||||
import scientifik.kmath.structures.MutableBuffer
|
||||
import scientifik.kmath.structures.MutableBuffer.Companion.boxing
|
||||
import scientifik.kmath.structures.MutableBufferFactory
|
||||
import scientifik.kmath.structures.NDStructure
|
||||
import scientifik.kmath.structures.get
|
||||
|
||||
class LUPDecomposition<T : Comparable<T>>(
|
||||
private val elementContext: Ring<T>,
|
||||
internal val lu: NDStructure<T>,
|
||||
val pivot: IntArray,
|
||||
private val even: Boolean
|
||||
) : LUPDecompositionFeature<T>, DeterminantFeature<T> {
|
||||
|
||||
/**
|
||||
* Returns the matrix L of the decomposition.
|
||||
*
|
||||
* L is a lower-triangular matrix with [Ring.one] in diagonal
|
||||
*/
|
||||
override val l: Matrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(LFeature)) { i, j ->
|
||||
when {
|
||||
j < i -> lu[i, j]
|
||||
j == i -> elementContext.one
|
||||
else -> elementContext.zero
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the matrix U of the decomposition.
|
||||
*
|
||||
* U is an upper-triangular matrix including the diagonal
|
||||
*/
|
||||
override val u: Matrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(UFeature)) { i, j ->
|
||||
if (j >= i) lu[i, j] else elementContext.zero
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the P rows permutation matrix.
|
||||
*
|
||||
* P is a sparse matrix with exactly one element set to [Ring.one] in
|
||||
* each row and each column, all other elements being set to [Ring.zero].
|
||||
*/
|
||||
override val p: Matrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j ->
|
||||
if (j == pivot[i]) elementContext.one else elementContext.zero
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the determinant of the matrix
|
||||
* @return determinant of the matrix
|
||||
*/
|
||||
override val determinant: T by lazy {
|
||||
with(elementContext) {
|
||||
(0 until lu.shape[0]).fold(if (even) one else -one) { value, i -> value * lu[i, i] }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class LUSolver<T : Comparable<T>, F : Field<T>>(
|
||||
val context: GenericMatrixContext<T, F>,
|
||||
val bufferFactory: MutableBufferFactory<T> = ::boxing,
|
||||
val singularityCheck: (T) -> Boolean
|
||||
) : LinearSolver<T> {
|
||||
|
||||
|
||||
private fun abs(value: T) =
|
||||
if (value > context.elementContext.zero) value else with(context.elementContext) { -value }
|
||||
|
||||
fun buildDecomposition(matrix: Matrix<T>): LUPDecomposition<T> {
|
||||
if (matrix.rowNum != matrix.colNum) {
|
||||
error("LU decomposition supports only square matrices")
|
||||
}
|
||||
|
||||
val m = matrix.colNum
|
||||
val pivot = IntArray(matrix.rowNum)
|
||||
|
||||
val lu = Mutable2DStructure.create(matrix.rowNum, matrix.colNum, bufferFactory) { i, j ->
|
||||
matrix[i, j]
|
||||
}
|
||||
|
||||
|
||||
with(context.elementContext) {
|
||||
// Initialize permutation array and parity
|
||||
for (row in 0 until m) {
|
||||
pivot[row] = row
|
||||
}
|
||||
var even = true
|
||||
|
||||
// Loop over columns
|
||||
for (col in 0 until m) {
|
||||
|
||||
// upper
|
||||
for (row in 0 until col) {
|
||||
var sum = lu[row, col]
|
||||
for (i in 0 until row) {
|
||||
sum -= lu[row, i] * lu[i, col]
|
||||
}
|
||||
lu[row, col] = sum
|
||||
}
|
||||
|
||||
// lower
|
||||
val max = (col until m).maxBy { row ->
|
||||
var sum = lu[row, col]
|
||||
for (i in 0 until col) {
|
||||
sum -= lu[row, i] * lu[i, col]
|
||||
}
|
||||
lu[row, col] = sum
|
||||
|
||||
abs(sum)
|
||||
} ?: col
|
||||
|
||||
// Singularity check
|
||||
if (singularityCheck(lu[max, col])) {
|
||||
error("Singular matrix")
|
||||
}
|
||||
|
||||
// Pivot if necessary
|
||||
if (max != col) {
|
||||
for (i in 0 until m) {
|
||||
lu[max, i] = lu[col, i]
|
||||
lu[col, i] = lu[max, i]
|
||||
}
|
||||
val temp = pivot[max]
|
||||
pivot[max] = pivot[col]
|
||||
pivot[col] = temp
|
||||
even = !even
|
||||
}
|
||||
|
||||
// Divide the lower elements by the "winning" diagonal elt.
|
||||
val luDiag = lu[col, col]
|
||||
for (row in col + 1 until m) {
|
||||
lu[row, col] = lu[row, col] / luDiag
|
||||
}
|
||||
}
|
||||
return LUPDecomposition(context.elementContext, lu, pivot, even)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a matrix with added decomposition feature
|
||||
*/
|
||||
fun decompose(matrix: Matrix<T>): Matrix<T> {
|
||||
if (matrix.hasFeature<LUPDecomposition<*>>()) {
|
||||
return matrix
|
||||
} else {
|
||||
val decomposition = buildDecomposition(matrix)
|
||||
return VirtualMatrix.wrap(matrix, decomposition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
|
||||
if (b.rowNum != a.colNum) {
|
||||
error("Matrix dimension mismatch expected ${a.rowNum}, but got ${b.colNum}")
|
||||
}
|
||||
|
||||
// Use existing decomposition if it is provided by matrix
|
||||
val decomposition = a.getFeature() ?: buildDecomposition(a)
|
||||
|
||||
with(decomposition) {
|
||||
with(context.elementContext) {
|
||||
// Apply permutations to b
|
||||
val bp = Mutable2DStructure.create(a.rowNum, a.colNum, bufferFactory) { i, j ->
|
||||
b[pivot[i], j]
|
||||
}
|
||||
|
||||
// Solve LY = b
|
||||
for (col in 0 until a.rowNum) {
|
||||
for (i in col + 1 until a.rowNum) {
|
||||
for (j in 0 until b.colNum) {
|
||||
bp[i, j] -= bp[col, j] * lu[i, col]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Solve UX = Y
|
||||
for (col in a.rowNum - 1 downTo 0) {
|
||||
for (j in 0 until b.colNum) {
|
||||
bp[col, j] /= lu[col, col]
|
||||
}
|
||||
for (i in 0 until col) {
|
||||
for (j in 0 until b.colNum) {
|
||||
bp[i, j] -= bp[col, j] * lu[i, col]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.produce(a.rowNum, a.colNum) { i, j -> bp[i, j] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inverse(a: Matrix<T>): Matrix<T> = solve(a, context.one(a.rowNum, a.colNum))
|
||||
|
||||
companion object {
|
||||
val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 }
|
||||
}
|
||||
}
|
@ -1,62 +1,45 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.Norm
|
||||
import scientifik.kmath.operations.RealField
|
||||
import scientifik.kmath.structures.VirtualBuffer
|
||||
import scientifik.kmath.structures.asSequence
|
||||
|
||||
|
||||
/**
|
||||
* A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors
|
||||
*/
|
||||
interface LinearSolver<T : Any, F : Field<T>> {
|
||||
fun solve(a: Matrix<T, F>, b: Matrix<T, F>): Matrix<T, F>
|
||||
fun solve(a: Matrix<T, F>, b: Vector<T, F>): Vector<T, F> = solve(a, b.toMatrix()).toVector()
|
||||
fun inverse(a: Matrix<T, F>): Matrix<T, F> = solve(a, Matrix.diagonal(a.rows, a.columns, a.context.field))
|
||||
interface LinearSolver<T : Any> {
|
||||
fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T>
|
||||
fun solve(a: Matrix<T>, b: Point<T>): Point<T> = solve(a, b.toMatrix()).toVector()
|
||||
fun inverse(a: Matrix<T>): Matrix<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert vector to array (copying content of array)
|
||||
*/
|
||||
fun <T : Any> Array<T>.toVector(field: Field<T>) = Vector.of(size, field) { this[it] }
|
||||
fun <T : Any> Array<T>.toVector(field: Field<T>) = Vector.generic(size, field) { this[it] }
|
||||
|
||||
fun DoubleArray.toVector() = Vector.real(this.size) { this[it] }
|
||||
fun List<Double>.toVector() = Vector.real(this.size) { this[it] }
|
||||
|
||||
object VectorL2Norm : Norm<Point<out Number>, Double> {
|
||||
override fun norm(arg: Point<out Number>): Double =
|
||||
kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() })
|
||||
}
|
||||
|
||||
typealias RealVector = Vector<Double, RealField>
|
||||
typealias RealMatrix = Matrix<Double>
|
||||
|
||||
|
||||
fun DoubleArray.toVector() = Vector.ofReal(this.size) { this[it] }
|
||||
fun List<Double>.toVector() = Vector.ofReal(this.size) { this[it] }
|
||||
|
||||
/**
|
||||
* Convert matrix to vector if it is possible
|
||||
*/
|
||||
fun <T : Any, F : Field<T>> Matrix<T, F>.toVector(): Vector<T, F> {
|
||||
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> Matrix<T>.toVector(): Point<T> =
|
||||
if (this.colNum == 1) {
|
||||
VirtualBuffer(rowNum){ get(it, 0) }
|
||||
} else error("Can't convert matrix with more than one column to vector")
|
||||
|
||||
fun <T : Any, F : Field<T>> Vector<T, F>.toMatrix(): Matrix<T, F> {
|
||||
// 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.space) { i, _ -> get(i) }
|
||||
}
|
||||
|
||||
object VectorL2Norm : Norm<Vector<out Number, *>, Double> {
|
||||
override fun norm(arg: Vector<out Number, *>): Double {
|
||||
return kotlin.math.sqrt(arg.sumByDouble { it.toDouble() })
|
||||
}
|
||||
}
|
||||
|
||||
typealias RealVector = Vector<Double, DoubleField>
|
||||
typealias RealMatrix = Matrix<Double, DoubleField>
|
||||
fun <T: Any> Point<T>.toMatrix(): Matrix<T> = VirtualMatrix(size, 1) { i, _ -> get(i) }
|
@ -1,187 +1,214 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.operations.*
|
||||
import scientifik.kmath.operations.RealField
|
||||
import scientifik.kmath.operations.Ring
|
||||
import scientifik.kmath.operations.sum
|
||||
import scientifik.kmath.structures.*
|
||||
import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory
|
||||
import scientifik.kmath.structures.Buffer.Companion.boxing
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
interface MatrixContext<T : Any> {
|
||||
/**
|
||||
* Produce a matrix with this context and given dimensions
|
||||
*/
|
||||
fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T>
|
||||
|
||||
infix fun Matrix<T>.dot(other: Matrix<T>): Matrix<T>
|
||||
|
||||
infix fun Matrix<T>.dot(vector: Point<T>): Point<T>
|
||||
|
||||
operator fun Matrix<T>.unaryMinus(): Matrix<T>
|
||||
|
||||
operator fun Matrix<T>.plus(b: Matrix<T>): Matrix<T>
|
||||
|
||||
operator fun Matrix<T>.minus(b: Matrix<T>): Matrix<T>
|
||||
|
||||
operator fun Matrix<T>.times(value: T): Matrix<T>
|
||||
|
||||
operator fun T.times(m: Matrix<T>): Matrix<T> = m * this
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Non-boxing double matrix
|
||||
*/
|
||||
val real = BufferMatrixContext(RealField, DoubleBufferFactory)
|
||||
|
||||
/**
|
||||
* 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
|
||||
* A structured matrix with custom buffer
|
||||
*/
|
||||
abstract class MatrixSpace<T : Any, F : Ring<T>>(val rows: Int, val columns: Int, val field: F) : Space<Matrix<T, F>> {
|
||||
fun <T : Any, R : Ring<T>> buffered(
|
||||
ring: R,
|
||||
bufferFactory: BufferFactory<T> = ::boxing
|
||||
): GenericMatrixContext<T, R> =
|
||||
BufferMatrixContext(ring, bufferFactory)
|
||||
|
||||
/**
|
||||
* Produce the element of this space
|
||||
* Automatic buffered matrix, unboxed if it is possible
|
||||
*/
|
||||
abstract fun produce(initializer: (Int, Int) -> T): Matrix<T, F>
|
||||
|
||||
/**
|
||||
* 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, F>
|
||||
|
||||
override val zero: Matrix<T, F> by lazy {
|
||||
produce { _, _ -> field.zero }
|
||||
inline fun <reified T : Any, R : Ring<T>> auto(ring: R): GenericMatrixContext<T, R> =
|
||||
buffered(ring, Buffer.Companion::auto)
|
||||
}
|
||||
}
|
||||
|
||||
// val one: Matrix<T> by lazy {
|
||||
// produce { i, j -> if (i == j) field.one else field.zero }
|
||||
// }
|
||||
interface GenericMatrixContext<T : Any, R : Ring<T>> : MatrixContext<T> {
|
||||
/**
|
||||
* The ring context for matrix elements
|
||||
*/
|
||||
val elementContext: R
|
||||
|
||||
override fun add(a: Matrix<T, F>, b: Matrix<T, F>): Matrix<T, F> {
|
||||
return produce { i, j -> with(field) { a[i, j] + b[i, j] } }
|
||||
/**
|
||||
* Produce a point compatible with matrix space
|
||||
*/
|
||||
fun point(size: Int, initializer: (Int) -> T): Point<T>
|
||||
|
||||
override infix fun Matrix<T>.dot(other: Matrix<T>): Matrix<T> {
|
||||
//TODO add typed error
|
||||
if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})")
|
||||
return produce(rowNum, other.colNum) { i, j ->
|
||||
val row = rows[i]
|
||||
val column = other.columns[j]
|
||||
with(elementContext) {
|
||||
sum(row.asSequence().zip(column.asSequence(), ::multiply))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun multiply(a: Matrix<T, F>, k: Double): Matrix<T, F> {
|
||||
//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 } }
|
||||
override infix fun Matrix<T>.dot(vector: Point<T>): Point<T> {
|
||||
//TODO add typed error
|
||||
if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})")
|
||||
return point(rowNum) { i ->
|
||||
val row = rows[i]
|
||||
with(elementContext) {
|
||||
sum(row.asSequence().zip(vector.asSequence(), ::multiply))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun Matrix<T>.unaryMinus() =
|
||||
produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } }
|
||||
|
||||
override operator fun Matrix<T>.plus(b: Matrix<T>): Matrix<T> {
|
||||
if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] + [${b.rowNum},${b.colNum}]")
|
||||
return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } }
|
||||
}
|
||||
|
||||
override operator fun Matrix<T>.minus(b: Matrix<T>): Matrix<T> {
|
||||
if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]")
|
||||
return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } }
|
||||
}
|
||||
|
||||
operator fun Matrix<T>.times(number: Number): Matrix<T> =
|
||||
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } }
|
||||
|
||||
operator fun Number.times(matrix: Matrix<T>): Matrix<T> = matrix * this
|
||||
|
||||
override fun Matrix<T>.times(value: T): Matrix<T> =
|
||||
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Dot product. Throws exception on dimension mismatch
|
||||
* Specialized 2-d structure
|
||||
*/
|
||||
fun multiply(a: Matrix<T, F>, b: Matrix<T, F>): Matrix<T, F> {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
interface Matrix<T : Any> : Structure2D<T> {
|
||||
val rowNum: Int
|
||||
val colNum: Int
|
||||
|
||||
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, F : Field<T>> Matrix<T, F>.dot(b: Matrix<T, F>): Matrix<T, F> = this.context.multiply(this, b)
|
||||
val features: Set<MatrixFeature>
|
||||
|
||||
/**
|
||||
* A matrix-like structure
|
||||
* Suggest new feature for this matrix. The result is the new matrix that may or may not reuse existing data structure.
|
||||
*
|
||||
* The implementation does not guarantee to check that matrix actually have the feature, so one should be careful to
|
||||
* add only those features that are valid.
|
||||
*/
|
||||
interface Matrix<T : Any, F: Ring<T>> : SpaceElement<Matrix<T, F>, MatrixSpace<T, F>> {
|
||||
/**
|
||||
* Number of rows
|
||||
*/
|
||||
val rows: Int
|
||||
/**
|
||||
* Number of columns
|
||||
*/
|
||||
val columns: Int
|
||||
fun suggestFeature(vararg features: MatrixFeature): Matrix<T>
|
||||
|
||||
/**
|
||||
* 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 fun get(index: IntArray): T = get(index[0], index[1])
|
||||
|
||||
override val self: Matrix<T, F>
|
||||
get() = this
|
||||
override val shape: IntArray get() = intArrayOf(rowNum, colNum)
|
||||
|
||||
fun transpose(): Matrix<T, F> {
|
||||
return object : Matrix<T, F> {
|
||||
override val context: MatrixSpace<T, F> = 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]
|
||||
val rows: Point<Point<T>>
|
||||
get() = VirtualBuffer(rowNum) { i ->
|
||||
VirtualBuffer(colNum) { j -> get(i, j) }
|
||||
}
|
||||
|
||||
val columns: Point<Point<T>>
|
||||
get() = VirtualBuffer(colNum) { j ->
|
||||
VirtualBuffer(rowNum) { i -> get(i, j) }
|
||||
}
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> = sequence {
|
||||
for (i in (0 until rowNum)) {
|
||||
for (j in (0 until colNum)) {
|
||||
yield(intArrayOf(i, j) to get(i, j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) =
|
||||
MatrixContext.real.produce(rows, columns, initializer)
|
||||
|
||||
/**
|
||||
* Create [ArrayMatrix] with custom field
|
||||
* Build a square matrix from given elements.
|
||||
*/
|
||||
fun <T : Any, F: Field<T>> of(rows: Int, columns: Int, field: F, 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, F: Field<T>> diagonal(rows: Int, columns: Int, field: F, values: (Int) -> T = { field.one }): Matrix<T, F> {
|
||||
return of(rows, columns, field) { i, j -> if (i == j) values(i) else field.zero }
|
||||
fun <T : Any> square(vararg elements: T): Matrix<T> {
|
||||
val size: Int = sqrt(elements.size.toDouble()).toInt()
|
||||
if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square")
|
||||
val buffer = elements.asBuffer()
|
||||
return BufferMatrix(size, size, buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
fun <T : Any> build(rows: Int, columns: Int): MatrixBuilder<T> = MatrixBuilder(rows, columns)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
typealias NDFieldFactory<T, F> = (IntArray) -> NDField<T, F>
|
||||
|
||||
internal fun <T : Any, F : Field<T>> genericNDFieldFactory(field: F): NDFieldFactory<T, F> = { index -> GenericNDField(index, field) }
|
||||
internal val realNDFieldFactory: NDFieldFactory<Double, DoubleField> = { index -> ExtendedNDField(index, DoubleField) }
|
||||
|
||||
|
||||
/**
|
||||
* NDArray-based implementation of vector space. By default uses slow [GenericNDField], but could be overridden with custom [NDField] factory.
|
||||
*/
|
||||
class ArrayMatrixSpace<T : Any, F : Field<T>>(
|
||||
rows: Int,
|
||||
columns: Int,
|
||||
field: F,
|
||||
val ndFactory: NDFieldFactory<T, F> = genericNDFieldFactory(field)
|
||||
) : MatrixSpace<T, F>(rows, columns, field) {
|
||||
|
||||
val ndField by lazy {
|
||||
ndFactory(intArrayOf(rows, columns))
|
||||
}
|
||||
|
||||
override fun produce(initializer: (Int, Int) -> T): Matrix<T, F> = ArrayMatrix(this, initializer)
|
||||
|
||||
override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace<T, F> {
|
||||
return ArrayMatrixSpace(rows, columns, field, ndFactory)
|
||||
class MatrixBuilder<T : Any>(val rows: Int, val columns: Int) {
|
||||
operator fun invoke(vararg elements: T): Matrix<T> {
|
||||
if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns")
|
||||
val buffer = elements.asBuffer()
|
||||
return BufferMatrix(rows, columns, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Member of [ArrayMatrixSpace] which wraps 2-D array
|
||||
* Check if matrix has the given feature class
|
||||
*/
|
||||
class ArrayMatrix<T : Any, F : Field<T>> internal constructor(override val context: ArrayMatrixSpace<T, F>, val element: NDElement<T, F>) : Matrix<T, F> {
|
||||
inline fun <reified T : Any> Matrix<*>.hasFeature(): Boolean = features.find { it is T } != null
|
||||
|
||||
constructor(context: ArrayMatrixSpace<T, F>, initializer: (Int, Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0], list[1]) })
|
||||
/**
|
||||
* Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria
|
||||
*/
|
||||
inline fun <reified T : Any> Matrix<*>.getFeature(): T? = features.filterIsInstance<T>().firstOrNull()
|
||||
|
||||
override val rows: Int get() = context.rows
|
||||
|
||||
override val columns: Int get() = context.columns
|
||||
|
||||
override fun get(i: Int, j: Int): T {
|
||||
return element[i, j]
|
||||
/**
|
||||
* Diagonal matrix of ones. The matrix is virtual no actual matrix is created
|
||||
*/
|
||||
fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R>.one(rows: Int, columns: Int): Matrix<T> =
|
||||
VirtualMatrix<T>(rows, columns) { i, j ->
|
||||
if (i == j) elementContext.one else elementContext.zero
|
||||
}
|
||||
|
||||
override val self: ArrayMatrix<T, F> get() = this
|
||||
|
||||
/**
|
||||
* A virtual matrix of zeroes
|
||||
*/
|
||||
fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R>.zero(rows: Int, columns: Int): Matrix<T> =
|
||||
VirtualMatrix<T>(rows, columns) { _, _ -> elementContext.zero }
|
||||
|
||||
class TransposedFeature<T : Any>(val original: Matrix<T>) : MatrixFeature
|
||||
|
||||
/**
|
||||
* Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A`
|
||||
*/
|
||||
fun <T : Any, R : Ring<T>> Matrix<T>.transpose(): Matrix<T> {
|
||||
return this.getFeature<TransposedFeature<T>>()?.original ?: VirtualMatrix(
|
||||
this.colNum,
|
||||
this.rowNum,
|
||||
setOf(TransposedFeature(this))
|
||||
) { i, j -> get(j, i) }
|
||||
}
|
||||
|
||||
infix fun Matrix<Double>.dot(other: Matrix<Double>): Matrix<Double> = with(MatrixContext.real) { dot(other) }
|
@ -0,0 +1,62 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
/**
|
||||
* A marker interface representing some matrix feature like diagonal, sparce, zero, etc. Features used to optimize matrix
|
||||
* operations performance in some cases.
|
||||
*/
|
||||
interface MatrixFeature
|
||||
|
||||
/**
|
||||
* The matrix with this feature is considered to have only diagonal non-null elements
|
||||
*/
|
||||
object DiagonalFeature : MatrixFeature
|
||||
|
||||
/**
|
||||
* Matrix with this feature has all zero elements
|
||||
*/
|
||||
object ZeroFeature : MatrixFeature
|
||||
|
||||
/**
|
||||
* Matrix with this feature have unit elements on diagonal and zero elements in all other places
|
||||
*/
|
||||
object UnitFeature : MatrixFeature
|
||||
|
||||
/**
|
||||
* Inverted matrix feature
|
||||
*/
|
||||
interface InverseMatrixFeature<T : Any> : MatrixFeature {
|
||||
val inverse: Matrix<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* A determinant container
|
||||
*/
|
||||
interface DeterminantFeature<T : Any> : MatrixFeature {
|
||||
val determinant: T
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun <T: Any> DeterminantFeature(determinant: T) = object: DeterminantFeature<T>{
|
||||
override val determinant: T = determinant
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower triangular matrix
|
||||
*/
|
||||
object LFeature: MatrixFeature
|
||||
|
||||
/**
|
||||
* Upper triangular feature
|
||||
*/
|
||||
object UFeature: MatrixFeature
|
||||
|
||||
/**
|
||||
* TODO add documentation
|
||||
*/
|
||||
interface LUPDecompositionFeature<T : Any> : MatrixFeature {
|
||||
val l: Matrix<T>
|
||||
val u: Matrix<T>
|
||||
val p: Matrix<T>
|
||||
}
|
||||
|
||||
//TODO add sparse matrix feature
|
@ -0,0 +1,40 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.structures.MutableBuffer
|
||||
import scientifik.kmath.structures.MutableBufferFactory
|
||||
import scientifik.kmath.structures.MutableNDStructure
|
||||
|
||||
class Mutable2DStructure<T>(val rowNum: Int, val colNum: Int, val buffer: MutableBuffer<T>) : MutableNDStructure<T> {
|
||||
override val shape: IntArray
|
||||
get() = intArrayOf(rowNum, colNum)
|
||||
|
||||
operator fun get(i: Int, j: Int): T = buffer[i * colNum + j]
|
||||
|
||||
override fun get(index: IntArray): T = get(index[0], index[1])
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> = sequence {
|
||||
for (i in 0 until rowNum) {
|
||||
for (j in 0 until colNum) {
|
||||
yield(intArrayOf(i, j) to get(i, j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(i: Int, j: Int, value: T) {
|
||||
buffer[i * colNum + j] = value
|
||||
}
|
||||
|
||||
override fun set(index: IntArray, value: T) = set(index[0], index[1], value)
|
||||
|
||||
companion object {
|
||||
fun <T> create(
|
||||
rowNum: Int,
|
||||
colNum: Int,
|
||||
bufferFactory: MutableBufferFactory<T>,
|
||||
init: (i: Int, j: Int) -> T
|
||||
): Mutable2DStructure<T> {
|
||||
val buffer = bufferFactory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) }
|
||||
return Mutable2DStructure(rowNum, colNum, buffer)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +1,125 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.histogram.Point
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.RealField
|
||||
import scientifik.kmath.operations.Space
|
||||
import scientifik.kmath.operations.SpaceElement
|
||||
import scientifik.kmath.structures.NDElement
|
||||
import scientifik.kmath.structures.get
|
||||
import scientifik.kmath.structures.Buffer
|
||||
import scientifik.kmath.structures.BufferFactory
|
||||
import scientifik.kmath.structures.asSequence
|
||||
|
||||
typealias Point<T> = Buffer<T>
|
||||
|
||||
/**
|
||||
* A linear space for vectors.
|
||||
* Could be used on any point-like structure
|
||||
*/
|
||||
abstract class VectorSpace<T : Any, S : Space<T>>(val size: Int, val space: S) : Space<Point<T>> {
|
||||
interface VectorSpace<T : Any, S : Space<T>> : Space<Point<T>> {
|
||||
|
||||
abstract fun produce(initializer: (Int) -> T): Vector<T, S>
|
||||
val size: Int
|
||||
|
||||
override val zero: Vector<T, S> by lazy { produce { space.zero } }
|
||||
val space: S
|
||||
|
||||
override fun add(a: Point<T>, b: Point<T>): Vector<T, S> = produce { with(space) { a[it] + b[it] } }
|
||||
fun produce(initializer: (Int) -> T): Point<T>
|
||||
|
||||
override fun multiply(a: Point<T>, k: Double): Vector<T, S> = produce { with(space) { a[it] * k } }
|
||||
/**
|
||||
* Produce a space-element of this vector space for expressions
|
||||
*/
|
||||
fun produceElement(initializer: (Int) -> T): Vector<T, S>
|
||||
|
||||
override val zero: Point<T> get() = produce { space.zero }
|
||||
|
||||
override fun add(a: Point<T>, b: Point<T>): Point<T> = produce { with(space) { a[it] + b[it] } }
|
||||
|
||||
override fun multiply(a: Point<T>, k: Number): Point<T> = produce { with(space) { a[it] * k } }
|
||||
|
||||
//TODO add basis
|
||||
|
||||
companion object {
|
||||
|
||||
private val realSpaceCache = HashMap<Int, BufferVectorSpace<Double, RealField>>()
|
||||
|
||||
/**
|
||||
* Non-boxing double vector space
|
||||
*/
|
||||
fun real(size: Int): BufferVectorSpace<Double, RealField> {
|
||||
return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, RealField, Buffer.DoubleBufferFactory) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A structured vector space with custom buffer
|
||||
*/
|
||||
fun <T : Any, S : Space<T>> buffered(
|
||||
size: Int,
|
||||
space: S,
|
||||
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing
|
||||
): VectorSpace<T, S> = BufferVectorSpace(size, space, bufferFactory)
|
||||
|
||||
/**
|
||||
* Automatic buffered vector, unboxed if it is possible
|
||||
*/
|
||||
inline fun <reified T : Any, S : Space<T>> smart(size: Int, space: S): VectorSpace<T, S> =
|
||||
buffered(size, space, Buffer.Companion::auto)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A point coupled to the linear space
|
||||
*/
|
||||
interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, VectorSpace<T, S>>, Point<T>, Iterable<T> {
|
||||
interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, Vector<T, S>, VectorSpace<T, S>>, Point<T> {
|
||||
override val size: Int get() = context.size
|
||||
|
||||
override operator fun plus(b: Point<T>): Vector<T, S> = context.add(self, b)
|
||||
override operator fun minus(b: Point<T>): Vector<T, S> = context.add(self, context.multiply(b, -1.0))
|
||||
override operator fun times(k: Number): Vector<T, S> = context.multiply(self, k.toDouble())
|
||||
override operator fun div(k: Number): Vector<T, S> = context.multiply(self, 1.0 / k.toDouble())
|
||||
override operator fun plus(b: Point<T>): Vector<T, S> = context.add(this, b).wrap()
|
||||
override operator fun minus(b: Point<T>): Vector<T, S> = context.add(this, context.multiply(b, -1.0)).wrap()
|
||||
override operator fun times(k: Number): Vector<T, S> = context.multiply(this, k.toDouble()).wrap()
|
||||
override operator fun div(k: Number): Vector<T, S> = context.multiply(this, 1.0 / k.toDouble()).wrap()
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create vector with custom field
|
||||
*/
|
||||
fun <T : Any, F : Field<T>> of(size: Int, field: F, initializer: (Int) -> T) =
|
||||
ArrayVector(ArrayVectorSpace(size, field), initializer)
|
||||
fun <T : Any, S : Space<T>> generic(size: Int, field: S, initializer: (Int) -> T): Vector<T, S> =
|
||||
VectorSpace.buffered(size, field).produceElement(initializer)
|
||||
|
||||
private val realSpaceCache = HashMap<Int, ArrayVectorSpace<Double, DoubleField>>()
|
||||
fun real(size: Int, initializer: (Int) -> Double): Vector<Double, RealField> =
|
||||
VectorSpace.real(size).produceElement(initializer)
|
||||
|
||||
private fun getRealSpace(size: Int): ArrayVectorSpace<Double, DoubleField> {
|
||||
return realSpaceCache.getOrPut(size){ArrayVectorSpace(size, DoubleField, realNDFieldFactory)}
|
||||
}
|
||||
fun ofReal(vararg elements: Double): Vector<Double, RealField> =
|
||||
VectorSpace.real(elements.size).produceElement { elements[it] }
|
||||
|
||||
/**
|
||||
* Create vector of [Double]
|
||||
*/
|
||||
fun ofReal(size: Int, initializer: (Int) -> Double) =
|
||||
ArrayVector(getRealSpace(size), initializer)
|
||||
|
||||
fun ofReal(vararg point: Double) = point.toVector()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayVectorSpace<T : Any, F : Field<T>>(
|
||||
size: Int,
|
||||
field: F,
|
||||
val ndFactory: NDFieldFactory<T, F> = genericNDFieldFactory(field)
|
||||
) : VectorSpace<T, F>(size, field) {
|
||||
val ndField by lazy {
|
||||
ndFactory(intArrayOf(size))
|
||||
}
|
||||
|
||||
override fun produce(initializer: (Int) -> T): Vector<T, F> = ArrayVector(this, initializer)
|
||||
data class BufferVectorSpace<T : Any, S : Space<T>>(
|
||||
override val size: Int,
|
||||
override val space: S,
|
||||
val bufferFactory: BufferFactory<T>
|
||||
) : VectorSpace<T, S> {
|
||||
override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer)
|
||||
override fun produceElement(initializer: (Int) -> T): Vector<T, S> = BufferVector(this, produce(initializer))
|
||||
}
|
||||
|
||||
|
||||
class ArrayVector<T : Any, F : Field<T>> internal constructor(override val context: VectorSpace<T, F>, val element: NDElement<T, F>) : Vector<T, F> {
|
||||
|
||||
constructor(context: ArrayVectorSpace<T, F>, initializer: (Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0]) })
|
||||
data class BufferVector<T : Any, S : Space<T>>(override val context: VectorSpace<T, S>, val buffer: Buffer<T>) :
|
||||
Vector<T, S> {
|
||||
|
||||
init {
|
||||
if (context.size != element.shape[0]) {
|
||||
if (context.size != buffer.size) {
|
||||
error("Array dimension mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(index: Int): T {
|
||||
return element[index]
|
||||
return buffer[index]
|
||||
}
|
||||
|
||||
override val self: ArrayVector<T, F> get() = this
|
||||
override fun unwrap(): Point<T> = this
|
||||
|
||||
override fun iterator(): Iterator<T> = (0 until size).map { element[it] }.iterator()
|
||||
override fun Point<T>.wrap(): Vector<T, S> = BufferVector(context, this)
|
||||
|
||||
override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
|
||||
override fun iterator(): Iterator<T> = (0 until size).map { buffer[it] }.iterator()
|
||||
|
||||
override fun toString(): String =
|
||||
this.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
class VirtualMatrix<T : Any>(
|
||||
override val rowNum: Int,
|
||||
override val colNum: Int,
|
||||
override val features: Set<MatrixFeature> = emptySet(),
|
||||
val generator: (i: Int, j: Int) -> T
|
||||
) : Matrix<T> {
|
||||
override fun get(i: Int, j: Int): T = generator(i, j)
|
||||
|
||||
override fun suggestFeature(vararg features: MatrixFeature) =
|
||||
VirtualMatrix(rowNum, colNum, this.features + features, generator)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Matrix<*>) return false
|
||||
|
||||
if (rowNum != other.rowNum) return false
|
||||
if (colNum != other.colNum) return false
|
||||
|
||||
return elements().all { (index, value) -> value == other[index] }
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = rowNum
|
||||
result = 31 * result + colNum
|
||||
result = 31 * result + features.hashCode()
|
||||
result = 31 * result + generator.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Wrap a matrix adding additional features to it
|
||||
*/
|
||||
fun <T : Any> wrap(matrix: Matrix<T>, vararg features: MatrixFeature): Matrix<T> {
|
||||
return if (matrix is VirtualMatrix) {
|
||||
VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features, matrix.generator)
|
||||
} else {
|
||||
VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features) { i, j ->
|
||||
matrix[i, j]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
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}
|
@ -8,8 +8,8 @@ package scientifik.kmath.misc
|
||||
*
|
||||
* If step is negative, the same goes from upper boundary downwards
|
||||
*/
|
||||
fun ClosedFloatingPointRange<Double>.toSequence(step: Double): Sequence<Double> {
|
||||
return when {
|
||||
fun ClosedFloatingPointRange<Double>.toSequence(step: Double): Sequence<Double> =
|
||||
when {
|
||||
step == 0.0 -> error("Zero step in double progression")
|
||||
step > 0 -> sequence {
|
||||
var current = start
|
||||
@ -26,12 +26,11 @@ fun ClosedFloatingPointRange<Double>.toSequence(step: Double): Sequence<Double>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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")
|
||||
if (numPoints < 2) error("Can't create generic grid with less than two points")
|
||||
return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i }
|
||||
}
|
@ -1,37 +1,7 @@
|
||||
package scientifik.kmath.operations
|
||||
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
/**
|
||||
* Self value. Needed for static type checking.
|
||||
*/
|
||||
val self: T
|
||||
|
||||
/**
|
||||
* The context this element belongs to
|
||||
*/
|
||||
val context: S
|
||||
}
|
||||
|
||||
/**
|
||||
* A general interface representing linear context of some kind.
|
||||
* The context defines sum operation for its elements and multiplication by real value.
|
||||
* One must note that in some cases context is a singleton class, but in some cases it
|
||||
* works as a context for operations inside it.
|
||||
*
|
||||
* TODO do we need commutative context?
|
||||
*/
|
||||
interface Space<T> {
|
||||
/**
|
||||
* Neutral element for sum operation
|
||||
*/
|
||||
val zero: T
|
||||
|
||||
interface SpaceOperations<T> {
|
||||
/**
|
||||
* Addition operation for two context elements
|
||||
*/
|
||||
@ -40,7 +10,7 @@ interface Space<T> {
|
||||
/**
|
||||
* Multiplication operation for context element and real number
|
||||
*/
|
||||
fun multiply(a: T, k: Double): T
|
||||
fun multiply(a: T, k: Number): T
|
||||
|
||||
//Operation to be performed in this context
|
||||
operator fun T.unaryMinus(): T = multiply(this, -1.0)
|
||||
@ -50,69 +20,61 @@ interface Space<T> {
|
||||
operator fun T.times(k: Number) = multiply(this, k.toDouble())
|
||||
operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble())
|
||||
operator fun Number.times(b: T) = b * this
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The element of linear context
|
||||
* @param T self type of the element. Needed for static type checking
|
||||
* @param S the type of space
|
||||
* A general interface representing linear context of some kind.
|
||||
* The context defines sum operation for its elements and multiplication by real value.
|
||||
* One must note that in some cases context is a singleton class, but in some cases it
|
||||
* works as a context for operations inside it.
|
||||
*
|
||||
* TODO do we need non-commutative context?
|
||||
*/
|
||||
interface SpaceElement<T, S : Space<T>> : MathElement<T, S> {
|
||||
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 times(k: Number): T = context.multiply(self, k.toDouble())
|
||||
operator fun div(k: Number): T = context.multiply(self, 1.0 / k.toDouble())
|
||||
interface Space<T> : SpaceOperations<T> {
|
||||
/**
|
||||
* Neutral element for sum operation
|
||||
*/
|
||||
val zero: T
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as {@link Space} but with additional multiplication operation
|
||||
*/
|
||||
interface Ring<T> : Space<T> {
|
||||
/**
|
||||
* neutral operation for multiplication
|
||||
*/
|
||||
val one: T
|
||||
|
||||
interface RingOperations<T> : SpaceOperations<T> {
|
||||
/**
|
||||
* Multiplication for two field elements
|
||||
*/
|
||||
fun multiply(a: T, b: T): T
|
||||
|
||||
operator fun T.times(b: T): T = multiply(this, b)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring element
|
||||
* The same as {@link Space} but with additional multiplication operation
|
||||
*/
|
||||
interface RingElement<T, S : Ring<T>> : SpaceElement<T, S> {
|
||||
override val context: S
|
||||
interface Ring<T> : Space<T>, RingOperations<T> {
|
||||
/**
|
||||
* neutral operation for multiplication
|
||||
*/
|
||||
val one: T
|
||||
|
||||
operator fun times(b: T): T = context.multiply(self, 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
|
||||
}
|
||||
|
||||
/**
|
||||
* All ring operations but without neutral elements
|
||||
*/
|
||||
interface FieldOperations<T> : RingOperations<T> {
|
||||
fun divide(a: T, b: T): T
|
||||
|
||||
operator fun T.div(b: T): T = divide(this, b)
|
||||
}
|
||||
|
||||
/**
|
||||
* Four operations algebra
|
||||
*/
|
||||
interface Field<T> : Ring<T> {
|
||||
fun divide(a: T, b: T): T
|
||||
|
||||
operator fun T.div(b: T): T = divide(this, b)
|
||||
interface Field<T> : Ring<T>, FieldOperations<T> {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Field element
|
||||
*/
|
||||
interface FieldElement<T, S : Field<T>> : RingElement<T, S> {
|
||||
override val context: S
|
||||
|
||||
operator fun div(b: T): T = context.divide(self, b)
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package scientifik.kmath.operations
|
||||
|
||||
/**
|
||||
* The generic mathematics elements which is able to store its context
|
||||
* @param T the type of space operation results
|
||||
* @param I self type of the element. Needed for static type checking
|
||||
* @param C the type of mathematical context for this element
|
||||
*/
|
||||
interface MathElement<C> {
|
||||
/**
|
||||
* The context this element belongs to
|
||||
*/
|
||||
val context: C
|
||||
}
|
||||
|
||||
interface MathWrapper<T, I> {
|
||||
fun unwrap(): T
|
||||
fun T.wrap(): I
|
||||
}
|
||||
|
||||
/**
|
||||
* The element of linear context
|
||||
* @param T the type of space operation results
|
||||
* @param I self type of the element. Needed for static type checking
|
||||
* @param S the type of space
|
||||
*/
|
||||
interface SpaceElement<T, I : SpaceElement<T, I, S>, S : Space<T>> : MathElement<S>, MathWrapper<T, I> {
|
||||
|
||||
operator fun plus(b: T) = context.add(unwrap(), b).wrap()
|
||||
operator fun minus(b: T) = context.add(unwrap(), context.multiply(b, -1.0)).wrap()
|
||||
operator fun times(k: Number) = context.multiply(unwrap(), k.toDouble()).wrap()
|
||||
operator fun div(k: Number) = context.multiply(unwrap(), 1.0 / k.toDouble()).wrap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring element
|
||||
*/
|
||||
interface RingElement<T, I : RingElement<T, I, R>, R : Ring<T>> : SpaceElement<T, I, R> {
|
||||
operator fun times(b: T) = context.multiply(unwrap(), b).wrap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Field element
|
||||
*/
|
||||
interface FieldElement<T, I : FieldElement<T, I, F>, F : Field<T>> : RingElement<T, I, F> {
|
||||
override val context: F
|
||||
operator fun div(b: T) = context.divide(unwrap(), b).wrap()
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package scientifik.kmath.operations
|
||||
|
||||
import scientifik.kmath.structures.Buffer
|
||||
import scientifik.kmath.structures.asSequence
|
||||
|
||||
fun <T> Space<T>.sum(data : Iterable<T>): T = data.fold(zero) { left, right -> add(left,right) }
|
||||
fun <T> Space<T>.sum(data : Sequence<T>): T = data.fold(zero) { left, right -> add(left, right) }
|
@ -1,45 +1,97 @@
|
||||
package scientifik.kmath.operations
|
||||
|
||||
import scientifik.kmath.structures.Buffer
|
||||
import scientifik.kmath.structures.MutableBuffer
|
||||
import scientifik.kmath.structures.ObjectBuffer
|
||||
import scientifik.memory.MemoryReader
|
||||
import scientifik.memory.MemorySpec
|
||||
import scientifik.memory.MemoryWriter
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* A field for complex numbers
|
||||
*/
|
||||
object ComplexField : Field<Complex> {
|
||||
object ComplexField : ExtendedFieldOperations<Complex>, 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)
|
||||
val i = Complex(0.0, 1.0)
|
||||
|
||||
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
|
||||
override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im)
|
||||
|
||||
override fun multiply(a: Complex, k: Number): Complex = Complex(a.re * k.toDouble(), a.im * k.toDouble())
|
||||
|
||||
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 {
|
||||
val norm = b.square
|
||||
return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm)
|
||||
}
|
||||
|
||||
override fun sin(arg: Complex): Complex = i / 2 * (exp(-i * arg) - exp(i * arg))
|
||||
|
||||
override fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2
|
||||
|
||||
override fun power(arg: Complex, pow: Number): Complex =
|
||||
arg.abs.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta))
|
||||
|
||||
override fun exp(arg: Complex): Complex = exp(arg.re) * (cos(arg.im) + i * sin(arg.im))
|
||||
|
||||
override fun ln(arg: Complex): Complex = ln(arg.abs) + i * atan2(arg.im, arg.re)
|
||||
|
||||
operator fun Double.plus(c: Complex) = add(this.toComplex(), c)
|
||||
|
||||
operator fun Double.minus(c: Complex) = add(this.toComplex(), -c)
|
||||
|
||||
operator fun Complex.plus(d: Double) = d + this
|
||||
|
||||
operator fun Complex.minus(d: Double) = add(this, -d.toComplex())
|
||||
|
||||
operator fun Double.times(c: Complex) = Complex(c.re * this, c.im * this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
data class Complex(val re: Double, val im: Double) : FieldElement<Complex, Complex, ComplexField> {
|
||||
override fun unwrap(): Complex = this
|
||||
|
||||
override fun Complex.wrap(): Complex = this
|
||||
|
||||
override val context: ComplexField get() = ComplexField
|
||||
|
||||
/**
|
||||
* A complex conjugate
|
||||
*/
|
||||
val conjugate: Complex
|
||||
get() = Complex(re, -im)
|
||||
val conjugate: Complex get() = Complex(re, -im)
|
||||
|
||||
val square: Double
|
||||
get() = re * re + im * im
|
||||
val square: Double get() = re * re + im * im
|
||||
|
||||
val abs: Double
|
||||
get() = kotlin.math.sqrt(square)
|
||||
val abs: Double get() = sqrt(square)
|
||||
|
||||
companion object {
|
||||
val theta: Double get() = atan(im / re)
|
||||
|
||||
companion object : MemorySpec<Complex> {
|
||||
override val objectSize: Int = 16
|
||||
|
||||
override fun MemoryReader.read(offset: Int): Complex =
|
||||
Complex(readDouble(offset), readDouble(offset + 8))
|
||||
|
||||
override fun MemoryWriter.write(offset: Int, value: Complex) {
|
||||
writeDouble(offset, value.re)
|
||||
writeDouble(offset + 8, value.im)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Double.toComplex() = Complex(this, 0.0)
|
||||
|
||||
fun Buffer.Companion.complex(size: Int, init: (Int) -> Complex): Buffer<Complex> {
|
||||
return ObjectBuffer.create(Complex, size, init)
|
||||
}
|
||||
|
||||
fun MutableBuffer.Companion.complex(size: Int, init: (Int) -> Complex): Buffer<Complex> {
|
||||
return ObjectBuffer.create(Complex, size, init)
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package scientifik.kmath.operations
|
||||
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* Advanced Number-like field that implements basic operations
|
||||
*/
|
||||
interface ExtendedField<N : Any> :
|
||||
Field<N>,
|
||||
TrigonometricOperations<N>,
|
||||
PowerOperations<N>,
|
||||
ExponentialOperations<N>
|
||||
|
||||
|
||||
/**
|
||||
* Field for real values
|
||||
*/
|
||||
object RealField : ExtendedField<Real>, Norm<Real, Real> {
|
||||
override val zero: Real = Real(0.0)
|
||||
override fun add(a: Real, b: Real): Real = Real(a.value + b.value)
|
||||
override val one: Real = Real(1.0)
|
||||
override fun multiply(a: Real, b: Real): Real = Real(a.value * b.value)
|
||||
override fun multiply(a: Real, k: Double): Real = Real(a.value * k)
|
||||
override fun divide(a: Real, b: Real): Real = Real(a.value / b.value)
|
||||
|
||||
override fun sin(arg: Real): Real = Real(kotlin.math.sin(arg.value))
|
||||
override fun cos(arg: Real): Real = Real(kotlin.math.cos(arg.value))
|
||||
|
||||
override fun power(arg: Real, pow: Double): Real = Real(arg.value.pow(pow))
|
||||
|
||||
override fun exp(arg: Real): Real = Real(kotlin.math.exp(arg.value))
|
||||
|
||||
override fun ln(arg: Real): Real = Real(kotlin.math.ln(arg.value))
|
||||
|
||||
override fun norm(arg: Real): Real = Real(kotlin.math.abs(arg.value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Real field element wrapping double.
|
||||
*
|
||||
* TODO inline does not work due to compiler bug. Waiting for fix for KT-27586
|
||||
*/
|
||||
inline class Real(val value: Double) : FieldElement<Real, RealField> {
|
||||
|
||||
//values are dynamically calculated to save memory
|
||||
override val self
|
||||
get() = this
|
||||
|
||||
override val context
|
||||
get() = RealField
|
||||
|
||||
companion object {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for double without boxing. Does not produce appropriate field element
|
||||
*/
|
||||
object DoubleField : ExtendedField<Double>, Norm<Double, Double> {
|
||||
override val zero: Double = 0.0
|
||||
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 val one: Double = 1.0
|
||||
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)
|
||||
|
||||
override fun norm(arg: Double): Double = kotlin.math.abs(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for double without boxing. Does not produce appropriate field element
|
||||
*/
|
||||
object IntField : Field<Int>{
|
||||
override val zero: Int = 0
|
||||
override fun add(a: Int, b: Int): Int = a + b
|
||||
override fun multiply(a: Int, b: Int): Int = a * b
|
||||
override fun multiply(a: Int, k: Double): Int = (k*a).toInt()
|
||||
override val one: Int = 1
|
||||
override fun divide(a: Int, b: Int): Int = a / b
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package scientifik.kmath.operations
|
||||
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* Advanced Number-like field that implements basic operations
|
||||
*/
|
||||
interface ExtendedFieldOperations<T> :
|
||||
FieldOperations<T>,
|
||||
TrigonometricOperations<T>,
|
||||
PowerOperations<T>,
|
||||
ExponentialOperations<T>
|
||||
|
||||
interface ExtendedField<T> : ExtendedFieldOperations<T>, Field<T>
|
||||
|
||||
/**
|
||||
* Real field element wrapping double.
|
||||
*
|
||||
* TODO inline does not work due to compiler bug. Waiting for fix for KT-27586
|
||||
*/
|
||||
inline class Real(val value: Double) : FieldElement<Double, Real, RealField> {
|
||||
override fun unwrap(): Double = value
|
||||
|
||||
override fun Double.wrap(): Real = Real(value)
|
||||
|
||||
override val context get() = RealField
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for double without boxing. Does not produce appropriate field element
|
||||
*/
|
||||
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
|
||||
object RealField : Field<Double>, ExtendedFieldOperations<Double>, Norm<Double, Double> {
|
||||
override val zero: Double = 0.0
|
||||
override fun add(a: Double, b: Double): Double = a + b
|
||||
override fun multiply(a: Double, b: Double): Double = a * b
|
||||
override fun multiply(a: Double, k: Number): Double = a * k.toDouble()
|
||||
|
||||
override val one: Double = 1.0
|
||||
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: Number): Double = arg.pow(pow.toDouble())
|
||||
|
||||
override fun exp(arg: Double): Double = kotlin.math.exp(arg)
|
||||
override fun ln(arg: Double): Double = kotlin.math.ln(arg)
|
||||
|
||||
override fun norm(arg: Double): Double = kotlin.math.abs(arg)
|
||||
|
||||
override fun Double.unaryMinus(): Double = -this
|
||||
|
||||
override fun Double.minus(b: Double): Double = this - b
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for [Int] without boxing. Does not produce corresponding field element
|
||||
*/
|
||||
object IntRing : Ring<Int>, Norm<Int, Int> {
|
||||
override val zero: Int = 0
|
||||
override fun add(a: Int, b: Int): Int = a + b
|
||||
override fun multiply(a: Int, b: Int): Int = a * b
|
||||
override fun multiply(a: Int, k: Number): Int = (k * a)
|
||||
override val one: Int = 1
|
||||
|
||||
override fun norm(arg: Int): Int = arg
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for [Short] without boxing. Does not produce appropriate field element
|
||||
*/
|
||||
object ShortRing : Ring<Short>, Norm<Short, Short> {
|
||||
override val zero: Short = 0
|
||||
override fun add(a: Short, b: Short): Short = (a + b).toShort()
|
||||
override fun multiply(a: Short, b: Short): Short = (a * b).toShort()
|
||||
override fun multiply(a: Short, k: Number): Short = (a * k)
|
||||
override val one: Short = 1
|
||||
|
||||
override fun norm(arg: Short): Short = arg
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for [Byte] values
|
||||
*/
|
||||
object ByteRing : Ring<Byte>, Norm<Byte, Byte> {
|
||||
override val zero: Byte = 0
|
||||
override fun add(a: Byte, b: Byte): Byte = (a + b).toByte()
|
||||
override fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte()
|
||||
override fun multiply(a: Byte, k: Number): Byte = (a * k)
|
||||
override val one: Byte = 1
|
||||
|
||||
override fun norm(arg: Byte): Byte = arg
|
||||
}
|
||||
|
||||
/**
|
||||
* A field for [Long] values
|
||||
*/
|
||||
object LongRing : Ring<Long>, Norm<Long, Long> {
|
||||
override val zero: Long = 0
|
||||
override fun add(a: Long, b: Long): Long = (a + b)
|
||||
override fun multiply(a: Long, b: Long): Long = (a * b)
|
||||
override fun multiply(a: Long, k: Number): Long = (a * k)
|
||||
override val one: Long = 1
|
||||
|
||||
override fun norm(arg: Long): Long = arg
|
||||
}
|
@ -10,7 +10,7 @@ package scientifik.kmath.operations
|
||||
* It also allows to override behavior for optional operations
|
||||
*
|
||||
*/
|
||||
interface TrigonometricOperations<T> : Field<T> {
|
||||
interface TrigonometricOperations<T> : FieldOperations<T> {
|
||||
fun sin(arg: T): T
|
||||
fun cos(arg: T): T
|
||||
|
||||
@ -19,10 +19,10 @@ interface TrigonometricOperations<T> : Field<T> {
|
||||
fun ctg(arg: T): T = cos(arg) / sin(arg)
|
||||
}
|
||||
|
||||
fun <T : FieldElement<T, out TrigonometricOperations<T>>> sin(arg: T): T = arg.context.sin(arg)
|
||||
fun <T : FieldElement<T, out TrigonometricOperations<T>>> cos(arg: T): T = arg.context.cos(arg)
|
||||
fun <T : FieldElement<T, out TrigonometricOperations<T>>> tg(arg: T): T = arg.context.tg(arg)
|
||||
fun <T : FieldElement<T, out TrigonometricOperations<T>>> ctg(arg: T): T = arg.context.ctg(arg)
|
||||
fun <T : MathElement<out TrigonometricOperations<T>>> sin(arg: T): T = arg.context.sin(arg)
|
||||
fun <T : MathElement<out TrigonometricOperations<T>>> cos(arg: T): T = arg.context.cos(arg)
|
||||
fun <T : MathElement<out TrigonometricOperations<T>>> tg(arg: T): T = arg.context.tg(arg)
|
||||
fun <T : MathElement<out TrigonometricOperations<T>>> ctg(arg: T): T = arg.context.ctg(arg)
|
||||
|
||||
/* Power and roots */
|
||||
|
||||
@ -30,12 +30,13 @@ fun <T : FieldElement<T, out TrigonometricOperations<T>>> ctg(arg: T): T = arg.c
|
||||
* A context extension to include power operations like square roots, etc
|
||||
*/
|
||||
interface PowerOperations<T> {
|
||||
fun power(arg: T, pow: Double): T
|
||||
fun power(arg: T, pow: Number): T
|
||||
fun sqrt(arg: T) = power(arg, 0.5)
|
||||
}
|
||||
|
||||
infix fun <T : MathElement<T, out PowerOperations<T>>> T.pow(power: Double): T = context.power(this, power)
|
||||
fun <T : MathElement<T, out PowerOperations<T>>> sqrt(arg: T): T = arg pow 0.5
|
||||
fun <T : MathElement<T, out PowerOperations<T>>> sqr(arg: T): T = arg pow 2.0
|
||||
infix fun <T : MathElement<out PowerOperations<T>>> T.pow(power: Double): T = context.power(this, power)
|
||||
fun <T : MathElement<out PowerOperations<T>>> sqrt(arg: T): T = arg pow 0.5
|
||||
fun <T : MathElement<out PowerOperations<T>>> sqr(arg: T): T = arg pow 2.0
|
||||
|
||||
/* Exponential */
|
||||
|
||||
@ -44,11 +45,11 @@ interface ExponentialOperations<T> {
|
||||
fun ln(arg: T): T
|
||||
}
|
||||
|
||||
fun <T : MathElement<T, out ExponentialOperations<T>>> exp(arg: T): T = arg.context.exp(arg)
|
||||
fun <T : MathElement<T, out ExponentialOperations<T>>> ln(arg: T): T = arg.context.ln(arg)
|
||||
fun <T : MathElement<out ExponentialOperations<T>>> exp(arg: T): T = arg.context.exp(arg)
|
||||
fun <T : MathElement<out ExponentialOperations<T>>> ln(arg: T): T = arg.context.ln(arg)
|
||||
|
||||
interface Norm<in T, out R> {
|
||||
interface Norm<in T: Any, out R> {
|
||||
fun norm(arg: T): R
|
||||
}
|
||||
|
||||
fun <T : MathElement<T, out Norm<T, R>>, R> norm(arg: T): R = arg.context.norm(arg)
|
||||
fun <T : MathElement<out Norm<T, R>>, R> norm(arg: T): R = arg.context.norm(arg)
|
@ -0,0 +1,73 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.FieldElement
|
||||
|
||||
|
||||
class BoxingNDField<T, F : Field<T>>(
|
||||
override val shape: IntArray,
|
||||
override val elementContext: F,
|
||||
val bufferFactory: BufferFactory<T>
|
||||
) : BufferedNDField<T, F> {
|
||||
|
||||
override val strides: Strides = DefaultStrides(shape)
|
||||
|
||||
fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer<T> =
|
||||
bufferFactory(size, initializer)
|
||||
|
||||
override fun check(vararg elements: NDBuffer<T>) {
|
||||
if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides")
|
||||
}
|
||||
|
||||
override val zero by lazy { produce { zero } }
|
||||
override val one by lazy { produce { one } }
|
||||
|
||||
override fun produce(initializer: F.(IntArray) -> T) =
|
||||
BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) })
|
||||
|
||||
override fun map(arg: NDBuffer<T>, transform: F.(T) -> T): BufferedNDFieldElement<T, F> {
|
||||
check(arg)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) })
|
||||
|
||||
// val buffer = arg.buffer.transform { _, value -> elementContext.transform(value) }
|
||||
// return BufferedNDFieldElement(this, buffer)
|
||||
|
||||
}
|
||||
|
||||
override fun mapIndexed(
|
||||
arg: NDBuffer<T>,
|
||||
transform: F.(index: IntArray, T) -> T
|
||||
): BufferedNDFieldElement<T, F> {
|
||||
check(arg)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(arg.strides.linearSize) { offset ->
|
||||
elementContext.transform(
|
||||
arg.strides.index(offset),
|
||||
arg.buffer[offset]
|
||||
)
|
||||
})
|
||||
|
||||
// val buffer =
|
||||
// arg.buffer.transform { offset, value -> elementContext.transform(arg.strides.index(offset), value) }
|
||||
// return BufferedNDFieldElement(this, buffer)
|
||||
}
|
||||
|
||||
override fun combine(
|
||||
a: NDBuffer<T>,
|
||||
b: NDBuffer<T>,
|
||||
transform: F.(T, T) -> T
|
||||
): BufferedNDFieldElement<T, F> {
|
||||
check(a, b)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
|
||||
}
|
||||
|
||||
override fun NDBuffer<T>.toElement(): FieldElement<NDBuffer<T>, *, out BufferedNDField<T, F>> =
|
||||
BufferedNDFieldElement(this@BoxingNDField, buffer)
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.*
|
||||
|
||||
interface BufferedNDAlgebra<T, C>: NDAlgebra<T, C, NDBuffer<T>>{
|
||||
val strides: Strides
|
||||
|
||||
override fun check(vararg elements: NDBuffer<T>) {
|
||||
if (!elements.all { it.strides == this.strides }) error("Strides mismatch")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any [NDStructure] to buffered structure using strides from this context.
|
||||
* If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes
|
||||
*
|
||||
* If the argument is [NDBuffer] with different strides structure, the new element will be produced.
|
||||
*/
|
||||
fun NDStructure<T>.toBuffer(): NDBuffer<T> {
|
||||
return if (this is NDBuffer<T> && this.strides == this@BufferedNDAlgebra.strides) {
|
||||
this
|
||||
} else {
|
||||
produce { index -> get(index) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a buffer to element of this algebra
|
||||
*/
|
||||
fun NDBuffer<T>.toElement(): MathElement<out BufferedNDAlgebra<T, C>>
|
||||
}
|
||||
|
||||
|
||||
interface BufferedNDSpace<T, S : Space<T>> : NDSpace<T, S, NDBuffer<T>>, BufferedNDAlgebra<T,S> {
|
||||
override fun NDBuffer<T>.toElement(): SpaceElement<NDBuffer<T>, *, out BufferedNDSpace<T, S>>
|
||||
}
|
||||
|
||||
interface BufferedNDRing<T, R : Ring<T>> : NDRing<T, R, NDBuffer<T>>, BufferedNDSpace<T, R> {
|
||||
override fun NDBuffer<T>.toElement(): RingElement<NDBuffer<T>, *, out BufferedNDRing<T, R>>
|
||||
}
|
||||
|
||||
interface BufferedNDField<T, F : Field<T>> : NDField<T, F, NDBuffer<T>>, BufferedNDRing<T, F> {
|
||||
override fun NDBuffer<T>.toElement(): FieldElement<NDBuffer<T>, *, out BufferedNDField<T, F>>
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.*
|
||||
|
||||
/**
|
||||
* Base interface for an element with context, containing strides
|
||||
*/
|
||||
interface BufferedNDElement<T, C> : NDBuffer<T>, NDElement<T, C, NDBuffer<T>> {
|
||||
override val context: BufferedNDAlgebra<T, C>
|
||||
|
||||
override val strides get() = context.strides
|
||||
|
||||
override val shape: IntArray get() = context.shape
|
||||
}
|
||||
|
||||
class BufferedNDSpaceElement<T, S : Space<T>>(
|
||||
override val context: BufferedNDSpace<T, S>,
|
||||
override val buffer: Buffer<T>
|
||||
) : BufferedNDElement<T, S>, SpaceElement<NDBuffer<T>, BufferedNDSpaceElement<T, S>, BufferedNDSpace<T, S>> {
|
||||
|
||||
override fun unwrap(): NDBuffer<T> = this
|
||||
|
||||
override fun NDBuffer<T>.wrap(): BufferedNDSpaceElement<T, S> {
|
||||
context.check(this)
|
||||
return BufferedNDSpaceElement(context, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
class BufferedNDRingElement<T, R : Ring<T>>(
|
||||
override val context: BufferedNDRing<T, R>,
|
||||
override val buffer: Buffer<T>
|
||||
) : BufferedNDElement<T, R>, RingElement<NDBuffer<T>, BufferedNDRingElement<T, R>, BufferedNDRing<T, R>> {
|
||||
|
||||
override fun unwrap(): NDBuffer<T> = this
|
||||
|
||||
override fun NDBuffer<T>.wrap(): BufferedNDRingElement<T, R> {
|
||||
context.check(this)
|
||||
return BufferedNDRingElement(context, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
class BufferedNDFieldElement<T, F : Field<T>>(
|
||||
override val context: BufferedNDField<T, F>,
|
||||
override val buffer: Buffer<T>
|
||||
) : BufferedNDElement<T, F>, FieldElement<NDBuffer<T>, BufferedNDFieldElement<T, F>, BufferedNDField<T, F>> {
|
||||
|
||||
override fun unwrap(): NDBuffer<T> = this
|
||||
|
||||
override fun NDBuffer<T>.wrap(): BufferedNDFieldElement<T, F> {
|
||||
context.check(this)
|
||||
return BufferedNDFieldElement(context, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||
*/
|
||||
operator fun <T : Any, F : Field<T>> Function1<T, T>.invoke(ndElement: BufferedNDElement<T, F>) =
|
||||
ndElement.context.run { map(ndElement) { invoke(it) }.toElement() }
|
||||
|
||||
/* plus and minus */
|
||||
|
||||
/**
|
||||
* Summation operation for [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun <T : Any, F : Space<T>> BufferedNDElement<T, F>.plus(arg: T) =
|
||||
context.map(this) { it + arg }.wrap()
|
||||
|
||||
/**
|
||||
* Subtraction operation between [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun <T : Any, F : Space<T>> BufferedNDElement<T, F>.minus(arg: T) =
|
||||
context.map(this) { it - arg }.wrap()
|
||||
|
||||
/* prod and div */
|
||||
|
||||
/**
|
||||
* Product operation for [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun <T : Any, F : Ring<T>> BufferedNDElement<T, F>.times(arg: T) =
|
||||
context.map(this) { it * arg }.wrap()
|
||||
|
||||
/**
|
||||
* Division operation between [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun <T : Any, F : Field<T>> BufferedNDElement<T, F>.div(arg: T) =
|
||||
context.map(this) { it / arg }.wrap()
|
@ -1,20 +1,70 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
|
||||
typealias BufferFactory<T> = (Int, (Int) -> T) -> Buffer<T>
|
||||
typealias MutableBufferFactory<T> = (Int, (Int) -> T) -> MutableBuffer<T>
|
||||
|
||||
|
||||
/**
|
||||
* A generic random access structure for both primitives and objects
|
||||
*/
|
||||
interface Buffer<T> {
|
||||
|
||||
/**
|
||||
* The size of the buffer
|
||||
*/
|
||||
val size: Int
|
||||
|
||||
/**
|
||||
* Get element at given index
|
||||
*/
|
||||
operator fun get(index: Int): T
|
||||
|
||||
/**
|
||||
* Iterate over all elements
|
||||
*/
|
||||
operator fun iterator(): Iterator<T>
|
||||
|
||||
/**
|
||||
* Check content eqiality with another buffer
|
||||
*/
|
||||
fun contentEquals(other: Buffer<*>): Boolean =
|
||||
asSequence().mapIndexed { index, value -> value == other[index] }.all { it }
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Create a boxing buffer of given type
|
||||
*/
|
||||
inline fun <T> boxing(size: Int, initializer: (Int) -> T): Buffer<T> = ListBuffer(List(size, initializer))
|
||||
|
||||
/**
|
||||
* Create most appropriate immutable buffer for given type avoiding boxing wherever possible
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Any> auto(size: Int, crossinline initializer: (Int) -> T): Buffer<T> {
|
||||
return when (T::class) {
|
||||
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer<T>
|
||||
Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer<T>
|
||||
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer<T>
|
||||
Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer<T>
|
||||
else -> boxing(size, initializer)
|
||||
}
|
||||
}
|
||||
|
||||
val DoubleBufferFactory: BufferFactory<Double> =
|
||||
{ size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) }
|
||||
val ShortBufferFactory: BufferFactory<Short> =
|
||||
{ size, initializer -> ShortBuffer(ShortArray(size, initializer)) }
|
||||
val IntBufferFactory: BufferFactory<Int> = { size, initializer -> IntBuffer(IntArray(size, initializer)) }
|
||||
val LongBufferFactory: BufferFactory<Long> = { size, initializer -> LongBuffer(LongArray(size, initializer)) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Buffer<T>.asSequence(): Sequence<T> = iterator().asSequence()
|
||||
|
||||
fun <T> Buffer<T>.asIterable(): Iterable<T> = iterator().asSequence().asIterable()
|
||||
|
||||
interface MutableBuffer<T> : Buffer<T> {
|
||||
operator fun set(index: Int, value: T)
|
||||
|
||||
@ -22,10 +72,32 @@ interface MutableBuffer<T> : Buffer<T> {
|
||||
* A shallow copy of the buffer
|
||||
*/
|
||||
fun copy(): MutableBuffer<T>
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a boxing mutable buffer of given type
|
||||
*/
|
||||
inline fun <T> boxing(size: Int, initializer: (Int) -> T): MutableBuffer<T> =
|
||||
MutableListBuffer(MutableList(size, initializer))
|
||||
|
||||
/**
|
||||
* Create most appropriate mutable buffer for given type avoiding boxing wherever possible
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Any> auto(size: Int, initializer: (Int) -> T): MutableBuffer<T> {
|
||||
return when (T::class) {
|
||||
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer<T>
|
||||
Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as MutableBuffer<T>
|
||||
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer<T>
|
||||
Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer<T>
|
||||
else -> boxing(size, initializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline class ListBuffer<T>(private val list: List<T>) : Buffer<T> {
|
||||
inline class ListBuffer<T>(val list: List<T>) : Buffer<T> {
|
||||
|
||||
override val size: Int
|
||||
get() = list.size
|
||||
@ -35,7 +107,12 @@ inline class ListBuffer<T>(private val list: List<T>) : Buffer<T> {
|
||||
override fun iterator(): Iterator<T> = list.iterator()
|
||||
}
|
||||
|
||||
inline class MutableListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> {
|
||||
fun <T> List<T>.asBuffer() = ListBuffer(this)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
inline fun <T> ListBuffer(size: Int, init: (Int) -> T) = List(size, init).asBuffer()
|
||||
|
||||
inline class MutableListBuffer<T>(val list: MutableList<T>) : MutableBuffer<T> {
|
||||
|
||||
override val size: Int
|
||||
get() = list.size
|
||||
@ -47,12 +124,11 @@ inline class MutableListBuffer<T>(private val list: MutableList<T>) : MutableBuf
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> = list.iterator()
|
||||
|
||||
override fun copy(): MutableBuffer<T> = MutableListBuffer(ArrayList(list))
|
||||
}
|
||||
|
||||
class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
|
||||
//Can't inline because array invariant
|
||||
//Can't inline because array is invariant
|
||||
override val size: Int
|
||||
get() = array.size
|
||||
|
||||
@ -67,9 +143,10 @@ class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
|
||||
override fun copy(): MutableBuffer<T> = ArrayBuffer(array.copyOf())
|
||||
}
|
||||
|
||||
inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double> {
|
||||
override val size: Int
|
||||
get() = array.size
|
||||
fun <T> Array<T>.asBuffer() = ArrayBuffer(this)
|
||||
|
||||
inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer<Double> {
|
||||
override val size: Int get() = array.size
|
||||
|
||||
override fun get(index: Int): Double = array[index]
|
||||
|
||||
@ -77,14 +154,45 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double
|
||||
array[index] = value
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Double> = array.iterator()
|
||||
override fun iterator() = array.iterator()
|
||||
|
||||
override fun copy(): MutableBuffer<Double> = DoubleBuffer(array.copyOf())
|
||||
}
|
||||
|
||||
inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> {
|
||||
override val size: Int
|
||||
get() = array.size
|
||||
@Suppress("FunctionName")
|
||||
inline fun DoubleBuffer(size: Int, init: (Int) -> Double) = DoubleBuffer(DoubleArray(size) { init(it) })
|
||||
|
||||
/**
|
||||
* Transform buffer of doubles into array for high performance operations
|
||||
*/
|
||||
val Buffer<out Double>.array: DoubleArray
|
||||
get() = if (this is DoubleBuffer) {
|
||||
array
|
||||
} else {
|
||||
DoubleArray(size) { get(it) }
|
||||
}
|
||||
|
||||
fun DoubleArray.asBuffer() = DoubleBuffer(this)
|
||||
|
||||
inline class ShortBuffer(val array: ShortArray) : MutableBuffer<Short> {
|
||||
override val size: Int get() = array.size
|
||||
|
||||
override fun get(index: Int): Short = array[index]
|
||||
|
||||
override fun set(index: Int, value: Short) {
|
||||
array[index] = value
|
||||
}
|
||||
|
||||
override fun iterator() = array.iterator()
|
||||
|
||||
override fun copy(): MutableBuffer<Short> = ShortBuffer(array.copyOf())
|
||||
|
||||
}
|
||||
|
||||
fun ShortArray.asBuffer() = ShortBuffer(this)
|
||||
|
||||
inline class IntBuffer(val array: IntArray) : MutableBuffer<Int> {
|
||||
override val size: Int get() = array.size
|
||||
|
||||
override fun get(index: Int): Int = array[index]
|
||||
|
||||
@ -92,17 +200,55 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> {
|
||||
array[index] = value
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Int> = array.iterator()
|
||||
override fun iterator() = array.iterator()
|
||||
|
||||
override fun copy(): MutableBuffer<Int> = IntBuffer(array.copyOf())
|
||||
|
||||
}
|
||||
|
||||
inline class ReadOnlyBuffer<T>(private val buffer: MutableBuffer<T>) : Buffer<T> {
|
||||
fun IntArray.asBuffer() = IntBuffer(this)
|
||||
|
||||
inline class LongBuffer(val array: LongArray) : MutableBuffer<Long> {
|
||||
override val size: Int get() = array.size
|
||||
|
||||
override fun get(index: Int): Long = array[index]
|
||||
|
||||
override fun set(index: Int, value: Long) {
|
||||
array[index] = value
|
||||
}
|
||||
|
||||
override fun iterator() = array.iterator()
|
||||
|
||||
override fun copy(): MutableBuffer<Long> = LongBuffer(array.copyOf())
|
||||
|
||||
}
|
||||
|
||||
fun LongArray.asBuffer() = LongBuffer(this)
|
||||
|
||||
inline class ReadOnlyBuffer<T>(val buffer: MutableBuffer<T>) : Buffer<T> {
|
||||
override val size: Int get() = buffer.size
|
||||
|
||||
override fun get(index: Int): T = buffer.get(index)
|
||||
|
||||
override fun iterator(): Iterator<T> = buffer.iterator()
|
||||
override fun iterator() = buffer.iterator()
|
||||
}
|
||||
|
||||
/**
|
||||
* A buffer with content calculated on-demand. The calculated contect is not stored, so it is recalculated on each call.
|
||||
* Useful when one needs single element from the buffer.
|
||||
*/
|
||||
class VirtualBuffer<T>(override val size: Int, private val generator: (Int) -> T) : Buffer<T> {
|
||||
override fun get(index: Int): T = generator(index)
|
||||
|
||||
override fun iterator(): Iterator<T> = (0 until size).asSequence().map(generator).iterator()
|
||||
|
||||
override fun contentEquals(other: Buffer<*>): Boolean {
|
||||
return if (other is VirtualBuffer) {
|
||||
this.size == other.size && this.generator == other.generator
|
||||
} else {
|
||||
super.contentEquals(other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,25 +261,6 @@ fun <T> Buffer<T>.asReadOnly(): Buffer<T> = if (this is MutableBuffer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create most appropriate immutable buffer for given type avoiding boxing wherever possible
|
||||
* Typealias for buffer transformations
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Any> buffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> {
|
||||
return when (T::class) {
|
||||
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer<T>
|
||||
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer<T>
|
||||
else -> ArrayBuffer(Array(size, initializer))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create most appropriate mutable buffer for given type avoiding boxing wherever possible
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Any> mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> {
|
||||
return when (T::class) {
|
||||
Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer<T>
|
||||
Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer<T>
|
||||
else -> ArrayBuffer(Array(size, initializer))
|
||||
}
|
||||
}
|
||||
typealias BufferTransform<T, R> = (Buffer<T>) -> Buffer<R>
|
@ -0,0 +1,134 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Complex
|
||||
import scientifik.kmath.operations.ComplexField
|
||||
import scientifik.kmath.operations.FieldElement
|
||||
import scientifik.kmath.operations.complex
|
||||
|
||||
typealias ComplexNDElement = BufferedNDFieldElement<Complex, ComplexField>
|
||||
|
||||
/**
|
||||
* An optimized nd-field for complex numbers
|
||||
*/
|
||||
class ComplexNDField(override val shape: IntArray) :
|
||||
BufferedNDField<Complex, ComplexField>,
|
||||
ExtendedNDField<Complex, ComplexField, NDBuffer<Complex>> {
|
||||
|
||||
override val strides: Strides = DefaultStrides(shape)
|
||||
|
||||
override val elementContext: ComplexField get() = ComplexField
|
||||
override val zero by lazy { produce { zero } }
|
||||
override val one by lazy { produce { one } }
|
||||
|
||||
inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer<Complex> =
|
||||
Buffer.complex(size) { initializer(it) }
|
||||
|
||||
/**
|
||||
* Inline transform an NDStructure to another structure
|
||||
*/
|
||||
override fun map(
|
||||
arg: NDBuffer<Complex>,
|
||||
transform: ComplexField.(Complex) -> Complex
|
||||
): ComplexNDElement {
|
||||
check(arg)
|
||||
val array = buildBuffer(arg.strides.linearSize) { offset -> ComplexField.transform(arg.buffer[offset]) }
|
||||
return BufferedNDFieldElement(this, array)
|
||||
}
|
||||
|
||||
override fun produce(initializer: ComplexField.(IntArray) -> Complex): ComplexNDElement {
|
||||
val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
|
||||
return BufferedNDFieldElement(this, array)
|
||||
}
|
||||
|
||||
override fun mapIndexed(
|
||||
arg: NDBuffer<Complex>,
|
||||
transform: ComplexField.(index: IntArray, Complex) -> Complex
|
||||
): ComplexNDElement {
|
||||
check(arg)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(arg.strides.linearSize) { offset ->
|
||||
elementContext.transform(
|
||||
arg.strides.index(offset),
|
||||
arg.buffer[offset]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun combine(
|
||||
a: NDBuffer<Complex>,
|
||||
b: NDBuffer<Complex>,
|
||||
transform: ComplexField.(Complex, Complex) -> Complex
|
||||
): ComplexNDElement {
|
||||
check(a, b)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
|
||||
}
|
||||
|
||||
override fun NDBuffer<Complex>.toElement(): FieldElement<NDBuffer<Complex>, *, out BufferedNDField<Complex, ComplexField>> =
|
||||
BufferedNDFieldElement(this@ComplexNDField, buffer)
|
||||
|
||||
override fun power(arg: NDBuffer<Complex>, pow: Number) = map(arg) { power(it, pow) }
|
||||
|
||||
override fun exp(arg: NDBuffer<Complex>) = map(arg) { exp(it) }
|
||||
|
||||
override fun ln(arg: NDBuffer<Complex>) = map(arg) { ln(it) }
|
||||
|
||||
override fun sin(arg: NDBuffer<Complex>) = map(arg) { sin(it) }
|
||||
|
||||
override fun cos(arg: NDBuffer<Complex>) = map(arg) { cos(it) }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fast element production using function inlining
|
||||
*/
|
||||
inline fun BufferedNDField<Complex, ComplexField>.produceInline(crossinline initializer: ComplexField.(Int) -> Complex): ComplexNDElement {
|
||||
val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.initializer(offset) }
|
||||
return BufferedNDFieldElement(this, buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Map one [ComplexNDElement] using function with indexes
|
||||
*/
|
||||
inline fun ComplexNDElement.mapIndexed(crossinline transform: ComplexField.(index: IntArray, Complex) -> Complex) =
|
||||
context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) }
|
||||
|
||||
/**
|
||||
* Map one [ComplexNDElement] using function without indexes
|
||||
*/
|
||||
inline fun ComplexNDElement.map(crossinline transform: ComplexField.(Complex) -> Complex): ComplexNDElement {
|
||||
val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.transform(buffer[offset]) }
|
||||
return BufferedNDFieldElement(context, buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||
*/
|
||||
operator fun Function1<Complex, Complex>.invoke(ndElement: ComplexNDElement) =
|
||||
ndElement.map { this@invoke(it) }
|
||||
|
||||
|
||||
/* plus and minus */
|
||||
|
||||
/**
|
||||
* Summation operation for [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun ComplexNDElement.plus(arg: Complex) =
|
||||
map { it + arg }
|
||||
|
||||
/**
|
||||
* Subtraction operation between [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun ComplexNDElement.minus(arg: Complex) =
|
||||
map { it - arg }
|
||||
|
||||
operator fun ComplexNDElement.plus(arg: Double) =
|
||||
map { it + arg }
|
||||
|
||||
operator fun ComplexNDElement.minus(arg: Double) =
|
||||
map { it - arg }
|
||||
|
||||
fun NDField.Companion.complex(vararg shape: Int) = ComplexNDField(shape)
|
@ -1,42 +1,46 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.ExponentialOperations
|
||||
import scientifik.kmath.operations.ExtendedField
|
||||
import scientifik.kmath.operations.PowerOperations
|
||||
import scientifik.kmath.operations.TrigonometricOperations
|
||||
import scientifik.kmath.operations.*
|
||||
|
||||
|
||||
/**
|
||||
* NDField that supports [ExtendedField] operations on its elements
|
||||
*/
|
||||
class ExtendedNDField<N : Any, F : ExtendedField<N>>(shape: IntArray, field: F) : NDField<N, F>(shape, field),
|
||||
TrigonometricOperations<NDElement<N, F>>,
|
||||
PowerOperations<NDElement<N, F>>,
|
||||
ExponentialOperations<NDElement<N, F>> {
|
||||
|
||||
override fun produceStructure(initializer: F.(IntArray) -> N): NDStructure<N> {
|
||||
return genericNdStructure(shape) { field.initializer(it) }
|
||||
}
|
||||
|
||||
override fun power(arg: NDElement<N, F>, pow: Double): NDElement<N, F> {
|
||||
return arg.transform { d -> with(field) { power(d, pow) } }
|
||||
}
|
||||
|
||||
override fun exp(arg: NDElement<N, F>): NDElement<N, F> {
|
||||
return arg.transform { d -> with(field) { exp(d) } }
|
||||
}
|
||||
|
||||
override fun ln(arg: NDElement<N, F>): NDElement<N, F> {
|
||||
return arg.transform { d -> with(field) { ln(d) } }
|
||||
}
|
||||
|
||||
override fun sin(arg: NDElement<N, F>): NDElement<N, F> {
|
||||
return arg.transform { d -> with(field) { sin(d) } }
|
||||
}
|
||||
|
||||
override fun cos(arg: NDElement<N, F>): NDElement<N, F> {
|
||||
return arg.transform { d -> with(field) { cos(d) } }
|
||||
}
|
||||
}
|
||||
interface ExtendedNDField<T : Any, F, N : NDStructure<T>> :
|
||||
NDField<T, F, N>,
|
||||
TrigonometricOperations<N>,
|
||||
PowerOperations<N>,
|
||||
ExponentialOperations<N>
|
||||
where F : ExtendedFieldOperations<T>, F : Field<T>
|
||||
|
||||
|
||||
///**
|
||||
// * NDField that supports [ExtendedField] operations on its elements
|
||||
// */
|
||||
//class ExtendedNDFieldWrapper<T : Any, F : ExtendedField<T>, N : NDStructure<T>>(private val ndField: NDField<T, F, N>) :
|
||||
// ExtendedNDField<T, F, N>, NDField<T, F, N> by ndField {
|
||||
//
|
||||
// override val shape: IntArray get() = ndField.shape
|
||||
// override val elementContext: F get() = ndField.elementContext
|
||||
//
|
||||
// override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer)
|
||||
//
|
||||
// override fun power(arg: N, pow: Double): N {
|
||||
// return produce { with(elementContext) { power(arg[it], pow) } }
|
||||
// }
|
||||
//
|
||||
// override fun exp(arg: N): N {
|
||||
// return produce { with(elementContext) { exp(arg[it]) } }
|
||||
// }
|
||||
//
|
||||
// override fun ln(arg: N): N {
|
||||
// return produce { with(elementContext) { ln(arg[it]) } }
|
||||
// }
|
||||
//
|
||||
// override fun sin(arg: N): N {
|
||||
// return produce { with(elementContext) { sin(arg[it]) } }
|
||||
// }
|
||||
//
|
||||
// override fun cos(arg: N): N {
|
||||
// return produce { with(elementContext) { cos(arg[it]) } }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
//
|
||||
//class LazyStructureField<T: Any>(val field: Field<T>): Field<LazyStructure<T>>{
|
||||
//
|
||||
//}
|
||||
//
|
||||
//class LazyStructure<T : Any> : NDStructure<T> {
|
||||
//
|
||||
// override val shape: IntArray
|
||||
// get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
//
|
||||
// override fun get(index: IntArray): T {
|
||||
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
// }
|
||||
//
|
||||
// override fun iterator(): Iterator<Pair<IntArray, T>> {
|
||||
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
// }
|
||||
//}
|
@ -0,0 +1,144 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.Ring
|
||||
import scientifik.kmath.operations.Space
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
|
||||
/**
|
||||
* An exception is thrown when the expected ans actual shape of NDArray differs
|
||||
*/
|
||||
class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException()
|
||||
|
||||
|
||||
/**
|
||||
* The base interface for all nd-algebra implementations
|
||||
* @param T the type of nd-structure element
|
||||
* @param C the type of the element context
|
||||
* @param N the type of the structure
|
||||
*/
|
||||
interface NDAlgebra<T, C, N : NDStructure<T>> {
|
||||
val shape: IntArray
|
||||
val elementContext: C
|
||||
|
||||
/**
|
||||
* Produce a new [N] structure using given initializer function
|
||||
*/
|
||||
fun produce(initializer: C.(IntArray) -> T): N
|
||||
|
||||
/**
|
||||
* Map elements from one structure to another one
|
||||
*/
|
||||
fun map(arg: N, transform: C.(T) -> T): N
|
||||
|
||||
/**
|
||||
* Map indexed elements
|
||||
*/
|
||||
fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N
|
||||
|
||||
/**
|
||||
* Combine two structures into one
|
||||
*/
|
||||
fun combine(a: N, b: N, transform: C.(T, T) -> T): N
|
||||
|
||||
/**
|
||||
* Check if given elements are consistent with this context
|
||||
*/
|
||||
fun check(vararg elements: N) {
|
||||
elements.forEach {
|
||||
if (!shape.contentEquals(it.shape)) {
|
||||
throw ShapeMismatchException(shape, it.shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* element-by-element invoke a function working on [T] on a [NDStructure]
|
||||
*/
|
||||
operator fun Function1<T, T>.invoke(structure: N) = map(structure) { value -> this@invoke(value) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An nd-space over element space
|
||||
*/
|
||||
interface NDSpace<T, S : Space<T>, N : NDStructure<T>> : Space<N>, NDAlgebra<T, S, N> {
|
||||
/**
|
||||
* Element-by-element addition
|
||||
*/
|
||||
override fun add(a: N, b: N): N = combine(a, b) { aValue, bValue -> add(aValue, bValue) }
|
||||
|
||||
/**
|
||||
* Multiply all elements by constant
|
||||
*/
|
||||
override fun multiply(a: N, k: Number): N = map(a) { multiply(it, k) }
|
||||
|
||||
operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) }
|
||||
operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) }
|
||||
|
||||
operator fun T.plus(arg: N) = map(arg) { value -> add(this@plus, value) }
|
||||
operator fun T.minus(arg: N) = map(arg) { value -> add(-this@minus, value) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An nd-ring over element ring
|
||||
*/
|
||||
interface NDRing<T, R : Ring<T>, N : NDStructure<T>> : Ring<N>, NDSpace<T, R, N> {
|
||||
|
||||
/**
|
||||
* Element-by-element multiplication
|
||||
*/
|
||||
override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) }
|
||||
|
||||
operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) }
|
||||
operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Field for n-dimensional structures.
|
||||
* @param shape - the list of dimensions of the array
|
||||
* @param elementField - operations field defined on individual array element
|
||||
* @param T - the type of the element contained in ND structure
|
||||
* @param F - field of structure elements
|
||||
* @param R - actual nd-element type of this field
|
||||
*/
|
||||
interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F, N> {
|
||||
|
||||
/**
|
||||
* Element-by-element division
|
||||
*/
|
||||
override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) }
|
||||
|
||||
operator fun N.div(arg: T) = map(this) { value -> divide(arg, value) }
|
||||
operator fun T.div(arg: N) = map(arg) { divide(it, this@div) }
|
||||
|
||||
companion object {
|
||||
|
||||
private val realNDFieldCache = HashMap<IntArray, RealNDField>()
|
||||
|
||||
/**
|
||||
* Create a nd-field for [Double] values or pull it from cache if it was created previously
|
||||
*/
|
||||
fun real(vararg shape: Int) = realNDFieldCache.getOrPut(shape) { RealNDField(shape) }
|
||||
|
||||
/**
|
||||
* Create a nd-field with boxing generic buffer
|
||||
*/
|
||||
fun <T : Any, F : Field<T>> buffered(
|
||||
shape: IntArray,
|
||||
field: F,
|
||||
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing
|
||||
) =
|
||||
BoxingNDField(shape, field, bufferFactory)
|
||||
|
||||
/**
|
||||
* Create a most suitable implementation for nd-field using reified class.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Any, F : Field<T>> auto(field: F, vararg shape: Int): BufferedNDField<T, F> =
|
||||
when {
|
||||
T::class == Double::class -> real(*shape) as BufferedNDField<T, F>
|
||||
else -> BoxingNDField(shape, field, Buffer.Companion::auto)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.RealField
|
||||
import scientifik.kmath.operations.Ring
|
||||
import scientifik.kmath.operations.Space
|
||||
|
||||
/**
|
||||
* The root for all [NDStructure] based algebra elements. Does not implement algebra element root because of problems with recursive self-types
|
||||
* @param T the type of the element of the structure
|
||||
* @param C the type of the context for the element
|
||||
* @param N the type of the underlying [NDStructure]
|
||||
*/
|
||||
interface NDElement<T, C, N : NDStructure<T>> : NDStructure<T> {
|
||||
|
||||
val context: NDAlgebra<T, C, N>
|
||||
|
||||
fun unwrap(): N
|
||||
|
||||
fun N.wrap(): NDElement<T, C, N>
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a optimized NDArray of doubles
|
||||
*/
|
||||
fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }) =
|
||||
NDField.real(*shape).produce(initializer)
|
||||
|
||||
|
||||
fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }) =
|
||||
real(intArrayOf(dim)) { initializer(it[0]) }
|
||||
|
||||
|
||||
fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }) =
|
||||
real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) }
|
||||
|
||||
fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }) =
|
||||
real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
|
||||
|
||||
|
||||
/**
|
||||
* Simple boxing NDArray
|
||||
*/
|
||||
fun <T : Any, F : Field<T>> buffered(
|
||||
shape: IntArray,
|
||||
field: F,
|
||||
initializer: F.(IntArray) -> T
|
||||
): BufferedNDElement<T, F> {
|
||||
val ndField = BoxingNDField(shape, field, Buffer.Companion::boxing)
|
||||
return ndField.produce(initializer)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any, F : Field<T>> auto(
|
||||
shape: IntArray,
|
||||
field: F,
|
||||
noinline initializer: F.(IntArray) -> T
|
||||
): BufferedNDFieldElement<T, F> {
|
||||
val ndField = NDField.auto(field, *shape)
|
||||
return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun <T, C, N : NDStructure<T>> NDElement<T, C, N>.mapIndexed(transform: C.(index: IntArray, T) -> T) =
|
||||
context.mapIndexed(unwrap(), transform).wrap()
|
||||
|
||||
fun <T, C, N : NDStructure<T>> NDElement<T, C, N>.map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap()
|
||||
|
||||
|
||||
/**
|
||||
* Element by element application of any operation on elements to the whole [NDElement]
|
||||
*/
|
||||
operator fun <T, C, N : NDStructure<T>> Function1<T, T>.invoke(ndElement: NDElement<T, C, N>) =
|
||||
ndElement.map { value -> this@invoke(value) }
|
||||
|
||||
/* plus and minus */
|
||||
|
||||
/**
|
||||
* Summation operation for [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, S : Space<T>, N : NDStructure<T>> NDElement<T, S, N>.plus(arg: T) =
|
||||
map { value -> arg + value }
|
||||
|
||||
/**
|
||||
* Subtraction operation between [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, S : Space<T>, N : NDStructure<T>> NDElement<T, S, N>.minus(arg: T) =
|
||||
map { value -> arg - value }
|
||||
|
||||
/* prod and div */
|
||||
|
||||
/**
|
||||
* Product operation for [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, R : Ring<T>, N : NDStructure<T>> NDElement<T, R, N>.times(arg: T) =
|
||||
map { value -> arg * value }
|
||||
|
||||
/**
|
||||
* Division operation between [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, F : Field<T>, N : NDStructure<T>> NDElement<T, F, N>.div(arg: T) =
|
||||
map { value -> arg / value }
|
||||
|
||||
|
||||
// /**
|
||||
// * Reverse sum operation
|
||||
// */
|
||||
// operator fun T.plus(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
|
||||
// field.run { this@plus + arg[index] }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Reverse minus operation
|
||||
// */
|
||||
// operator fun T.minus(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
|
||||
// field.run { this@minus - arg[index] }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Reverse product operation
|
||||
// */
|
||||
// operator fun T.times(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
|
||||
// field.run { this@times * arg[index] }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Reverse division operation
|
||||
// */
|
||||
// operator fun T.div(arg: NDStructure<T>): NDElement<T, F> = produce { index ->
|
||||
// field.run { this@div / arg[index] }
|
||||
// }
|
@ -1,209 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import scientifik.kmath.operations.Field
|
||||
import scientifik.kmath.operations.FieldElement
|
||||
|
||||
/**
|
||||
* An exception is thrown when the expected ans actual shape of NDArray differs
|
||||
*/
|
||||
class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException()
|
||||
|
||||
/**
|
||||
* Field for n-dimensional arrays.
|
||||
* @param shape - the list of dimensions of the array
|
||||
* @param field - operations field defined on individual array element
|
||||
* @param T the type of the element contained in NDArray
|
||||
*/
|
||||
abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Field<NDElement<T, F>> {
|
||||
|
||||
abstract fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T>
|
||||
|
||||
/**
|
||||
* Create new instance of NDArray using field shape and given initializer
|
||||
* The producer takes list of indices as argument and returns contained value
|
||||
*/
|
||||
fun produce(initializer: F.(IntArray) -> T): NDElement<T, F> = NDStructureElement(this, produceStructure(initializer))
|
||||
|
||||
override val zero: NDElement<T, F> by lazy {
|
||||
produce { zero }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the shape of given NDArray and throw exception if it does not coincide with shape of the field
|
||||
*/
|
||||
private fun checkShape(vararg elements: NDElement<T, F>) {
|
||||
elements.forEach {
|
||||
if (!shape.contentEquals(it.shape)) {
|
||||
throw ShapeMismatchException(shape, it.shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Element-by-element addition
|
||||
*/
|
||||
override fun add(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||
checkShape(a, b)
|
||||
return produce { with(field) { a[it] + b[it] } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply all elements by cinstant
|
||||
*/
|
||||
override fun multiply(a: NDElement<T, F>, k: Double): NDElement<T, F> {
|
||||
checkShape(a)
|
||||
return produce { with(field) { a[it] * k } }
|
||||
}
|
||||
|
||||
override val one: NDElement<T, F>
|
||||
get() = produce { one }
|
||||
|
||||
/**
|
||||
* Element-by-element multiplication
|
||||
*/
|
||||
override fun multiply(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||
checkShape(a)
|
||||
return produce { with(field) { a[it] * b[it] } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Element-by-element division
|
||||
*/
|
||||
override fun divide(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||
checkShape(a)
|
||||
return produce { with(field) { a[it] / b[it] } }
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Reverse sum operation
|
||||
// */
|
||||
// operator fun T.plus(arg: NDElement<T, F>): NDElement<T, F> = arg + this
|
||||
//
|
||||
// /**
|
||||
// * Reverse minus operation
|
||||
// */
|
||||
// operator fun T.minus(arg: NDElement<T, F>): NDElement<T, F> = arg.transformIndexed { _, value ->
|
||||
// with(arg.context.field) {
|
||||
// this@minus - value
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Reverse product operation
|
||||
// */
|
||||
// operator fun T.times(arg: NDElement<T, F>): NDElement<T, F> = arg * this
|
||||
//
|
||||
// /**
|
||||
// * Reverse division operation
|
||||
// */
|
||||
// operator fun T.div(arg: NDElement<T, F>): NDElement<T, F> = arg.transformIndexed { _, value ->
|
||||
// with(arg.context.field) {
|
||||
// this@div / value
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
interface NDElement<T, F : Field<T>>: FieldElement<NDElement<T, F>, NDField<T, F>>, NDStructure<T>
|
||||
|
||||
inline fun <T, F : Field<T>> NDElement<T, F>.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement<T, F> = context.produce { action(it, get(*it)) }
|
||||
inline fun <T, F : Field<T>> NDElement<T, F>.transform(crossinline action: F.(T) -> T): NDElement<T, F> = context.produce { action(get(*it)) }
|
||||
|
||||
|
||||
/**
|
||||
* Read-only [NDStructure] coupled to the context.
|
||||
*/
|
||||
class NDStructureElement<T, F : Field<T>>(override val context: NDField<T, F>, private val structure: NDStructure<T>) : NDElement<T,F>, NDStructure<T> by structure {
|
||||
|
||||
//TODO ensure structure is immutable
|
||||
|
||||
override val self: NDElement<T, F> get() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||
*/
|
||||
operator fun <T, F : Field<T>> Function1<T, T>.invoke(ndElement: NDElement<T, F>): NDElement<T, F> = ndElement.transform {value -> this@invoke(value) }
|
||||
|
||||
/* plus and minus */
|
||||
|
||||
/**
|
||||
* Summation operation for [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, F : Field<T>> NDElement<T, F>.plus(arg: T): NDElement<T, F> = transform {value ->
|
||||
with(context.field) {
|
||||
arg + value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtraction operation between [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, F : Field<T>> NDElement<T, F>.minus(arg: T): NDElement<T, F> = transform {value ->
|
||||
with(context.field) {
|
||||
arg - value
|
||||
}
|
||||
}
|
||||
|
||||
/* prod and div */
|
||||
|
||||
/**
|
||||
* Product operation for [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, F : Field<T>> NDElement<T, F>.times(arg: T): NDElement<T, F> = transform { value ->
|
||||
with(context.field) {
|
||||
arg * value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Division operation between [NDElement] and single element
|
||||
*/
|
||||
operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> = transform { value ->
|
||||
with(context.field) {
|
||||
arg / value
|
||||
}
|
||||
}
|
||||
|
||||
class GenericNDField<T : Any, F : Field<T>>(shape: IntArray, field: F) : NDField<T, F>(shape, field) {
|
||||
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> = genericNdStructure(shape) { field.initializer(it) }
|
||||
}
|
||||
|
||||
//typealias NDFieldFactory<T> = (IntArray)->NDField<T>
|
||||
|
||||
object NDArrays {
|
||||
/**
|
||||
* Create a platform-optimized NDArray of doubles
|
||||
*/
|
||||
fun realNDArray(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement<Double, DoubleField> {
|
||||
return ExtendedNDField(shape, DoubleField).produce(initializer)
|
||||
}
|
||||
|
||||
fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement<Double, DoubleField> {
|
||||
return realNDArray(intArrayOf(dim)) { initializer(it[0]) }
|
||||
}
|
||||
|
||||
fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement<Double, DoubleField> {
|
||||
return realNDArray(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) }
|
||||
}
|
||||
|
||||
fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement<Double, DoubleField> {
|
||||
return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
|
||||
}
|
||||
|
||||
inline fun produceReal(shape: IntArray, block: ExtendedNDField<Double, DoubleField>.() -> NDElement<Double, DoubleField>) =
|
||||
ExtendedNDField(shape, DoubleField).run(block)
|
||||
|
||||
// /**
|
||||
// * Simple boxing NDField
|
||||
// */
|
||||
// fun <T : Any> fieldFactory(field: Field<T>): NDFieldFactory<T> = { shape -> GenericNDField(shape, field) }
|
||||
|
||||
/**
|
||||
* Simple boxing NDArray
|
||||
*/
|
||||
fun <T : Any, F : Field<T>> create(field: F, shape: IntArray, initializer: (IntArray) -> T): NDElement<T, F> {
|
||||
return GenericNDField(shape, field).produce { initializer(it) }
|
||||
}
|
||||
}
|
@ -11,6 +11,45 @@ interface NDStructure<T> {
|
||||
operator fun get(index: IntArray): T
|
||||
|
||||
fun elements(): Sequence<Pair<IntArray, T>>
|
||||
|
||||
companion object {
|
||||
fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean {
|
||||
return when {
|
||||
st1 === st2 -> true
|
||||
st1 is BufferNDStructure<*> && st2 is BufferNDStructure<*> && st1.strides == st2.strides -> st1.buffer.contentEquals(
|
||||
st2.buffer
|
||||
)
|
||||
else -> st1.elements().all { (index, value) -> value == st2[index] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a NDStructure with explicit buffer factory
|
||||
*
|
||||
* Strides should be reused if possible
|
||||
*/
|
||||
fun <T> build(
|
||||
strides: Strides,
|
||||
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
|
||||
initializer: (IntArray) -> T
|
||||
) =
|
||||
BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) })
|
||||
|
||||
/**
|
||||
* Inline create NDStructure with non-boxing buffer implementation if it is possible
|
||||
*/
|
||||
inline fun <reified T : Any> auto(strides: Strides, crossinline initializer: (IntArray) -> T) =
|
||||
BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) })
|
||||
|
||||
fun <T> build(
|
||||
shape: IntArray,
|
||||
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
|
||||
initializer: (IntArray) -> T
|
||||
) = build(DefaultStrides(shape), bufferFactory, initializer)
|
||||
|
||||
inline fun <reified T : Any> auto(shape: IntArray, crossinline initializer: (IntArray) -> T) =
|
||||
auto(DefaultStrides(shape), initializer)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index)
|
||||
@ -19,7 +58,7 @@ interface MutableNDStructure<T> : NDStructure<T> {
|
||||
operator fun set(index: IntArray, value: T)
|
||||
}
|
||||
|
||||
fun <T> MutableNDStructure<T>.transformInPlace(action: (IntArray, T) -> T) {
|
||||
fun <T> MutableNDStructure<T>.mapInPlace(action: (IntArray, T) -> T) {
|
||||
elements().forEach { (index, oldValue) ->
|
||||
this[index] = action(index, oldValue)
|
||||
}
|
||||
@ -49,6 +88,9 @@ interface Strides {
|
||||
*/
|
||||
fun index(offset: Int): IntArray
|
||||
|
||||
/**
|
||||
* The size of linear buffer to accommodate all elements of ND-structure corresponding to strides
|
||||
*/
|
||||
val linearSize: Int
|
||||
|
||||
/**
|
||||
@ -60,7 +102,7 @@ interface Strides {
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultStrides(override val shape: IntArray) : Strides {
|
||||
class DefaultStrides private constructor(override val shape: IntArray) : Strides {
|
||||
/**
|
||||
* Strides for memory access
|
||||
*/
|
||||
@ -77,7 +119,7 @@ class DefaultStrides(override val shape: IntArray) : Strides {
|
||||
|
||||
override fun offset(index: IntArray): Int {
|
||||
return index.mapIndexed { i, value ->
|
||||
if (value < 0 || value >= shape[i]) {
|
||||
if (value < 0 || value >= this.shape[i]) {
|
||||
throw RuntimeException("Index $value out of shape bounds: (0,${this.shape[i]})")
|
||||
}
|
||||
value * strides[i]
|
||||
@ -98,19 +140,40 @@ class DefaultStrides(override val shape: IntArray) : Strides {
|
||||
|
||||
override val linearSize: Int
|
||||
get() = strides[shape.size]
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DefaultStrides) return false
|
||||
|
||||
if (!shape.contentEquals(other.shape)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
abstract class GenericNDStructure<T, B : Buffer<T>> : NDStructure<T> {
|
||||
protected abstract val buffer: B
|
||||
protected abstract val strides: Strides
|
||||
override fun hashCode(): Int {
|
||||
return shape.contentHashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val defaultStridesCache = HashMap<IntArray, Strides>()
|
||||
|
||||
/**
|
||||
* Cached builder for default strides
|
||||
*/
|
||||
operator fun invoke(shape: IntArray): Strides = defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) }
|
||||
}
|
||||
}
|
||||
|
||||
interface NDBuffer<T> : NDStructure<T> {
|
||||
val buffer: Buffer<T>
|
||||
val strides: Strides
|
||||
|
||||
override fun get(index: IntArray): T = buffer[strides.offset(index)]
|
||||
|
||||
override val shape: IntArray
|
||||
get() = strides.shape
|
||||
override val shape: IntArray get() = strides.shape
|
||||
|
||||
override fun elements()=
|
||||
strides.indices().map { it to this[it] }
|
||||
override fun elements() = strides.indices().map { it to this[it] }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,29 +182,58 @@ abstract class GenericNDStructure<T, B : Buffer<T>> : NDStructure<T> {
|
||||
class BufferNDStructure<T>(
|
||||
override val strides: Strides,
|
||||
override val buffer: Buffer<T>
|
||||
) : GenericNDStructure<T, Buffer<T>>() {
|
||||
) : NDBuffer<T> {
|
||||
|
||||
init {
|
||||
if (strides.linearSize != buffer.size) {
|
||||
error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(index: IntArray): T = buffer[strides.offset(index)]
|
||||
|
||||
override val shape: IntArray get() = strides.shape
|
||||
|
||||
override fun elements() = strides.indices().map { it to this[it] }
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return when {
|
||||
this === other -> true
|
||||
other is BufferNDStructure<*> && this.strides == other.strides -> this.buffer.contentEquals(other.buffer)
|
||||
other is NDStructure<*> -> elements().all { (index, value) -> value == other[index] }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
|
||||
BufferNDStructure<T>(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) })
|
||||
|
||||
inline fun <reified T : Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
|
||||
ndStructure(DefaultStrides(shape), initializer)
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = strides.hashCode()
|
||||
result = 31 * result + buffer.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable ND buffer based on linear [Buffer]
|
||||
* Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure]
|
||||
*/
|
||||
inline fun <T, reified R : Any> NDStructure<T>.mapToBuffer(
|
||||
factory: BufferFactory<R> = Buffer.Companion::auto,
|
||||
crossinline transform: (T) -> R
|
||||
): BufferNDStructure<R> {
|
||||
return if (this is BufferNDStructure<T>) {
|
||||
BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) })
|
||||
} else {
|
||||
val strides = DefaultStrides(shape)
|
||||
BufferNDStructure(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable ND buffer based on linear [autoBuffer]
|
||||
*/
|
||||
class MutableBufferNDStructure<T>(
|
||||
override val strides: Strides,
|
||||
override val buffer: MutableBuffer<T>
|
||||
) : GenericNDStructure<T, MutableBuffer<T>>(), MutableNDStructure<T> {
|
||||
) : NDBuffer<T>, MutableNDStructure<T> {
|
||||
|
||||
init {
|
||||
if (strides.linearSize != buffer.size) {
|
||||
@ -152,25 +244,10 @@ class MutableBufferNDStructure<T>(
|
||||
override fun set(index: IntArray, value: T) = buffer.set(strides.offset(index), value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create optimized mutable structure for given type
|
||||
*/
|
||||
inline fun <reified T : Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
|
||||
MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
|
||||
|
||||
inline fun <reified T : Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
|
||||
mutableNdStructure(DefaultStrides(shape), initializer)
|
||||
|
||||
/**
|
||||
* Create universal mutable structure
|
||||
*/
|
||||
fun <T> genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure<T> {
|
||||
val strides = DefaultStrides(shape)
|
||||
val sequence = sequence {
|
||||
strides.indices().forEach {
|
||||
yield(initializer(it))
|
||||
}
|
||||
}
|
||||
val buffer = MutableListBuffer(sequence.toMutableList())
|
||||
return MutableBufferNDStructure(strides, buffer)
|
||||
inline fun <reified T : Any> NDStructure<T>.combine(
|
||||
struct: NDStructure<T>,
|
||||
crossinline block: (T, T) -> T
|
||||
): NDStructure<T> {
|
||||
if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination")
|
||||
return NDStructure.auto(shape) { block(this[it], struct[it]) }
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.memory.*
|
||||
|
||||
/**
|
||||
* A non-boxing buffer based on [ByteBuffer] storage
|
||||
*/
|
||||
open class ObjectBuffer<T : Any>(protected val memory: Memory, protected val spec: MemorySpec<T>) : Buffer<T> {
|
||||
|
||||
override val size: Int get() = memory.size / spec.objectSize
|
||||
|
||||
private val reader = memory.reader()
|
||||
|
||||
override fun get(index: Int): T = reader.read(spec, spec.objectSize * index)
|
||||
|
||||
override fun iterator(): Iterator<T> = (0 until size).asSequence().map { get(it) }.iterator()
|
||||
|
||||
|
||||
companion object {
|
||||
fun <T : Any> create(spec: MemorySpec<T>, size: Int) =
|
||||
ObjectBuffer(Memory.allocate(size * spec.objectSize), spec)
|
||||
|
||||
inline fun <T : Any> create(
|
||||
spec: MemorySpec<T>,
|
||||
size: Int,
|
||||
crossinline initializer: (Int) -> T
|
||||
): ObjectBuffer<T> =
|
||||
MutableObjectBuffer(Memory.allocate(size * spec.objectSize), spec).also { buffer ->
|
||||
(0 until size).forEach {
|
||||
buffer[it] = initializer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MutableObjectBuffer<T : Any>(memory: Memory, spec: MemorySpec<T>) : ObjectBuffer<T>(memory, spec),
|
||||
MutableBuffer<T> {
|
||||
|
||||
private val writer = memory.writer()
|
||||
|
||||
override fun set(index: Int, value: T) = writer.write(spec, spec.objectSize * index, value)
|
||||
|
||||
override fun copy(): MutableBuffer<T> = MutableObjectBuffer(memory.copy(), spec)
|
||||
|
||||
companion object {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.ExtendedFieldOperations
|
||||
import scientifik.kmath.operations.Field
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
/**
|
||||
* A simple field over linear buffers of [Double]
|
||||
*/
|
||||
object RealBufferFieldOperations : ExtendedFieldOperations<Buffer<Double>> {
|
||||
override fun add(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
|
||||
require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " }
|
||||
return if (a is DoubleBuffer && b is DoubleBuffer) {
|
||||
val aArray = a.array
|
||||
val bArray = b.array
|
||||
DoubleBuffer(DoubleArray(a.size) { aArray[it] + bArray[it] })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(a.size) { a[it] + b[it] })
|
||||
}
|
||||
}
|
||||
|
||||
override fun multiply(a: Buffer<Double>, k: Number): DoubleBuffer {
|
||||
val kValue = k.toDouble()
|
||||
return if (a is DoubleBuffer) {
|
||||
val aArray = a.array
|
||||
DoubleBuffer(DoubleArray(a.size) { aArray[it] * kValue })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(a.size) { a[it] * kValue })
|
||||
}
|
||||
}
|
||||
|
||||
override fun multiply(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
|
||||
require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " }
|
||||
return if (a is DoubleBuffer && b is DoubleBuffer) {
|
||||
val aArray = a.array
|
||||
val bArray = b.array
|
||||
DoubleBuffer(DoubleArray(a.size) { aArray[it] * bArray[it] })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(a.size) { a[it] * b[it] })
|
||||
}
|
||||
}
|
||||
|
||||
override fun divide(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
|
||||
require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " }
|
||||
return if (a is DoubleBuffer && b is DoubleBuffer) {
|
||||
val aArray = a.array
|
||||
val bArray = b.array
|
||||
DoubleBuffer(DoubleArray(a.size) { aArray[it] / bArray[it] })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(a.size) { a[it] / b[it] })
|
||||
}
|
||||
}
|
||||
|
||||
override fun sin(arg: Buffer<Double>): DoubleBuffer {
|
||||
return if (arg is DoubleBuffer) {
|
||||
val array = arg.array
|
||||
DoubleBuffer(DoubleArray(arg.size) { sin(array[it]) })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(arg.size) { sin(arg[it]) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun cos(arg: Buffer<Double>): DoubleBuffer {
|
||||
return if (arg is DoubleBuffer) {
|
||||
val array = arg.array
|
||||
DoubleBuffer(DoubleArray(arg.size) { cos(array[it]) })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(arg.size) { cos(arg[it]) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer {
|
||||
return if (arg is DoubleBuffer) {
|
||||
val array = arg.array
|
||||
DoubleBuffer(DoubleArray(arg.size) { array[it].pow(pow.toDouble()) })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(arg.size) { arg[it].pow(pow.toDouble()) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun exp(arg: Buffer<Double>): DoubleBuffer {
|
||||
return if (arg is DoubleBuffer) {
|
||||
val array = arg.array
|
||||
DoubleBuffer(DoubleArray(arg.size) { exp(array[it]) })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(arg.size) { exp(arg[it]) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun ln(arg: Buffer<Double>): DoubleBuffer {
|
||||
return if (arg is DoubleBuffer) {
|
||||
val array = arg.array
|
||||
DoubleBuffer(DoubleArray(arg.size) { ln(array[it]) })
|
||||
} else {
|
||||
DoubleBuffer(DoubleArray(arg.size) { ln(arg[it]) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RealBufferField(val size: Int) : Field<Buffer<Double>>, ExtendedFieldOperations<Buffer<Double>> {
|
||||
|
||||
override val zero: Buffer<Double> by lazy { DoubleBuffer(size) { 0.0 } }
|
||||
|
||||
override val one: Buffer<Double> by lazy { DoubleBuffer(size) { 1.0 } }
|
||||
|
||||
override fun add(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
|
||||
require(a.size == size) { "The buffer size ${a.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.add(a, b)
|
||||
}
|
||||
|
||||
override fun multiply(a: Buffer<Double>, k: Number): DoubleBuffer {
|
||||
require(a.size == size) { "The buffer size ${a.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.multiply(a, k)
|
||||
}
|
||||
|
||||
override fun multiply(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
|
||||
require(a.size == size) { "The buffer size ${a.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.multiply(a, b)
|
||||
}
|
||||
|
||||
|
||||
override fun divide(a: Buffer<Double>, b: Buffer<Double>): DoubleBuffer {
|
||||
require(a.size == size) { "The buffer size ${a.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.divide(a, b)
|
||||
}
|
||||
|
||||
override fun sin(arg: Buffer<Double>): DoubleBuffer {
|
||||
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.sin(arg)
|
||||
}
|
||||
|
||||
override fun cos(arg: Buffer<Double>): DoubleBuffer {
|
||||
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.cos(arg)
|
||||
}
|
||||
|
||||
override fun power(arg: Buffer<Double>, pow: Number): DoubleBuffer {
|
||||
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.power(arg, pow)
|
||||
}
|
||||
|
||||
override fun exp(arg: Buffer<Double>): DoubleBuffer {
|
||||
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.exp(arg)
|
||||
}
|
||||
|
||||
override fun ln(arg: Buffer<Double>): DoubleBuffer {
|
||||
require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
|
||||
return RealBufferFieldOperations.ln(arg)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.FieldElement
|
||||
import scientifik.kmath.operations.RealField
|
||||
|
||||
typealias RealNDElement = BufferedNDFieldElement<Double, RealField>
|
||||
|
||||
class RealNDField(override val shape: IntArray) :
|
||||
BufferedNDField<Double, RealField>,
|
||||
ExtendedNDField<Double, RealField, NDBuffer<Double>> {
|
||||
|
||||
override val strides: Strides = DefaultStrides(shape)
|
||||
|
||||
override val elementContext: RealField get() = RealField
|
||||
override val zero by lazy { produce { zero } }
|
||||
override val one by lazy { produce { one } }
|
||||
|
||||
inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer<Double> =
|
||||
DoubleBuffer(DoubleArray(size) { initializer(it) })
|
||||
|
||||
/**
|
||||
* Inline transform an NDStructure to
|
||||
*/
|
||||
override fun map(
|
||||
arg: NDBuffer<Double>,
|
||||
transform: RealField.(Double) -> Double
|
||||
): RealNDElement {
|
||||
check(arg)
|
||||
val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
|
||||
return BufferedNDFieldElement(this, array)
|
||||
}
|
||||
|
||||
override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
|
||||
val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
|
||||
return BufferedNDFieldElement(this, array)
|
||||
}
|
||||
|
||||
override fun mapIndexed(
|
||||
arg: NDBuffer<Double>,
|
||||
transform: RealField.(index: IntArray, Double) -> Double
|
||||
): RealNDElement {
|
||||
check(arg)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(arg.strides.linearSize) { offset ->
|
||||
elementContext.transform(
|
||||
arg.strides.index(offset),
|
||||
arg.buffer[offset]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun combine(
|
||||
a: NDBuffer<Double>,
|
||||
b: NDBuffer<Double>,
|
||||
transform: RealField.(Double, Double) -> Double
|
||||
): RealNDElement {
|
||||
check(a, b)
|
||||
return BufferedNDFieldElement(
|
||||
this,
|
||||
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
|
||||
}
|
||||
|
||||
override fun NDBuffer<Double>.toElement(): FieldElement<NDBuffer<Double>, *, out BufferedNDField<Double, RealField>> =
|
||||
BufferedNDFieldElement(this@RealNDField, buffer)
|
||||
|
||||
override fun power(arg: NDBuffer<Double>, pow: Number) = map(arg) { power(it, pow) }
|
||||
|
||||
override fun exp(arg: NDBuffer<Double>) = map(arg) { exp(it) }
|
||||
|
||||
override fun ln(arg: NDBuffer<Double>) = map(arg) { ln(it) }
|
||||
|
||||
override fun sin(arg: NDBuffer<Double>) = map(arg) { sin(it) }
|
||||
|
||||
override fun cos(arg: NDBuffer<Double>) = map(arg) { cos(it) }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fast element production using function inlining
|
||||
*/
|
||||
inline fun BufferedNDField<Double, RealField>.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement {
|
||||
val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) }
|
||||
return BufferedNDFieldElement(this, DoubleBuffer(array))
|
||||
}
|
||||
|
||||
/**
|
||||
* Map one [RealNDElement] using function with indexes
|
||||
*/
|
||||
inline fun RealNDElement.mapIndexed(crossinline transform: RealField.(index: IntArray, Double) -> Double) =
|
||||
context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) }
|
||||
|
||||
/**
|
||||
* Map one [RealNDElement] using function without indexes
|
||||
*/
|
||||
inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> Double): RealNDElement {
|
||||
val array = DoubleArray(strides.linearSize) { offset -> RealField.transform(buffer[offset]) }
|
||||
return BufferedNDFieldElement(context, DoubleBuffer(array))
|
||||
}
|
||||
|
||||
/**
|
||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||
*/
|
||||
operator fun Function1<Double, Double>.invoke(ndElement: RealNDElement) =
|
||||
ndElement.map { this@invoke(it) }
|
||||
|
||||
|
||||
/* plus and minus */
|
||||
|
||||
/**
|
||||
* Summation operation for [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun RealNDElement.plus(arg: Double) =
|
||||
map { it + arg }
|
||||
|
||||
/**
|
||||
* Subtraction operation between [BufferedNDElement] and single element
|
||||
*/
|
||||
operator fun RealNDElement.minus(arg: Double) =
|
||||
map { it - arg }
|
@ -0,0 +1,96 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.RingElement
|
||||
import scientifik.kmath.operations.ShortRing
|
||||
|
||||
|
||||
typealias ShortNDElement = BufferedNDRingElement<Short, ShortRing>
|
||||
|
||||
class ShortNDRing(override val shape: IntArray) :
|
||||
BufferedNDRing<Short, ShortRing> {
|
||||
|
||||
override val strides: Strides = DefaultStrides(shape)
|
||||
|
||||
override val elementContext: ShortRing get() = ShortRing
|
||||
override val zero by lazy { produce { ShortRing.zero } }
|
||||
override val one by lazy { produce { ShortRing.one } }
|
||||
|
||||
inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer<Short> =
|
||||
ShortBuffer(ShortArray(size) { initializer(it) })
|
||||
|
||||
/**
|
||||
* Inline transform an NDStructure to
|
||||
*/
|
||||
override fun map(
|
||||
arg: NDBuffer<Short>,
|
||||
transform: ShortRing.(Short) -> Short
|
||||
): ShortNDElement {
|
||||
check(arg)
|
||||
val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) }
|
||||
return BufferedNDRingElement(this, array)
|
||||
}
|
||||
|
||||
override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement {
|
||||
val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
|
||||
return BufferedNDRingElement(this, array)
|
||||
}
|
||||
|
||||
override fun mapIndexed(
|
||||
arg: NDBuffer<Short>,
|
||||
transform: ShortRing.(index: IntArray, Short) -> Short
|
||||
): ShortNDElement {
|
||||
check(arg)
|
||||
return BufferedNDRingElement(
|
||||
this,
|
||||
buildBuffer(arg.strides.linearSize) { offset ->
|
||||
elementContext.transform(
|
||||
arg.strides.index(offset),
|
||||
arg.buffer[offset]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun combine(
|
||||
a: NDBuffer<Short>,
|
||||
b: NDBuffer<Short>,
|
||||
transform: ShortRing.(Short, Short) -> Short
|
||||
): ShortNDElement {
|
||||
check(a, b)
|
||||
return BufferedNDRingElement(
|
||||
this,
|
||||
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
|
||||
}
|
||||
|
||||
override fun NDBuffer<Short>.toElement(): RingElement<NDBuffer<Short>, *, out BufferedNDRing<Short, ShortRing>> =
|
||||
BufferedNDRingElement(this@ShortNDRing, buffer)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fast element production using function inlining
|
||||
*/
|
||||
inline fun BufferedNDRing<Short, ShortRing>.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement {
|
||||
val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) }
|
||||
return BufferedNDRingElement(this, ShortBuffer(array))
|
||||
}
|
||||
|
||||
/**
|
||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||
*/
|
||||
operator fun Function1<Short, Short>.invoke(ndElement: ShortNDElement) =
|
||||
ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) }
|
||||
|
||||
|
||||
/* plus and minus */
|
||||
|
||||
/**
|
||||
* Summation operation for [StridedNDFieldElement] and single element
|
||||
*/
|
||||
operator fun ShortNDElement.plus(arg: Short) =
|
||||
context.produceInline { i -> (buffer[i] + arg).toShort() }
|
||||
|
||||
/**
|
||||
* Subtraction operation between [StridedNDFieldElement] and single element
|
||||
*/
|
||||
operator fun ShortNDElement.minus(arg: Short) =
|
||||
context.produceInline { i -> (buffer[i] - arg).toShort() }
|
@ -0,0 +1,94 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
/**
|
||||
* A structure that is guaranteed to be one-dimensional
|
||||
*/
|
||||
interface Structure1D<T> : NDStructure<T>, Buffer<T> {
|
||||
override val dimension: Int get() = 1
|
||||
|
||||
override fun get(index: IntArray): T {
|
||||
if (index.size != 1) error("Index dimension mismatch. Expected 1 but found ${index.size}")
|
||||
return get(index[0])
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> = (0 until size).asSequence().map { get(it) }.iterator()
|
||||
}
|
||||
|
||||
/**
|
||||
* A 1D wrapper for nd-structure
|
||||
*/
|
||||
private inline class Structure1DWrapper<T>(val structure: NDStructure<T>) : Structure1D<T> {
|
||||
|
||||
override val shape: IntArray get() = structure.shape
|
||||
override val size: Int get() = structure.shape[0]
|
||||
|
||||
override fun get(index: Int): T = structure[index]
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch
|
||||
*/
|
||||
fun <T> NDStructure<T>.as1D(): Structure1D<T> = if (shape.size == 1) {
|
||||
Structure1DWrapper(this)
|
||||
} else {
|
||||
error("Can't create 1d-structure from ${shape.size}d-structure")
|
||||
}
|
||||
|
||||
fun <T> NDBuffer<T>.as1D(): Structure1D<T> = if (shape.size == 1) {
|
||||
Buffer1DWrapper(this.buffer)
|
||||
} else {
|
||||
error("Can't create 1d-structure from ${shape.size}d-structure")
|
||||
}
|
||||
|
||||
/**
|
||||
* A structure wrapper for buffer
|
||||
*/
|
||||
private inline class Buffer1DWrapper<T>(val buffer: Buffer<T>) : Structure1D<T> {
|
||||
override val shape: IntArray get() = intArrayOf(buffer.size)
|
||||
|
||||
override val size: Int get() = buffer.size
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> =
|
||||
asSequence().mapIndexed { index, value -> intArrayOf(index) to value }
|
||||
|
||||
override fun get(index: Int): T = buffer.get(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent this buffer as 1D structure
|
||||
*/
|
||||
fun <T> Buffer<T>.asND(): Structure1D<T> = Buffer1DWrapper(this)
|
||||
|
||||
/**
|
||||
* A structure that is guaranteed to be two-dimensional
|
||||
*/
|
||||
interface Structure2D<T> : NDStructure<T> {
|
||||
operator fun get(i: Int, j: Int): T
|
||||
|
||||
override fun get(index: IntArray): T {
|
||||
if (index.size != 2) error("Index dimension mismatch. Expected 2 but found ${index.size}")
|
||||
return get(index[0], index[1])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A 2D wrapper for nd-structure
|
||||
*/
|
||||
private inline class Structure2DWrapper<T>(val structure: NDStructure<T>) : Structure2D<T> {
|
||||
override fun get(i: Int, j: Int): T = structure[i, j]
|
||||
|
||||
override val shape: IntArray get() = structure.shape
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch
|
||||
*/
|
||||
fun <T> NDStructure<T>.as2D(): Structure2D<T> = if (shape.size == 2) {
|
||||
Structure2DWrapper(this)
|
||||
} else {
|
||||
error("Can't create 2d-structure from ${shape.size}d-structure")
|
||||
}
|
@ -2,17 +2,17 @@ package scientifik.kmath.expressions
|
||||
|
||||
import scientifik.kmath.operations.Complex
|
||||
import scientifik.kmath.operations.ComplexField
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import scientifik.kmath.operations.RealField
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FieldExpressionContextTest {
|
||||
class ExpressionFieldTest {
|
||||
@Test
|
||||
fun testExpression() {
|
||||
val context = FieldExpressionContext(DoubleField)
|
||||
val context = ExpressionField(RealField)
|
||||
val expression = with(context) {
|
||||
val x = variable("x", 2.0)
|
||||
x * x + 2 * x + 1.0
|
||||
x * x + 2 * x + one
|
||||
}
|
||||
assertEquals(expression("x" to 1.0), 4.0)
|
||||
assertEquals(expression(), 9.0)
|
||||
@ -20,10 +20,10 @@ class FieldExpressionContextTest {
|
||||
|
||||
@Test
|
||||
fun testComplex() {
|
||||
val context = FieldExpressionContext(ComplexField)
|
||||
val context = ExpressionField(ComplexField)
|
||||
val expression = with(context) {
|
||||
val x = variable("x", Complex(2.0, 0.0))
|
||||
x * x + 2 * x + 1.0
|
||||
x * x + 2 * x + one
|
||||
}
|
||||
assertEquals(expression("x" to Complex(1.0, 0.0)), Complex(4.0, 0.0))
|
||||
assertEquals(expression(), Complex(9.0, 0.0))
|
||||
@ -31,23 +31,23 @@ class FieldExpressionContextTest {
|
||||
|
||||
@Test
|
||||
fun separateContext() {
|
||||
fun <T> FieldExpressionContext<T>.expression(): Expression<T>{
|
||||
fun <T> ExpressionField<T>.expression(): Expression<T> {
|
||||
val x = variable("x")
|
||||
return x * x + 2 * x + 1.0
|
||||
return x * x + 2 * x + one
|
||||
}
|
||||
|
||||
val expression = FieldExpressionContext(DoubleField).expression()
|
||||
val expression = ExpressionField(RealField).expression()
|
||||
assertEquals(expression("x" to 1.0), 4.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueExpression() {
|
||||
val expressionBuilder: FieldExpressionContext<Double>.()->Expression<Double> = {
|
||||
val expressionBuilder: ExpressionField<Double>.() -> Expression<Double> = {
|
||||
val x = variable("x")
|
||||
x * x + 2 * x + 1.0
|
||||
x * x + 2 * x + one
|
||||
}
|
||||
|
||||
val expression = FieldExpressionContext(DoubleField).expressionBuilder()
|
||||
val expression = ExpressionField(RealField).expressionBuilder()
|
||||
assertEquals(expression("x" to 1.0), 4.0)
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ArrayMatrixTest {
|
||||
|
||||
@Test
|
||||
fun testSum() {
|
||||
val vector1 = Vector.ofReal(5) { it.toDouble() }
|
||||
val vector2 = Vector.ofReal(5) { 5 - it.toDouble() }
|
||||
val sum = vector1 + vector2
|
||||
assertEquals(5.0, sum[2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVectorToMatrix() {
|
||||
val vector = Vector.ofReal(5) { it.toDouble() }
|
||||
val matrix = vector.toMatrix()
|
||||
assertEquals(4.0, matrix[4, 0])
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testDot() {
|
||||
val vector1 = Vector.ofReal(5) { it.toDouble() }
|
||||
val vector2 = Vector.ofReal(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,54 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MatrixTest {
|
||||
|
||||
@Test
|
||||
fun testSum() {
|
||||
val vector1 = Vector.real(5) { it.toDouble() }
|
||||
val vector2 = Vector.real(5) { 5 - it.toDouble() }
|
||||
val sum = vector1 + vector2
|
||||
assertEquals(5.0, sum[2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVectorToMatrix() {
|
||||
val vector = Vector.real(5) { it.toDouble() }
|
||||
val matrix = vector.toMatrix()
|
||||
assertEquals(4.0, matrix[4, 0])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTranspose() {
|
||||
val matrix = MatrixContext.real.one(3, 3)
|
||||
val transposed = matrix.transpose()
|
||||
assertEquals(matrix, transposed)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testDot() {
|
||||
val vector1 = Vector.real(5) { it.toDouble() }
|
||||
val vector2 = Vector.real(5) { 5 - it.toDouble() }
|
||||
|
||||
val matrix1 = vector1.toMatrix()
|
||||
val matrix2 = vector2.toMatrix().transpose()
|
||||
val product = MatrixContext.real.run { matrix1 dot matrix2 }
|
||||
|
||||
|
||||
assertEquals(5.0, product[1, 0])
|
||||
assertEquals(6.0, product[2, 2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuilder() {
|
||||
val matrix = Matrix.build<Double>(2, 3)(
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 2.0
|
||||
)
|
||||
|
||||
assertEquals(2.0, matrix[1, 2])
|
||||
}
|
||||
}
|
@ -1,21 +1,41 @@
|
||||
package scientifik.kmath.linear
|
||||
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class RealLUSolverTest {
|
||||
@Test
|
||||
fun testInvertOne() {
|
||||
val matrix = Matrix.diagonal(2, 2, DoubleField)
|
||||
val inverted = RealLUSolver.inverse(matrix)
|
||||
assertTrue { Matrix.equals(matrix,inverted) }
|
||||
val matrix = MatrixContext.real.one(2, 2)
|
||||
val inverted = LUSolver.real.inverse(matrix)
|
||||
assertEquals(matrix, inverted)
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun testInvert() {
|
||||
// val matrix = realMatrix(2,2){}
|
||||
// val inverted = RealLUSolver.inverse(matrix)
|
||||
// assertTrue { Matrix.equals(matrix,inverted) }
|
||||
// }
|
||||
@Test
|
||||
fun testInvert() {
|
||||
val matrix = Matrix.square(
|
||||
3.0, 1.0,
|
||||
1.0, 3.0
|
||||
)
|
||||
|
||||
val decomposed = LUSolver.real.decompose(matrix)
|
||||
val decomposition = decomposed.getFeature<LUPDecomposition<Double>>()!!
|
||||
|
||||
//Check determinant
|
||||
assertEquals(8.0, decomposition.determinant)
|
||||
|
||||
//Check decomposition
|
||||
with(MatrixContext.real) {
|
||||
assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u)
|
||||
}
|
||||
|
||||
val inverted = LUSolver.real.inverse(decomposed)
|
||||
|
||||
val expected = Matrix.square(
|
||||
0.375, -0.125,
|
||||
-0.125, 0.375
|
||||
)
|
||||
|
||||
assertEquals(expected, inverted)
|
||||
}
|
||||
}
|
@ -6,11 +6,9 @@ import kotlin.test.assertEquals
|
||||
class RealFieldTest {
|
||||
@Test
|
||||
fun testSqrt() {
|
||||
|
||||
//fails because KT-27586
|
||||
val sqrt = with(RealField) {
|
||||
sqrt(25 * one)
|
||||
}
|
||||
assertEquals(5.0, sqrt.value)
|
||||
assertEquals(5.0, sqrt)
|
||||
}
|
||||
}
|
@ -1,17 +1,14 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import org.junit.Test
|
||||
import scientifik.kmath.operations.Complex
|
||||
import scientifik.kmath.operations.complex
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ComplexBufferSpecTest {
|
||||
@Test
|
||||
fun testComplexBuffer() {
|
||||
val buffer = Complex.createBuffer(20)
|
||||
(0 until 20).forEach {
|
||||
buffer[it] = Complex(it.toDouble(), -it.toDouble())
|
||||
}
|
||||
|
||||
val buffer = Buffer.complex(20) { Complex(it.toDouble(), -it.toDouble()) }
|
||||
assertEquals(Complex(5.0, -5.0), buffer[5])
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.DoubleField
|
||||
import scientifik.kmath.structures.NDArrays.create
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
class GenericNDFieldTest{
|
||||
@Test
|
||||
fun testStrides(){
|
||||
val ndArray = create(DoubleField, intArrayOf(10,10)){(it[0]+it[1]).toDouble()}
|
||||
assertEquals(ndArray[5,5], 10.0)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
class NDFieldTest {
|
||||
@Test
|
||||
fun testStrides() {
|
||||
val ndArray = NDElement.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() }
|
||||
assertEquals(ndArray[5, 5], 10.0)
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Norm
|
||||
import scientifik.kmath.structures.NDArrays.produceReal
|
||||
import scientifik.kmath.structures.NDArrays.real2DArray
|
||||
import scientifik.kmath.structures.NDElement.Companion.real2D
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NumberNDFieldTest {
|
||||
val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() }
|
||||
val array2 = real2DArray(3, 3) { i, j -> (i - j).toDouble() }
|
||||
val array1 = real2D(3, 3) { i, j -> (i + j).toDouble() }
|
||||
val array2 = real2D(3, 3) { i, j -> (i - j).toDouble() }
|
||||
|
||||
@Test
|
||||
fun testSum() {
|
||||
@ -27,7 +26,7 @@ class NumberNDFieldTest {
|
||||
@Test
|
||||
fun testGeneration() {
|
||||
|
||||
val array = real2DArray(3, 3) { i, j -> (i * 10 + j).toDouble() }
|
||||
val array = real2D(3, 3) { i, j -> (i * 10 + j).toDouble() }
|
||||
|
||||
for (i in 0..2) {
|
||||
for (j in 0..2) {
|
||||
@ -51,15 +50,20 @@ class NumberNDFieldTest {
|
||||
assertEquals(2.0, result[0, 2])
|
||||
}
|
||||
|
||||
object L2Norm : Norm<NDElement<out Number, *>, Double> {
|
||||
override fun norm(arg: NDElement<out Number, *>): Double {
|
||||
return kotlin.math.sqrt(arg.sumByDouble { it.second.toDouble() })
|
||||
@Test
|
||||
fun combineTest() {
|
||||
val division = array1.combine(array2, Double::div)
|
||||
}
|
||||
|
||||
object L2Norm : Norm<NDStructure<out Number>, Double> {
|
||||
override fun norm(arg: NDStructure<out Number>): Double {
|
||||
return kotlin.math.sqrt(arg.elements().sumByDouble { it.second.toDouble() })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInternalContext() {
|
||||
produceReal(array1.shape) {
|
||||
NDField.real(*array1.shape).run {
|
||||
with(L2Norm) {
|
||||
1 + norm(array1) + exp(array2)
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
actual class LongCounter{
|
||||
private var sum: Long = 0
|
||||
actual fun decrement() {sum--}
|
||||
actual fun increment() {sum++}
|
||||
actual fun reset() {sum = 0}
|
||||
actual fun sum(): Long = sum
|
||||
actual fun add(l: Long) {sum+=l}
|
||||
}
|
||||
actual class DoubleCounter{
|
||||
private var sum: Double = 0.0
|
||||
actual fun reset() {sum = 0.0}
|
||||
actual fun sum(): Double = sum
|
||||
actual fun add(d: Double) {sum+=d}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
|
||||
/**
|
||||
* A specification for serialization and deserialization objects to buffer
|
||||
*/
|
||||
interface BufferSpec<T : Any> {
|
||||
fun fromBuffer(buffer: ByteBuffer): T
|
||||
fun toBuffer(value: T): ByteBuffer
|
||||
}
|
||||
|
||||
/**
|
||||
* A [BufferSpec] with fixed unit size. Allows storage of any object without boxing.
|
||||
*/
|
||||
interface FixedSizeBufferSpec<T : Any> : BufferSpec<T> {
|
||||
val unitSize: Int
|
||||
|
||||
/**
|
||||
* Read an object from buffer in current position
|
||||
*/
|
||||
fun ByteBuffer.readObject(): T {
|
||||
val buffer = ByteArray(unitSize)
|
||||
get(buffer)
|
||||
return fromBuffer(ByteBuffer.wrap(buffer))
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an object from buffer in given index (not buffer position
|
||||
*/
|
||||
fun ByteBuffer.readObject(index: Int): T {
|
||||
val dup = duplicate()
|
||||
dup.position(index*unitSize)
|
||||
return dup.readObject()
|
||||
}
|
||||
|
||||
/**
|
||||
* Write object to [ByteBuffer] in current buffer position
|
||||
*/
|
||||
fun ByteBuffer.writeObject(obj: T) {
|
||||
val buffer = toBuffer(obj).apply { rewind() }
|
||||
assert(buffer.limit() == unitSize)
|
||||
put(buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an object in given index
|
||||
*/
|
||||
fun ByteBuffer.writeObject(index: Int, obj: T) {
|
||||
val dup = duplicate()
|
||||
dup.position(index*unitSize)
|
||||
dup.writeObject(obj)
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Complex
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
object ComplexBufferSpec : FixedSizeBufferSpec<Complex> {
|
||||
override val unitSize: Int = 16
|
||||
|
||||
override fun fromBuffer(buffer: ByteBuffer): Complex {
|
||||
val re = buffer.getDouble(0)
|
||||
val im = buffer.getDouble(8)
|
||||
return Complex(re, im)
|
||||
}
|
||||
|
||||
override fun toBuffer(value: Complex): ByteBuffer = ByteBuffer.allocate(16).apply {
|
||||
putDouble(value.re)
|
||||
putDouble(value.im)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mutable buffer which ignores boxing
|
||||
*/
|
||||
fun Complex.Companion.createBuffer(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size)
|
||||
|
@ -1,28 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class ObjectBuffer<T : Any>(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec<T>) : MutableBuffer<T> {
|
||||
override val size: Int
|
||||
get() = buffer.limit() / spec.unitSize
|
||||
|
||||
override fun get(index: Int): T = with(spec) { buffer.readObject(index) }
|
||||
|
||||
override fun iterator(): Iterator<T> = (0 until size).asSequence().map { get(it) }.iterator()
|
||||
|
||||
override fun set(index: Int, value: T) = with(spec) { buffer.writeObject(index, value) }
|
||||
|
||||
override fun copy(): MutableBuffer<T> {
|
||||
val dup = buffer.duplicate()
|
||||
val copy = ByteBuffer.allocate(dup.capacity())
|
||||
dup.rewind()
|
||||
copy.put(dup)
|
||||
copy.flip()
|
||||
return ObjectBuffer(copy, spec)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : Any> create(spec: FixedSizeBufferSpec<T>, size: Int) =
|
||||
ObjectBuffer<T>(ByteBuffer.allocate(size * spec.unitSize), spec)
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.Real
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
object RealBufferSpec : FixedSizeBufferSpec<Real> {
|
||||
override val unitSize: Int = 8
|
||||
|
||||
override fun fromBuffer(buffer: ByteBuffer): Real = Real(buffer.double)
|
||||
|
||||
override fun toBuffer(value: Real): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value.value) }
|
||||
}
|
||||
|
||||
object DoubleBufferSpec : FixedSizeBufferSpec<Double> {
|
||||
override val unitSize: Int = 8
|
||||
|
||||
override fun fromBuffer(buffer: ByteBuffer): Double = buffer.double
|
||||
|
||||
override fun toBuffer(value: Double): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value) }
|
||||
}
|
||||
|
||||
fun Double.Companion.createBuffer(size: Int) = ObjectBuffer.create(DoubleBufferSpec, size)
|
||||
fun Real.Companion.createBuffer(size: Int) = ObjectBuffer.create(RealBufferSpec, size)
|
@ -1,42 +0,0 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.mingwX64, 'mingw')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":kmath-core")
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion"
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
api 'org.jetbrains.kotlin:kotlin-test-common'
|
||||
api 'org.jetbrains.kotlin:kotlin-test-annotations-common'
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||
}
|
||||
}
|
||||
jvmTest {
|
||||
dependencies {
|
||||
implementation 'org.jetbrains.kotlin:kotlin-test'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-test-junit'
|
||||
}
|
||||
}
|
||||
// mingwMain {
|
||||
// }
|
||||
// mingwTest {
|
||||
// }
|
||||
}
|
||||
}
|
46
kmath-coroutines/build.gradle.kts
Normal file
46
kmath-coroutines/build.gradle.kts
Normal file
@ -0,0 +1,46 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val coroutinesVersion: String by rootProject.extra
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":kmath-core"))
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,4 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
expect fun <R> runBlocking(context: CoroutineContext = EmptyCoroutineContext, function: suspend CoroutineScope.()->R): R
|
||||
|
||||
val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default
|
@ -1,79 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import scientifik.kmath.operations.Field
|
||||
|
||||
class LazyNDField<T, F : Field<T>>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : NDField<T, F>(shape, field) {
|
||||
|
||||
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> = LazyNDStructure(this) { initializer(field, it) }
|
||||
|
||||
|
||||
override fun add(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||
return LazyNDStructure(this) { index ->
|
||||
val aDeferred = a.deferred(index)
|
||||
val bDeferred = b.deferred(index)
|
||||
aDeferred.await() + bDeferred.await()
|
||||
}
|
||||
}
|
||||
|
||||
override fun multiply(a: NDElement<T, F>, k: Double): NDElement<T, F> {
|
||||
return LazyNDStructure(this) { index -> a.await(index) * k }
|
||||
}
|
||||
|
||||
override fun multiply(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||
return LazyNDStructure(this) { index ->
|
||||
val aDeferred = a.deferred(index)
|
||||
val bDeferred = b.deferred(index)
|
||||
aDeferred.await() * bDeferred.await()
|
||||
}
|
||||
}
|
||||
|
||||
override fun divide(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||
return LazyNDStructure(this) { index ->
|
||||
val aDeferred = a.deferred(index)
|
||||
val bDeferred = b.deferred(index)
|
||||
aDeferred.await() / bDeferred.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LazyNDStructure<T, F : Field<T>>(override val context: LazyNDField<T, F>, val function: suspend F.(IntArray) -> T) : NDElement<T, F>, NDStructure<T> {
|
||||
override val self: NDElement<T, F> get() = this
|
||||
override val shape: IntArray get() = context.shape
|
||||
|
||||
private val cache = HashMap<IntArray, Deferred<T>>()
|
||||
|
||||
fun deferred(index: IntArray) = cache.getOrPut(index) { context.scope.async(context = Dispatchers.Math) { function.invoke(context.field, index) } }
|
||||
|
||||
suspend fun await(index: IntArray): T = deferred(index).await()
|
||||
|
||||
override fun get(index: IntArray): T = runBlocking {
|
||||
deferred(index).await()
|
||||
}
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> {
|
||||
val strides = DefaultStrides(shape)
|
||||
return strides.indices().map { index -> index to runBlocking { await(index) } }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> NDElement<T, *>.deferred(index: IntArray) = if (this is LazyNDStructure<T, *>) this.deferred(index) else CompletableDeferred(get(index))
|
||||
|
||||
suspend fun <T> NDElement<T, *>.await(index: IntArray) = if (this is LazyNDStructure<T, *>) this.await(index) else get(index)
|
||||
|
||||
fun <T, F : Field<T>> NDElement<T, F>.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure<T, F> {
|
||||
return if (this is LazyNDStructure<T, F>) {
|
||||
this
|
||||
} else {
|
||||
val context = LazyNDField(context.shape, context.field)
|
||||
LazyNDStructure(context) { get(it) }
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, F : Field<T>> LazyNDStructure<T, F>.transformIndexed(crossinline action: suspend F.(IntArray, T) -> T) = LazyNDStructure(context) { index ->
|
||||
action.invoke(this, index, await(index))
|
||||
}
|
||||
|
||||
inline fun <T, F : Field<T>> LazyNDStructure<T, F>.transform(crossinline action: suspend F.(T) -> T) = LazyNDStructure(context) { index ->
|
||||
action.invoke(this, await(index))
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import scientifik.kmath.operations.IntField
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
class LazyNDFieldTest {
|
||||
@Test
|
||||
fun testLazyStructure() {
|
||||
var counter = 0
|
||||
val regularStructure = NDArrays.create(IntField, intArrayOf(2, 2, 2)) { it[0] + it[1] - it[2] }
|
||||
val result = (regularStructure.lazy() + 2).transform {
|
||||
counter++
|
||||
it * it
|
||||
}
|
||||
assertEquals(4, result[0,0,0])
|
||||
assertEquals(1, counter)
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class LazyNDStructure<T>(
|
||||
val scope: CoroutineScope,
|
||||
override val shape: IntArray,
|
||||
val function: suspend (IntArray) -> T
|
||||
) : NDStructure<T> {
|
||||
|
||||
private val cache = HashMap<IntArray, Deferred<T>>()
|
||||
|
||||
fun deferred(index: IntArray) = cache.getOrPut(index) {
|
||||
scope.async(context = Dispatchers.Math) {
|
||||
function(index)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(index: IntArray): T = deferred(index).await()
|
||||
|
||||
override fun get(index: IntArray): T = runBlocking {
|
||||
deferred(index).await()
|
||||
}
|
||||
|
||||
override fun elements(): Sequence<Pair<IntArray, T>> {
|
||||
val strides = DefaultStrides(shape)
|
||||
val res = runBlocking {
|
||||
strides.indices().toList().map { index -> index to await(index) }
|
||||
}
|
||||
return res.asSequence()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> NDStructure<T>.deferred(index: IntArray) =
|
||||
if (this is LazyNDStructure<T>) this.deferred(index) else CompletableDeferred(get(index))
|
||||
|
||||
suspend fun <T> NDStructure<T>.await(index: IntArray) =
|
||||
if (this is LazyNDStructure<T>) this.await(index) else get(index)
|
||||
|
||||
/**
|
||||
* PENDING would benifit from KEEP-176
|
||||
*/
|
||||
fun <T, R> NDStructure<T>.mapAsyncIndexed(scope: CoroutineScope, function: suspend (T, index: IntArray) -> R) =
|
||||
LazyNDStructure(scope, shape) { index -> function(get(index), index) }
|
||||
|
||||
fun <T, R> NDStructure<T>.mapAsync(scope: CoroutineScope, function: suspend (T) -> R) =
|
||||
LazyNDStructure(scope, shape) { index -> function(get(index)) }
|
@ -1,6 +0,0 @@
|
||||
package scientifik.kmath.structures
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
actual fun <R> runBlocking(context: CoroutineContext, function: suspend CoroutineScope.() -> R): R = kotlinx.coroutines.runBlocking(context, function)
|
34
kmath-histograms/build.gradle.kts
Normal file
34
kmath-histograms/build.gradle.kts
Normal file
@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
|
||||
sourceSets {
|
||||
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":kmath-core"))
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
/*
|
||||
* Common representation for atomic counters
|
||||
* TODO replace with atomics
|
||||
*/
|
||||
|
||||
expect class LongCounter() {
|
||||
fun decrement()
|
||||
fun increment()
|
||||
fun reset()
|
||||
fun sum(): Long
|
||||
fun add(l: Long)
|
||||
}
|
||||
|
||||
expect class DoubleCounter() {
|
||||
fun reset()
|
||||
fun sum(): Double
|
||||
fun add(d: Double)
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
import scientifik.kmath.linear.Point
|
||||
import scientifik.kmath.structures.ArrayBuffer
|
||||
import scientifik.kmath.structures.DoubleBuffer
|
||||
|
||||
/**
|
||||
* A simple geometric domain
|
||||
* TODO move to geometry module
|
||||
*/
|
||||
interface Domain<T : Any> {
|
||||
operator fun contains(vector: Point<out T>): Boolean
|
||||
val dimension: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* The bin in the histogram. The histogram is by definition always done in the real space
|
||||
*/
|
||||
interface Bin<T : Any> : Domain<T> {
|
||||
/**
|
||||
* The value of this bin
|
||||
*/
|
||||
val value: Number
|
||||
val center: Point<T>
|
||||
}
|
||||
|
||||
interface Histogram<T : Any, out B : Bin<T>> : Iterable<B> {
|
||||
|
||||
/**
|
||||
* Find existing bin, corresponding to given coordinates
|
||||
*/
|
||||
operator fun get(point: Point<out T>): B?
|
||||
|
||||
/**
|
||||
* Dimension of the histogram
|
||||
*/
|
||||
val dimension: Int
|
||||
|
||||
}
|
||||
|
||||
interface MutableHistogram<T : Any, out B : Bin<T>> : Histogram<T, B> {
|
||||
|
||||
/**
|
||||
* Increment appropriate bin
|
||||
*/
|
||||
fun putWithWeight(point: Point<out T>, weight: Double)
|
||||
|
||||
fun put(point: Point<out T>) = putWithWeight(point, 1.0)
|
||||
}
|
||||
|
||||
fun <T : Any> MutableHistogram<T, *>.put(vararg point: T) = put(ArrayBuffer(point))
|
||||
|
||||
fun MutableHistogram<Double, *>.put(vararg point: Number) =
|
||||
put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray()))
|
||||
|
||||
fun MutableHistogram<Double, *>.put(vararg point: Double) = put(DoubleBuffer(point))
|
||||
|
||||
fun <T : Any> MutableHistogram<T, *>.fill(sequence: Iterable<Point<T>>) = sequence.forEach { put(it) }
|
||||
|
||||
/**
|
||||
* Pass a sequence builder into histogram
|
||||
*/
|
||||
fun <T : Any> MutableHistogram<T, *>.fill(buider: suspend SequenceScope<Point<T>>.() -> Unit) =
|
||||
fill(sequence(buider).asIterable())
|
@ -1,43 +1,66 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
import scientifik.kmath.linear.Point
|
||||
import scientifik.kmath.linear.toVector
|
||||
import scientifik.kmath.operations.SpaceOperations
|
||||
import scientifik.kmath.structures.*
|
||||
import kotlin.math.floor
|
||||
|
||||
private operator fun RealPoint.minus(other: RealPoint) = ListBuffer((0 until size).map { get(it) - other[it] })
|
||||
|
||||
private inline fun <T> Buffer<out Double>.mapIndexed(crossinline mapper: (Int, Double) -> T): Sequence<T> = (0 until size).asSequence().map { mapper(it, get(it)) }
|
||||
data class BinDef<T : Comparable<T>>(val space: SpaceOperations<Point<T>>, val center: Point<T>, val sizes: Point<T>) {
|
||||
fun contains(vector: Point<out T>): Boolean {
|
||||
if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}")
|
||||
val upper = space.run { center + sizes / 2.0 }
|
||||
val lower = space.run { center - sizes / 2.0 }
|
||||
return vector.asSequence().mapIndexed { i, value ->
|
||||
value in lower[i]..upper[i]
|
||||
}.all { it }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MultivariateBin<T : Comparable<T>>(val def: BinDef<T>, override val value: Number) : Bin<T> {
|
||||
|
||||
override fun contains(vector: Point<out T>): Boolean = def.contains(vector)
|
||||
|
||||
override val dimension: Int
|
||||
get() = def.center.size
|
||||
|
||||
override val center: Point<T>
|
||||
get() = def.center
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions.
|
||||
*/
|
||||
class FastHistogram(
|
||||
private val lower: RealPoint,
|
||||
private val upper: RealPoint,
|
||||
class RealHistogram(
|
||||
private val lower: Buffer<Double>,
|
||||
private val upper: Buffer<Double>,
|
||||
private val binNums: IntArray = IntArray(lower.size) { 20 }
|
||||
) : MutableHistogram<Double, PhantomBin<Double>> {
|
||||
) : MutableHistogram<Double, MultivariateBin<Double>> {
|
||||
|
||||
|
||||
private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 })
|
||||
|
||||
private val values: NDStructure<LongCounter> = ndStructure(strides) { LongCounter() }
|
||||
private val values: NDStructure<LongCounter> = NDStructure.auto(strides) { LongCounter() }
|
||||
|
||||
//private val weight: NDStructure<DoubleCounter?> = ndStructure(strides){null}
|
||||
|
||||
//TODO optimize binSize performance if needed
|
||||
private val binSize: RealPoint = ListBuffer((upper - lower).mapIndexed { index, value -> value / binNums[index] }.toList())
|
||||
|
||||
override val dimension: Int get() = lower.size
|
||||
|
||||
|
||||
private val binSize = DoubleBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] }
|
||||
|
||||
init {
|
||||
// argument checks
|
||||
if (lower.size != upper.size) error("Dimension mismatch in histogram lower and upper limits.")
|
||||
if (lower.size != binNums.size) error("Dimension mismatch in bin count.")
|
||||
if ((upper - lower).asSequence().any { it <= 0 }) error("Range for one of axis is not strictly positive")
|
||||
if ((0 until dimension).any { upper[it] - lower[it] < 0 }) error("Range for one of axis is not strictly positive")
|
||||
}
|
||||
|
||||
|
||||
override val dimension: Int get() = lower.size
|
||||
|
||||
|
||||
/**
|
||||
* Get internal [NDStructure] bin index for given axis
|
||||
*/
|
||||
@ -59,49 +82,41 @@ class FastHistogram(
|
||||
return getValue(getIndex(point))
|
||||
}
|
||||
|
||||
private fun getTemplate(index: IntArray): BinTemplate<Double> {
|
||||
private fun getDef(index: IntArray): BinDef<Double> {
|
||||
val center = index.mapIndexed { axis, i ->
|
||||
when (i) {
|
||||
0 -> Double.NEGATIVE_INFINITY
|
||||
strides.shape[axis] - 1 -> Double.POSITIVE_INFINITY
|
||||
else -> lower[axis] + (i.toDouble() - 0.5) * binSize[axis]
|
||||
}
|
||||
}.toVector()
|
||||
return BinTemplate(center, binSize)
|
||||
}.asBuffer()
|
||||
return BinDef(RealBufferFieldOperations, center, binSize)
|
||||
}
|
||||
|
||||
fun getTemplate(point: Buffer<out Double>): BinTemplate<Double> {
|
||||
return getTemplate(getIndex(point))
|
||||
fun getDef(point: Buffer<out Double>): BinDef<Double> {
|
||||
return getDef(getIndex(point))
|
||||
}
|
||||
|
||||
override fun get(point: Buffer<out Double>): PhantomBin<Double>? {
|
||||
override fun get(point: Buffer<out Double>): MultivariateBin<Double>? {
|
||||
val index = getIndex(point)
|
||||
return PhantomBin(getTemplate(index), getValue(index))
|
||||
return MultivariateBin(getDef(index), getValue(index))
|
||||
}
|
||||
|
||||
override fun put(point: Buffer<out Double>, weight: Double) {
|
||||
override fun putWithWeight(point: Buffer<out Double>, weight: Double) {
|
||||
if (weight != 1.0) TODO("Implement weighting")
|
||||
val index = getIndex(point)
|
||||
values[index].increment()
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<PhantomBin<Double>> = values.elements().map { (index, value) ->
|
||||
PhantomBin(getTemplate(index), value.sum())
|
||||
override fun iterator(): Iterator<MultivariateBin<Double>> = values.elements().map { (index, value) ->
|
||||
MultivariateBin(getDef(index), value.sum())
|
||||
}.iterator()
|
||||
|
||||
/**
|
||||
* Convert this histogram into NDStructure containing bin values but not bin descriptions
|
||||
*/
|
||||
fun asNDStructure(): NDStructure<Number> {
|
||||
return ndStructure(this.values.shape) { values[it].sum() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a phantom lightweight immutable copy of this histogram
|
||||
*/
|
||||
fun asPhantomHistogram(): PhantomHistogram<Double> {
|
||||
val binTemplates = values.elements().associate { (index, _) -> getTemplate(index) to index }
|
||||
return PhantomHistogram(binTemplates, asNDStructure())
|
||||
return NDStructure.auto(this.values.shape) { values[it].sum() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -115,8 +130,11 @@ class FastHistogram(
|
||||
*)
|
||||
*```
|
||||
*/
|
||||
fun fromRanges(vararg ranges: ClosedFloatingPointRange<Double>): FastHistogram {
|
||||
return FastHistogram(ranges.map { it.start }.toVector(), ranges.map { it.endInclusive }.toVector())
|
||||
fun fromRanges(vararg ranges: ClosedFloatingPointRange<Double>): RealHistogram {
|
||||
return RealHistogram(
|
||||
ranges.map { it.start }.toVector(),
|
||||
ranges.map { it.endInclusive }.toVector()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,8 +146,8 @@ class FastHistogram(
|
||||
*)
|
||||
*```
|
||||
*/
|
||||
fun fromRanges(vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>): FastHistogram {
|
||||
return FastHistogram(
|
||||
fun fromRanges(vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>): RealHistogram {
|
||||
return RealHistogram(
|
||||
ListBuffer(ranges.map { it.first.start }),
|
||||
ListBuffer(ranges.map { it.first.endInclusive }),
|
||||
ranges.map { it.second }.toIntArray()
|
@ -1,5 +1,8 @@
|
||||
package scientifik.kmath.histogram
|
||||
package scietifik.kmath.histogram
|
||||
|
||||
import scientifik.kmath.histogram.RealHistogram
|
||||
import scientifik.kmath.histogram.fill
|
||||
import scientifik.kmath.histogram.put
|
||||
import scientifik.kmath.linear.Vector
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
@ -10,7 +13,7 @@ import kotlin.test.assertTrue
|
||||
class MultivariateHistogramTest {
|
||||
@Test
|
||||
fun testSinglePutHistogram() {
|
||||
val histogram = FastHistogram.fromRanges(
|
||||
val histogram = RealHistogram.fromRanges(
|
||||
(-1.0..1.0),
|
||||
(-1.0..1.0)
|
||||
)
|
||||
@ -23,7 +26,7 @@ class MultivariateHistogramTest {
|
||||
|
||||
@Test
|
||||
fun testSequentialPut() {
|
||||
val histogram = FastHistogram.fromRanges(
|
||||
val histogram = RealHistogram.fromRanges(
|
||||
(-1.0..1.0),
|
||||
(-1.0..1.0),
|
||||
(-1.0..1.0)
|
@ -0,0 +1,33 @@
|
||||
package scientifik.kmath.histogram
|
||||
|
||||
actual class LongCounter {
|
||||
private var sum: Long = 0
|
||||
actual fun decrement() {
|
||||
sum--
|
||||
}
|
||||
|
||||
actual fun increment() {
|
||||
sum++
|
||||
}
|
||||
|
||||
actual fun reset() {
|
||||
sum = 0
|
||||
}
|
||||
|
||||
actual fun sum(): Long = sum
|
||||
actual fun add(l: Long) {
|
||||
sum += l
|
||||
}
|
||||
}
|
||||
|
||||
actual class DoubleCounter {
|
||||
private var sum: Double = 0.0
|
||||
actual fun reset() {
|
||||
sum = 0.0
|
||||
}
|
||||
|
||||
actual fun sum(): Double = sum
|
||||
actual fun add(d: Double) {
|
||||
sum += d
|
||||
}
|
||||
}
|
@ -26,7 +26,8 @@ class UnivariateBin(val position: Double, val size: Double, val counter: LongCou
|
||||
/**
|
||||
* Univariate histogram with log(n) bin search speed
|
||||
*/
|
||||
class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : MutableHistogram<Double,UnivariateBin> {
|
||||
class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) :
|
||||
MutableHistogram<Double, UnivariateBin> {
|
||||
|
||||
private val bins: TreeMap<Double, UnivariateBin> = TreeMap()
|
||||
|
||||
@ -58,7 +59,7 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U
|
||||
(get(value) ?: createBin(value)).inc()
|
||||
}
|
||||
|
||||
override fun put(point: Buffer<out Double>, weight: Double) {
|
||||
override fun putWithWeight(point: Buffer<out Double>, weight: Double) {
|
||||
if (weight != 1.0) TODO("Implement weighting")
|
||||
put(point[0])
|
||||
}
|
||||
@ -75,8 +76,14 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U
|
||||
val sorted = borders.sortedArray()
|
||||
return UnivariateHistogram { value ->
|
||||
when {
|
||||
value < sorted.first() -> UnivariateBin(Double.NEGATIVE_INFINITY, Double.MAX_VALUE)
|
||||
value > sorted.last() -> UnivariateBin(Double.POSITIVE_INFINITY, Double.MAX_VALUE)
|
||||
value < sorted.first() -> UnivariateBin(
|
||||
Double.NEGATIVE_INFINITY,
|
||||
Double.MAX_VALUE
|
||||
)
|
||||
value > sorted.last() -> UnivariateBin(
|
||||
Double.POSITIVE_INFINITY,
|
||||
Double.MAX_VALUE
|
||||
)
|
||||
else -> {
|
||||
val index = (0 until sorted.size).first { value > sorted[it] }
|
||||
val left = sorted[index]
|
@ -1,10 +0,0 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "kotlin"
|
||||
id "me.champeau.gradle.jmh" version "0.4.7"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':kmath-core')
|
||||
//jmh project(':kmath-core')
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user