NDStructures redone

This commit is contained in:
Alexander Nozik 2018-10-21 18:41:39 +03:00
parent f894175897
commit f950313f41
15 changed files with 351 additions and 376 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
.gradle .gradle
/build/ **/build/
/.idea/ /.idea/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.0-rc-146' ext.kotlin_version = '1.3.0-rc-190'
repositories { repositories {
jcenter() jcenter()

View File

@ -1,6 +1,6 @@
plugins { plugins {
id 'kotlin-multiplatform'// version '1.3.0-rc-116' id 'kotlin-multiplatform'// version '1.3.0-rc-116'
id "me.champeau.gradle.jmh" version "0.4.5" id "me.champeau.gradle.jmh" version "0.4.7"
} }
repositories { repositories {
@ -8,9 +8,14 @@ repositories {
mavenCentral() mavenCentral()
} }
dependencies{
jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}
kotlin { kotlin {
targets { targets {
fromPreset(presets.jvm, 'jvm') fromPreset(presets.jvmWithJava, 'jvm')
//fromPreset(presets.jvm, 'jvm')
fromPreset(presets.js, 'js') fromPreset(presets.js, 'js')
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
// For Linux, preset should be changed to e.g. presets.linuxX64 // For Linux, preset should be changed to e.g. presets.linuxX64

View File

@ -1,18 +1,19 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import scientifik.kmath.structures.MutableNDArray import scientifik.kmath.structures.MutableNDStructure
import scientifik.kmath.structures.NDArray import scientifik.kmath.structures.NDStructure
import scientifik.kmath.structures.NDArrays import scientifik.kmath.structures.genericNdStructure
import scientifik.kmath.structures.get
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
/** /**
* Implementation copier from Apache common-maths * Implementation based on Apache common-maths LU-decomposition
*/ */
abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) { abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
private val field get() = matrix.context.field private val field get() = matrix.context.field
/** Entries of LU decomposition. */ /** Entries of LU decomposition. */
internal val lu: NDArray<T> internal val lu: NDStructure<T>
/** Pivot permutation associated with LU decomposition. */ /** Pivot permutation associated with LU decomposition. */
internal val pivot: IntArray internal val pivot: IntArray
/** Parity of the permutation associated with the LU decomposition. */ /** Parity of the permutation associated with the LU decomposition. */
@ -85,26 +86,18 @@ abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
} }
} }
// /**
// * Get a solver for finding the A X = B solution in exact linear
// * sense.
// * @return a solver
// */
// val solver: DecompositionSolver
// get() = Solver(lu, pivot, singular)
/** /**
* In-place transformation for [MutableNDArray], using given transformation for each element * In-place transformation for [MutableNDArray], using given transformation for each element
*/ */
operator fun <T> MutableNDArray<T>.set(i: Int, j: Int, value: T) { operator fun <T> MutableNDStructure<T>.set(i: Int, j: Int, value: T) {
this[listOf(i, j)] = value this[intArrayOf(i, j)] = value
} }
abstract fun isSingular(value: T): Boolean abstract fun isSingular(value: T): Boolean
private fun abs(value: T) = if (value > matrix.context.field.zero) value else with(matrix.context.field) { -value } private fun abs(value: T) = if (value > matrix.context.field.zero) value else with(matrix.context.field) { -value }
private fun calculateLU(): Pair<NDArray<T>, IntArray> { private fun calculateLU(): Pair<NDStructure<T>, IntArray> {
if (matrix.rows != matrix.columns) { if (matrix.rows != matrix.columns) {
error("LU decomposition supports only square matrices") error("LU decomposition supports only square matrices")
} }
@ -112,7 +105,7 @@ abstract class LUDecomposition<T : Comparable<T>>(val matrix: Matrix<T>) {
val m = matrix.columns val m = matrix.columns
val pivot = IntArray(matrix.rows) val pivot = IntArray(matrix.rows)
//TODO fix performance //TODO fix performance
val lu: MutableNDArray<T> = NDArrays.createMutable(matrix.context.field, listOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } val lu: MutableNDStructure<T> = genericNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] }
with(matrix.context.field) { with(matrix.context.field) {
@ -203,44 +196,6 @@ class RealLUDecomposition(matrix: Matrix<Double>, private val singularityThresho
/** Specialized solver. */ /** Specialized solver. */
object RealLUSolver : LinearSolver<Double> { object RealLUSolver : LinearSolver<Double> {
//
// /** {@inheritDoc} */
// override fun solve(b: RealVector): RealVector {
// val m = pivot.size
// if (b.getDimension() != m) {
// throw DimensionMismatchException(b.getDimension(), m)
// }
// if (singular) {
// throw SingularMatrixException()
// }
//
// val bp = DoubleArray(m)
//
// // Apply permutations to b
// for (row in 0 until m) {
// bp[row] = b.getEntry(pivot[row])
// }
//
// // Solve LY = b
// for (col in 0 until m) {
// val bpCol = bp[col]
// for (i in col + 1 until m) {
// bp[i] -= bpCol * lu[i][col]
// }
// }
//
// // Solve UX = Y
// for (col in m - 1 downTo 0) {
// bp[col] /= lu[col][col]
// val bpCol = bp[col]
// for (i in 0 until col) {
// bp[i] -= bpCol * lu[i][col]
// }
// }
//
// return ArrayRealVector(bp, false)
// }
fun decompose(mat: Matrix<Double>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) fun decompose(mat: Matrix<Double>, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold)

View File

@ -4,10 +4,10 @@ import scientifik.kmath.operations.DoubleField
import scientifik.kmath.operations.Field import scientifik.kmath.operations.Field
import scientifik.kmath.operations.Space import scientifik.kmath.operations.Space
import scientifik.kmath.operations.SpaceElement import scientifik.kmath.operations.SpaceElement
import scientifik.kmath.structures.GenericNDField
import scientifik.kmath.structures.NDArray import scientifik.kmath.structures.NDArray
import scientifik.kmath.structures.NDArrays.createFactory import scientifik.kmath.structures.NDField
import scientifik.kmath.structures.NDFieldFactory import scientifik.kmath.structures.get
import scientifik.kmath.structures.realNDFieldFactory
/** /**
* The space for linear elements. Supports scalar product alongside with standard linear operations. * The space for linear elements. Supports scalar product alongside with standard linear operations.
@ -193,6 +193,11 @@ interface Vector<T : Any> : SpaceElement<Vector<T>, VectorSpace<T>> {
} }
} }
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. * NDArray-based implementation of vector space. By default uses slow [SimpleNDField], but could be overridden with custom [NDField] factory.
@ -201,11 +206,11 @@ class ArrayMatrixSpace<T : Any>(
rows: Int, rows: Int,
columns: Int, columns: Int,
field: Field<T>, field: Field<T>,
val ndFactory: NDFieldFactory<T> = createFactory(field) val ndFactory: NDFieldFactory<T> = genericNDFieldFactory(field)
) : MatrixSpace<T>(rows, columns, field) { ) : MatrixSpace<T>(rows, columns, field) {
val ndField by lazy { val ndField by lazy {
ndFactory(listOf(rows, columns)) ndFactory(intArrayOf(rows, columns))
} }
override fun produce(initializer: (Int, Int) -> T): Matrix<T> = ArrayMatrix(this, initializer) override fun produce(initializer: (Int, Int) -> T): Matrix<T> = ArrayMatrix(this, initializer)
@ -218,10 +223,10 @@ class ArrayMatrixSpace<T : Any>(
class ArrayVectorSpace<T : Any>( class ArrayVectorSpace<T : Any>(
size: Int, size: Int,
field: Field<T>, field: Field<T>,
val ndFactory: NDFieldFactory<T> = createFactory(field) val ndFactory: NDFieldFactory<T> = genericNDFieldFactory(field)
) : VectorSpace<T>(size, field) { ) : VectorSpace<T>(size, field) {
val ndField by lazy { val ndField by lazy {
ndFactory(listOf(size)) ndFactory(intArrayOf(size))
} }
override fun produce(initializer: (Int) -> T): Vector<T> = ArrayVector(this, initializer) override fun produce(initializer: (Int) -> T): Vector<T> = ArrayVector(this, initializer)
@ -306,6 +311,6 @@ 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, j -> get(i) } return Matrix.of(size, 1, context.field) { i, _ -> get(i) }
} }

View File

@ -1,112 +0,0 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.Field
/**
* A generic buffer for both primitives and objects
*/
interface Buffer<T> {
operator fun get(index: Int): T
operator fun set(index: Int, value: T)
/**
* A shallow copy of the buffer
*/
fun copy(): Buffer<T>
}
/**
* Generic implementation of NDField based on continuous buffer
*/
abstract class BufferNDField<T>(shape: List<Int>, field: Field<T>) : NDField<T>(shape, field) {
/**
* Strides for memory access
*/
private val strides: List<Int> by lazy {
ArrayList<Int>(shape.size).apply {
var current = 1
add(1)
shape.forEach {
current *= it
add(current)
}
}
}
protected fun offset(index: List<Int>): Int {
return index.mapIndexed { i, value ->
if (value < 0 || value >= shape[i]) {
throw RuntimeException("Index out of shape bounds: ($i,$value)")
}
value * strides[i]
}.sum()
}
//TODO introduce a fast way to calculate index of the next element?
protected fun index(offset: Int): List<Int> {
return sequence {
var current = offset
var strideIndex = strides.size - 2
while (strideIndex >= 0) {
yield(current / strides[strideIndex])
current %= strides[strideIndex]
strideIndex--
}
}.toList().reversed()
}
private val capacity: Int
get() = strides[shape.size]
protected abstract fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer<T>
override fun produce(initializer: (List<Int>) -> T): NDArray<T> {
val buffer = createBuffer(capacity) { initializer(index(it)) }
return BufferNDArray(this, buffer)
}
/**
* Produce mutable NDArray instance
*/
fun produceMutable(initializer: (List<Int>) -> T): MutableNDArray<T> {
val buffer = createBuffer(capacity) { initializer(index(it)) }
return MutableBufferedNDArray(this, buffer)
}
private open class BufferNDArray<T>(override val context: BufferNDField<T>, val data: Buffer<T>) : NDArray<T> {
override fun get(vararg index: Int): T {
return data[context.offset(index.asList())]
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BufferNDArray<*>) return false
if (context != other.context) return false
if (data != other.data) return false
return true
}
override fun hashCode(): Int {
var result = context.hashCode()
result = 31 * result + data.hashCode()
return result
}
override val self: NDArray<T> get() = this
}
private class MutableBufferedNDArray<T>(context: BufferNDField<T>, data: Buffer<T>): BufferNDArray<T>(context,data), MutableNDArray<T>{
override operator fun set(index: List<Int>, value: T){
data[context.offset(index)] = value
}
}
}

View File

@ -0,0 +1,74 @@
package scientifik.kmath.structures
/**
* A generic linear buffer for both primitives and objects
*/
interface Buffer<T> {
val size: Int
operator fun get(index: Int): T
/**
* A shallow copy of the buffer
*/
fun copy(): Buffer<T>
}
interface MutableBuffer<T> : Buffer<T> {
operator fun set(index: Int, value: T)
/**
* A shallow copy of the buffer
*/
override fun copy(): MutableBuffer<T>
}
inline class ListBuffer<T>(private val list: MutableList<T>) : MutableBuffer<T> {
override val size: Int
get() = list.size
override fun get(index: Int): T = list[index]
override fun set(index: Int, value: T) {
list[index] = value
}
override fun copy(): MutableBuffer<T> = ListBuffer(ArrayList(list))
}
class ArrayBuffer<T>(private val array: Array<T>) : MutableBuffer<T> {
override val size: Int
get() = array.size
override fun get(index: Int): T = array[index]
override fun set(index: Int, value: T) {
array[index] = value
}
override fun copy(): MutableBuffer<T> = ArrayBuffer(array.copyOf())
}
class DoubleBuffer(private val array: DoubleArray) : MutableBuffer<Double> {
override val size: Int
get() = array.size
override fun get(index: Int): Double = array[index]
override fun set(index: Int, value: Double) {
array[index] = value
}
override fun copy(): MutableBuffer<Double> = DoubleBuffer(array.copyOf())
}
inline fun <reified T : Any> buffer(size: Int, noinline initializer: (Int) -> T): Buffer<T> {
return ArrayBuffer(Array(size, initializer))
}
inline fun <reified T : Any> mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer<T> {
return ArrayBuffer(Array(size, initializer))
}

View File

@ -1,72 +0,0 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.Field
typealias NDFieldFactory<T> = (shape: List<Int>) -> NDField<T>
/**
* The factory class for fast platform-dependent implementation of NDField of doubles
*/
expect val realNDFieldFactory: NDFieldFactory<Double>
class SimpleNDField<T : Any>(field: Field<T>, shape: List<Int>) : BufferNDField<T>(shape, field) {
override fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer<T> {
val array = ArrayList<T>(capacity)
(0 until capacity).forEach {
array.add(initializer(it))
}
return BufferOfObjects(array)
}
private class BufferOfObjects<T>(val array: ArrayList<T>) : Buffer<T> {
override fun get(index: Int): T = array[index]
override fun set(index: Int, value: T) {
array[index] = value
}
override fun copy(): Buffer<T> = BufferOfObjects(ArrayList(array))
}
}
object NDArrays {
/**
* Create a platform-optimized NDArray of doubles
*/
fun realNDArray(shape: List<Int>, initializer: (List<Int>) -> Double = { 0.0 }): NDArray<Double> {
return realNDFieldFactory(shape).produce(initializer)
}
fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray<Double> {
return realNDArray(listOf(dim)) { initializer(it[0]) }
}
fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray<Double> {
return realNDArray(listOf(dim1, dim2)) { initializer(it[0], it[1]) }
}
fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDArray<Double> {
return realNDArray(listOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
}
/**
* Simple boxing NDField
*/
fun <T : Any> createFactory(field: Field<T>): NDFieldFactory<T> = { shape -> SimpleNDField(field, shape) }
/**
* Simple boxing NDArray
*/
fun <T : Any> create(field: Field<T>, shape: List<Int>, initializer: (List<Int>) -> T): NDArray<T> {
return SimpleNDField(field, shape).produce { initializer(it) }
}
/**
* Mutable boxing NDArray
*/
fun <T : Any> createMutable(field: Field<T>, shape: List<Int>, initializer: (List<Int>) -> T): MutableNDArray<T> {
return SimpleNDField(field, shape).produceMutable { initializer(it) }
}
}

View File

@ -1,12 +1,13 @@
package scientifik.kmath.structures package scientifik.kmath.structures
import scientifik.kmath.operations.DoubleField
import scientifik.kmath.operations.Field import scientifik.kmath.operations.Field
import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.FieldElement
/** /**
* An exception is thrown when the expected ans actual shape of NDArray differs * An exception is thrown when the expected ans actual shape of NDArray differs
*/ */
class ShapeMismatchException(val expected: List<Int>, val actual: List<Int>) : RuntimeException() class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException()
/** /**
* Field for n-dimensional arrays. * Field for n-dimensional arrays.
@ -14,13 +15,15 @@ class ShapeMismatchException(val expected: List<Int>, val actual: List<Int>) : R
* @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: List<Int>, val field: Field<T>) : Field<NDArray<T>> { abstract class NDField<T>(val shape: IntArray, val field: Field<T>) : Field<NDArray<T>> {
abstract fun produceStructure(initializer: (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
*/ */
abstract fun produce(initializer: (List<Int>) -> T): NDArray<T> fun produce(initializer: (IntArray) -> T): NDArray<T> = NDArray(this, produceStructure(initializer))
override val zero: NDArray<T> by lazy { override val zero: NDArray<T> by lazy {
produce { this.field.zero } produce { this.field.zero }
@ -31,7 +34,7 @@ abstract class NDField<T>(val shape: List<Int>, val field: Field<T>) : Field<NDA
*/ */
private fun checkShape(vararg arrays: NDArray<T>) { private fun checkShape(vararg arrays: NDArray<T>) {
arrays.forEach { arrays.forEach {
if (shape != it.shape) { if (!shape.contentEquals(it.shape)) {
throw ShapeMismatchException(shape, it.shape) throw ShapeMismatchException(shape, it.shape)
} }
} }
@ -71,76 +74,44 @@ abstract class NDField<T>(val shape: List<Int>, val field: Field<T>) : Field<NDA
checkShape(a) checkShape(a)
return produce { with(field) { a[it] / b[it] } } return produce { with(field) { a[it] / b[it] } }
} }
/**
* Reverse sum operation
*/
operator fun <T> T.plus(arg: NDArray<T>): NDArray<T> = arg + this
/**
* Reverse minus operation
*/
operator fun <T> T.minus(arg: NDArray<T>): NDArray<T> = arg.transform { _, value ->
with(arg.context.field) {
this@minus - value
}
} }
/** /**
* Many-dimensional array * Reverse product operation
*/ */
interface NDArray<T> : FieldElement<NDArray<T>, NDField<T>> { operator fun <T> T.times(arg: NDArray<T>): NDArray<T> = arg * this
/** /**
* The list of dimensions of this NDArray * Reverse division operation
*/ */
val shape: List<Int> operator fun <T> T.div(arg: NDArray<T>): NDArray<T> = arg.transform { _, value ->
get() = context.shape with(arg.context.field) {
this@div / value
/**
* The number of dimentsions for this array
*/
val dimension: Int
get() = shape.size
/**
* Get the element with given indexes. If number of indexes is different from {@link dimension}, throws exception.
*/
operator fun get(vararg index: Int): T
operator fun get(index: List<Int>): T {
return get(*index.toIntArray())
}
operator fun iterator(): Iterator<Pair<List<Int>, T>> {
return iterateIndexes(shape).map { Pair(it, this[it]) }.iterator()
}
/**
* Generate new NDArray, using given transformation for each element
*/
fun transform(action: (List<Int>, T) -> T): NDArray<T> = context.produce { action(it, this[it]) }
companion object {
/**
* Iterate over all indexes in the nd-shape
*/
fun iterateIndexes(shape: List<Int>): Sequence<List<Int>> {
return if (shape.size == 1) {
(0 until shape[0]).asSequence().map { listOf(it) }
} else {
val tailShape = ArrayList(shape).apply { removeAt(0) }
val tailSequence: List<List<Int>> = iterateIndexes(tailShape).toList()
(0 until shape[0]).asSequence().map { firstIndex ->
//adding first element to each of provided index lists
tailSequence.map { listOf(firstIndex) + it }.asSequence()
}.flatten()
}
} }
} }
} }
/** /**
* In-place mutable [NDArray] * NDStructure coupled to the context. Emulates Python ndarray
*/ */
interface MutableNDArray<T> : NDArray<T> { data class NDArray<T>(override val context: NDField<T>, private val structure: NDStructure<T>) : FieldElement<NDArray<T>, NDField<T>>, NDStructure<T> by structure {
operator fun set(index: List<Int>, value: T) override val self: NDArray<T>
} get() = this
/** fun transform(action: (IntArray, T) -> T): NDArray<T> = context.produce { action(it, get(*it)) }
* In-place transformation for [MutableNDArray], using given transformation for each element
*/
fun <T> MutableNDArray<T>.transformInPlace(action: (List<Int>, T) -> T) {
for ((index, oldValue) in this) {
this[index] = action(index, oldValue)
}
} }
/** /**
@ -159,11 +130,6 @@ operator fun <T> NDArray<T>.plus(arg: T): NDArray<T> = transform { _, value ->
} }
} }
/**
* Reverse sum operation
*/
operator fun <T> T.plus(arg: NDArray<T>): NDArray<T> = arg + this
/** /**
* Subtraction operation between [NDArray] and single element * Subtraction operation between [NDArray] and single element
*/ */
@ -173,15 +139,6 @@ operator fun <T> NDArray<T>.minus(arg: T): NDArray<T> = transform { _, value ->
} }
} }
/**
* Reverse minus operation
*/
operator fun <T> T.minus(arg: NDArray<T>): NDArray<T> = arg.transform { _, value ->
with(arg.context.field) {
this@minus - value
}
}
/* prod and div */ /* prod and div */
/** /**
@ -193,11 +150,6 @@ operator fun <T> NDArray<T>.times(arg: T): NDArray<T> = transform { _, value ->
} }
} }
/**
* Reverse product operation
*/
operator fun <T> T.times(arg: NDArray<T>): NDArray<T> = arg * this
/** /**
* Division operation between [NDArray] and single element * Division operation between [NDArray] and single element
*/ */
@ -207,12 +159,41 @@ operator fun <T> NDArray<T>.div(arg: T): NDArray<T> = transform { _, value ->
} }
} }
/** class GenericNDField<T : Any>(shape: IntArray, field: Field<T>) : NDField<T>(shape, field) {
* Reverse division operation override fun produceStructure(initializer: (IntArray) -> T): NDStructure<T> = genericNdStructure(shape, initializer)
*/
operator fun <T> T.div(arg: NDArray<T>): NDArray<T> = arg.transform { _, value ->
with(arg.context.field) {
this@div / value
}
} }
//typealias NDFieldFactory<T> = (IntArray)->NDField<T>
object NDArrays {
/**
* Create a platform-optimized NDArray of doubles
*/
fun realNDArray(shape: IntArray, initializer: (IntArray) -> Double = { 0.0 }): NDArray<Double> {
return GenericNDField(shape, DoubleField).produce(initializer)
}
fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray<Double> {
return realNDArray(intArrayOf(dim)) { initializer(it[0]) }
}
fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray<Double> {
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> {
return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) }
}
// /**
// * Simple boxing NDField
// */
// fun <T : Any> fieldFactory(field: Field<T>): NDFieldFactory<T> = { shape -> GenericNDField(shape, field) }
/**
* Simple boxing NDArray
*/
fun <T : Any> create(field: Field<T>, shape: IntArray, initializer: (IntArray) -> T): NDArray<T> {
return GenericNDField(shape, field).produce { initializer(it) }
}
}

View File

@ -0,0 +1,174 @@
package scientifik.kmath.structures
interface NDStructure<T> : Iterable<Pair<IntArray, T>> {
val shape: IntArray
val dimension
get() = shape.size
operator fun get(index: IntArray): T
}
operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index)
interface MutableNDStructure<T> : NDStructure<T> {
operator fun set(index: IntArray, value: T)
}
fun <T> MutableNDStructure<T>.transformInPlace(action: (IntArray, T) -> T) {
for ((index, oldValue) in this) {
this[index] = action(index, oldValue)
}
}
/**
* A way to convert ND index to linear one and back
*/
interface Strides {
/**
* Shape of NDstructure
*/
val shape: IntArray
/**
* Array strides
*/
val strides: List<Int>
/**
* Get linear index from multidimensional index
*/
fun offset(index: IntArray): Int
/**
* Get multidimensional from linear
*/
fun index(offset: Int): IntArray
val linearSize: Int
/**
* Iterate over ND indices in a natural order
*/
fun indices(): Sequence<IntArray> {
//TODO introduce a fast way to calculate index of the next element?
return (0 until linearSize).asSequence().map { index(it) }
}
}
class DefaultStrides(override val shape: IntArray) : Strides {
/**
* Strides for memory access
*/
override val strides by lazy {
sequence {
var current = 1
yield(1)
shape.forEach {
current *= it
yield(current)
}
}.toList()
}
override fun offset(index: IntArray): Int {
return index.mapIndexed { i, value ->
if (value < 0 || value >= shape[i]) {
throw RuntimeException("Index out of shape bounds: ($i,$value)")
}
value * strides[i]
}.sum()
}
override fun index(offset: Int): IntArray {
return sequence {
var current = offset
var strideIndex = strides.size - 2
while (strideIndex >= 0) {
yield(current / strides[strideIndex])
current %= strides[strideIndex]
strideIndex--
}
}.toList().reversed().toIntArray()
}
override val linearSize: Int
get() = strides[shape.size]
}
abstract class GenericNDStructure<T, B : Buffer<T>> : NDStructure<T> {
protected abstract val buffer: B
protected abstract val strides: Strides
override fun get(index: IntArray): T = buffer[strides.offset(index)]
override val shape: IntArray
get() = strides.shape
override fun iterator(): Iterator<Pair<IntArray, T>> =
strides.indices().map { it to this[it] }.iterator()
}
/**
* Boxing generic [NDStructure]
*/
class BufferNDStructure<T>(
override val strides: Strides,
override val buffer: Buffer<T>
) : GenericNDStructure<T, Buffer<T>>() {
init {
if (strides.linearSize != buffer.size) {
error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}")
}
}
}
inline fun <reified T: Any> ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
BufferNDStructure<T>(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))})
inline fun <reified T: Any> ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
ndStructure(DefaultStrides(shape), initializer)
/**
* Mutable ND buffer based on linear [Buffer]
*/
class MutableBufferNDStructure<T>(
override val strides: Strides,
override val buffer: MutableBuffer<T>
) : GenericNDStructure<T, MutableBuffer<T>>(), MutableNDStructure<T> {
init {
if (strides.linearSize != buffer.size) {
error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}")
}
}
override fun set(index: IntArray, value: T) = buffer.set(strides.offset(index), value)
}
/**
* Create optimized mutable structure for given type
*/
inline fun <reified T: Any> mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) =
MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) })
inline fun <reified T: Any> mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) =
mutableNdStructure(DefaultStrides(shape), initializer)
/**
* Create universal mutable structure
*/
fun <T> genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure<T>{
val strides = DefaultStrides(shape)
val sequence = sequence{
strides.indices().forEach{
yield(initializer(it))
}
}
val buffer = ListBuffer<T>(sequence.toMutableList())
return MutableBufferNDStructure(strides, buffer)
}

View File

@ -7,15 +7,15 @@ class ArrayMatrixTest {
@Test @Test
fun testSum() { fun testSum() {
val vector1 = realVector(5) { it.toDouble() } val vector1 = Vector.ofReal(5) { it.toDouble() }
val vector2 = realVector(5) { 5 - it.toDouble() } val vector2 = Vector.ofReal(5) { 5 - it.toDouble() }
val sum = vector1 + vector2 val sum = vector1 + vector2
assertEquals(5.0, sum[2]) assertEquals(5.0, sum[2])
} }
@Test @Test
fun testVectorToMatrix() { fun testVectorToMatrix() {
val vector = realVector(5) { it.toDouble() } val vector = Vector.ofReal(5) { it.toDouble() }
val matrix = vector.toMatrix() val matrix = vector.toMatrix()
assertEquals(4.0, matrix[4, 0]) assertEquals(4.0, matrix[4, 0])
} }
@ -23,8 +23,8 @@ class ArrayMatrixTest {
@Test @Test
fun testDot() { fun testDot() {
val vector1 = realVector(5) { it.toDouble() } val vector1 = Vector.ofReal(5) { it.toDouble() }
val vector2 = realVector(5) { 5 - it.toDouble() } val vector2 = Vector.ofReal(5) { 5 - it.toDouble() }
val product = vector1.toMatrix() dot (vector2.toMatrix().transpose()) val product = vector1.toMatrix() dot (vector2.toMatrix().transpose())

View File

@ -6,10 +6,10 @@ import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class SimpleNDFieldTest{ class GenericNDFieldTest{
@Test @Test
fun testStrides(){ fun testStrides(){
val ndArray = create(DoubleField, listOf(10,10)){(it[0]+it[1]).toDouble()} val ndArray = create(DoubleField, intArrayOf(10,10)){(it[0]+it[1]).toDouble()}
assertEquals(ndArray[5,5], 10.0) assertEquals(ndArray[5,5], 10.0)
} }

View File

@ -1,12 +1,11 @@
package scietifik.kmath.structures package scietifik.kmath.structures
import org.openjdk.jmh.annotations.*
import java.nio.IntBuffer import java.nio.IntBuffer
@Fork(1) @Fork(1)
@Warmup(iterations = 2) @Warmup(iterations = 2)
@Measurement(iterations = 50) @Measurement(iterations = 5)
@State(Scope.Benchmark) @State(Scope.Benchmark)
open class ArrayBenchmark { open class ArrayBenchmark {

View File

@ -1,8 +0,0 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.DoubleField
/**
* Using boxing implementation for js
*/
actual val realNDFieldFactory: NDFieldFactory<Double> = NDArrays.createFactory(DoubleField)

View File

@ -1,26 +0,0 @@
package scientifik.kmath.structures
import scientifik.kmath.operations.DoubleField
import java.nio.DoubleBuffer
private class RealNDField(shape: List<Int>) : BufferNDField<Double>(shape, DoubleField) {
override fun createBuffer(capacity: Int, initializer: (Int) -> Double): Buffer<Double> {
val array = DoubleArray(capacity, initializer)
val buffer = DoubleBuffer.wrap(array)
return BufferOfDoubles(buffer)
}
private class BufferOfDoubles(val buffer: DoubleBuffer): Buffer<Double>{
override fun get(index: Int): Double = buffer.get(index)
override fun set(index: Int, value: Double) {
buffer.put(index, value)
}
override fun copy(): Buffer<Double> {
return BufferOfDoubles(buffer)
}
}
}
actual val realNDFieldFactory: NDFieldFactory<Double> = { shape -> RealNDField(shape) }