Merge pull request #24 from altavir/dev
dev
This commit is contained in:
commit
9808b2800c
@ -58,11 +58,11 @@ repositories {
|
|||||||
|
|
||||||
Then use regular dependency like
|
Then use regular dependency like
|
||||||
```groovy
|
```groovy
|
||||||
compile(group: 'scientifik', name: 'kmath-core-jvm', version: '0.0.1-SNAPSHOT')
|
compile(group: 'scientifik', name: 'kmath-core', version: '0.0.1-SNAPSHOT')
|
||||||
```
|
```
|
||||||
or in kotlin
|
or in kotlin
|
||||||
```kotlin
|
```kotlin
|
||||||
compile(group = "scientifik", name = "kmath-core-jvm", version = "0.0.1-SNAPSHOT")
|
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).
|
Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath).
|
||||||
|
24
build.gradle
24
build.gradle
@ -1,24 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
ext.kotlin_version = '1.3.0'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
apply plugin: 'maven-publish'
|
|
||||||
apply plugin: "com.jfrog.artifactory"
|
|
||||||
|
|
||||||
group = 'scientifik'
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
|
||||||
}
|
|
||||||
|
|
||||||
if(file('artifactory.gradle').exists()){
|
|
||||||
apply from: 'artifactory.gradle'
|
|
||||||
}
|
|
35
build.gradle.kts
Normal file
35
build.gradle.kts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
buildscript {
|
||||||
|
extra["kotlinVersion"] = "1.3.11"
|
||||||
|
extra["ioVersion"] = "0.1.2-dev-2"
|
||||||
|
extra["coroutinesVersion"] = "1.0.1"
|
||||||
|
|
||||||
|
val kotlinVersion: String by extra
|
||||||
|
val ioVersion: String by extra
|
||||||
|
val coroutinesVersion: String by extra
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||||
|
classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.jfrog.artifactory") version "4.8.1" apply false
|
||||||
|
// id("org.jetbrains.kotlin.multiplatform") apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply(plugin = "maven-publish")
|
||||||
|
apply(plugin = "com.jfrog.artifactory")
|
||||||
|
|
||||||
|
group = "scientifik"
|
||||||
|
version = "0.0.2-dev-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(file("artifactory.gradle").exists()){
|
||||||
|
apply(from = "artifactory.gradle")
|
||||||
|
}
|
@ -1,10 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'kotlin-multiplatform'
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -19,7 +14,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
|
api 'org.jetbrains.kotlin:kotlin-stdlib-common'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commonTest {
|
commonTest {
|
||||||
@ -30,7 +25,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmTest {
|
jvmTest {
|
||||||
@ -41,7 +36,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jsMain {
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
|
api 'org.jetbrains.kotlin:kotlin-stdlib-js'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsTest {
|
jsTest {
|
||||||
|
@ -1,63 +1,42 @@
|
|||||||
package scientifik.kmath.histogram
|
package scientifik.kmath.histogram
|
||||||
|
|
||||||
import scientifik.kmath.linear.RealVector
|
|
||||||
import scientifik.kmath.linear.toVector
|
import scientifik.kmath.linear.toVector
|
||||||
import scientifik.kmath.structures.Buffer
|
import scientifik.kmath.structures.*
|
||||||
import scientifik.kmath.structures.NDStructure
|
|
||||||
import scientifik.kmath.structures.ndStructure
|
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
class MultivariateBin(override val center: RealVector, val sizes: RealVector, val counter: LongCounter = LongCounter()) : Bin<Double> {
|
private operator fun RealPoint.minus(other: RealPoint) = ListBuffer((0 until size).map { get(it) - other[it] })
|
||||||
init {
|
|
||||||
if (center.size != sizes.size) error("Dimension mismatch in bin creation. Expected ${center.size}, but found ${sizes.size}")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun contains(vector: Buffer<out Double>): Boolean {
|
private inline fun <T> Buffer<out Double>.mapIndexed(crossinline mapper: (Int, Double) -> T): Sequence<T> = (0 until size).asSequence().map { mapper(it, get(it)) }
|
||||||
if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}")
|
|
||||||
return vector.asSequence().mapIndexed { i, value -> value in (center[i] - sizes[i] / 2)..(center[i] + sizes[i] / 2) }.all { it }
|
|
||||||
}
|
|
||||||
|
|
||||||
override val value: Number get() = counter.sum()
|
|
||||||
internal operator fun inc() = this.also { counter.increment() }
|
|
||||||
|
|
||||||
override val dimension: Int get() = center.size
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions
|
* 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(
|
class FastHistogram(
|
||||||
private val lower: RealVector,
|
private val lower: RealPoint,
|
||||||
private val upper: RealVector,
|
private val upper: RealPoint,
|
||||||
private val binNums: IntArray = IntArray(lower.size) { 20 }
|
private val binNums: IntArray = IntArray(lower.size) { 20 }
|
||||||
) : Histogram<Double, MultivariateBin> {
|
) : MutableHistogram<Double, PhantomBin<Double>> {
|
||||||
|
|
||||||
|
|
||||||
|
private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 })
|
||||||
|
|
||||||
|
private val values: NDStructure<LongCounter> = ndStructure(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())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// argument checks
|
// argument checks
|
||||||
if (lower.size != upper.size) error("Dimension mismatch in histogram lower and upper limits.")
|
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 (lower.size != binNums.size) error("Dimension mismatch in bin count.")
|
||||||
if ((upper - lower).any { it <= 0 }) error("Range for one of axis is not strictly positive")
|
if ((upper - lower).asSequence().any { it <= 0 }) error("Range for one of axis is not strictly positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override val dimension: Int get() = lower.size
|
override val dimension: Int get() = lower.size
|
||||||
|
|
||||||
//TODO optimize binSize performance if needed
|
|
||||||
private val binSize = (upper - lower).mapIndexed { index, value -> value / binNums[index] }.toVector()
|
|
||||||
|
|
||||||
private val bins: NDStructure<MultivariateBin> by lazy {
|
|
||||||
val actualSizes = IntArray(binNums.size) { binNums[it] + 2 }
|
|
||||||
ndStructure(actualSizes) { indexArray ->
|
|
||||||
val center = indexArray.mapIndexed { axis, index ->
|
|
||||||
when (index) {
|
|
||||||
0 -> Double.NEGATIVE_INFINITY
|
|
||||||
actualSizes[axis] -> Double.POSITIVE_INFINITY
|
|
||||||
else -> lower[axis] + (index - 1) * binSize[axis]
|
|
||||||
}
|
|
||||||
}.toVector()
|
|
||||||
MultivariateBin(center, binSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get internal [NDStructure] bin index for given axis
|
* Get internal [NDStructure] bin index for given axis
|
||||||
@ -70,17 +49,60 @@ class FastHistogram(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getIndex(point: Buffer<out Double>): IntArray = IntArray(dimension) { getIndex(it, point[it]) }
|
||||||
|
|
||||||
override fun get(point: Buffer<out Double>): MultivariateBin? {
|
private fun getValue(index: IntArray): Long {
|
||||||
val index = IntArray(dimension) { getIndex(it, point[it]) }
|
return values[index].sum()
|
||||||
return bins[index]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun put(point: Buffer<out Double>) {
|
fun getValue(point: Buffer<out Double>): Long {
|
||||||
this[point]?.inc() ?: error("Could not find appropriate bin (should not be possible)")
|
return getValue(getIndex(point))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<MultivariateBin> = bins.asSequence().map { it.second }.iterator()
|
private fun getTemplate(index: IntArray): BinTemplate<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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTemplate(point: Buffer<out Double>): BinTemplate<Double> {
|
||||||
|
return getTemplate(getIndex(point))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(point: Buffer<out Double>): PhantomBin<Double>? {
|
||||||
|
val index = getIndex(point)
|
||||||
|
return PhantomBin(getTemplate(index), getValue(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun put(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())
|
||||||
|
}.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())
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@ -108,8 +130,8 @@ class FastHistogram(
|
|||||||
*/
|
*/
|
||||||
fun fromRanges(vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>): FastHistogram {
|
fun fromRanges(vararg ranges: Pair<ClosedFloatingPointRange<Double>, Int>): FastHistogram {
|
||||||
return FastHistogram(
|
return FastHistogram(
|
||||||
ranges.map { it.first.start }.toVector(),
|
ListBuffer(ranges.map { it.first.start }),
|
||||||
ranges.map { it.first.endInclusive }.toVector(),
|
ListBuffer(ranges.map { it.first.endInclusive }),
|
||||||
ranges.map { it.second }.toIntArray()
|
ranges.map { it.second }.toIntArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
package scientifik.kmath.histogram
|
package scientifik.kmath.histogram
|
||||||
|
|
||||||
import scientifik.kmath.operations.Space
|
|
||||||
import scientifik.kmath.structures.ArrayBuffer
|
import scientifik.kmath.structures.ArrayBuffer
|
||||||
import scientifik.kmath.structures.Buffer
|
import scientifik.kmath.structures.Buffer
|
||||||
|
import scientifik.kmath.structures.DoubleBuffer
|
||||||
|
|
||||||
|
typealias Point<T> = Buffer<T>
|
||||||
|
|
||||||
|
typealias RealPoint = Buffer<Double>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple geometric domain
|
* A simple geometric domain
|
||||||
* TODO move to geometry module
|
* TODO move to geometry module
|
||||||
*/
|
*/
|
||||||
interface Domain<T: Any> {
|
interface Domain<T: Any> {
|
||||||
operator fun contains(vector: Buffer<out T>): Boolean
|
operator fun contains(vector: Point<out T>): Boolean
|
||||||
val dimension: Int
|
val dimension: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +25,7 @@ interface Bin<T: Any> : Domain<T> {
|
|||||||
* The value of this bin
|
* The value of this bin
|
||||||
*/
|
*/
|
||||||
val value: Number
|
val value: Number
|
||||||
val center: Buffer<T>
|
val center: Point<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Histogram<T: Any, out B : Bin<T>> : Iterable<B> {
|
interface Histogram<T: Any, out B : Bin<T>> : Iterable<B> {
|
||||||
@ -29,34 +33,31 @@ interface Histogram<T: Any, out B : Bin<T>> : Iterable<B> {
|
|||||||
/**
|
/**
|
||||||
* Find existing bin, corresponding to given coordinates
|
* Find existing bin, corresponding to given coordinates
|
||||||
*/
|
*/
|
||||||
operator fun get(point: Buffer<out T>): B?
|
operator fun get(point: Point<out T>): B?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dimension of the histogram
|
* Dimension of the histogram
|
||||||
*/
|
*/
|
||||||
val dimension: Int
|
val dimension: Int
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MutableHistogram<T: Any, out B : Bin<T>>: Histogram<T,B>{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment appropriate bin
|
* Increment appropriate bin
|
||||||
*/
|
*/
|
||||||
fun put(point: Buffer<out T>)
|
fun put(point: Point<out T>, weight: Double = 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T: Any> Histogram<T,*>.put(vararg point: T) = put(ArrayBuffer(point))
|
fun <T: Any> MutableHistogram<T,*>.put(vararg point: T) = put(ArrayBuffer(point))
|
||||||
|
|
||||||
fun <T: Any> Histogram<T,*>.fill(sequence: Iterable<Buffer<T>>) = sequence.forEach { put(it) }
|
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
|
* Pass a sequence builder into histogram
|
||||||
*/
|
*/
|
||||||
fun <T: Any> Histogram<T, *>.fill(buider: suspend SequenceScope<Buffer<T>>.() -> Unit) = fill(sequence(buider).asIterable())
|
fun <T: Any> MutableHistogram<T, *>.fill(buider: suspend SequenceScope<Point<T>>.() -> Unit) = fill(sequence(buider).asIterable())
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>>
|
|
||||||
}
|
|
@ -0,0 +1,63 @@
|
|||||||
|
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]!!]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package scientifik.kmath.linear
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import scientifik.kmath.operations.Field
|
||||||
import scientifik.kmath.structures.MutableNDStructure
|
import scientifik.kmath.structures.MutableNDStructure
|
||||||
import scientifik.kmath.structures.NDStructure
|
import scientifik.kmath.structures.NDStructure
|
||||||
import scientifik.kmath.structures.genericNdStructure
|
import scientifik.kmath.structures.genericNdStructure
|
||||||
@ -9,7 +11,7 @@ import kotlin.math.absoluteValue
|
|||||||
/**
|
/**
|
||||||
* Implementation based on Apache common-maths LU-decomposition
|
* Implementation based on Apache common-maths LU-decomposition
|
||||||
*/
|
*/
|
||||||
abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
|
abstract class LUDecomposition<T : Comparable<T>, F : Field<T>>(val matrix: Matrix<T, F>) {
|
||||||
|
|
||||||
private val field get() = matrix.context.field
|
private val field get() = matrix.context.field
|
||||||
/** Entries of LU decomposition. */
|
/** Entries of LU decomposition. */
|
||||||
@ -31,7 +33,7 @@ abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
|
|||||||
* L is a lower-triangular matrix
|
* L is a lower-triangular matrix
|
||||||
* @return the L matrix (or null if decomposed matrix is singular)
|
* @return the L matrix (or null if decomposed matrix is singular)
|
||||||
*/
|
*/
|
||||||
val l: Matrix<out T> by lazy {
|
val l: Matrix<out T, F> by lazy {
|
||||||
matrix.context.produce { i, j ->
|
matrix.context.produce { i, j ->
|
||||||
when {
|
when {
|
||||||
j < i -> lu[i, j]
|
j < i -> lu[i, j]
|
||||||
@ -48,7 +50,7 @@ abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
|
|||||||
* U is an upper-triangular matrix
|
* U is an upper-triangular matrix
|
||||||
* @return the U matrix (or null if decomposed matrix is singular)
|
* @return the U matrix (or null if decomposed matrix is singular)
|
||||||
*/
|
*/
|
||||||
val u: Matrix<out T> by lazy {
|
val u: Matrix<out T, F> by lazy {
|
||||||
matrix.context.produce { i, j ->
|
matrix.context.produce { i, j ->
|
||||||
if (j >= i) lu[i, j] else field.zero
|
if (j >= i) lu[i, j] else field.zero
|
||||||
}
|
}
|
||||||
@ -64,7 +66,7 @@ abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
|
|||||||
* @return the P rows permutation matrix (or null if decomposed matrix is singular)
|
* @return the P rows permutation matrix (or null if decomposed matrix is singular)
|
||||||
* @see .getPivot
|
* @see .getPivot
|
||||||
*/
|
*/
|
||||||
val p: Matrix<out T> by lazy {
|
val p: Matrix<out T, F> by lazy {
|
||||||
matrix.context.produce { i, j ->
|
matrix.context.produce { i, j ->
|
||||||
//TODO ineffective. Need sparse matrix for that
|
//TODO ineffective. Need sparse matrix for that
|
||||||
if (j == pivot[i]) field.one else field.zero
|
if (j == pivot[i]) field.one else field.zero
|
||||||
@ -181,7 +183,7 @@ abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RealLUDecomposition(matrix: Matrix<Double>, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : LUDecomposition<Double>(matrix) {
|
class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : LUDecomposition<Double, DoubleField>(matrix) {
|
||||||
override fun isSingular(value: Double): Boolean {
|
override fun isSingular(value: Double): Boolean {
|
||||||
return value.absoluteValue < singularityThreshold
|
return value.absoluteValue < singularityThreshold
|
||||||
}
|
}
|
||||||
@ -194,12 +196,12 @@ class RealLUDecomposition(matrix: Matrix<Double>, private val singularityThresho
|
|||||||
|
|
||||||
|
|
||||||
/** Specialized solver. */
|
/** Specialized solver. */
|
||||||
object RealLUSolver : LinearSolver<Double> {
|
object RealLUSolver : LinearSolver<Double, DoubleField> {
|
||||||
|
|
||||||
|
|
||||||
fun decompose(mat: Matrix<Double>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold)
|
fun decompose(mat: Matrix<Double, DoubleField>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold)
|
||||||
|
|
||||||
override fun solve(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> {
|
override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix {
|
||||||
val decomposition = decompose(a, a.context.field.zero)
|
val decomposition = decompose(a, a.context.field.zero)
|
||||||
|
|
||||||
if (b.rows != a.rows) {
|
if (b.rows != a.rows) {
|
||||||
|
@ -2,282 +2,16 @@ package scientifik.kmath.linear
|
|||||||
|
|
||||||
import scientifik.kmath.operations.DoubleField
|
import scientifik.kmath.operations.DoubleField
|
||||||
import scientifik.kmath.operations.Field
|
import scientifik.kmath.operations.Field
|
||||||
import scientifik.kmath.operations.Space
|
import scientifik.kmath.operations.Norm
|
||||||
import scientifik.kmath.operations.SpaceElement
|
|
||||||
import scientifik.kmath.structures.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The space for linear elements. Supports scalar product alongside with standard linear operations.
|
|
||||||
* @param T type of individual element of the vector or matrix
|
|
||||||
* @param V the type of vector space element
|
|
||||||
*/
|
|
||||||
abstract class MatrixSpace<T : Any>(val rows: Int, val columns: Int, val field: Field<T>) : Space<Matrix<T>> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produce the element of this space
|
|
||||||
*/
|
|
||||||
abstract fun produce(initializer: (Int, Int) -> T): Matrix<T>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produce new matrix space with given dimensions. The space produced could be raised from cache since [MatrixSpace] does not have mutable elements
|
|
||||||
*/
|
|
||||||
abstract fun produceSpace(rows: Int, columns: Int): MatrixSpace<T>
|
|
||||||
|
|
||||||
override val zero: Matrix<T> by lazy {
|
|
||||||
produce { _, _ -> field.zero }
|
|
||||||
}
|
|
||||||
|
|
||||||
// val one: Matrix<T> by lazy {
|
|
||||||
// produce { i, j -> if (i == j) field.one else field.zero }
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun add(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
|
|
||||||
return produce { i, j -> with(field) { a[i, j] + b[i, j] } }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun multiply(a: Matrix<T>, k: Double): Matrix<T> {
|
|
||||||
//TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser
|
|
||||||
return produce { i, j -> with(field) { a[i, j] * k } }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dot product. Throws exception on dimension mismatch
|
|
||||||
*/
|
|
||||||
fun multiply(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
|
|
||||||
if (a.rows != b.columns) {
|
|
||||||
//TODO replace by specific exception
|
|
||||||
error("Dimension mismatch in linear structure dot product: [${a.rows},${a.columns}]*[${b.rows},${b.columns}]")
|
|
||||||
}
|
|
||||||
return produceSpace(a.rows, b.columns).produce { i, j ->
|
|
||||||
(0 until a.columns).asSequence().map { k -> field.multiply(a[i, k], b[k, j]) }.reduce { first, second -> field.add(first, second) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other !is MatrixSpace<*>) return false
|
|
||||||
|
|
||||||
if (rows != other.rows) return false
|
|
||||||
if (columns != other.columns) return false
|
|
||||||
if (field != other.field) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = rows
|
|
||||||
result = 31 * result + columns
|
|
||||||
result = 31 * result + field.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
infix fun <T : Any> Matrix<T>.dot(b: Matrix<T>): Matrix<T> = this.context.multiply(this, b)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A matrix-like structure
|
|
||||||
*/
|
|
||||||
interface Matrix<T : Any> : SpaceElement<Matrix<T>, MatrixSpace<T>> {
|
|
||||||
/**
|
|
||||||
* Number of rows
|
|
||||||
*/
|
|
||||||
val rows: Int
|
|
||||||
/**
|
|
||||||
* Number of columns
|
|
||||||
*/
|
|
||||||
val columns: Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get element in row [i] and column [j]. Throws error in case of call ounside structure dimensions
|
|
||||||
*/
|
|
||||||
operator fun get(i: Int, j: Int): T
|
|
||||||
|
|
||||||
override val self: Matrix<T>
|
|
||||||
get() = this
|
|
||||||
|
|
||||||
fun transpose(): Matrix<T> {
|
|
||||||
return object : Matrix<T> {
|
|
||||||
override val context: MatrixSpace<T> = this@Matrix.context
|
|
||||||
override val rows: Int = this@Matrix.columns
|
|
||||||
override val columns: Int = this@Matrix.rows
|
|
||||||
override fun get(i: Int, j: Int): T = this@Matrix[j, i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create [ArrayMatrix] with custom field
|
|
||||||
*/
|
|
||||||
fun <T : Any> of(rows: Int, columns: Int, field: Field<T>, initializer: (Int, Int) -> T) =
|
|
||||||
ArrayMatrix(ArrayMatrixSpace(rows, columns, field), initializer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create [ArrayMatrix] of doubles. The implementation in general should be faster than generic one due to boxing.
|
|
||||||
*/
|
|
||||||
fun ofReal(rows: Int, columns: Int, initializer: (Int, Int) -> Double) =
|
|
||||||
ArrayMatrix(ArrayMatrixSpace(rows, columns, DoubleField, realNDFieldFactory), initializer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a diagonal value matrix. By default value equals [Field.one].
|
|
||||||
*/
|
|
||||||
fun <T : Any> diagonal(rows: Int, columns: Int, field: Field<T>, values: (Int) -> T = { field.one }): Matrix<T> {
|
|
||||||
return of(rows, columns, field) { i, j -> if (i == j) values(i) else field.zero }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equality check on two generic matrices
|
|
||||||
*/
|
|
||||||
fun equals(mat1: Matrix<*>, mat2: Matrix<*>): Boolean {
|
|
||||||
if (mat1 === mat2) return true
|
|
||||||
if (mat1.context != mat2.context) return false
|
|
||||||
for (i in 0 until mat1.rows) {
|
|
||||||
for (j in 0 until mat2.columns) {
|
|
||||||
if (mat1[i, j] != mat2[i, j]) return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A linear space for vectors
|
|
||||||
*/
|
|
||||||
abstract class VectorSpace<T : Any>(val size: Int, val field: Field<T>) : Space<Vector<T>> {
|
|
||||||
|
|
||||||
abstract fun produce(initializer: (Int) -> T): Vector<T>
|
|
||||||
|
|
||||||
override val zero: Vector<T> by lazy { produce { field.zero } }
|
|
||||||
|
|
||||||
override fun add(a: Vector<T>, b: Vector<T>): Vector<T> = produce { with(field) { a[it] + b[it] } }
|
|
||||||
|
|
||||||
override fun multiply(a: Vector<T>, k: Double): Vector<T> = produce { with(field) { a[it] * k } }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
interface Vector<T : Any> : SpaceElement<Vector<T>, VectorSpace<T>>, Buffer<T>, Iterable<T> {
|
|
||||||
override val size: Int get() = context.size
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Create vector with custom field
|
|
||||||
*/
|
|
||||||
fun <T : Any> of(size: Int, field: Field<T>, initializer: (Int) -> T) =
|
|
||||||
ArrayVector(ArrayVectorSpace(size, field), initializer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create vector of [Double]
|
|
||||||
*/
|
|
||||||
fun ofReal(size: Int, initializer: (Int) -> Double) =
|
|
||||||
ArrayVector(ArrayVectorSpace(size, DoubleField, realNDFieldFactory), initializer)
|
|
||||||
|
|
||||||
fun 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias NDFieldFactory<T> = (IntArray) -> NDField<T>
|
|
||||||
|
|
||||||
internal fun <T : Any> genericNDFieldFactory(field: Field<T>): NDFieldFactory<T> = { index -> GenericNDField(index, field) }
|
|
||||||
internal val realNDFieldFactory: NDFieldFactory<Double> = { index -> GenericNDField(index, DoubleField) }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NDArray-based implementation of vector space. By default uses slow [SimpleNDField], but could be overridden with custom [NDField] factory.
|
|
||||||
*/
|
|
||||||
class ArrayMatrixSpace<T : Any>(
|
|
||||||
rows: Int,
|
|
||||||
columns: Int,
|
|
||||||
field: Field<T>,
|
|
||||||
val ndFactory: NDFieldFactory<T> = genericNDFieldFactory(field)
|
|
||||||
) : MatrixSpace<T>(rows, columns, field) {
|
|
||||||
|
|
||||||
val ndField by lazy {
|
|
||||||
ndFactory(intArrayOf(rows, columns))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun produce(initializer: (Int, Int) -> T): Matrix<T> = ArrayMatrix(this, initializer)
|
|
||||||
|
|
||||||
override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace<T> {
|
|
||||||
return ArrayMatrixSpace(rows, columns, field, ndFactory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArrayVectorSpace<T : Any>(
|
|
||||||
size: Int,
|
|
||||||
field: Field<T>,
|
|
||||||
val ndFactory: NDFieldFactory<T> = genericNDFieldFactory(field)
|
|
||||||
) : VectorSpace<T>(size, field) {
|
|
||||||
val ndField by lazy {
|
|
||||||
ndFactory(intArrayOf(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun produce(initializer: (Int) -> T): Vector<T> = ArrayVector(this, initializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Member of [ArrayMatrixSpace] which wraps 2-D array
|
|
||||||
*/
|
|
||||||
class ArrayMatrix<T : Any> internal constructor(override val context: ArrayMatrixSpace<T>, val array: NDArray<T>) : Matrix<T> {
|
|
||||||
|
|
||||||
constructor(context: ArrayMatrixSpace<T>, initializer: (Int, Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0], list[1]) })
|
|
||||||
|
|
||||||
override val rows: Int get() = context.rows
|
|
||||||
|
|
||||||
override val columns: Int get() = context.columns
|
|
||||||
|
|
||||||
override fun get(i: Int, j: Int): T {
|
|
||||||
return array[i, j]
|
|
||||||
}
|
|
||||||
|
|
||||||
override val self: ArrayMatrix<T> get() = this
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ArrayVector<T : Any> internal constructor(override val context: ArrayVectorSpace<T>, val array: NDArray<T>) : Vector<T> {
|
|
||||||
|
|
||||||
constructor(context: ArrayVectorSpace<T>, initializer: (Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0]) })
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (context.size != array.shape[0]) {
|
|
||||||
error("Array dimension mismatch")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun get(index: Int): T {
|
|
||||||
return array[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
override val self: ArrayVector<T> get() = this
|
|
||||||
|
|
||||||
override fun iterator(): Iterator<T> = (0 until size).map { array[it] }.iterator()
|
|
||||||
|
|
||||||
override fun copy(): ArrayVector<T> = ArrayVector(context, array)
|
|
||||||
|
|
||||||
override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias RealVector = Vector<Double>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors
|
* A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors
|
||||||
*/
|
*/
|
||||||
interface LinearSolver<T : Any> {
|
interface LinearSolver<T : Any, F : Field<T>> {
|
||||||
fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T>
|
fun solve(a: Matrix<T, F>, b: Matrix<T, F>): Matrix<T, F>
|
||||||
fun solve(a: Matrix<T>, b: Vector<T>): Vector<T> = solve(a, b.toMatrix()).toVector()
|
fun solve(a: Matrix<T, F>, b: Vector<T, F>): Vector<T, F> = solve(a, b.toMatrix()).toVector()
|
||||||
fun inverse(a: Matrix<T>): Matrix<T> = solve(a, Matrix.diagonal(a.rows, a.columns, a.context.field))
|
fun inverse(a: Matrix<T, F>): Matrix<T, F> = solve(a, Matrix.diagonal(a.rows, a.columns, a.context.field))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,7 +25,7 @@ fun List<Double>.toVector() = Vector.ofReal(this.size) { this[it] }
|
|||||||
/**
|
/**
|
||||||
* Convert matrix to vector if it is possible
|
* Convert matrix to vector if it is possible
|
||||||
*/
|
*/
|
||||||
fun <T : Any> Matrix<T>.toVector(): Vector<T> {
|
fun <T : Any, F : Field<T>> Matrix<T, F>.toVector(): Vector<T, F> {
|
||||||
return when {
|
return when {
|
||||||
this.columns == 1 -> {
|
this.columns == 1 -> {
|
||||||
// if (this is ArrayMatrix) {
|
// if (this is ArrayMatrix) {
|
||||||
@ -307,7 +41,7 @@ fun <T : Any> Matrix<T>.toVector(): Vector<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> Vector<T>.toMatrix(): Matrix<T> {
|
fun <T : Any, F : Field<T>> Vector<T, F>.toMatrix(): Matrix<T, F> {
|
||||||
// return if (this is ArrayVector) {
|
// return if (this is ArrayVector) {
|
||||||
// //Reuse existing underlying array
|
// //Reuse existing underlying array
|
||||||
// ArrayMatrix(ArrayMatrixSpace(size, 1, context.field, context.ndFactory), array)
|
// ArrayMatrix(ArrayMatrixSpace(size, 1, context.field, context.ndFactory), array)
|
||||||
@ -315,6 +49,14 @@ fun <T : Any> Vector<T>.toMatrix(): Matrix<T> {
|
|||||||
// //Generic vector
|
// //Generic vector
|
||||||
// matrix(size, 1, context.field) { i, j -> get(i) }
|
// matrix(size, 1, context.field) { i, j -> get(i) }
|
||||||
// }
|
// }
|
||||||
return Matrix.of(size, 1, context.field) { i, _ -> 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>
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.*
|
||||||
|
import scientifik.kmath.structures.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The space for linear elements. Supports scalar product alongside with standard linear operations.
|
||||||
|
* @param T type of individual element of the vector or matrix
|
||||||
|
* @param V the type of vector space element
|
||||||
|
*/
|
||||||
|
abstract class MatrixSpace<T : Any, F : Ring<T>>(val rows: Int, val columns: Int, val field: F) : Space<Matrix<T, F>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the element of this space
|
||||||
|
*/
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// val one: Matrix<T> by lazy {
|
||||||
|
// produce { i, j -> if (i == j) field.one else field.zero }
|
||||||
|
// }
|
||||||
|
|
||||||
|
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] } }
|
||||||
|
}
|
||||||
|
|
||||||
|
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 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dot product. Throws exception on dimension mismatch
|
||||||
|
*/
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A matrix-like structure
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element in row [i] and column [j]. Throws error in case of call ounside structure dimensions
|
||||||
|
*/
|
||||||
|
operator fun get(i: Int, j: Int): T
|
||||||
|
|
||||||
|
override val self: Matrix<T, F>
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create [ArrayMatrix] with custom field
|
||||||
|
*/
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Member of [ArrayMatrixSpace] which wraps 2-D array
|
||||||
|
*/
|
||||||
|
class ArrayMatrix<T : Any, F : Field<T>> internal constructor(override val context: ArrayMatrixSpace<T, F>, val element: NDElement<T, F>) : Matrix<T, F> {
|
||||||
|
|
||||||
|
constructor(context: ArrayMatrixSpace<T, F>, initializer: (Int, Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0], list[1]) })
|
||||||
|
|
||||||
|
override val rows: Int get() = context.rows
|
||||||
|
|
||||||
|
override val columns: Int get() = context.columns
|
||||||
|
|
||||||
|
override fun get(i: Int, j: Int): T {
|
||||||
|
return element[i, j]
|
||||||
|
}
|
||||||
|
|
||||||
|
override val self: ArrayMatrix<T, F> get() = this
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package scientifik.kmath.linear
|
||||||
|
|
||||||
|
import scientifik.kmath.histogram.Point
|
||||||
|
import scientifik.kmath.operations.DoubleField
|
||||||
|
import scientifik.kmath.operations.Field
|
||||||
|
import scientifik.kmath.operations.Space
|
||||||
|
import scientifik.kmath.operations.SpaceElement
|
||||||
|
import scientifik.kmath.structures.NDElement
|
||||||
|
import scientifik.kmath.structures.get
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>> {
|
||||||
|
|
||||||
|
abstract fun produce(initializer: (Int) -> T): Vector<T, S>
|
||||||
|
|
||||||
|
override val zero: Vector<T, S> by lazy { produce { space.zero } }
|
||||||
|
|
||||||
|
override fun add(a: Point<T>, b: Point<T>): Vector<T, S> = produce { with(space) { a[it] + b[it] } }
|
||||||
|
|
||||||
|
override fun multiply(a: Point<T>, k: Double): Vector<T, S> = produce { with(space) { a[it] * k } }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A point coupled to the linear space
|
||||||
|
*/
|
||||||
|
interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, VectorSpace<T, S>>, Point<T>, Iterable<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())
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
private val realSpaceCache = HashMap<Int, ArrayVectorSpace<Double, DoubleField>>()
|
||||||
|
|
||||||
|
private fun getRealSpace(size: Int): ArrayVectorSpace<Double, DoubleField> {
|
||||||
|
return realSpaceCache.getOrPut(size){ArrayVectorSpace(size, DoubleField, realNDFieldFactory)}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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]) })
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (context.size != element.shape[0]) {
|
||||||
|
error("Array dimension mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(index: Int): T {
|
||||||
|
return element[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
override val self: ArrayVector<T, F> get() = this
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<T> = (0 until size).map { element[it] }.iterator()
|
||||||
|
|
||||||
|
override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() }
|
||||||
|
}
|
||||||
|
|
@ -38,4 +38,8 @@ data class Complex(val re: Double, val im: Double) : FieldElement<Complex, Compl
|
|||||||
val abs: Double
|
val abs: Double
|
||||||
get() = kotlin.math.sqrt(square)
|
get() = kotlin.math.sqrt(square)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -2,10 +2,20 @@ package scientifik.kmath.operations
|
|||||||
|
|
||||||
import kotlin.math.pow
|
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
|
* Field for real values
|
||||||
*/
|
*/
|
||||||
object RealField : Field<Real>, TrigonometricOperations<Real>, PowerOperations<Real>, ExponentialOperations<Real> {
|
object RealField : ExtendedField<Real>, Norm<Real, Real> {
|
||||||
override val zero: Real = Real(0.0)
|
override val zero: Real = Real(0.0)
|
||||||
override fun add(a: Real, b: Real): Real = Real(a.value + b.value)
|
override fun add(a: Real, b: Real): Real = Real(a.value + b.value)
|
||||||
override val one: Real = Real(1.0)
|
override val one: Real = Real(1.0)
|
||||||
@ -13,33 +23,24 @@ object RealField : Field<Real>, TrigonometricOperations<Real>, PowerOperations<R
|
|||||||
override fun multiply(a: Real, k: Double): Real = Real(a.value * k)
|
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 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 sin(arg: Real): Real = Real(kotlin.math.sin(arg.value))
|
||||||
override fun cos(arg: Real): Real = Real(kotlin.math.cos(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 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 exp(arg: Real): Real = Real(kotlin.math.exp(arg.value))
|
||||||
|
|
||||||
override fun ln(arg: Real): Real = Real(kotlin.math.ln(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.
|
* Real field element wrapping double.
|
||||||
*
|
*
|
||||||
* TODO could be replaced by inline class in kotlin 1.3 if it would allow to avoid boxing
|
* TODO inline does not work due to compiler bug. Waiting for fix for KT-27586
|
||||||
*/
|
*/
|
||||||
data class Real(val value: Double) : Number(), FieldElement<Real, RealField> {
|
inline class Real(val value: Double) : FieldElement<Real, RealField> {
|
||||||
/*
|
|
||||||
* The class uses composition instead of inheritance since Double is final
|
|
||||||
*/
|
|
||||||
|
|
||||||
override fun toByte(): Byte = value.toByte()
|
|
||||||
override fun toChar(): Char = value.toChar()
|
|
||||||
override fun toDouble(): Double = value
|
|
||||||
override fun toFloat(): Float = value.toFloat()
|
|
||||||
override fun toInt(): Int = value.toInt()
|
|
||||||
override fun toLong(): Long = value.toLong()
|
|
||||||
override fun toShort(): Short = value.toShort()
|
|
||||||
|
|
||||||
//values are dynamically calculated to save memory
|
//values are dynamically calculated to save memory
|
||||||
override val self
|
override val self
|
||||||
@ -48,24 +49,41 @@ data class Real(val value: Double) : Number(), FieldElement<Real, RealField> {
|
|||||||
override val context
|
override val context
|
||||||
get() = RealField
|
get() = RealField
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A field for double without boxing. Does not produce appropriate field element
|
* A field for double without boxing. Does not produce appropriate field element
|
||||||
*/
|
*/
|
||||||
object DoubleField : Field<Double>, TrigonometricOperations<Double>, PowerOperations<Double>, ExponentialOperations<Double> {
|
object DoubleField : ExtendedField<Double>, Norm<Double, Double> {
|
||||||
override val zero: Double = 0.0
|
override val zero: Double = 0.0
|
||||||
override fun add(a: Double, b: Double): Double = a + b
|
override fun add(a: Double, b: Double): Double = a + b
|
||||||
override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b
|
override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b
|
||||||
override val one: Double = 1.0
|
override val one: Double = 1.0
|
||||||
override fun divide(a: Double, b: Double): Double = a / b
|
override fun divide(a: Double, b: Double): Double = a / b
|
||||||
|
|
||||||
override fun sin(arg: Double): Double = kotlin.math.sin(arg)
|
override fun sin(arg: Double): Double = kotlin.math.sin(arg)
|
||||||
override fun cos(arg: Double): Double = kotlin.math.cos(arg)
|
override fun cos(arg: Double): Double = kotlin.math.cos(arg)
|
||||||
|
|
||||||
override fun power(arg: Double, pow: Double): Double = arg.pow(pow)
|
override fun power(arg: Double, pow: Double): Double = arg.pow(pow)
|
||||||
|
|
||||||
override fun exp(arg: Double): Double =kotlin.math.exp(arg)
|
override fun exp(arg: Double): Double = kotlin.math.exp(arg)
|
||||||
|
|
||||||
override fun ln(arg: Double): Double = kotlin.math.ln(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
|
||||||
}
|
}
|
@ -46,3 +46,9 @@ interface ExponentialOperations<T> {
|
|||||||
|
|
||||||
fun <T : MathElement<T, out ExponentialOperations<T>>> exp(arg: T): T = arg.context.exp(arg)
|
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<T, out ExponentialOperations<T>>> ln(arg: T): T = arg.context.ln(arg)
|
||||||
|
|
||||||
|
interface Norm<in T, out R> {
|
||||||
|
fun norm(arg: T): R
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : MathElement<T, out Norm<T, R>>, R> norm(arg: T): R = arg.context.norm(arg)
|
@ -2,30 +2,40 @@ package scientifik.kmath.structures
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic linear buffer for both primitives and objects
|
* A generic random access structure for both primitives and objects
|
||||||
*/
|
*/
|
||||||
interface Buffer<T> : Iterable<T> {
|
interface Buffer<T> {
|
||||||
|
|
||||||
val size: Int
|
val size: Int
|
||||||
|
|
||||||
operator fun get(index: Int): T
|
operator fun get(index: Int): T
|
||||||
|
|
||||||
/**
|
operator fun iterator(): Iterator<T>
|
||||||
* A shallow copy of the buffer
|
|
||||||
*/
|
|
||||||
fun copy(): Buffer<T>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Buffer<T>.asSequence(): Sequence<T> = iterator().asSequence()
|
||||||
|
|
||||||
interface MutableBuffer<T> : Buffer<T> {
|
interface MutableBuffer<T> : Buffer<T> {
|
||||||
operator fun set(index: Int, value: T)
|
operator fun set(index: Int, value: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shallow copy of the buffer
|
* A shallow copy of the buffer
|
||||||
*/
|
*/
|
||||||
override fun copy(): MutableBuffer<T>
|
fun copy(): MutableBuffer<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
inline class ListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> {
|
|
||||||
|
inline class ListBuffer<T>(private val list: List<T>) : Buffer<T> {
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
|
get() = list.size
|
||||||
|
|
||||||
|
override fun get(index: Int): T = list[index]
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<T> = list.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline class MutableListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> {
|
||||||
|
|
||||||
override val size: Int
|
override val size: Int
|
||||||
get() = list.size
|
get() = list.size
|
||||||
@ -36,12 +46,13 @@ inline class ListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T>
|
|||||||
list[index] = value
|
list[index] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<T> = list.iterator()
|
override fun iterator(): Iterator<T> = list.iterator()
|
||||||
|
|
||||||
override fun copy(): MutableBuffer<T> = ListBuffer(ArrayList(list))
|
override fun copy(): MutableBuffer<T> = MutableListBuffer(ArrayList(list))
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
|
class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
|
||||||
|
//Can't inline because array invariant
|
||||||
override val size: Int
|
override val size: Int
|
||||||
get() = array.size
|
get() = array.size
|
||||||
|
|
||||||
@ -51,12 +62,12 @@ class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
|
|||||||
array[index] = value
|
array[index] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<T> = array.iterator()
|
override fun iterator(): Iterator<T> = array.iterator()
|
||||||
|
|
||||||
override fun copy(): MutableBuffer<T> = ArrayBuffer(array.copyOf())
|
override fun copy(): MutableBuffer<T> = ArrayBuffer(array.copyOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double> {
|
inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double> {
|
||||||
override val size: Int
|
override val size: Int
|
||||||
get() = array.size
|
get() = array.size
|
||||||
|
|
||||||
@ -66,15 +77,63 @@ class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double> {
|
|||||||
array[index] = value
|
array[index] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<Double> = array.iterator()
|
override fun iterator(): Iterator<Double> = array.iterator()
|
||||||
|
|
||||||
override fun copy(): MutableBuffer<Double> = DoubleBuffer(array.copyOf())
|
override fun copy(): MutableBuffer<Double> = DoubleBuffer(array.copyOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Any> buffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> {
|
inline class IntBuffer(private val array: IntArray) : MutableBuffer<Int> {
|
||||||
return ArrayBuffer(Array(size, initializer))
|
override val size: Int
|
||||||
|
get() = array.size
|
||||||
|
|
||||||
|
override fun get(index: Int): Int = array[index]
|
||||||
|
|
||||||
|
override fun set(index: Int, value: Int) {
|
||||||
|
array[index] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<Int> = array.iterator()
|
||||||
|
|
||||||
|
override fun copy(): MutableBuffer<Int> = IntBuffer(array.copyOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Any> mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> {
|
inline class ReadOnlyBuffer<T>(private val buffer: MutableBuffer<T>) : Buffer<T> {
|
||||||
return ArrayBuffer(Array(size, initializer))
|
override val size: Int get() = buffer.size
|
||||||
|
|
||||||
|
override fun get(index: Int): T = buffer.get(index)
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<T> = buffer.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this buffer to read-only buffer
|
||||||
|
*/
|
||||||
|
fun <T> Buffer<T>.asReadOnly(): Buffer<T> = if (this is MutableBuffer) {
|
||||||
|
ReadOnlyBuffer(this)
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create most appropriate immutable buffer for given type avoiding boxing wherever possible
|
||||||
|
*/
|
||||||
|
@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))
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import scientifik.kmath.operations.ExponentialOperations
|
||||||
|
import scientifik.kmath.operations.ExtendedField
|
||||||
|
import scientifik.kmath.operations.PowerOperations
|
||||||
|
import scientifik.kmath.operations.TrigonometricOperations
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
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.
|
||||||
|
// }
|
||||||
|
//}
|
@ -15,25 +15,25 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run
|
|||||||
* @param field - operations field defined on individual array element
|
* @param field - operations field defined on individual array element
|
||||||
* @param T the type of the element contained in NDArray
|
* @param T the type of the element contained in NDArray
|
||||||
*/
|
*/
|
||||||
abstract class NDField<T>(val shape: IntArray, val field: Field<T>) : Field<NDArray<T>> {
|
abstract class NDField<T, F : Field<T>>(val shape: IntArray, val field: F) : Field<NDElement<T, F>> {
|
||||||
|
|
||||||
abstract fun produceStructure(initializer: (IntArray) -> T): NDStructure<T>
|
abstract fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new instance of NDArray using field shape and given initializer
|
* Create new instance of NDArray using field shape and given initializer
|
||||||
* The producer takes list of indices as argument and returns contained value
|
* The producer takes list of indices as argument and returns contained value
|
||||||
*/
|
*/
|
||||||
fun produce(initializer: (IntArray) -> T): NDArray<T> = NDArray(this, produceStructure(initializer))
|
fun produce(initializer: F.(IntArray) -> T): NDElement<T, F> = NDStructureElement(this, produceStructure(initializer))
|
||||||
|
|
||||||
override val zero: NDArray<T> by lazy {
|
override val zero: NDElement<T, F> by lazy {
|
||||||
produce { this.field.zero }
|
produce { zero }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the shape of given NDArray and throw exception if it does not coincide with shape of the field
|
* Check the shape of given NDArray and throw exception if it does not coincide with shape of the field
|
||||||
*/
|
*/
|
||||||
private fun checkShape(vararg arrays: NDArray<T>) {
|
private fun checkShape(vararg elements: NDElement<T, F>) {
|
||||||
arrays.forEach {
|
elements.forEach {
|
||||||
if (!shape.contentEquals(it.shape)) {
|
if (!shape.contentEquals(it.shape)) {
|
||||||
throw ShapeMismatchException(shape, it.shape)
|
throw ShapeMismatchException(shape, it.shape)
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ abstract class NDField<T>(val shape: IntArray, val field: Field<T>) : Field<NDAr
|
|||||||
/**
|
/**
|
||||||
* Element-by-element addition
|
* Element-by-element addition
|
||||||
*/
|
*/
|
||||||
override fun add(a: NDArray<T>, b: NDArray<T>): NDArray<T> {
|
override fun add(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||||
checkShape(a, b)
|
checkShape(a, b)
|
||||||
return produce { with(field) { a[it] + b[it] } }
|
return produce { with(field) { a[it] + b[it] } }
|
||||||
}
|
}
|
||||||
@ -51,18 +51,18 @@ abstract class NDField<T>(val shape: IntArray, val field: Field<T>) : Field<NDAr
|
|||||||
/**
|
/**
|
||||||
* Multiply all elements by cinstant
|
* Multiply all elements by cinstant
|
||||||
*/
|
*/
|
||||||
override fun multiply(a: NDArray<T>, k: Double): NDArray<T> {
|
override fun multiply(a: NDElement<T, F>, k: Double): NDElement<T, F> {
|
||||||
checkShape(a)
|
checkShape(a)
|
||||||
return produce { with(field) { a[it] * k } }
|
return produce { with(field) { a[it] * k } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val one: NDArray<T>
|
override val one: NDElement<T, F>
|
||||||
get() = produce { this.field.one }
|
get() = produce { one }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element-by-element multiplication
|
* Element-by-element multiplication
|
||||||
*/
|
*/
|
||||||
override fun multiply(a: NDArray<T>, b: NDArray<T>): NDArray<T> {
|
override fun multiply(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||||
checkShape(a)
|
checkShape(a)
|
||||||
return produce { with(field) { a[it] * b[it] } }
|
return produce { with(field) { a[it] * b[it] } }
|
||||||
}
|
}
|
||||||
@ -70,73 +70,77 @@ abstract class NDField<T>(val shape: IntArray, val field: Field<T>) : Field<NDAr
|
|||||||
/**
|
/**
|
||||||
* Element-by-element division
|
* Element-by-element division
|
||||||
*/
|
*/
|
||||||
override fun divide(a: NDArray<T>, b: NDArray<T>): NDArray<T> {
|
override fun divide(a: NDElement<T, F>, b: NDElement<T, F>): NDElement<T, F> {
|
||||||
checkShape(a)
|
checkShape(a)
|
||||||
return produce { with(field) { a[it] / b[it] } }
|
return produce { with(field) { a[it] / b[it] } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Reverse sum operation
|
// * Reverse sum operation
|
||||||
*/
|
// */
|
||||||
operator fun <T> T.plus(arg: NDArray<T>): NDArray<T> = arg + this
|
// operator fun T.plus(arg: NDElement<T, F>): NDElement<T, F> = arg + this
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Reverse minus operation
|
// * Reverse minus operation
|
||||||
*/
|
// */
|
||||||
operator fun <T> T.minus(arg: NDArray<T>): NDArray<T> = arg.transform { _, value ->
|
// operator fun T.minus(arg: NDElement<T, F>): NDElement<T, F> = arg.transformIndexed { _, value ->
|
||||||
with(arg.context.field) {
|
// with(arg.context.field) {
|
||||||
this@minus - value
|
// this@minus - value
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Reverse product operation
|
// * Reverse product operation
|
||||||
*/
|
// */
|
||||||
operator fun <T> T.times(arg: NDArray<T>): NDArray<T> = arg * this
|
// operator fun T.times(arg: NDElement<T, F>): NDElement<T, F> = arg * this
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Reverse division operation
|
// * Reverse division operation
|
||||||
*/
|
// */
|
||||||
operator fun <T> T.div(arg: NDArray<T>): NDArray<T> = arg.transform { _, value ->
|
// operator fun T.div(arg: NDElement<T, F>): NDElement<T, F> = arg.transformIndexed { _, value ->
|
||||||
with(arg.context.field) {
|
// with(arg.context.field) {
|
||||||
this@div / value
|
// 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)) }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable [NDStructure] coupled to the context. Emulates Python ndarray
|
* Read-only [NDStructure] coupled to the context.
|
||||||
*/
|
*/
|
||||||
data class NDArray<T>(override val context: NDField<T>, private val structure: NDStructure<T>) : FieldElement<NDArray<T>, NDField<T>>, NDStructure<T> by structure {
|
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
|
//TODO ensure structure is immutable
|
||||||
|
|
||||||
override val self: NDArray<T>
|
override val self: NDElement<T, F> get() = this
|
||||||
get() = this
|
|
||||||
|
|
||||||
fun transform(action: (IntArray, T) -> T): NDArray<T> = context.produce { action(it, get(*it)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
* Element by element application of any operation on elements to the whole array. Just like in numpy
|
||||||
*/
|
*/
|
||||||
operator fun <T> Function1<T, T>.invoke(ndArray: NDArray<T>): NDArray<T> = ndArray.transform { _, value -> this(value) }
|
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 */
|
/* plus and minus */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summation operation for [NDArray] and single element
|
* Summation operation for [NDElement] and single element
|
||||||
*/
|
*/
|
||||||
operator fun <T> NDArray<T>.plus(arg: T): NDArray<T> = transform { _, value ->
|
operator fun <T, F : Field<T>> NDElement<T, F>.plus(arg: T): NDElement<T, F> = transform {value ->
|
||||||
with(context.field) {
|
with(context.field) {
|
||||||
arg + value
|
arg + value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subtraction operation between [NDArray] and single element
|
* Subtraction operation between [NDElement] and single element
|
||||||
*/
|
*/
|
||||||
operator fun <T> NDArray<T>.minus(arg: T): NDArray<T> = transform { _, value ->
|
operator fun <T, F : Field<T>> NDElement<T, F>.minus(arg: T): NDElement<T, F> = transform {value ->
|
||||||
with(context.field) {
|
with(context.field) {
|
||||||
arg - value
|
arg - value
|
||||||
}
|
}
|
||||||
@ -145,25 +149,25 @@ operator fun <T> NDArray<T>.minus(arg: T): NDArray<T> = transform { _, value ->
|
|||||||
/* prod and div */
|
/* prod and div */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Product operation for [NDArray] and single element
|
* Product operation for [NDElement] and single element
|
||||||
*/
|
*/
|
||||||
operator fun <T> NDArray<T>.times(arg: T): NDArray<T> = transform { _, value ->
|
operator fun <T, F : Field<T>> NDElement<T, F>.times(arg: T): NDElement<T, F> = transform { value ->
|
||||||
with(context.field) {
|
with(context.field) {
|
||||||
arg * value
|
arg * value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Division operation between [NDArray] and single element
|
* Division operation between [NDElement] and single element
|
||||||
*/
|
*/
|
||||||
operator fun <T> NDArray<T>.div(arg: T): NDArray<T> = transform { _, value ->
|
operator fun <T, F : Field<T>> NDElement<T, F>.div(arg: T): NDElement<T, F> = transform { value ->
|
||||||
with(context.field) {
|
with(context.field) {
|
||||||
arg / value
|
arg / value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenericNDField<T : Any>(shape: IntArray, field: Field<T>) : NDField<T>(shape, field) {
|
class GenericNDField<T : Any, F : Field<T>>(shape: IntArray, field: F) : NDField<T, F>(shape, field) {
|
||||||
override fun produceStructure(initializer: (IntArray) -> T): NDStructure<T> = genericNdStructure(shape, initializer)
|
override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure<T> = genericNdStructure(shape) { field.initializer(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
//typealias NDFieldFactory<T> = (IntArray)->NDField<T>
|
//typealias NDFieldFactory<T> = (IntArray)->NDField<T>
|
||||||
@ -172,22 +176,25 @@ object NDArrays {
|
|||||||
/**
|
/**
|
||||||
* Create a platform-optimized NDArray of doubles
|
* Create a platform-optimized NDArray of doubles
|
||||||
*/
|
*/
|
||||||
fun realNDArray(shape: IntArray, initializer: (IntArray) -> Double = { 0.0 }): NDArray<Double> {
|
fun realNDArray(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement<Double, DoubleField> {
|
||||||
return GenericNDField(shape, DoubleField).produce(initializer)
|
return ExtendedNDField(shape, DoubleField).produce(initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray<Double> {
|
fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement<Double, DoubleField> {
|
||||||
return realNDArray(intArrayOf(dim)) { initializer(it[0]) }
|
return realNDArray(intArrayOf(dim)) { initializer(it[0]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray<Double> {
|
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]) }
|
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 }): NDArray<Double> {
|
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]) }
|
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
|
// * Simple boxing NDField
|
||||||
// */
|
// */
|
||||||
@ -196,7 +203,7 @@ object NDArrays {
|
|||||||
/**
|
/**
|
||||||
* Simple boxing NDArray
|
* Simple boxing NDArray
|
||||||
*/
|
*/
|
||||||
fun <T : Any> create(field: Field<T>, shape: IntArray, initializer: (IntArray) -> T): NDArray<T> {
|
fun <T : Any, F : Field<T>> create(field: F, shape: IntArray, initializer: (IntArray) -> T): NDElement<T, F> {
|
||||||
return GenericNDField(shape, field).produce { initializer(it) }
|
return GenericNDField(shape, field).produce { initializer(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package scientifik.kmath.structures
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
|
||||||
interface NDStructure<T> : Iterable<Pair<IntArray, T>> {
|
interface NDStructure<T> {
|
||||||
|
|
||||||
val shape: IntArray
|
val shape: IntArray
|
||||||
|
|
||||||
@ -9,6 +9,8 @@ interface NDStructure<T> : Iterable<Pair<IntArray, T>> {
|
|||||||
get() = shape.size
|
get() = shape.size
|
||||||
|
|
||||||
operator fun get(index: IntArray): T
|
operator fun get(index: IntArray): T
|
||||||
|
|
||||||
|
fun elements(): Sequence<Pair<IntArray, T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index)
|
operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index)
|
||||||
@ -18,7 +20,7 @@ interface MutableNDStructure<T> : NDStructure<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> MutableNDStructure<T>.transformInPlace(action: (IntArray, T) -> T) {
|
fun <T> MutableNDStructure<T>.transformInPlace(action: (IntArray, T) -> T) {
|
||||||
for ((index, oldValue) in this) {
|
elements().forEach { (index, oldValue) ->
|
||||||
this[index] = action(index, oldValue)
|
this[index] = action(index, oldValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,22 +78,22 @@ class DefaultStrides(override val shape: IntArray) : Strides {
|
|||||||
override fun offset(index: IntArray): Int {
|
override fun offset(index: IntArray): Int {
|
||||||
return index.mapIndexed { i, value ->
|
return index.mapIndexed { i, value ->
|
||||||
if (value < 0 || value >= shape[i]) {
|
if (value < 0 || value >= shape[i]) {
|
||||||
throw RuntimeException("Index $value out of shape bounds: (0,${shape[i]})")
|
throw RuntimeException("Index $value out of shape bounds: (0,${this.shape[i]})")
|
||||||
}
|
}
|
||||||
value * strides[i]
|
value * strides[i]
|
||||||
}.sum()
|
}.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun index(offset: Int): IntArray {
|
override fun index(offset: Int): IntArray {
|
||||||
return sequence {
|
val res = IntArray(shape.size)
|
||||||
var current = offset
|
var current = offset
|
||||||
var strideIndex = strides.size - 2
|
var strideIndex = strides.size - 2
|
||||||
while (strideIndex >= 0) {
|
while (strideIndex >= 0) {
|
||||||
yield(current / strides[strideIndex])
|
res[strideIndex] = (current / strides[strideIndex])
|
||||||
current %= strides[strideIndex]
|
current %= strides[strideIndex]
|
||||||
strideIndex--
|
strideIndex--
|
||||||
}
|
}
|
||||||
}.toList().reversed().toIntArray()
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
override val linearSize: Int
|
override val linearSize: Int
|
||||||
@ -107,8 +109,8 @@ abstract class GenericNDStructure<T, B : Buffer<T>> : NDStructure<T> {
|
|||||||
override val shape: IntArray
|
override val shape: IntArray
|
||||||
get() = strides.shape
|
get() = strides.shape
|
||||||
|
|
||||||
override fun iterator(): Iterator<Pair<IntArray, T>> =
|
override fun elements()=
|
||||||
strides.indices().map { it to this[it] }.iterator()
|
strides.indices().map { it to this[it] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,10 +128,10 @@ class BufferNDStructure<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T: Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
|
inline fun <reified T : Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
|
||||||
BufferNDStructure<T>(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))})
|
BufferNDStructure<T>(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) })
|
||||||
|
|
||||||
inline fun <reified T: Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
|
inline fun <reified T : Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
|
||||||
ndStructure(DefaultStrides(shape), initializer)
|
ndStructure(DefaultStrides(shape), initializer)
|
||||||
|
|
||||||
|
|
||||||
@ -153,22 +155,22 @@ class MutableBufferNDStructure<T>(
|
|||||||
/**
|
/**
|
||||||
* Create optimized mutable structure for given type
|
* Create optimized mutable structure for given type
|
||||||
*/
|
*/
|
||||||
inline fun <reified T: Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
|
inline fun <reified T : Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
|
||||||
MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
|
MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
|
||||||
|
|
||||||
inline fun <reified T: Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
|
inline fun <reified T : Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
|
||||||
mutableNdStructure(DefaultStrides(shape), initializer)
|
mutableNdStructure(DefaultStrides(shape), initializer)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create universal mutable structure
|
* Create universal mutable structure
|
||||||
*/
|
*/
|
||||||
fun <T> genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure<T>{
|
fun <T> genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure<T> {
|
||||||
val strides = DefaultStrides(shape)
|
val strides = DefaultStrides(shape)
|
||||||
val sequence = sequence{
|
val sequence = sequence {
|
||||||
strides.indices().forEach{
|
strides.indices().forEach {
|
||||||
yield(initializer(it))
|
yield(initializer(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val buffer = ListBuffer<T>(sequence.toMutableList())
|
val buffer = MutableListBuffer(sequence.toMutableList())
|
||||||
return MutableBufferNDStructure(strides, buffer)
|
return MutableBufferNDStructure(strides, buffer)
|
||||||
}
|
}
|
||||||
|
@ -39,4 +39,15 @@ class FieldExpressionContextTest {
|
|||||||
val expression = FieldExpressionContext(DoubleField).expression()
|
val expression = FieldExpressionContext(DoubleField).expression()
|
||||||
assertEquals(expression("x" to 1.0), 4.0)
|
assertEquals(expression("x" to 1.0), 4.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun valueExpression() {
|
||||||
|
val expressionBuilder: FieldExpressionContext<Double>.()->Expression<Double> = {
|
||||||
|
val x = variable("x")
|
||||||
|
x * x + 2 * x + 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
val expression = FieldExpressionContext(DoubleField).expressionBuilder()
|
||||||
|
assertEquals(expression("x" to 1.0), 4.0)
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,10 +14,11 @@ class MultivariateHistogramTest {
|
|||||||
(-1.0..1.0),
|
(-1.0..1.0),
|
||||||
(-1.0..1.0)
|
(-1.0..1.0)
|
||||||
)
|
)
|
||||||
histogram.put(0.6, 0.6)
|
histogram.put(0.55, 0.55)
|
||||||
val bin = histogram.find { it.value.toInt() > 0 }!!
|
val bin = histogram.find { it.value.toInt() > 0 }!!
|
||||||
assertTrue { bin.contains(Vector.ofReal(0.6, 0.6)) }
|
assertTrue { bin.contains(Vector.ofReal(0.55, 0.55)) }
|
||||||
assertFalse { bin.contains(Vector.ofReal(-0.6, 0.6)) }
|
assertTrue { bin.contains(Vector.ofReal(0.6, 0.5)) }
|
||||||
|
assertFalse { bin.contains(Vector.ofReal(-0.55, 0.55)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -6,9 +6,11 @@ import kotlin.test.assertEquals
|
|||||||
class RealFieldTest {
|
class RealFieldTest {
|
||||||
@Test
|
@Test
|
||||||
fun testSqrt() {
|
fun testSqrt() {
|
||||||
|
|
||||||
|
//fails because KT-27586
|
||||||
val sqrt = with(RealField) {
|
val sqrt = with(RealField) {
|
||||||
sqrt(25 * one)
|
sqrt( 25 * one)
|
||||||
}
|
}
|
||||||
assertEquals(5.0, sqrt.toDouble())
|
assertEquals(5.0, sqrt.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,14 @@
|
|||||||
package scientifik.kmath.structures
|
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.NDArrays.real2DArray
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class RealNDFieldTest {
|
class NumberNDFieldTest {
|
||||||
val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() }
|
val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() }
|
||||||
val array2 = real2DArray(3, 3) { i, j -> (i - j).toDouble() }
|
val array2 = real2DArray(3, 3) { i, j -> (i - j).toDouble() }
|
||||||
|
|
||||||
@ -29,7 +32,7 @@ class RealNDFieldTest {
|
|||||||
for (i in 0..2) {
|
for (i in 0..2) {
|
||||||
for (j in 0..2) {
|
for (j in 0..2) {
|
||||||
val expected = (i * 10 + j).toDouble()
|
val expected = (i * 10 + j).toDouble()
|
||||||
assertEquals(expected, array[i, j],"Error at index [$i, $j]")
|
assertEquals(expected, array[i, j], "Error at index [$i, $j]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,6 +41,28 @@ class RealNDFieldTest {
|
|||||||
fun testExternalFunction() {
|
fun testExternalFunction() {
|
||||||
val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 }
|
val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 }
|
||||||
val result = function(array1) + 1.0
|
val result = function(array1) + 1.0
|
||||||
assertEquals(10.0, result[1,1])
|
assertEquals(10.0, result[1, 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLibraryFunction() {
|
||||||
|
val abs: (Double) -> Double = ::abs
|
||||||
|
val result = abs(array2)
|
||||||
|
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 testInternalContext() {
|
||||||
|
produceReal(array1.shape) {
|
||||||
|
with(L2Norm) {
|
||||||
|
1 + norm(array1) + exp(array2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ class UnivariateBin(val position: Double, val size: Double, val counter: LongCou
|
|||||||
/**
|
/**
|
||||||
* Univariate histogram with log(n) bin search speed
|
* Univariate histogram with log(n) bin search speed
|
||||||
*/
|
*/
|
||||||
class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : Histogram<Double,UnivariateBin> {
|
class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : MutableHistogram<Double,UnivariateBin> {
|
||||||
|
|
||||||
private val bins: TreeMap<Double, UnivariateBin> = TreeMap()
|
private val bins: TreeMap<Double, UnivariateBin> = TreeMap()
|
||||||
|
|
||||||
@ -58,7 +58,10 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U
|
|||||||
(get(value) ?: createBin(value)).inc()
|
(get(value) ?: createBin(value)).inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun put(point: Buffer<out Double>) = put(point[0])
|
override fun put(point: Buffer<out Double>, weight: Double) {
|
||||||
|
if (weight != 1.0) TODO("Implement weighting")
|
||||||
|
put(point[0])
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram {
|
fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram {
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
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)
|
||||||
|
|
@ -0,0 +1,28 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
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)
|
@ -0,0 +1,17 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import scientifik.kmath.operations.Complex
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(Complex(5.0, -5.0), buffer[5])
|
||||||
|
}
|
||||||
|
}
|
42
kmath-coroutines/build.gradle
Normal file
42
kmath-coroutines/build.gradle
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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 {
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
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
|
@ -0,0 +1,79 @@
|
|||||||
|
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))
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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,6 @@
|
|||||||
|
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)
|
55
kmath-io/build.gradle
Normal file
55
kmath-io/build.gradle
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
targets {
|
||||||
|
fromPreset(presets.jvm, 'jvm')
|
||||||
|
fromPreset(presets.js, 'js')
|
||||||
|
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||||
|
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||||
|
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||||
|
//fromPreset(presets.mingwX64, 'mingw')
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":kmath-core")
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-common'
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||||
|
api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test'
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-junit'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsMain {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.jetbrains.kotlin:kotlin-test-js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// mingwMain {
|
||||||
|
// }
|
||||||
|
// mingwTest {
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,7 @@ plugins {
|
|||||||
id "me.champeau.gradle.jmh" version "0.4.7"
|
id "me.champeau.gradle.jmh" version "0.4.7"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':kmath-core')
|
compile project(':kmath-core')
|
||||||
jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
//jmh project(':kmath-core')
|
||||||
}
|
}
|
@ -4,8 +4,7 @@ import org.openjdk.jmh.annotations.*
|
|||||||
import java.nio.IntBuffer
|
import java.nio.IntBuffer
|
||||||
|
|
||||||
|
|
||||||
@Fork(1)
|
@Warmup(iterations = 1)
|
||||||
@Warmup(iterations = 2)
|
|
||||||
@Measurement(iterations = 5)
|
@Measurement(iterations = 5)
|
||||||
@State(Scope.Benchmark)
|
@State(Scope.Benchmark)
|
||||||
open class ArrayBenchmark {
|
open class ArrayBenchmark {
|
||||||
@ -30,7 +29,6 @@ open class ArrayBenchmark {
|
|||||||
for (i in 1..10000) {
|
for (i in 1..10000) {
|
||||||
res += array[10000 - i]
|
res += array[10000 - i]
|
||||||
}
|
}
|
||||||
print(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@ -39,7 +37,6 @@ open class ArrayBenchmark {
|
|||||||
for (i in 1..10000) {
|
for (i in 1..10000) {
|
||||||
res += arrayBuffer.get(10000 - i)
|
res += arrayBuffer.get(10000 - i)
|
||||||
}
|
}
|
||||||
print(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@ -48,6 +45,5 @@ open class ArrayBenchmark {
|
|||||||
for (i in 1..10000) {
|
for (i in 1..10000) {
|
||||||
res += nativeBuffer.get(10000 - i)
|
res += nativeBuffer.get(10000 - i)
|
||||||
}
|
}
|
||||||
print(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package scientifik.kmath.structures
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.*
|
||||||
|
import scientifik.kmath.operations.Complex
|
||||||
|
|
||||||
|
@Warmup(iterations = 1)
|
||||||
|
@Measurement(iterations = 5)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
open class BufferBenchmark {
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
fun genericDoubleBufferReadWrite() {
|
||||||
|
val buffer = Double.createBuffer(size)
|
||||||
|
(0 until size).forEach {
|
||||||
|
buffer[it] = it.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
(0 until size).forEach {
|
||||||
|
buffer[it]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
fun complexBufferReadWrite() {
|
||||||
|
val buffer = Complex.createBuffer(size/2)
|
||||||
|
(0 until size/2).forEach {
|
||||||
|
buffer[it] = Complex(it.toDouble(), -it.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
(0 until size/2).forEach {
|
||||||
|
buffer[it]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val size = 1000
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven { url = 'https://plugins.gradle.org/m2/' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableFeaturePreview('GRADLE_METADATA')
|
|
||||||
|
|
||||||
rootProject.name = 'kmath'
|
|
||||||
include ':kmath-core'
|
|
||||||
include ':kmath-jmh'
|
|
||||||
|
|
16
settings.gradle.kts
Normal file
16
settings.gradle.kts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://plugins.gradle.org/m2/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//enableFeaturePreview("GRADLE_METADATA")
|
||||||
|
|
||||||
|
rootProject.name = "kmath"
|
||||||
|
include(
|
||||||
|
":kmath-core",
|
||||||
|
":kmath-io",
|
||||||
|
":kmath-coroutines",
|
||||||
|
":kmath-jmh"
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user