diff --git a/.gitignore b/.gitignore index 5b55fc854..d07c3c850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .gradle -/build/ +**/build/ /.idea/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) diff --git a/build.gradle b/build.gradle index 04700f377..14ecae465 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.0-rc-146' + ext.kotlin_version = '1.3.0-rc-190' repositories { jcenter() diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index 8e0e6ca2e..948dc87bf 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -1,6 +1,6 @@ plugins { 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 { @@ -8,9 +8,14 @@ repositories { mavenCentral() } +dependencies{ + jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + kotlin { targets { - fromPreset(presets.jvm, 'jvm') + fromPreset(presets.jvmWithJava, 'jvm') + //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 diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt index e51d574f8..0b71405c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -1,18 +1,19 @@ package scientifik.kmath.linear -import scientifik.kmath.structures.MutableNDArray -import scientifik.kmath.structures.NDArray -import scientifik.kmath.structures.NDArrays +import scientifik.kmath.structures.MutableNDStructure +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.genericNdStructure +import scientifik.kmath.structures.get import kotlin.math.absoluteValue /** - * Implementation copier from Apache common-maths + * Implementation based on Apache common-maths LU-decomposition */ abstract class LUDecomposition>(val matrix: Matrix) { private val field get() = matrix.context.field /** Entries of LU decomposition. */ - internal val lu: NDArray + internal val lu: NDStructure /** Pivot permutation associated with LU decomposition. */ internal val pivot: IntArray /** Parity of the permutation associated with the LU decomposition. */ @@ -85,26 +86,18 @@ abstract class LUDecomposition>(val matrix: Matrix) { } } -// /** -// * Get a solver for finding the A X = B solution in exact linear -// * sense. -// * @return a solver -// */ -// val solver: DecompositionSolver -// get() = Solver(lu, pivot, singular) - /** * In-place transformation for [MutableNDArray], using given transformation for each element */ - operator fun MutableNDArray.set(i: Int, j: Int, value: T) { - this[listOf(i, j)] = value + operator fun MutableNDStructure.set(i: Int, j: Int, value: T) { + this[intArrayOf(i, j)] = value } abstract fun isSingular(value: T): Boolean private fun abs(value: T) = if (value > matrix.context.field.zero) value else with(matrix.context.field) { -value } - private fun calculateLU(): Pair, IntArray> { + private fun calculateLU(): Pair, IntArray> { if (matrix.rows != matrix.columns) { error("LU decomposition supports only square matrices") } @@ -112,7 +105,7 @@ abstract class LUDecomposition>(val matrix: Matrix) { val m = matrix.columns val pivot = IntArray(matrix.rows) //TODO fix performance - val lu: MutableNDArray = NDArrays.createMutable(matrix.context.field, listOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } + val lu: MutableNDStructure = genericNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } with(matrix.context.field) { @@ -203,44 +196,6 @@ class RealLUDecomposition(matrix: Matrix, private val singularityThresho /** Specialized solver. */ object RealLUSolver : LinearSolver { -// -// /** {@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, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt index 68961a2db..097a65bb4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -4,10 +4,10 @@ import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.structures.GenericNDField import scientifik.kmath.structures.NDArray -import scientifik.kmath.structures.NDArrays.createFactory -import scientifik.kmath.structures.NDFieldFactory -import scientifik.kmath.structures.realNDFieldFactory +import scientifik.kmath.structures.NDField +import scientifik.kmath.structures.get /** * The space for linear elements. Supports scalar product alongside with standard linear operations. @@ -193,6 +193,11 @@ interface Vector : SpaceElement, VectorSpace> { } } +typealias NDFieldFactory = (IntArray) -> NDField + +internal fun genericNDFieldFactory(field: Field): NDFieldFactory = { index -> GenericNDField(index, field) } +internal val realNDFieldFactory: NDFieldFactory = { index -> GenericNDField(index, DoubleField) } + /** * 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( rows: Int, columns: Int, field: Field, - val ndFactory: NDFieldFactory = createFactory(field) + val ndFactory: NDFieldFactory = genericNDFieldFactory(field) ) : MatrixSpace(rows, columns, field) { val ndField by lazy { - ndFactory(listOf(rows, columns)) + ndFactory(intArrayOf(rows, columns)) } override fun produce(initializer: (Int, Int) -> T): Matrix = ArrayMatrix(this, initializer) @@ -218,10 +223,10 @@ class ArrayMatrixSpace( class ArrayVectorSpace( size: Int, field: Field, - val ndFactory: NDFieldFactory = createFactory(field) + val ndFactory: NDFieldFactory = genericNDFieldFactory(field) ) : VectorSpace(size, field) { val ndField by lazy { - ndFactory(listOf(size)) + ndFactory(intArrayOf(size)) } override fun produce(initializer: (Int) -> T): Vector = ArrayVector(this, initializer) @@ -306,6 +311,6 @@ fun Vector.toMatrix(): Matrix { // //Generic vector // 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) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt deleted file mode 100644 index aab0f1b97..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ /dev/null @@ -1,112 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Field - - -/** - * A generic buffer for both primitives and objects - */ -interface Buffer { - operator fun get(index: Int): T - operator fun set(index: Int, value: T) - - /** - * A shallow copy of the buffer - */ - fun copy(): Buffer -} - -/** - * Generic implementation of NDField based on continuous buffer - */ -abstract class BufferNDField(shape: List, field: Field) : NDField(shape, field) { - - /** - * Strides for memory access - */ - private val strides: List by lazy { - ArrayList(shape.size).apply { - var current = 1 - add(1) - shape.forEach { - current *= it - add(current) - } - } - } - - protected fun offset(index: List): 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 { - 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 - - override fun produce(initializer: (List) -> T): NDArray { - val buffer = createBuffer(capacity) { initializer(index(it)) } - return BufferNDArray(this, buffer) - } - - /** - * Produce mutable NDArray instance - */ - fun produceMutable(initializer: (List) -> T): MutableNDArray { - val buffer = createBuffer(capacity) { initializer(index(it)) } - return MutableBufferedNDArray(this, buffer) - } - - - private open class BufferNDArray(override val context: BufferNDField, val data: Buffer) : NDArray { - - 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 get() = this - } - - private class MutableBufferedNDArray(context: BufferNDField, data: Buffer): BufferNDArray(context,data), MutableNDArray{ - override operator fun set(index: List, value: T){ - data[context.offset(index)] = value - } - } -} - - diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt new file mode 100644 index 000000000..70407e482 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -0,0 +1,74 @@ +package scientifik.kmath.structures + + +/** + * A generic linear buffer for both primitives and objects + */ +interface Buffer { + + val size: Int + + operator fun get(index: Int): T + + /** + * A shallow copy of the buffer + */ + fun copy(): Buffer +} + +interface MutableBuffer : Buffer { + operator fun set(index: Int, value: T) + + /** + * A shallow copy of the buffer + */ + override fun copy(): MutableBuffer +} + +inline class ListBuffer(private val list: MutableList) : MutableBuffer { + + 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 = ListBuffer(ArrayList(list)) +} + +class ArrayBuffer(private val array: Array) : MutableBuffer { + 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 = ArrayBuffer(array.copyOf()) +} + +class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { + 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 = DoubleBuffer(array.copyOf()) +} + +inline fun buffer(size: Int, noinline initializer: (Int) -> T): Buffer { + return ArrayBuffer(Array(size, initializer)) +} + +inline fun mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { + return ArrayBuffer(Array(size, initializer)) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt deleted file mode 100644 index 739e91832..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt +++ /dev/null @@ -1,72 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Field - -typealias NDFieldFactory = (shape: List) -> NDField - -/** - * The factory class for fast platform-dependent implementation of NDField of doubles - */ -expect val realNDFieldFactory: NDFieldFactory - - -class SimpleNDField(field: Field, shape: List) : BufferNDField(shape, field) { - override fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer { - val array = ArrayList(capacity) - (0 until capacity).forEach { - array.add(initializer(it)) - } - - return BufferOfObjects(array) - } - - private class BufferOfObjects(val array: ArrayList) : Buffer { - override fun get(index: Int): T = array[index] - - override fun set(index: Int, value: T) { - array[index] = value - } - - override fun copy(): Buffer = BufferOfObjects(ArrayList(array)) - } -} - -object NDArrays { - /** - * Create a platform-optimized NDArray of doubles - */ - fun realNDArray(shape: List, initializer: (List) -> Double = { 0.0 }): NDArray { - return realNDFieldFactory(shape).produce(initializer) - } - - fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray { - return realNDArray(listOf(dim)) { initializer(it[0]) } - } - - fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray { - 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 { - return realNDArray(listOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - } - - /** - * Simple boxing NDField - */ - fun createFactory(field: Field): NDFieldFactory = { shape -> SimpleNDField(field, shape) } - - /** - * Simple boxing NDArray - */ - fun create(field: Field, shape: List, initializer: (List) -> T): NDArray { - return SimpleNDField(field, shape).produce { initializer(it) } - } - - /** - * Mutable boxing NDArray - */ - fun createMutable(field: Field, shape: List, initializer: (List) -> T): MutableNDArray { - return SimpleNDField(field, shape).produceMutable { initializer(it) } - } -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArray.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt similarity index 51% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArray.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 85f60704f..7e0032597 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -1,12 +1,13 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement /** * An exception is thrown when the expected ans actual shape of NDArray differs */ -class ShapeMismatchException(val expected: List, val actual: List) : RuntimeException() +class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() /** * Field for n-dimensional arrays. @@ -14,13 +15,15 @@ class ShapeMismatchException(val expected: List, val actual: List) : R * @param field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ -abstract class NDField(val shape: List, val field: Field) : Field> { +abstract class NDField(val shape: IntArray, val field: Field) : Field> { + + abstract fun produceStructure(initializer: (IntArray) -> T): NDStructure /** * Create new instance of NDArray using field shape and given initializer * The producer takes list of indices as argument and returns contained value */ - abstract fun produce(initializer: (List) -> T): NDArray + fun produce(initializer: (IntArray) -> T): NDArray = NDArray(this, produceStructure(initializer)) override val zero: NDArray by lazy { produce { this.field.zero } @@ -31,7 +34,7 @@ abstract class NDField(val shape: List, val field: Field) : Field) { arrays.forEach { - if (shape != it.shape) { + if (!shape.contentEquals(it.shape)) { throw ShapeMismatchException(shape, it.shape) } } @@ -71,76 +74,44 @@ abstract class NDField(val shape: List, val field: Field) : Field : FieldElement, NDField> { /** - * The list of dimensions of this NDArray + * Reverse sum operation */ - val shape: List - get() = context.shape + operator fun T.plus(arg: NDArray): NDArray = arg + this /** - * The number of dimentsions for this array + * Reverse minus operation */ - 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): T { - return get(*index.toIntArray()) - } - - operator fun iterator(): Iterator, T>> { - return iterateIndexes(shape).map { Pair(it, this[it]) }.iterator() + operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field) { + this@minus - value + } } /** - * Generate new NDArray, using given transformation for each element + * Reverse product operation */ - fun transform(action: (List, T) -> T): NDArray = context.produce { action(it, this[it]) } + operator fun T.times(arg: NDArray): NDArray = arg * this - companion object { - /** - * Iterate over all indexes in the nd-shape - */ - fun iterateIndexes(shape: List): Sequence> { - return if (shape.size == 1) { - (0 until shape[0]).asSequence().map { listOf(it) } - } else { - val tailShape = ArrayList(shape).apply { removeAt(0) } - val tailSequence: List> = 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() - } + /** + * Reverse division operation + */ + operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field) { + this@div / value } } } /** - * In-place mutable [NDArray] + * NDStructure coupled to the context. Emulates Python ndarray */ -interface MutableNDArray : NDArray { - operator fun set(index: List, value: T) -} +data class NDArray(override val context: NDField, private val structure: NDStructure) : FieldElement, NDField>, NDStructure by structure { + override val self: NDArray + get() = this -/** - * In-place transformation for [MutableNDArray], using given transformation for each element - */ -fun MutableNDArray.transformInPlace(action: (List, T) -> T) { - for ((index, oldValue) in this) { - this[index] = action(index, oldValue) - } + fun transform(action: (IntArray, T) -> T): NDArray = context.produce { action(it, get(*it)) } } /** @@ -159,11 +130,6 @@ operator fun NDArray.plus(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse sum operation - */ -operator fun T.plus(arg: NDArray): NDArray = arg + this - /** * Subtraction operation between [NDArray] and single element */ @@ -173,15 +139,6 @@ operator fun NDArray.minus(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse minus operation - */ -operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> - with(arg.context.field) { - this@minus - value - } -} - /* prod and div */ /** @@ -193,11 +150,6 @@ operator fun NDArray.times(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse product operation - */ -operator fun T.times(arg: NDArray): NDArray = arg * this - /** * Division operation between [NDArray] and single element */ @@ -207,12 +159,41 @@ operator fun NDArray.div(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse division operation - */ -operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> - with(arg.context.field) { - this@div / value - } +class GenericNDField(shape: IntArray, field: Field) : NDField(shape, field) { + override fun produceStructure(initializer: (IntArray) -> T): NDStructure = genericNdStructure(shape, initializer) } +//typealias NDFieldFactory = (IntArray)->NDField + +object NDArrays { + /** + * Create a platform-optimized NDArray of doubles + */ + fun realNDArray(shape: IntArray, initializer: (IntArray) -> Double = { 0.0 }): NDArray { + return GenericNDField(shape, DoubleField).produce(initializer) + } + + fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray { + return realNDArray(intArrayOf(dim)) { initializer(it[0]) } + } + + fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray { + 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 { + return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } + } + +// /** +// * Simple boxing NDField +// */ +// fun fieldFactory(field: Field): NDFieldFactory = { shape -> GenericNDField(shape, field) } + + /** + * Simple boxing NDArray + */ + fun create(field: Field, shape: IntArray, initializer: (IntArray) -> T): NDArray { + return GenericNDField(shape, field).produce { initializer(it) } + } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt new file mode 100644 index 000000000..ba3a91487 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -0,0 +1,174 @@ +package scientifik.kmath.structures + + +interface NDStructure : Iterable> { + + val shape: IntArray + + val dimension + get() = shape.size + + operator fun get(index: IntArray): T +} + +operator fun NDStructure.get(vararg index: Int): T = get(index) + +interface MutableNDStructure : NDStructure { + operator fun set(index: IntArray, value: T) +} + +fun MutableNDStructure.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 + + /** + * 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 { + //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> : NDStructure { + 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> = + strides.indices().map { it to this[it] }.iterator() +} + +/** + * Boxing generic [NDStructure] + */ +class BufferNDStructure( + override val strides: Strides, + override val buffer: Buffer +) : GenericNDStructure>() { + + init { + if (strides.linearSize != buffer.size) { + error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") + } + } +} + +inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = + BufferNDStructure(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))}) + +inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = + ndStructure(DefaultStrides(shape), initializer) + + +/** + * Mutable ND buffer based on linear [Buffer] + */ +class MutableBufferNDStructure( + override val strides: Strides, + override val buffer: MutableBuffer +) : GenericNDStructure>(), MutableNDStructure { + + 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 mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = + MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + +inline fun mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = + mutableNdStructure(DefaultStrides(shape), initializer) + +/** + * Create universal mutable structure + */ +fun genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure{ + val strides = DefaultStrides(shape) + val sequence = sequence{ + strides.indices().forEach{ + yield(initializer(it)) + } + } + val buffer = ListBuffer(sequence.toMutableList()) + return MutableBufferNDStructure(strides, buffer) +} diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt index ecc2ca28c..f767d682b 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt @@ -7,15 +7,15 @@ class ArrayMatrixTest { @Test fun testSum() { - val vector1 = realVector(5) { it.toDouble() } - val vector2 = realVector(5) { 5 - it.toDouble() } + val vector1 = Vector.ofReal(5) { it.toDouble() } + val vector2 = Vector.ofReal(5) { 5 - it.toDouble() } val sum = vector1 + vector2 assertEquals(5.0, sum[2]) } @Test fun testVectorToMatrix() { - val vector = realVector(5) { it.toDouble() } + val vector = Vector.ofReal(5) { it.toDouble() } val matrix = vector.toMatrix() assertEquals(4.0, matrix[4, 0]) } @@ -23,8 +23,8 @@ class ArrayMatrixTest { @Test fun testDot() { - val vector1 = realVector(5) { it.toDouble() } - val vector2 = realVector(5) { 5 - it.toDouble() } + val vector1 = Vector.ofReal(5) { it.toDouble() } + val vector2 = Vector.ofReal(5) { 5 - it.toDouble() } val product = vector1.toMatrix() dot (vector2.toMatrix().transpose()) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/SimpleNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt similarity index 70% rename from kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/SimpleNDFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt index 94c1e15cc..338f5f052 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/SimpleNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt @@ -6,10 +6,10 @@ import kotlin.test.Test import kotlin.test.assertEquals -class SimpleNDFieldTest{ +class GenericNDFieldTest{ @Test 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) } diff --git a/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt b/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt index 3430b14d4..9658e8e75 100644 --- a/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt +++ b/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt @@ -1,12 +1,11 @@ package scietifik.kmath.structures -import org.openjdk.jmh.annotations.* import java.nio.IntBuffer @Fork(1) @Warmup(iterations = 2) -@Measurement(iterations = 50) +@Measurement(iterations = 5) @State(Scope.Benchmark) open class ArrayBenchmark { diff --git a/kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt b/kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt deleted file mode 100644 index 32d66a04b..000000000 --- a/kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt +++ /dev/null @@ -1,8 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.DoubleField - -/** - * Using boxing implementation for js - */ -actual val realNDFieldFactory: NDFieldFactory = NDArrays.createFactory(DoubleField) \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt deleted file mode 100644 index 19d6c4041..000000000 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt +++ /dev/null @@ -1,26 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.DoubleField -import java.nio.DoubleBuffer - -private class RealNDField(shape: List) : BufferNDField(shape, DoubleField) { - override fun createBuffer(capacity: Int, initializer: (Int) -> Double): Buffer { - val array = DoubleArray(capacity, initializer) - val buffer = DoubleBuffer.wrap(array) - return BufferOfDoubles(buffer) - } - - private class BufferOfDoubles(val buffer: DoubleBuffer): Buffer{ - override fun get(index: Int): Double = buffer.get(index) - - override fun set(index: Int, value: Double) { - buffer.put(index, value) - } - - override fun copy(): Buffer { - return BufferOfDoubles(buffer) - } - } -} - -actual val realNDFieldFactory: NDFieldFactory = { shape -> RealNDField(shape) } \ No newline at end of file