From bb6401299255e48bca08a9c107d46a0873d5bbfc Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 17 Dec 2018 12:32:47 +0300 Subject: [PATCH 01/70] Complex extensions --- doc/contexts.md | 3 +++ .../kmath/linear/LUDecomposition.kt | 1 - .../scientifik/kmath/operations/Complex.kt | 19 ++++++++++++++++--- .../scientifik/kmath/structures/Buffers.kt | 6 ++---- .../kmath/structures/NDStructure.kt | 3 +++ 5 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 doc/contexts.md diff --git a/doc/contexts.md b/doc/contexts.md new file mode 100644 index 000000000..df0ea4e1d --- /dev/null +++ b/doc/contexts.md @@ -0,0 +1,3 @@ +# Context-oriented programming + +One of problems \ No newline at end of file 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 223e63419..6a94042b7 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -198,7 +198,6 @@ class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: /** Specialized solver. */ object RealLUSolver : LinearSolver { - fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 8d3c01d28..46388edaf 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -6,12 +6,14 @@ package scientifik.kmath.operations object ComplexField : Field { override val zero: Complex = Complex(0.0, 0.0) + override val one: Complex = Complex(1.0, 0.0) + + val i = Complex(0.0, 1.0) + override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im) override fun multiply(a: Complex, k: Double): Complex = Complex(a.re * k, a.im * k) - override val one: Complex = Complex(1.0, 0.0) - override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) override fun divide(a: Complex, b: Complex): Complex = Complex(a.re * b.re + a.im * b.im, a.re * b.im - a.im * b.re) / b.square @@ -41,5 +43,16 @@ data class Complex(val re: Double, val im: Double) : FieldElement(private val array: Array) : MutableBuffer { } inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { - override val size: Int - get() = array.size + override val size: Int get() = array.size override fun get(index: Int): Double = array[index] @@ -83,8 +82,7 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { - override val size: Int - get() = array.size + override val size: Int get() = array.size override fun get(index: Int): Int = array[index] diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index f9521cf93..6e159c9dd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -49,6 +49,9 @@ interface Strides { */ fun index(offset: Int): IntArray + /** + * The size of linear buffer to accommodate all elements of ND-structure corresponding to strides + */ val linearSize: Int /** From 280bc9e507a9c93e898372ccd7dab57e6115c4f8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 17 Dec 2018 12:44:03 +0300 Subject: [PATCH 02/70] Cached strides creation --- .../kmath/linear/LUDecomposition.kt | 4 +- .../kmath/structures/ExtendedNDField.kt | 2 +- .../scientifik/kmath/structures/NDField.kt | 7 +-- .../kmath/structures/NDStructure.kt | 50 ++++++++++++------- 4 files changed, 37 insertions(+), 26 deletions(-) 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 6a94042b7..3a697cadd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -4,8 +4,8 @@ import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.structures.MutableNDStructure import scientifik.kmath.structures.NDStructure -import scientifik.kmath.structures.genericNdStructure import scientifik.kmath.structures.get +import scientifik.kmath.structures.mutableNdStructure import kotlin.math.absoluteValue /** @@ -107,7 +107,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr val m = matrix.columns val pivot = IntArray(matrix.rows) //TODO fix performance - val lu: MutableNDStructure = genericNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } + val lu: MutableNDStructure = mutableNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } with(matrix.context.field) { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 93ffad5a3..ccdd35e5b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -15,7 +15,7 @@ class ExtendedNDField>(shape: IntArray, field: F) ExponentialOperations> { override fun produceStructure(initializer: F.(IntArray) -> N): NDStructure { - return genericNdStructure(shape) { field.initializer(it) } + return ndStructure(shape) { field.initializer(it) } } override fun power(arg: NDElement, pow: Double): NDElement { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index cddac5801..1757e7d71 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -167,7 +167,7 @@ operator fun > NDElement.div(arg: T): NDElement = tr } class GenericNDField>(shape: IntArray, field: F) : NDField(shape, field) { - override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = genericNdStructure(shape) { field.initializer(it) } + override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = ndStructure(shape) { field.initializer(it) } } //typealias NDFieldFactory = (IntArray)->NDField @@ -195,11 +195,6 @@ object NDArrays { inline fun produceReal(shape: IntArray, block: ExtendedNDField.() -> NDElement) = ExtendedNDField(shape, DoubleField).run(block) -// /** -// * Simple boxing NDField -// */ -// fun fieldFactory(field: Field): NDFieldFactory = { shape -> GenericNDField(shape, field) } - /** * Simple boxing NDArray */ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 6e159c9dd..f722cbfaa 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -63,7 +63,7 @@ interface Strides { } } -class DefaultStrides(override val shape: IntArray) : Strides { +class DefaultStrides private constructor(override val shape: IntArray) : Strides { /** * Strides for memory access */ @@ -101,6 +101,15 @@ class DefaultStrides(override val shape: IntArray) : Strides { override val linearSize: Int get() = strides[shape.size] + + companion object { + private val defaultStridesCache = HashMap() + + /** + * Cached builder for default strides + */ + operator fun invoke(shape: IntArray): Strides = defaultStridesCache.getOrPut(shape) { DefaultStrides(shape) } + } } abstract class GenericNDStructure> : NDStructure { @@ -112,7 +121,7 @@ abstract class GenericNDStructure> : NDStructure { override val shape: IntArray get() = strides.shape - override fun elements()= + override fun elements() = strides.indices().map { it to this[it] } } @@ -131,13 +140,20 @@ class BufferNDStructure( } } +/** + * Create a most suitable nd-structure avoiding boxing if possible using given strides. + * + * Strides should be reused if possible + */ inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = BufferNDStructure(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) }) +/** + * Create a most suitable nd-structure avoiding boxing if possible using default strides with given shape + */ inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = ndStructure(DefaultStrides(shape), initializer) - /** * Mutable ND buffer based on linear [Buffer] */ @@ -156,7 +172,7 @@ class MutableBufferNDStructure( } /** - * Create optimized mutable structure for given type + * The same as [ndStructure], but mutable */ inline fun mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) @@ -164,16 +180,16 @@ inline fun mutableNdStructure(strides: Strides, noinline initi 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 = MutableListBuffer(sequence.toMutableList()) - return MutableBufferNDStructure(strides, buffer) -} +///** +// * 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 = MutableListBuffer(sequence.toMutableList()) +// return MutableBufferNDStructure(strides, buffer) +//} From deed7f597c5795e1e5c3b41904d184c3fb50ad45 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 17 Dec 2018 12:53:20 +0300 Subject: [PATCH 03/70] Matrix doe product moved out of context --- .../kotlin/scientifik/kmath/linear/Matrix.kt | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 243eb2d6c..905263534 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -1,5 +1,6 @@ package scientifik.kmath.linear +import scientifik.kmath.histogram.Point import scientifik.kmath.operations.* import scientifik.kmath.structures.* @@ -37,19 +38,6 @@ abstract class MatrixSpace>(val rows: Int, val columns: Int return produce { i, j -> with(field) { a[i, j] * k } } } - /** - * Dot product. Throws exception on dimension mismatch - */ - fun multiply(a: Matrix, b: Matrix): Matrix { - 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 @@ -69,8 +57,6 @@ abstract class MatrixSpace>(val rows: Int, val columns: Int } } -infix fun > Matrix.dot(b: Matrix): Matrix = this.context.multiply(this, b) - /** * A matrix-like structure */ @@ -138,6 +124,31 @@ interface Matrix> : SpaceElement, MatrixSpace> Matrix.dot(b: Matrix): Matrix { + if (columns != b.rows) { + //TODO replace by specific exception + error("Dimension mismatch in linear structure dot product: [$rows,$columns]*[${b.rows},${b.columns}]") + } + return context.produceSpace(rows, b.columns).produce { i, j -> + (0 until columns).asSequence().map { k -> context.field.multiply(this[i, k], b[k, j]) }.reduce { first, second -> context.field.add(first, second) } + } +} + +/** + * Matrix x Vector dot product. + */ +infix fun > Matrix.dot(b: Point): Matrix { + if (columns != b.size) { + //TODO replace by specific exception + error("Dimension mismatch in linear structure dot product: [$rows,$columns]*[${b.size},1]") + } + return context.produceSpace(rows, 1).produce { i, j -> + (0 until columns).asSequence().map { k -> context.field.multiply(this[i, k], b[k]) }.reduce { first, second -> context.field.add(first, second) } + } +} From 88c20843a4cca1e0ff02f565392aff615b5766a8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 24 Dec 2018 15:19:35 +0300 Subject: [PATCH 04/70] Matrix and linear algebra redone --- build.gradle.kts | 10 +- .../kmath/histogram/FastHistogram.kt | 4 +- .../kmath/linear/LUDecomposition.kt | 59 ++-- .../scientifik/kmath/linear/LinearAlgrebra.kt | 33 ++- .../kotlin/scientifik/kmath/linear/Matrix.kt | 272 ++++++++---------- .../kotlin/scientifik/kmath/linear/Vector.kt | 97 +++---- .../scientifik/kmath/operations/Algebra.kt | 7 +- .../scientifik/kmath/operations/Fields.kt | 10 +- .../scientifik/kmath/structures/Buffers.kt | 48 +++- .../kmath/structures/ExtendedNDField.kt | 32 +-- .../scientifik/kmath/structures/NDField.kt | 39 +-- .../kmath/structures/NDStructure.kt | 64 ++++- .../kmath/linear/ArrayMatrixTest.kt | 34 --- .../scientifik/kmath/linear/MatrixTest.kt | 46 +++ .../kmath/linear/RealLUSolverTest.kt | 7 +- .../kmath/operations/RealFieldTest.kt | 1 - .../kmath/structures/NumberNDFieldTest.kt | 11 +- 17 files changed, 418 insertions(+), 356 deletions(-) delete mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5b95be71a..409bbe606 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,14 @@ buildscript { - extra["kotlinVersion"] = "1.3.11" + extra["kotlinVersion"] = "1.3.20-eap-52" extra["ioVersion"] = "0.1.2-dev-2" - extra["coroutinesVersion"] = "1.0.1" + extra["coroutinesVersion"] = "1.1.0" val kotlinVersion: String by extra val ioVersion: String by extra val coroutinesVersion: String by extra repositories { + maven ("https://dl.bintray.com/kotlin/kotlin-eap") jcenter() } @@ -28,6 +29,11 @@ allprojects { group = "scientifik" version = "0.0.2-dev-1" + + repositories{ + maven ("https://dl.bintray.com/kotlin/kotlin-eap") + jcenter() + } } if(file("artifactory.gradle").exists()){ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt index e48979430..705a8e7ca 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -20,7 +20,7 @@ class FastHistogram( private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 }) - private val values: NDStructure = ndStructure(strides) { LongCounter() } + private val values: NDStructure = inlineNDStructure(strides) { LongCounter() } //private val weight: NDStructure = ndStructure(strides){null} @@ -93,7 +93,7 @@ class FastHistogram( * Convert this histogram into NDStructure containing bin values but not bin descriptions */ fun asNDStructure(): NDStructure { - return ndStructure(this.values.shape) { values[it].sum() } + return inlineNdStructure(this.values.shape) { values[it].sum() } } /** 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 3a697cadd..3f14787f2 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -2,10 +2,7 @@ package scientifik.kmath.linear import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field -import scientifik.kmath.structures.MutableNDStructure -import scientifik.kmath.structures.NDStructure -import scientifik.kmath.structures.get -import scientifik.kmath.structures.mutableNdStructure +import scientifik.kmath.structures.* import kotlin.math.absoluteValue /** @@ -13,7 +10,7 @@ import kotlin.math.absoluteValue */ abstract class LUDecomposition, F : Field>(val matrix: Matrix) { - private val field get() = matrix.context.field + private val field get() = matrix.context.ring /** Entries of LU decomposition. */ internal val lu: NDStructure /** Pivot permutation associated with LU decomposition. */ @@ -34,11 +31,11 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * @return the L matrix (or null if decomposed matrix is singular) */ val l: Matrix by lazy { - matrix.context.produce { i, j -> + matrix.context.produce(matrix.numRows, matrix.numCols) { i, j -> when { j < i -> lu[i, j] - j == i -> matrix.context.field.one - else -> matrix.context.field.zero + j == i -> matrix.context.ring.one + else -> matrix.context.ring.zero } } } @@ -51,7 +48,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * @return the U matrix (or null if decomposed matrix is singular) */ val u: Matrix by lazy { - matrix.context.produce { i, j -> + matrix.context.produce(matrix.numRows, matrix.numCols) { i, j -> if (j >= i) lu[i, j] else field.zero } } @@ -67,7 +64,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * @see .getPivot */ val p: Matrix by lazy { - matrix.context.produce { i, j -> + matrix.context.produce(matrix.numRows, matrix.numCols) { i, j -> //TODO ineffective. Need sparse matrix for that if (j == pivot[i]) field.one else field.zero } @@ -79,9 +76,9 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr */ val determinant: T get() { - with(matrix.context.field) { + with(matrix.context.ring) { var determinant = if (even) one else -one - for (i in 0 until matrix.rows) { + for (i in 0 until matrix.numRows) { determinant *= lu[i, i] } return determinant @@ -97,20 +94,20 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr 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.ring.zero) value else with(matrix.context.ring) { -value } private fun calculateLU(): Pair, IntArray> { - if (matrix.rows != matrix.columns) { + if (matrix.numRows != matrix.numCols) { error("LU decomposition supports only square matrices") } - val m = matrix.columns - val pivot = IntArray(matrix.rows) + val m = matrix.numCols + val pivot = IntArray(matrix.numRows) //TODO fix performance - val lu: MutableNDStructure = mutableNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } + val lu: MutableNDStructure = MutableNdStructure(intArrayOf(matrix.numRows, matrix.numCols), ::boxingMutableBuffer) { index: IntArray -> matrix[index[0], index[1]] } - with(matrix.context.field) { + with(matrix.context.ring) { // Initialize permutation array and parity for (row in 0 until m) { pivot[row] = row @@ -201,50 +198,50 @@ object RealLUSolver : LinearSolver { fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix { - val decomposition = decompose(a, a.context.field.zero) + val decomposition = decompose(a, a.context.ring.zero) - if (b.rows != a.rows) { - error("Matrix dimension mismatch expected ${a.rows}, but got ${b.rows}") + if (b.numRows != a.numCols) { + error("Matrix dimension mismatch expected ${a.numRows}, but got ${b.numCols}") } // Apply permutations to b - val bp = Array(a.rows) { DoubleArray(b.columns) } - for (row in 0 until a.rows) { + val bp = Array(a.numRows) { DoubleArray(b.numCols) } + for (row in 0 until a.numRows) { val bpRow = bp[row] val pRow = decomposition.pivot[row] - for (col in 0 until b.columns) { + for (col in 0 until b.numCols) { bpRow[col] = b[pRow, col] } } // Solve LY = b - for (col in 0 until a.rows) { + for (col in 0 until a.numRows) { val bpCol = bp[col] - for (i in col + 1 until a.rows) { + for (i in col + 1 until a.numRows) { val bpI = bp[i] val luICol = decomposition.lu[i, col] - for (j in 0 until b.columns) { + for (j in 0 until b.numCols) { bpI[j] -= bpCol[j] * luICol } } } // Solve UX = Y - for (col in a.rows - 1 downTo 0) { + for (col in a.numRows - 1 downTo 0) { val bpCol = bp[col] val luDiag = decomposition.lu[col, col] - for (j in 0 until b.columns) { + for (j in 0 until b.numCols) { bpCol[j] /= luDiag } for (i in 0 until col) { val bpI = bp[i] val luICol = decomposition.lu[i, col] - for (j in 0 until b.columns) { + for (j in 0 until b.numCols) { bpI[j] -= bpCol[j] * luICol } } } - return a.context.produce { i, j -> bp[i][j] } + return a.context.produce(a.numRows, a.numCols) { i, j -> bp[i][j] } } } 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 acd27dbd9..c2e54bf3b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -3,6 +3,9 @@ package scientifik.kmath.linear import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.asSequence +import scientifik.kmath.structures.boxingBuffer /** @@ -11,23 +14,22 @@ import scientifik.kmath.operations.Norm interface LinearSolver> { fun solve(a: Matrix, b: Matrix): Matrix fun solve(a: Matrix, b: Vector): Vector = solve(a, b.toMatrix()).toVector() - fun inverse(a: Matrix): Matrix = solve(a, Matrix.diagonal(a.rows, a.columns, a.context.field)) + fun inverse(a: Matrix): Matrix = solve(a, a.context.one) } /** * Convert vector to array (copying content of array) */ -fun Array.toVector(field: Field) = Vector.of(size, field) { this[it] } +fun Array.toVector(field: Field) = Vector.generic(size, field) { this[it] } -fun DoubleArray.toVector() = Vector.ofReal(this.size) { this[it] } -fun List.toVector() = Vector.ofReal(this.size) { this[it] } +fun DoubleArray.toVector() = Vector.real(this.size) { this[it] } +fun List.toVector() = Vector.real(this.size) { this[it] } /** * Convert matrix to vector if it is possible */ -fun > Matrix.toVector(): Vector { - return when { - this.columns == 1 -> { +fun > Matrix.toVector(): Vector { + return if (this.numCols == 1) { // if (this is ArrayMatrix) { // //Reuse existing underlying array // ArrayVector(ArrayVectorSpace(rows, context.field, context.ndFactory), array) @@ -35,26 +37,27 @@ fun > Matrix.toVector(): Vector { // //Generic vector // vector(rows, context.field) { get(it, 0) } // } - Vector.of(rows, context.field) { get(it, 0) } - } - else -> error("Can't convert matrix with more than one column to vector") - } + Vector.generic(numRows, context.ring) { get(it, 0) } + } else error("Can't convert matrix with more than one column to vector") } -fun > Vector.toMatrix(): Matrix { +fun > Vector.toMatrix(): Matrix { +// val context = StructureMatrixContext(size, 1, context.space) +// // return if (this is ArrayVector) { // //Reuse existing underlying array -// ArrayMatrix(ArrayMatrixSpace(size, 1, context.field, context.ndFactory), array) +// StructureMatrix(context,this.buffer) // } else { // //Generic vector // matrix(size, 1, context.field) { i, j -> get(i) } // } - return Matrix.of(size, 1, context.space) { i, _ -> get(i) } + //return Matrix.of(size, 1, context.space) { i, _ -> get(i) } + return StructureMatrixSpace(size, 1, context.space, ::boxingBuffer).produce { i, _ -> get(i) } } object VectorL2Norm : Norm, Double> { override fun norm(arg: Vector): Double { - return kotlin.math.sqrt(arg.sumByDouble { it.toDouble() }) + return kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 905263534..13a54cb5d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -1,198 +1,156 @@ package scientifik.kmath.linear import scientifik.kmath.histogram.Point -import scientifik.kmath.operations.* +import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space +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>(val rows: Int, val columns: Int, val field: F) : Space> { + +interface MatrixSpace> : Space> { + /** + * The ring context for matrix elements + */ + val ring: R + + val rowNum: Int + val colNum: Int + + val shape get() = intArrayOf(rowNum, colNum) /** - * Produce the element of this space + * Produce a matrix with this context and given dimensions */ - abstract fun produce(initializer: (Int, Int) -> T): Matrix + fun produce(rows: Int = rowNum, columns: Int = colNum, initializer: (i: Int, j: Int) -> T): Matrix /** - * Produce new matrix space with given dimensions. The space produced could be raised from cache since [MatrixSpace] does not have mutable elements + * Produce a point compatible with matrix space */ - abstract fun produceSpace(rows: Int, columns: Int): MatrixSpace + fun point(size: Int, initializer: (Int) -> T): Point - override val zero: Matrix by lazy { - produce { _, _ -> field.zero } - } + override val zero: Matrix get() = produce { _, _ -> ring.zero } -// val one: Matrix by lazy { -// produce { i, j -> if (i == j) field.one else field.zero } -// } + val one get() = produce { i, j -> if (i == j) ring.one else ring.zero } - override fun add(a: Matrix, b: Matrix): Matrix { - return produce { i, j -> with(field) { a[i, j] + b[i, j] } } - } + override fun add(a: Matrix, b: Matrix): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } - override fun multiply(a: Matrix, k: Double): Matrix { - //TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser - return produce { i, j -> with(field) { a[i, j] * k } } - } - - override 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 - } -} - -/** - * A matrix-like structure - */ -interface Matrix> : SpaceElement, MatrixSpace> { - /** - * 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 - get() = this - - fun transpose(): Matrix { - return object : Matrix { - override val context: MatrixSpace = 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] - } - } + override fun multiply(a: Matrix, k: Double): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } companion object { + /** + * Non-boxing double matrix + */ + fun real(rows: Int, columns: Int): MatrixSpace = StructureMatrixSpace(rows, columns, DoubleField, DoubleBufferFactory) /** - * Create [ArrayMatrix] with custom field + * A structured matrix with custom buffer */ - fun > of(rows: Int, columns: Int, field: F, initializer: (Int, Int) -> T) = - ArrayMatrix(ArrayMatrixSpace(rows, columns, field), initializer) + fun > buffered(rows: Int, columns: Int, ring: R, bufferFactory: BufferFactory = ::boxingBuffer): MatrixSpace = StructureMatrixSpace(rows, columns, ring, bufferFactory) /** - * Create [ArrayMatrix] of doubles. The implementation in general should be faster than generic one due to boxing. + * Automatic buffered matrix, unboxed if it is possible */ - fun ofReal(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = - ArrayMatrix(ArrayMatrixSpace(rows, columns, DoubleField, realNDFieldFactory), initializer) + inline fun > smart(rows: Int, columns: Int, ring: R): MatrixSpace = buffered(rows, columns, ring, ::inlineBuffer) + } +} - /** - * Create a diagonal value matrix. By default value equals [Field.one]. - */ - fun > diagonal(rows: Int, columns: Int, field: F, values: (Int) -> T = { field.one }): Matrix { - return of(rows, columns, field) { i, j -> if (i == j) values(i) else field.zero } + +/** + * Specialized 2-d structure + */ +interface Matrix> : NDStructure, SpaceElement, MatrixSpace> { + operator fun get(i: Int, j: Int): T + + override fun get(index: IntArray): T = get(index[0], index[1]) + + override val shape: IntArray get() = context.shape + + val numRows get() = context.rowNum + val numCols get() = context.colNum + + //TODO replace by lazy buffers + val rows: List> + get() = (0 until numRows).map { i -> + context.point(numCols) { j -> get(i, j) } } - /** - * 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 + val columns: List> + get() = (0 until numCols).map { j -> + context.point(numRows) { i -> get(i, j) } + } + + companion object { + fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = + MatrixSpace.real(rows, columns).produce(rows, columns, initializer) + } +} + +infix fun > Matrix.dot(other: Matrix): Matrix { + //TODO add typed error + if (this.numCols != other.numRows) error("Matrix dot operation dimension mismatch: ($numRows, $numCols) x (${other.numRows}, ${other.numCols})") + return context.produce(numRows, other.numCols) { i, j -> + val row = rows[i] + val column = other.columns[j] + with(context.ring) { + row.asSequence().zip(column.asSequence(), ::multiply).sum() } } } -/** - * Dot product. Throws exception on dimension mismatch - */ -infix fun > Matrix.dot(b: Matrix): Matrix { - if (columns != b.rows) { - //TODO replace by specific exception - error("Dimension mismatch in linear structure dot product: [$rows,$columns]*[${b.rows},${b.columns}]") - } - return context.produceSpace(rows, b.columns).produce { i, j -> - (0 until columns).asSequence().map { k -> context.field.multiply(this[i, k], b[k, j]) }.reduce { first, second -> context.field.add(first, second) } +infix fun > Matrix.dot(vector: Point): Point { + //TODO add typed error + if (this.numCols != vector.size) error("Matrix dot vector operation dimension mismatch: ($numRows, $numCols) x (${vector.size})") + return context.point(numRows) { i -> + val row = rows[i] + with(context.ring) { + row.asSequence().zip(vector.asSequence(), ::multiply).sum() + } } } -/** - * Matrix x Vector dot product. - */ -infix fun > Matrix.dot(b: Point): Matrix { - if (columns != b.size) { - //TODO replace by specific exception - error("Dimension mismatch in linear structure dot product: [$rows,$columns]*[${b.size},1]") - } - return context.produceSpace(rows, 1).produce { i, j -> - (0 until columns).asSequence().map { k -> context.field.multiply(this[i, k], b[k]) }.reduce { first, second -> context.field.add(first, second) } +data class StructureMatrixSpace>( + override val rowNum: Int, + override val colNum: Int, + override val ring: R, + private val bufferFactory: BufferFactory +) : MatrixSpace { + + override val shape: IntArray = intArrayOf(rowNum, colNum) + + private val strides = DefaultStrides(shape) + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { + return if (rows == rowNum && columns == colNum) { + val structure = NdStructure(strides, bufferFactory) { initializer(it[0], it[1]) } + StructureMatrix(this, structure) + } else { + val context = StructureMatrixSpace(rows, columns, ring, bufferFactory) + val structure = NdStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) } + StructureMatrix(context, structure) + } } + + override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) } - - -typealias NDFieldFactory = (IntArray) -> NDField - -internal fun > genericNDFieldFactory(field: F): NDFieldFactory = { index -> GenericNDField(index, field) } -internal val realNDFieldFactory: NDFieldFactory = { 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>( - rows: Int, - columns: Int, - field: F, - val ndFactory: NDFieldFactory = genericNDFieldFactory(field) -) : MatrixSpace(rows, columns, field) { - - val ndField by lazy { - ndFactory(intArrayOf(rows, columns)) +data class StructureMatrix>(override val context: StructureMatrixSpace, val structure: NDStructure) : Matrix { + init { + if (structure.shape.size != 2 || structure.shape[0] != context.rowNum || structure.shape[1] != context.colNum) { + error("Dimension mismatch for structure, (${context.rowNum}, ${context.colNum}) expected, but ${structure.shape} found") + } } - override fun produce(initializer: (Int, Int) -> T): Matrix = ArrayMatrix(this, initializer) + override val shape: IntArray get() = structure.shape + override val self: Matrix get() = this - override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace { - return ArrayMatrixSpace(rows, columns, field, ndFactory) - } + override fun get(index: IntArray): T = structure[index] + + override fun get(i: Int, j: Int): T = structure[i, j] + + override fun elements(): Sequence> = structure.elements() } -/** - * Member of [ArrayMatrixSpace] which wraps 2-D array - */ -class ArrayMatrix> internal constructor(override val context: ArrayMatrixSpace, val element: NDElement) : Matrix { - - constructor(context: ArrayMatrixSpace, 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 get() = this -} +//TODO produce transposed matrix via reference without creating new space and structure +fun > Matrix.transpose(): Matrix = + context.produce(numCols, numRows) { i, j -> get(j, i) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 5ab9907de..756f85959 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -2,32 +2,58 @@ 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 +import scientifik.kmath.structures.* /** * A linear space for vectors. * Could be used on any point-like structure */ -abstract class VectorSpace>(val size: Int, val space: S) : Space> { +interface VectorSpace> : Space> { - abstract fun produce(initializer: (Int) -> T): Vector + val size: Int - override val zero: Vector by lazy { produce { space.zero } } + val space: S + + fun produce(initializer: (Int) -> T): Vector + + override val zero: Vector get() = produce { space.zero } override fun add(a: Point, b: Point): Vector = produce { with(space) { a[it] + b[it] } } override fun multiply(a: Point, k: Double): Vector = produce { with(space) { a[it] * k } } + + //TODO add basis + + companion object { + + private val realSpaceCache = HashMap>() + + /** + * Non-boxing double vector space + */ + fun real(size: Int): BufferVectorSpace { + return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, DoubleField, DoubleBufferFactory) } + } + + /** + * A structured vector space with custom buffer + */ + fun > buffered(size: Int, space: S, bufferFactory: BufferFactory = ::boxingBuffer): VectorSpace = BufferVectorSpace(size, space, bufferFactory) + + /** + * Automatic buffered vector, unboxed if it is possible + */ + inline fun > smart(size: Int, space: S): VectorSpace = buffered(size, space, ::inlineBuffer) + } } /** * A point coupled to the linear space */ -interface Vector> : SpaceElement, VectorSpace>, Point, Iterable { +interface Vector> : SpaceElement, VectorSpace>, Point { override val size: Int get() = context.size override operator fun plus(b: Point): Vector = context.add(self, b) @@ -39,65 +65,40 @@ interface Vector> : SpaceElement, VectorSpace> of(size: Int, field: F, initializer: (Int) -> T) = - ArrayVector(ArrayVectorSpace(size, field), initializer) + fun > generic(size: Int, field: F, initializer: (Int) -> T) = + VectorSpace.buffered(size, field).produce(initializer) - private val realSpaceCache = HashMap>() + fun real(size: Int, initializer: (Int) -> Double) = VectorSpace.real(size).produce(initializer) + fun ofReal(vararg elements: Double) = VectorSpace.real(elements.size).produce{elements[it]} - private fun getRealSpace(size: Int): ArrayVectorSpace { - 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>( - size: Int, - field: F, - val ndFactory: NDFieldFactory = genericNDFieldFactory(field) -) : VectorSpace(size, field) { - val ndField by lazy { - ndFactory(intArrayOf(size)) - } - - override fun produce(initializer: (Int) -> T): Vector = ArrayVector(this, initializer) +data class BufferVectorSpace>( + override val size: Int, + override val space: S, + val bufferFactory: BufferFactory +) : VectorSpace { + override fun produce(initializer: (Int) -> T): Vector = BufferVector(this, bufferFactory(size, initializer)) } -class ArrayVector> internal constructor(override val context: VectorSpace, val element: NDElement) : Vector { - - constructor(context: ArrayVectorSpace, initializer: (Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0]) }) +data class BufferVector>(override val context: VectorSpace, val buffer: Buffer) : Vector { init { - if (context.size != element.shape[0]) { + if (context.size != buffer.size) { error("Array dimension mismatch") } } override fun get(index: Int): T { - return element[index] + return buffer[index] } - override val self: ArrayVector get() = this + override val self: BufferVector get() = this - override fun iterator(): Iterator = (0 until size).map { element[it] }.iterator() + override fun iterator(): Iterator = (0 until size).map { buffer[it] }.iterator() - override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } + override fun toString(): String = this.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 9a56a4e91..51f3e75b3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -51,6 +51,9 @@ interface Space { operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun Number.times(b: T) = b * this + //TODO move to external extensions when they are available + fun Iterable.sum(): T = fold(zero) { left, right -> left + right } + fun Sequence.sum(): T = fold(zero) { left, right -> left + right } } /** @@ -111,8 +114,8 @@ interface Field : Ring { /** * Field element */ -interface FieldElement> : RingElement { - override val context: S +interface FieldElement> : RingElement { + override val context: F operator fun div(b: T): T = context.divide(self, b) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt index d993d37b4..eabae8ea4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt @@ -5,11 +5,11 @@ import kotlin.math.pow /** * Advanced Number-like field that implements basic operations */ -interface ExtendedField : - Field, - TrigonometricOperations, - PowerOperations, - ExponentialOperations +interface ExtendedField : + Field, + TrigonometricOperations, + PowerOperations, + ExponentialOperations /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 8bbb87057..442047dca 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -11,10 +11,14 @@ interface Buffer { operator fun get(index: Int): T operator fun iterator(): Iterator + + fun contentEquals(other: Buffer<*>): Boolean = asSequence().mapIndexed { index, value -> value == other[index] }.all { it } } fun Buffer.asSequence(): Sequence = iterator().asSequence() +fun Buffer.asIterable(): Iterable = iterator().asSequence().asIterable() + interface MutableBuffer : Buffer { operator fun set(index: Int, value: T) @@ -95,6 +99,20 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer { override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) } +inline class LongBuffer(private val array: LongArray) : MutableBuffer { + override val size: Int get() = array.size + + override fun get(index: Int): Long = array[index] + + override fun set(index: Int, value: Long) { + array[index] = value + } + + override fun iterator(): Iterator = array.iterator() + + override fun copy(): MutableBuffer = LongBuffer(array.copyOf()) +} + inline class ReadOnlyBuffer(private val buffer: MutableBuffer) : Buffer { override val size: Int get() = buffer.size @@ -112,26 +130,46 @@ fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { this } +/** + * Create a boxing buffer of given type + */ +fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) + /** * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun buffer(size: Int, noinline initializer: (Int) -> T): Buffer { +inline fun inlineBuffer(size: Int, noinline initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer - else -> ArrayBuffer(Array(size, initializer)) + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer + else -> boxingBuffer(size, initializer) } } +/** + * Create a boxing mutable buffer of given type + */ +fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) + /** * Create most appropriate mutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { +inline fun inlineMutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer - else -> ArrayBuffer(Array(size, initializer)) + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer + else -> boxingMutableBuffer(size, initializer) } -} \ No newline at end of file +} + +typealias BufferFactory = (Int, (Int) -> T) -> Buffer +typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer + +val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } +val IntBufferFactory: BufferFactory = { size, initializer -> IntBuffer(IntArray(size, initializer)) } +val LongBufferFactory: BufferFactory = { size, initializer -> LongBuffer(LongArray(size, initializer)) } + diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index ccdd35e5b..915b09419 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -9,33 +9,33 @@ import scientifik.kmath.operations.TrigonometricOperations /** * NDField that supports [ExtendedField] operations on its elements */ -class ExtendedNDField>(shape: IntArray, field: F) : NDField(shape, field), - TrigonometricOperations>, - PowerOperations>, - ExponentialOperations> { +class ExtendedNDField>(shape: IntArray, field: F) : NDField(shape, field), + TrigonometricOperations>, + PowerOperations>, + ExponentialOperations> { - override fun produceStructure(initializer: F.(IntArray) -> N): NDStructure { - return ndStructure(shape) { field.initializer(it) } + override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure { + return NdStructure(shape, ::boxingBuffer) { field.initializer(it) } } - override fun power(arg: NDElement, pow: Double): NDElement { - return arg.transform { d -> with(field) { power(d, pow) } } + override fun power(arg: NDStructure, pow: Double): NDElement { + return produce { with(field) { power(arg[it], pow) } } } - override fun exp(arg: NDElement): NDElement { - return arg.transform { d -> with(field) { exp(d) } } + override fun exp(arg: NDStructure): NDElement { + return produce { with(field) { exp(arg[it]) } } } - override fun ln(arg: NDElement): NDElement { - return arg.transform { d -> with(field) { ln(d) } } + override fun ln(arg: NDStructure): NDElement { + return produce { with(field) { ln(arg[it]) } } } - override fun sin(arg: NDElement): NDElement { - return arg.transform { d -> with(field) { sin(d) } } + override fun sin(arg: NDStructure): NDElement { + return produce { with(field) { sin(arg[it]) } } } - override fun cos(arg: NDElement): NDElement { - return arg.transform { d -> with(field) { cos(d) } } + override fun cos(arg: NDStructure): NDElement { + return produce { with(field) { cos(arg[it]) } } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 1757e7d71..c2d474f9b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -15,7 +15,7 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run * @param field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ -abstract class NDField>(val shape: IntArray, val field: F) : Field> { +abstract class NDField>(val shape: IntArray, val field: F) : Field> { abstract fun produceStructure(initializer: F.(IntArray) -> T): NDStructure @@ -29,10 +29,14 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie produce { zero } } + override val one: NDElement by lazy { + produce { one } + } + /** * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field */ - private fun checkShape(vararg elements: NDElement) { + private fun checkShape(vararg elements: NDStructure) { elements.forEach { if (!shape.contentEquals(it.shape)) { throw ShapeMismatchException(shape, it.shape) @@ -43,7 +47,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie /** * Element-by-element addition */ - override fun add(a: NDElement, b: NDElement): NDElement { + override fun add(a: NDStructure, b: NDStructure): NDElement { checkShape(a, b) return produce { with(field) { a[it] + b[it] } } } @@ -51,18 +55,15 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie /** * Multiply all elements by cinstant */ - override fun multiply(a: NDElement, k: Double): NDElement { + override fun multiply(a: NDStructure, k: Double): NDElement { checkShape(a) return produce { with(field) { a[it] * k } } } - override val one: NDElement - get() = produce { one } - /** * Element-by-element multiplication */ - override fun multiply(a: NDElement, b: NDElement): NDElement { + override fun multiply(a: NDStructure, b: NDStructure): NDElement { checkShape(a) return produce { with(field) { a[it] * b[it] } } } @@ -70,7 +71,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie /** * Element-by-element division */ - override fun divide(a: NDElement, b: NDElement): NDElement { + override fun divide(a: NDStructure, b: NDStructure): NDElement { checkShape(a) return produce { with(field) { a[it] / b[it] } } } @@ -105,7 +106,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie } -interface NDElement>: FieldElement, NDField>, NDStructure +interface NDElement> : FieldElement, NDField>, NDStructure inline fun > NDElement.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement = context.produce { action(it, get(*it)) } inline fun > NDElement.transform(crossinline action: F.(T) -> T): NDElement = context.produce { action(get(*it)) } @@ -114,24 +115,24 @@ inline fun > NDElement.transform(crossinline action: F.(T) /** * Read-only [NDStructure] coupled to the context. */ -class NDStructureElement>(override val context: NDField, private val structure: NDStructure) : NDElement, NDStructure by structure { +class NDStructureElement>(override val context: NDField, private val structure: NDStructure) : NDElement, NDStructure by structure { //TODO ensure structure is immutable - override val self: NDElement get() = this + override val self: NDElement get() = this } /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ -operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.transform {value -> this@invoke(value) } +operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.transform { value -> this@invoke(value) } /* plus and minus */ /** * Summation operation for [NDElement] and single element */ -operator fun > NDElement.plus(arg: T): NDElement = transform {value -> +operator fun > NDElement.plus(arg: T): NDElement = transform { value -> with(context.field) { arg + value } @@ -140,7 +141,7 @@ operator fun > NDElement.plus(arg: T): NDElement = t /** * Subtraction operation between [NDElement] and single element */ -operator fun > NDElement.minus(arg: T): NDElement = transform {value -> +operator fun > NDElement.minus(arg: T): NDElement = transform { value -> with(context.field) { arg - value } @@ -167,7 +168,7 @@ operator fun > NDElement.div(arg: T): NDElement = tr } class GenericNDField>(shape: IntArray, field: F) : NDField(shape, field) { - override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = ndStructure(shape) { field.initializer(it) } + override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = NdStructure(shape, ::boxingBuffer) { field.initializer(it) } } //typealias NDFieldFactory = (IntArray)->NDField @@ -192,8 +193,10 @@ object NDArrays { return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } } - inline fun produceReal(shape: IntArray, block: ExtendedNDField.() -> NDElement) = - ExtendedNDField(shape, DoubleField).run(block) + inline fun produceReal(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { + val field = ExtendedNDField(shape, DoubleField) + return NDStructureElement(field, field.run(block)) + } /** * Simple boxing NDArray diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index f722cbfaa..8900d0d9b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -80,7 +80,7 @@ class DefaultStrides private constructor(override val shape: IntArray) : Strides override fun offset(index: IntArray): Int { return index.mapIndexed { i, value -> - if (value < 0 || value >= shape[i]) { + if (value < 0 || value >= this.shape[i]) { throw RuntimeException("Index $value out of shape bounds: (0,${this.shape[i]})") } value * strides[i] @@ -138,24 +138,48 @@ class BufferNDStructure( error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") } } + + override fun equals(other: Any?): Boolean { + return when { + this === other -> true + other is BufferNDStructure<*> -> this.strides == other.strides && this.buffer.contentEquals(other.buffer) + other is NDStructure<*> -> elements().all { (index, value) -> value == other[index] } + else -> false + } + } + + override fun hashCode(): Int { + var result = strides.hashCode() + result = 31 * result + buffer.hashCode() + return result + } + } /** - * Create a most suitable nd-structure avoiding boxing if possible using given strides. + * Create a NDStructure with explicit buffer factory * * Strides should be reused if possible */ -inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, buffer(strides.linearSize) { i -> initializer(strides.index(i)) }) +@Suppress("FunctionName") +fun NdStructure(strides: Strides, bufferFactory: BufferFactory, initializer: (IntArray) -> T) = + BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** - * Create a most suitable nd-structure avoiding boxing if possible using default strides with given shape + * Inline create NDStructure with non-boxing buffer implementation if it is possible */ -inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = - ndStructure(DefaultStrides(shape), initializer) +inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = + BufferNDStructure(strides, inlineBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + +@Suppress("FunctionName") +fun NdStructure(shape: IntArray, bufferFactory: BufferFactory, initializer: (IntArray) -> T) = + NdStructure(DefaultStrides(shape), bufferFactory, initializer) + +inline fun inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = + inlineNDStructure(DefaultStrides(shape), initializer) /** - * Mutable ND buffer based on linear [Buffer] + * Mutable ND buffer based on linear [inlineBuffer] */ class MutableBufferNDStructure( override val strides: Strides, @@ -172,13 +196,27 @@ class MutableBufferNDStructure( } /** - * The same as [ndStructure], but mutable + * The same as [inlineNDStructure], but mutable */ -inline fun mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) +@Suppress("FunctionName") +fun MutableNdStructure(strides: Strides, bufferFactory: MutableBufferFactory, initializer: (IntArray) -> T) = + MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) + +inline fun inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = + MutableBufferNDStructure(strides, inlineMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + +@Suppress("FunctionName") +fun MutableNdStructure(shape: IntArray, bufferFactory: MutableBufferFactory, initializer: (IntArray) -> T) = + MutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) + +inline fun inlineMutableNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = + inlineMutableNdStructure(DefaultStrides(shape), initializer) + +inline fun NDStructure.combine(struct: NDStructure, crossinline block: (T, T) -> T): NDStructure { + if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination") + return inlineNdStructure(shape) { block(this[it], struct[it]) } +} -inline fun mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = - mutableNdStructure(DefaultStrides(shape), initializer) ///** // * Create universal mutable structure diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt deleted file mode 100644 index f767d682b..000000000 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package scientifik.kmath.linear - -import kotlin.test.Test -import kotlin.test.assertEquals - -class ArrayMatrixTest { - - @Test - fun testSum() { - val vector1 = Vector.ofReal(5) { it.toDouble() } - val vector2 = Vector.ofReal(5) { 5 - it.toDouble() } - val sum = vector1 + vector2 - assertEquals(5.0, sum[2]) - } - - @Test - fun testVectorToMatrix() { - val vector = Vector.ofReal(5) { it.toDouble() } - val matrix = vector.toMatrix() - assertEquals(4.0, matrix[4, 0]) - } - - - @Test - fun testDot() { - val vector1 = Vector.ofReal(5) { it.toDouble() } - val vector2 = Vector.ofReal(5) { 5 - it.toDouble() } - val product = vector1.toMatrix() dot (vector2.toMatrix().transpose()) - - - assertEquals(5.0, product[1, 0]) - assertEquals(6.0, product[2, 2]) - } -} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt new file mode 100644 index 000000000..87aad3d1d --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -0,0 +1,46 @@ +package scientifik.kmath.linear + +import kotlin.test.Test +import kotlin.test.assertEquals + +class MatrixTest { + + @Test + fun testSum() { + val vector1 = Vector.real(5) { it.toDouble() } + val vector2 = Vector.real(5) { 5 - it.toDouble() } + val sum = vector1 + vector2 + assertEquals(5.0, sum[2]) + } + + @Test + fun testVectorToMatrix() { + val vector = Vector.real(5) { it.toDouble() } + val matrix = vector.toMatrix() + assertEquals(4.0, matrix[4, 0]) + } + + @Test + fun testTranspose(){ + val matrix = MatrixSpace.real(3,3).one + val transposed = matrix.transpose() + assertEquals(matrix.context, transposed.context) + assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure) + assertEquals(matrix, transposed) + } + + + @Test + fun testDot() { + val vector1 = Vector.real(5) { it.toDouble() } + val vector2 = Vector.real(5) { 5 - it.toDouble() } + + val matrix1 = vector1.toMatrix() + val matrix2 = vector2.toMatrix().transpose() + val product = matrix1 dot matrix2 + + + assertEquals(5.0, product[1, 0]) + assertEquals(6.0, product[2, 2]) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 472d5262f..bd1eb8147 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -1,15 +1,14 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.DoubleField import kotlin.test.Test -import kotlin.test.assertTrue +import kotlin.test.assertEquals class RealLUSolverTest { @Test fun testInvertOne() { - val matrix = Matrix.diagonal(2, 2, DoubleField) + val matrix = MatrixSpace.real(2,2).one val inverted = RealLUSolver.inverse(matrix) - assertTrue { Matrix.equals(matrix,inverted) } + assertEquals(matrix,inverted) } // @Test diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt index 0d33204df..a705fc8f9 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -6,7 +6,6 @@ import kotlin.test.assertEquals class RealFieldTest { @Test fun testSqrt() { - //fails because KT-27586 val sqrt = with(RealField) { sqrt( 25 * one) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 990adb0c7..17546a30d 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -51,9 +51,14 @@ class NumberNDFieldTest { assertEquals(2.0, result[0, 2]) } - object L2Norm : Norm, Double> { - override fun norm(arg: NDElement): Double { - return kotlin.math.sqrt(arg.sumByDouble { it.second.toDouble() }) + @Test + fun combineTest(){ + val division = array1.combine(array2, Double::div) + } + + object L2Norm : Norm, Double> { + override fun norm(arg: NDStructure): Double { + return kotlin.math.sqrt(arg.elements().sumByDouble { it.second.toDouble() }) } } From db37c19ed09590d80df6bfdf574f0ba5dd42e402 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 26 Dec 2018 12:56:43 +0300 Subject: [PATCH 05/70] A general idea of context-oriented approach in doc --- doc/contexts.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/doc/contexts.md b/doc/contexts.md index df0ea4e1d..d4076a7c9 100644 --- a/doc/contexts.md +++ b/doc/contexts.md @@ -1,3 +1,50 @@ -# Context-oriented programming +# Context-oriented mathematics -One of problems \ No newline at end of file +## The problem +A known problem for implementing mathematics in statically-typed languages (and not only in them) is that different +sets of mathematical operation could be defined on the same mathematical objects. Sometimes there is not single way to +treat some operations like basic arithmetic operations on Java/Kotlin `Number`. Sometimes there are different ways to do +the same thing like Euclidean and elliptic geometry vector spaces defined over real vectors. Another problem arises when +one wants to add some kind of behavior to existing entity. In dynamic languages those problems are usually solved +by adding dynamic context-specific behaviors in runtime, but this solution has a lot of drawbacks. + +## Context-oriented approach +One of possible solutions to those problems is to completely separate object numerical representations from behaviors. +In terms of kotlin it means to have separate class to represent some entity without any operations, +for example a complex number: + +```kotlin +data class Complex(val re: Double, val im: Double) +``` +And a separate class or singleton, representing operation on those complex numbers: +```kotlin +object: ComplexOperations{ + operator fun Complex.plus(other: Complex) = Complex(re + other.re, im + other.im) + operator fun Complex.minus(other: Complex) = Complex(re - other.re, im - other.im) +} +``` + +In Java, application of such external operations could be very cumbersome, but Kotlin has a unique feature which allows +to treat this situation: blocks with receivers. So in kotlin, operation on complex number could beimplemented as: +```kotlin +with(ComplexOperations){c1 + c2 - c3} +``` +Kotlin also allows to create functions with receivers: +```kotlin +fun ComplexOperations.doSomethingWithComplex(c1: Complex, c2: Complex, c3: Complex) = c1 + c2 - c3 + +ComplexOperations.doComethingWithComplex(c1,c2,c3) +``` + +In fact, whole parts of proram could run in a mathematical context or even multiple nested contexts. + +In `kmath` contexts are responsible not only for operations, but also for raw object creation and advanced features. + +## Other possibilities + +An obvious candidate to get more or less the same functionality is type-class feature. It allows to bind a behavior to +a specific type without modifying the type itself. On a plus side, type-classes do not require explicit context +declaration, so the code looks cleaner. On the minus side, if there are different sets of behaviors for the same types, +it is impossible to combine them in the single module. Also, unlike type-classes, context could have parameters or even +state. For example in `kmath`, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize +performance in case of large amount of structures. \ No newline at end of file From ad5e14e9a2438649f5b5fa2f0247d3267039253f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 26 Dec 2018 14:35:51 +0300 Subject: [PATCH 06/70] Doc update. Name refactoring in NDField --- doc/contexts.md | 2 +- doc/operations.md | 38 +++++++++++++++++++ .../scientifik/kmath/operations/Complex.kt | 21 +++++----- .../scientifik/kmath/structures/NDField.kt | 18 ++++----- .../kmath/structures/GenericNDFieldTest.kt | 2 +- .../kmath/structures/NumberNDFieldTest.kt | 12 +++--- .../kmath/structures/LazyNDFieldTest.kt | 2 +- 7 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 doc/operations.md diff --git a/doc/contexts.md b/doc/contexts.md index d4076a7c9..b6b6be44c 100644 --- a/doc/contexts.md +++ b/doc/contexts.md @@ -36,7 +36,7 @@ fun ComplexOperations.doSomethingWithComplex(c1: Complex, c2: Complex, c3: Compl ComplexOperations.doComethingWithComplex(c1,c2,c3) ``` -In fact, whole parts of proram could run in a mathematical context or even multiple nested contexts. +In fact, whole parts of program could run in a mathematical context or even multiple nested contexts. In `kmath` contexts are responsible not only for operations, but also for raw object creation and advanced features. diff --git a/doc/operations.md b/doc/operations.md new file mode 100644 index 000000000..99c8eacac --- /dev/null +++ b/doc/operations.md @@ -0,0 +1,38 @@ +## Spaces and fields + +An obvious first choice of mathematical objects to implement in context-oriented style are algebra elements like spaces, +rings and fields. Those are located in a `scientifik.kmath.operations.Algebra.kt` file. Alongside algebric context +themselves, the file includes definitions for algebra elements such as `FieldElement`. A `FieldElement` object +stores a reference to the `Field` which contains a additive and multiplicative operations for it, meaning +it has one fixed context attached to it and does not require explicit external context. So those `MathElements` could be +operated without context: +```kotlin +val c1 = Complex(1.0, 2.0) +val c2 = ComplexField.i +val c3 = c1 + c2 +``` +`ComplexField` also features special operations to mix complex numbers with real numbers like: +```kotlin +val c1 = Complex(1.0,2.0) +val c2 = ComplexField.run{ c1 - 1.0} //returns [re:0.0, im: 2.0] +val c3 = ComplexField.run{ c1 - i*2.0} +``` + +**Note**: In theory it is possible to add behaviors directly to the context, but currently kotlin syntax does not support +that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) for news. + +## Nested fields + +Algebra contexts allow to create more complex structures. For example, it is possible to create a `Matrix` from complex +elements like this: +```kotlin +val element = NDElements.create(field = ComplexField, shape = intArrayOf(2,2)){index: IntArray -> + Complex(index[0] - index[1], index[0] + index[1]) +} +``` +The `element` in this example is a member of `Field` of 2-d structures, each element of which is a member of its own +`ComplexField`. The important thing is that one does not need to create a special nd-structure to hold complex +numbers and implements operations on it, one need just to provide a field for its elements. + +**Note**: Fields themselves do not solve problem of JVM boxing, but it is possible to solve with special contexts like +`BufferSpec`. This feature is in development phase. \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 46388edaf..e8a80a451 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -18,6 +18,15 @@ object ComplexField : Field { override fun divide(a: Complex, b: Complex): Complex = Complex(a.re * b.re + a.im * b.im, a.re * b.im - a.im * b.re) / b.square + operator fun Double.plus(c: Complex) = this.toComplex() + c + + operator fun Double.minus(c: Complex) = this.toComplex() - c + + operator fun Complex.plus(d: Double) = d + this + + operator fun Complex.minus(d: Double) = this - d.toComplex() + + operator fun Double.times(c: Complex) = Complex(c.re * this, c.im * this) } /** @@ -45,14 +54,4 @@ data class Complex(val re: Double, val im: Double) : FieldElement>(shape: IntArray, field: F) : NDField //typealias NDFieldFactory = (IntArray)->NDField -object NDArrays { +object NDElements { /** * Create a platform-optimized NDArray of doubles */ - fun realNDArray(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement { + fun realNDElement(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement { return ExtendedNDField(shape, DoubleField).produce(initializer) } - fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { - return realNDArray(intArrayOf(dim)) { initializer(it[0]) } + fun real1DElement(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { + return realNDElement(intArrayOf(dim)) { initializer(it[0]) } } - fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { - return realNDArray(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + fun real2DElement(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { + return realNDElement(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } } - fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement { - return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } + fun real3DElement(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement { + return realNDElement(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } } - inline fun produceReal(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { + inline fun real(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { val field = ExtendedNDField(shape, DoubleField) return NDStructureElement(field, field.run(block)) } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt index 338f5f052..0bc118e3b 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt @@ -1,7 +1,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.DoubleField -import scientifik.kmath.structures.NDArrays.create +import scientifik.kmath.structures.NDElements.create import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 17546a30d..5d5ee97cf 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,16 +1,16 @@ 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.NDElements.real +import scientifik.kmath.structures.NDElements.real2DElement import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test import kotlin.test.assertEquals class NumberNDFieldTest { - val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() } - val array2 = real2DArray(3, 3) { i, j -> (i - j).toDouble() } + val array1 = real2DElement(3, 3) { i, j -> (i + j).toDouble() } + val array2 = real2DElement(3, 3) { i, j -> (i - j).toDouble() } @Test fun testSum() { @@ -27,7 +27,7 @@ class NumberNDFieldTest { @Test fun testGeneration() { - val array = real2DArray(3, 3) { i, j -> (i * 10 + j).toDouble() } + val array = real2DElement(3, 3) { i, j -> (i * 10 + j).toDouble() } for (i in 0..2) { for (j in 0..2) { @@ -64,7 +64,7 @@ class NumberNDFieldTest { @Test fun testInternalContext() { - produceReal(array1.shape) { + real(array1.shape) { with(L2Norm) { 1 + norm(array1) + exp(array2) } diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index 403fe2d31..6dce9b533 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -9,7 +9,7 @@ class LazyNDFieldTest { @Test fun testLazyStructure() { var counter = 0 - val regularStructure = NDArrays.create(IntField, intArrayOf(2, 2, 2)) { it[0] + it[1] - it[2] } + val regularStructure = NDElements.create(IntField, intArrayOf(2, 2, 2)) { it[0] + it[1] - it[2] } val result = (regularStructure.lazy() + 2).transform { counter++ it * it From 5f567e7716a5ee7a903c1a34bec05e6b011dccd9 Mon Sep 17 00:00:00 2001 From: "breandan.considine" Date: Thu, 27 Dec 2018 21:59:47 -0500 Subject: [PATCH 07/70] add gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55741 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 +++++++++++++++++++++++ gradlew.bat | 84 +++++++++++ 4 files changed, 261 insertions(+) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..457aad0d98108420a977756b7145c93c8910b076 GIT binary patch literal 55741 zcmafab95)swq`n=q+{FmFHU~x*tTukPP${;?%1|%+qP{@&)oNB=H9vQ&04jq>W_Wa zIlI<5`};OZPVze#DhLQ9BnSuy|6c(C0sUWh5D=)pPibK#et@`)2>o{uxqnjRs=Jxt z{;Qz$SN;zFKZ?@)GU6h_ib{0SB6rf`V^Wd;x*0f00QKbfRGk9DJoEO!?Kogqd_sDH zMx6E=^l6Y$(tf@MRWk-z;eNisaBoAJU;VAaw||-L?+pKYU0{FTZ5>SipC$d@IxzpC zW9p!9WM%x{s-pa}s;h&(ot?46|1+d?#bo(AI0(q;-_HX0_d^71ZJivQ{*IT{H@8uA z(syt&cAzt~(sy)>RMAvLQAPcXN;T5M1vljL5Az2i(}gxHs#MoYbP#?6e6tc-gC8M^ zkTIDZ>6g61@cj7E`B)&UUFHo{U%9%l^cnsc&SULwSS4O*-%KUb|_H^O>xPN8Z z#S3l+%}W`w>*x1PYSc|~P#CPe%5Vh$vP%ZeWR_ddk&@H`e;Mmq54Ji zSmXpxw=H0DJ^CDtBZ&lDUfppUSM5SFF*x#{r&n>rLtV-KC#Rn*-)OUabqwnojq;L z{O(6h(7MwPM$FJV1e_o4W&y5;tE<6*ffr%;YmD=kx&J@e6uZVwk1 z+sP~2BRo+VpQa}0<|e2Q#kHPyglj2rbqkJaUQ2~&7AxEk@faL$qUvgP3f7lBV`j?@ zfZoR!YVY`oqGj)z#n@(|pSsVfp|^M!?mBwq>`HnGn|Lwg}^{8bgNZDBdl)U zjOs}YG(F4YKXUhNf3<7FzBQc*w(_To!i~598rUS38!!$FLso@qZ#<+Tff$~pm8mPa zsYDZ5NeEedFt zgR&?))b^pY-dBZKufVo=>p@}NUGND8s9ft&tC{RVJ7JPo-l;My{+b&YtZ*|ZSs zS(bNl>#J2B{g)2QOKEtVrm)g{dov@fs3wF2ju5_ug_b>Cp%HOQL8_4eds+C&`uM{j z*p*FUthBi6Nm$HKmJ8P==^vU#K?wlgTFfHEvB-YoB*sZR7vGl24F)I*Nz9_*eNrJs zo~fG|+_~!NdAGd=NtPY2%yj81G^UVW)_esfF@VYBdp<>c1VUuF{?Iu}fWs_7j6=>0 zb}y???OBZFv6|pfN~608|Cx2UVFs8`x)LyAFW<}TE0H(UX4E}vF~WSJs!6P zzz8G+#34!rFj9dooY{Jq*a7g2eo52=mWrZlCSP-RLIuV#>!UTh?MakKGZux8oHMOF>uR_wSn4&-VbI8qQj4OTR_Xr zZ#ifgIWA7g6NrJ|Ef`9*yNGoNhtTgkxej-i1(sJVt|E!;^v zpp8h*8E<|DlNVLnxtc*lG+wT#^)uFT1c#zjg5z@xpb{|CgL%TH;YQC*WL?&bE^;$h zkf1J|@@X!1MCf##X`=>)k%QqA;4{sxg^X$8%kudzkhM73%7^n8OQrte70ds}L^%gr zH+Oz#Co^LkCv!u6C)@wPjYLIlSrmR$?mB>#cB7iAweY?m-+Hb~xbDn+N(?q~yoHz? za^Kvv%qr`c40EQ&Yq-?(4{xO0P~L6=<90=8wrSLj;LKFdsh>~lEvdR)U0+~!1krd> zdW*i{>Nt*6wX~LgI|gJC z180wyQF6A=GD1jI#ARHoyVNX4J`5!|`r!^Z5|_?PtF1%^cq%PPVLjehLjs8;n-);L zs5A--{;j(WVqka!>C!oV7_}&(aZ5>)&oFI|lFZoZ{ zNQP?%K`~>HL6x+@j`Nxsw(1yKn8{t}zWh>C>*jKNhdChQs!pBy!f!L|^Q zHIA9|&XKJ4b9G>297=XG`Ga+UaDdm(!ZI{EfSl1-(;6M~Zo>HVlcl`egIwDsE|S&` zKa5mSS~#>2h_&Cv1!LX~a=#&#*f!gN9F2u$h_dKq8}ZJ(FpHlfh#f`cL!}HV>Aq4` zsBB^2T+(=g9ltTPouypP*xX+;H_y^o%b&Sx1^HV-B#D1~`{(n7aw^T55C4}J;emh< z{r8*gAEzl(9ZC;%$^FyEyn$IGGC*ze;MTw&;DjB~gU-f=oXiKABTNi4_(&L;AGi?3 z#R)?xy~_@$NDrtt-ytpB@B~`N;1T0;z5phLr$xmESr)?=tTm@HS{nFb;fP0sRl)OGb?0w!t0ZGu4U2-@D_a6BAX$&d5ZAtWw_Nl@8OHtBB7kJY|^p5wr+0C2sJcP)$oo(^w zlLeGq2Vp1sCX(YF=94u*@)?NoRcZ1mn0YVFdMEw;t7QL$e!)wU^{XQ7OMaqB`3`TP zJ;&&^GW05t;Ww6|UKpkVTCa@POoD|E86R?qd{VEH=`_t%0 z-czLo{rH+V1x7F)$Q5LCO?DIZ@ET;v8t5*oGjVG>0u+5QTr>bA`M2%ZqfbmAj~N6mXe z1cYcOFeWa|G~L<>F2-1zeSMi-8Is)`Ia5ZhyEM>7t^@l6=rPEzkPWrF4e%X{BSH4J4 z4h05<8@6g;HBhjgBJ;?sn47LGa+m05egguPL&_yy*4YoATckIMh6r5E$CSyOvY69I zo1)gSPXMAQ7-o^{(=?*teMK^k@wcsMZ6DQ5LyvJ#!VTpCrI=#iy%%Hw{1@sjDhcP) z$IE3H$B7eu15&3uQnQblJ-K8y<>xUD4Gjkh*oWid_#TrF#nR2^?06M^1=47;kR>fd z6s)5|y%6HY^ z81d-%ex^hf*aTQGSKWOqTg_Lpjk46$a&b7d195BD=KOD<*Y*0JXo5Vo3o&be74kJe zi{NE_zM_ko8|qch zEXMt6vJVHL>im?Q;P4xY5Y(uMth%|GxX23$H5)~9UOjQCC~PLPkr>>%eE>2>cif&s ze;mpvoQnCjx&whu{eHg1rK$tZ9}k4mL6bg5S#`H>s$i_6p5~H4fsAfD%ay*JS)JZ9 zJuKe=tzXA8pT#1PW0f5vmH2LoY)TAj`I^x2}g?1 z{6Wo0QlowtCe?Ego$>!%#EkzeH$H&td zV}?4|ggqm1GdPJ2(eTq`NTxn|jTW!(f-CW?WDjv4G)It`Dx+HvTJoj`v(oU6_ATVD z+ZnhF+Ra`3M1`wSWwW-_)9hf}sA0sjUQOA|`Ld=y(QuHNM3JO6a+|s|0Mxk99xTukx6Lb8HYZbPYrL}lVd_(>G*n;530UD+A5i`y9$!&e%iBK{&TT-Ax`&xA_5x9iAV(^;cfY{gc(RK;{W_CH3IKaa?~ApY>L zJ>D0mT68M^cfMHR#)m>c#T^_^?@>dU%EU{gPFoWnAk@xZot$H<&j%nM^WDQrQzjne8* z?vXL#QSXc?^iC|g9kPDs4WMR_Tl;RC>r-vDS!8|tT!ZfUgQ>B+EVX{ zqM%M`oD~$pDz_X}?NV3Q5f^-bu-0K02^-G#%mq5r@6(`p*U83h7oSNlf8wH1tCjje zyHl4BVc+v_6NV^@ZL_?Yf+!J<+z5@Z2UdGfRTHR0gMUaLb$Kye;}Bn{XBc{B|DYEz)jgnch&U z&R;z(vdZR_wj4m0rms~JPUAvp@k=|Np@FsNjo)hj@V}7WZ}dH~7W!^8sIcw_nO2RS zUQes(NP%vwu9{$+sZuBLgg;wboE1mitkz$0np_uCBVPLgt8|rnd~ySm8oJJk##1UOtpPZ7A%daWDLwD!L0X&EAh?_BLG$(Z+1pp+ z28{EIhq1H!CL4FW4mSBWUy7O8chJ(l^jb5XLW;}_=L{$vQ8jOkq(C~_s$WO|{(9l@ zQ{x8pdLGU@{Ei|(_@mf_b4qL)EyrqQUMh1FPk1^9hO!QD-ot1{a_Z!dCtTH>QPm#K zYr5l!EMxg^&gM<=ur{;LHY`F;EVHNSnWtH*w(9TN?`x6x=lr~nv)gYB0Uww<<6!U% z-SA8?@H0f-&rm_LWD4B=B+u@!+wKZo0;fxeL&^5Ix&u&Na1=Mr>#|h4&*(>+-B3G* zd=_?HYSj&ITcYCF*GxXRSA~L2+fePn{FX6$;O!U~SkwqiZCgWHRb)-BF<0h=W)VBL z@ov1Q!#7;cp1h|eBW2I#UVH^*^BBFM9bW7;$uLFKC4Gj^gr&lPPBx;EDbI*JQe(y4 zKHv?^T+N%^*x%FPxNNrLe4{bldb>s8U@_}`!G=wPT3Qej%}THSSO&#G9HAg*nG{Q} zK+m^hx;X@mEVA1cTnB?$nH0+oI7-G1Ds}Y_n;!FP$QLZWS}x<-P8x()w?y)4zBqFJ zYP>OhW4UbB8ejRdY1az&eQ|G(<%Bj|Hs;nGLLh2@em}?A)NzdE^4!uB&G)D`6R@8+ z5G7gd1hO>%VgtA34ad1R*G%DMi{6$7)u;V#GBYurF_4Q(S4bHwsVF@ggRg?Pk_$W3 z7TQ#Y*-;ll#2JoRi;|u1Z%!KYS;RuuskrC7fK!|oorIpa%tze0=g2@M&IIRhKs-^y z8O|N7(dSvWjIpWv;eKyq|EG%8OD@v$JMK_fU$8;OIcz=D(pxS2x;hG!kVW${MwJcJ zlc{|GSg`J3xA7%csSAA4RRmZ*va{(ncF^vQA$0rZEwV66Dwyc;{I|%Hn)b*vb1XT- z_-~zY)D0*US4I+e`U-Fik%OsX2+X1Q`mdCp8=^4fqytmirtSiqwwc+6^xdh6QYHsI z_N(l`C&-S^@Mp3|2uQ@_6-77i8nd(T)k<1miq6yybAr5jGq^Idk#2=ye93h?^vN>p zRXm@47>Y-drcQU_JGgTw$-(%<4m*9Z;Le`an?}_hMDF^jSNpZc%EEH$Kxm^@yOxR5 zhHDEZ1rzU?fAo{6a3&baK3Rg;*irsT5;>4(MO1LFlmgM?7 zT=m94XR<0L6nZ>Ii^nZGg9x4MFdbw}|O1r`CAw%QZ%#Yc<3T||y< zn$o}91L-M3iW!~`sBtr_PK=yEZwrfEp&d8wiBGtrl6YoJVkIy&zJ@aCnJY)inrXe5 zf=XBdgcv@T|4oC5YR$C^Eb2|{VR?C2<0&ZxB_qD_PD%i6hEw{3DA^YT%Id-+vnDtvfN<=G= zCkDYvYP70#8p-u@oQRPDdl|wN#_F}`U#Pvu>0{~b>{`rG<&uO>(^0qr7&33umdn0x z{}R&Y`3J98LV$pH!GeG=|9g9d-3+aq9nD>gMa->?`3(*Knk=nvqi_137ALB%IpU~d zec`Fku9i7_nU|_eNXtNr$ygQa#1W?>4mBvg#5LFKEs~LHOHw=zmTA{$3@|>S3cv)~ zS^5Tn_Q81TrQ|RxiC@F+fT!$W1$F%%>b&lSg-zL7NyObkF2sL3|GD*YxpnpC@^SR_ z`RED~wPVV`6L7TlXAit#+}urG6g7L+j$agas;sYQ;W9T!vcgT=T3R?mRZ<>6)*uF# ztXcD?F`T1S?_}OZxKDw`3Qv3>OGsVxTU4vPv(j~{ikpOP1DX%$RBoXmtg18Zo(Ak` z(cb(Bn98*}T=v|xyYFOZUEf}^2n-5Ia^X~T6uJ)LaaOZ|4y$A13GIHN_ml%V1lVe@ zcF}2i6tCqfE7kO&rt!(*`$*|;O33rl=0-FEpeRi;fzc_)W-=0JT@Wp0SGqDa*Q5>nOZLwa6vQ;bXI0Z*6Q( z*hX|(cc*GJqdL0Z5Oz?((9hUv)HXoo+d-5k*fAhHXP>VP2g?vfmPpw$!%1#Zhe6F$ zC&BvTGnyB)VpOL%zpTZ3@b);hOzZSss%Sr{?^&>sV$nKt%|X;zqi@Z5ZjBfd9${bF zhHgZnKs||q;PPR6e7qYfF~EF_mQFe?kx!}*gT+g}FPX(lozL{eXrL*CqStIy!%nK} zQ2uFuTgt~1Nv6GuDG1)EWi}N2WD2n$KFOMfw!x^j+(30jD>b>VQ8RIBf%arg%J{Z3 za+??ojB!oH3LhMQ#W#3Hs*Sa3x%k2N2T7eS6wdLG^Y>u;em;UINK70-=4+--;_Kzb zOHSpw^SFChFEk*m7hDzOeMeO#;oR2WDsN)v;!V_8FTywhcruV2IE-!dgjO_OYw%~8wLyN*pc_G=oVny!A5c!SpU z*(ZC}JZWKzHD~Bq3R6+QSh=Z5&0}@pB`v|mwse7gpM0vex2)kIA)ZM&{EH7bV&M2* z+Hh0B>Sq9vu%ty*SKwZm*yMvVuM1A}kzt7%UWpeMTGd|YZ~Kd?l4rbg@quPJ;ju{0 z<+sPxtHiL*$iktWqJ6 zlhBcKtKE5cKZ;W*CH#}o+c`4A<4!*-B+jJFNaFI!8^@$XbZf3i|ZQnG| zdM;7e`QLH#CuQRn{UVO@f&LNG{aeR#RJkPqU3!>P*L%(ZyMwL!lw`8;OmG*x>?K5_ zTi%w-k!)}tc3y0x7Gf5Z*NhPFI7P1jxIN2rd}~o2hGlyxme(Wy6jBIyW*P68XqDz45>?m?|gzJG^zv@X?;KV z>MV?Kt{xw;Rsvr>moT|HxPJ}FP)@GyLO58lnD@D0S4kh&$A8Q zFP~3P27hpH`3P}0azm{iuw7(iXn~Z38?AC(p=m!VC~D;CsgVs90^qENpWbTCLB_r3nWXsI&ES-tyd`66jdPNz<+Cx& z=tSEhGXTW+&Idg)!Z%9zMhOEf2BQ?#pEU+1X@Oi%XWNG}b9d#Bz~AT?IgDO&S3Q-4~dc$g2h$IK9ey{tKD z{-A^klUva7RW*?s{jRBHUVROa&y;3i_tW%K4$b*2YaZAYe^AStc^g* zs{K(4w7r>3tp2GE;7lfvd8;GgjbYP>Eo-_jWERIlSPB>ZPU>ZfYjS> zTCgk|B9kf)FP-J<4MWKowgYnBLlR~Y`THBJLX;k4^jUfr*u-!1iU(ZlmWM4ldc;xr zvTIdoj^K63&|OaA+;SQSDkyo}d{Zu=)?lhAJ={E1e=;d#axb_aeVU)Sn!O|xb^G@bmC6{g}K8&^Pq8F7a)7vC(UAfR(=QL zO&pt2ddKaOyODgC=&4e^-vffexI8vG2XG+3-#b6zuP@XU-RVtjepY7LeA7J+@zJsk zJ*|mN;-SG0jkH2e*{~TzPQtH)crcqqghY*Xb$V=a4&>mh^|F|ye1mA^c&nQpaGxg5 zt(}Nf>t7|7Q`lml+%^XWQ&mu-hGK1u)Ch{S>++(q=Es?+MZ=%ogsd0&;#;!BA*!J< z?U4XT?7i77>N7&l_Vz^Djf*-quFfBiV=*IdBW`FIx<2`8MjD;Cc?5h|>4rcp((V#H zwEoQYgropCiRJx^68G%mgH`&)#6o@}1EtGI1lDWqxw9Ca4b>~NcFgh%HgIv~4Etu6 zJJ{f{5`!m}?U~}h&S0QkWyoj^t!!14;Qb$AvD_&So6+>&wS-n3f*g-SbTTRRBr$$I zl2wS+8)MU5IJv>et!N)&UeJc8k&q~?gs=VaJ*Ur z!lTWNoYGVrzxqmPd{)*ZGL~%7^kgT`^WL5_9qm2IYc}pjq+72E7m%d!4@Wd7q)ez> zTXQT!{7v;t5@A$MG}QQ5=^7f2tOk5a{%Vd9DaX2HK;&?9qHx`cEKSk2E3^-Nb>V04 zi{gHA1v4>+Zf&u5Bclw_1bykg36)$QYqLrpq|Kroip{(o7_e4aySSI-{oAknKIqI* zuMBY%eWOr~i$APnBh}^!;srah&vkxXX3sRAA{mCD1w(O2{_r|)zY?m0y7maLKSO^} zJvRk?ZFfnHp7^a7P>7_mVP_38V`mLGVk0*z_EB-k`Uke5il{>}O6tXiOKaCwiRRI& zCHKMK3DH`d_MH<=jzGcD4_Z4lQjR>?1Q}G>L^hHk4`Mp9)7@}6P@xq&@^4ONrBpY^ zb{f5|4Tdohu})49r=7{M$u=pEES7~hNEhb{pkRNeH&JAj4_#Xhs?=BMSA|sCj|-ue z2b>&I_7;U=GpZupu`ue=%JmCKKMxQKYvqRy7(jK{XBiZb6x|g7vFeBoMIPmDs}}kP z6kw+GsjEVNt5H`MXh#o7(J)v|4>JY<5u;8+``JIx*sTV?n`eW$WrM*FP1NwEsBRH) z>w}%Dke{aAIn*CbBav8{8>Fhy4OC3`OHfU28DKanu9qBgjj4umK$Ne+E>rrlND$6Z z77H-3Nl`g&V0Vz83j34$<;v@Qch6swCNz0U=QCJ`6onU!*x@5}f&ZLHQ;W1*snK4A zv8>pTilf}(#tP04WR;XmN9C6R>>{hAG%F!lveckAQ5j@Py`-P`IyNGSckslkoK&04 zkJyH}OFTvoPM}OW$@ft+%EI8@Z$=Wx|Ir&Azz8B2R+78XtvNjf!%VV*$<_#nG$7S^W1vO)ITq$zui`SpMKYLEJ z&%z)gu%tO&lF+om26S8r^p8+S$m|2#uWTAg-J%+HbnnGCeomM;(=!UvXbE-^h`Eso?5QGD>xkr0-6c zd+OCPHRf!+z{4u)ye`(~)s8R}=a%>}F*e|Oxhf5(isH-4Q^;(W+~)T68zxe7|FC6x zRoKupg7vh&sLAZNtz91HkldBMNn_wc<&lEMfKwIs0LER5`w@*Uc2rvJ)sLxcpBEmw z_U15bmcH(A8pqHUj*!i*^V0o5H`A&XW#+zp&ZtW9FJ1ViRA+M-?HX(@Wi*A3~%r!)uVHo7T%b;Dq;d60v4?*lKep39$Rxe&|Q9< z1HvUH9JTPX77KV#C*0j8Tzy!S@cv!=uqwjy8}V^yR+(HQ1Ps|HREc16xy92PPX)Qh zH|~PyUQVT5#kU}?&ah6@dQ=lxe;Tr83BM=VoU{Z-4atv!xW%IyT7Yln2a8YvK?A*m{>e^+Ip3ZkgmXjqpgP@}RloB}DOdzOk8 z=hl;=+6$DXusSY7r9cj7?Mi>>pe+up$}Mnq(*2H37(!;&&rv=|$6`p3>4HXAV7u5$ zcu&f5eyEYjk)AB#H<=kZFxQEvi?t+1z!s02Bk(onPnAAsk2uS0s=1sYyxA&aq7v*e9~{R?8eXIP9iB?F zuSEB!b3dCzBK>}S_ruA1sUD#!=hq~vk-Dl(lJc_8H}&-d>M~Q246<1&bRk@_TH#CmRR`5y=l@+c3>OqlcGvWxo7xZ?23jX`A}JWb71>}`7birHn7L*^li)Pk|p z6aLnY)Njkf0cOS`wmYskUE1R(G^taw2>cSzai=b?rsys8J7jJp7kwh_5$<#$>ik>n zTo(*E|&8B|uyep>=L>TlhZXO0v5%Pnr={4)_N7v(tJv+nB+- zCpu9*j0q`bDC$Z`DfR#mDRyHKVNq-4@>`&=C!{<{O`%L_|Je(fsrvE@*BH-d^PF*RBB0P0BDoQ7I&8XfH^MFN1(=-?RsD8FPDwqg#&^H{{w8c% z!+t1x2@U`akNcbU2b&5SuKL}62cvS8>xuM`fL07STUHzxIa#icUjYGv+hO;NAI(`z znF$$Jh-OJl>UG->sg>P{iK&({sN8ckqFQC8MmRbaM#b3@H2G?SHPLA;xn+_+eJAVp z4i$c?PHBjo29zD$1*K!dyl!g9pAMaEz{;McH(KG*$-vZuPttBo76ei09Tg)!kWcik z!hx3IxSuq^WZ9@I*c6$kZ`%02p-4i}wFijQjjGeX$c4+hMnMO7X42YvyRX(q_UzsV z?BLH*7~!tYGSTL>T8_HAGt$aZ{drhpo6z*g(X;coK&DV{iw%aKL(ZE|J1$4C8Bd>7 z6(krPg@lPxQ8{=;IOj}dygjJyFLZVtwfla9zQ#}+$a556{nH~UY%gDwpV#7kVNUQa z!)uk@MlB!lrV(PX#kOEJArVr0=mu;R1OHX2)8RUbBQX;Op*EddBZ!-unf1?+HyU^} zwVPDW9+);aKYtGjsH$>hPxOFqsG*36fEt_Cq16UWt5cI}4IdoHEr+6$cHg5yN4wfd z<+rTmYDFhz5j$0lpST+e5j&fvrnE(T$gr53`SHrv`9W8^p4cvX=y00V%6)7r?F4JA z_jK+wfX@i{k195(i*oSsHeK5^`wv(}a z0x?+I1Cm+avAbrjp?%7BTxWa#!ut9#Gi_h|={{D~yuIwq^wrG|@L%)S*q`}3&hP4d zy4R}EKtjUQxii!XtKbVaJmEJT<}Ln1CpUP`8NeTSERUWVPqPO&_gvr&5bjso9~~$w zTeW8FqzvH`e2L+-xj5!_|5h$ZKAFHfe(%*{*~ph&YU?$rn@S;n>PEAYac=6;EEHNO zt!y(T;j=OQI`!kSn7-h#G-OtoZ<7c8Ev~uSYQj_O#h&r)nB4J<(B*G9YA=6N;4?V5 zhM$!xtPE_r=cww(B3khaz?y6Rwx3(oX9*a*(xvLs8;?R4K4b+{c~Yl={ClKz%vNvOe_;ZdZ|MMp@YOjv zWs6^AzO#|l_MPjCTCGux)>R8MNds9+Vpav$DiPl0eQQg(lM3JLcjLX5bf;C@xnzPc zU2&@aAXG~Lyw8hGQ1?VgW8>%#biJ;M^}Eyu&(38VN$Hkq&#mec20LtJPfIxsFbJHV@ELb0sj`y%s1+L>V-?ffjq)#COqA^WIT{kRX<+d;0x zk6%2x&Z;%j%mCFU(-O=%I5Ce7Wgjqu|Zs2{p&bY|72J@ynNVItyn%Dd$ z{3%I`doEGA^KIduKZbd0tHx&n+#vkd(b$P)c0u6d3e;nV@Pu%P<9-=H_Cd}KLF49X zfsxG5QWBg;&h}xd+WdZnpM{=J;@;JE6X?mjGAQutgC6_}+%$yd*dniPqr^^{)Ocbd zyM$K&rQ#Ltey7&oOASgb00%XmS7x^2-5>jf`Sjco1@()*4`*{qK0+b=U911szmO3$ zZ4ChF-~ZgKXh1*+{|hqm4~YKX?bTjN3k$pyb?qb9%b-NCXCYv0yLIN%?gy|kO{*qH^MX)E@M&41-vMzVMfEz>1 zs97h4P+HrC+**{}!n3F}TDyfM8E1zIbdHK>$zw;f<|dMgj1lm+xPeElZdtu|#)F{1 ziT(KeUpe742Q6aD+O&ogZB)_8&CjUrbQ6cvI){fAI+uf*k-aqQ^&p=5=St=(5{fja zGAPhRZOt+sr)V~EZi(TlZO^bLYI}tR>y#53z>d)~RGq482zTx|>4x?F!bGp6UYrdj z`gzq`pjNv$&ty`ez|Mq?#XbPq(#FOln+0H_LBZXcp2og3P;qM&U)VcwaqfEalWvpR z>PPL*VIm~D^W6}xDYUFy=%Mv_a%gKqLrm{+d-F!B^_@bHC5bsIls44O%4^_;Q{{N% zwR`QhCh^sa&1(RC%oNQ2g#hCOIP|Y{@d~)WdoyRg@PT{GcvJU@uu^AdOCWufwN_i9 z%SvlQLm4((Ri#E-997XJu64Q!{$Q9Aw%zL_h_HLbq?-MYScQ=XQehrndOUr?t;cR(me*h4x)3KsmWs2FKnSlTLkgqYABguZ4l65WQI_Ao$N^k4oN{H zH|V_;cm1T>I?Mf5h=l$UW0O#&sTfYxa-xX6))3}?hqK3DIP(qZFCqHVyT$Liv29b> zBD!#UPgGW2N6bn8B&sO&Fjk(Ho_!EK40M9ZI7U)OS9JR^?#Hjo1@chH$W@_7nAj_| zX=u5`nYtX^rJ+{4K$?vfASu;YIOL?ubqZ`9J@XX?v2W>;j>f93RgTC?U(I7f4aNCW zX2~DHG{0>X)zk<*U&lu0-^A+L^O}~!Xl$q${=M)T5?K(4on~v7d>L*00g904y7W_Nm52UfANAzE)4pFr# zdR%fkM?f?Nn?LkU!f~J5cr30r-5W`?3eU*2TP8Kx*W;%y8%?a_F6pWa2|IbvU`5J) zNr8WKCrtYreuw&vTTLumAR;AY*K1q*aF0_nj^M1EG+pTyd_Xdv8|GJ_n2 zLj{?pSp_NTpQfLJa{f(-emaze1i7fh%9(}M-dTN}pqkP?zhn>6DFRw|vn+bGaIYKQ0)q)D)_qbCQ`)1_1nDT<;~aQ*a0_m`DFZ$Lg+k2QFp zDgU5dIe+}X3c!Oec^hlu53Q7vfjzVOZ)2|(uZtK!>Gw`UHj{gg-x@hhskk@OF1-EO zX*0&N6Fbv_BK0)JNQ!G}8c``qBqYfBdQnQSh+ksJ>WPc=_A+8GWGhDnNM%q1{vOnF zYf_EpfL0?IV^5~*1GtTieYu@6)X&b|WRm{3-q^}|y?!N?7)CK;lPN!Z7E|UPW0(;1 zjkJ=DlRA?LgVM2-wz$yCWE>f4g}9sd9^IVGyliu8$cu)Xa?97b z?<_38hRXt58317c*hyw8hZW*Rs#Qpjjl?mY4>F{?e95jZ7CNd}5}}_Ts;t@mHi^GR zp2D&&72OfG2I@~jq(~p5^uw!x&kyW!?(|z#vlC9?qX^eLiETM(Ho!8ft|{2=O}jE6 z3PJ_8)SQEW4<+wBn3+ii%6x14#VXszTKO4KRSq~wTw@2GPhJNtuMztG%%3H*C0LeL zSCc^p&q5$FoMFz)L11B<5j~7v0o3nAvO%Io)i9MRUWfxBVG+zi;Uu>PN;OF^Mv(q> zx|;PhY`7mCX-gr4hPm`*V$>)-?I+*`7|x0XK;9$*2@%06C2uAp=OUI%$$8tv!W_Oq zO=EO@K?tQwB;ab*?KhrcyQ)!yupNS3!QsV^nObV>g;r9d(szcA%3G56;?d}lwY_Sr z4stOU;F}=S5Xv)`@3~h#lPSn~Ttide5F&Ze z?Qp1!c86uOC4cK3wNDik3p|Zs#G-J+sBYRF%rlFfD+zWLbOE%YTg&&>-Y(S_xKIC4 z+gk$0MSV0c>aGFXvUdufAK<2g$f5-Z&)SJwC}8iYUvVrGF&t6{Ph_(n%GZqM)ekCg zl+%MnKi2Pw7}tLo40V;<%f2R$65OIue`VSVUvoK>i-xA3BV~)YtJ=R0ZIi-F-4zTR zgzDO3N)O3+Yyb`PE_`S0_O^E|?-74t{rRd-OE9-8^soo%vRupCfj@%{dDRX(^jp<% zpX3_Efvyh8yWsPVpr*w25@z(7&aW5zTK0<=^17O#?Wuopt2F9d{1BNWY@C>rl4$qI zYi6W(UZ~l#OxFG0;4+QRxLwF zv}qVrfr19>48N@0YoEWvXuw#x?#@&VR}WAQD^&#c5+wwFlPtCe$D%_Wm;1pkDlJ_| zQ~O0Nd@}tGam(9V8~RxfnU;(W3b9EXHRa`y{4scsKvo%$zeMIEZ@T$L%R@H~+rk}3 zsFd9e#>6u`U^~R?b(;Ay3IX z!A|}f9=EF&D!*I59#>&0_0bu}Ry_=h{684`3fRcDBw4qq&CJZq%*+gJW_FpGnVA{d z%-CjTW@ct)YP)TJ&(7@b%-h+omFh|*RjH)9=iC#Sk(m+ByKo|s3;ge%h0~)gp#gnj z*V>C7px-N5ua6DU^qIC4-J#}WnQvUojoZ;G;Kd&^{P2Qcf&Lf}cFs$9!Lf;rWulgj zx5+z#H)}q({q(_rZ3U;2Pj&9mS&qLsy8-LVCl&vm-y}yq<3dG3^xgf0=M{}HJGO?V z&ClT=DCQZ5vpQ2Ar(u#@ZqqHMcSHKr)11D4IXG8;*wI4~-UR(Ip`Frf#oVJUbitdY zqtp$bQk3*kHo+|;U-)a8FA;`^wXF(M|wxGm1{$+xKwvXUfOxF9PK~O zn~si;bR4=Sm-nWZHK)4D3x{p`{WM!}BB)kGy!_LdS(fUNqYW_U4Dvz}Rl^OU2YOWQfnC?MGKv6lk3PC}28;aCq9>5Z7Q;-uc9VA!wXxN4; z6=12qi0ee2#GKGHOVXJ0$jx>5GQb_r?V@96Bq8W>qpX^o1cO;BA)-Xse>Xm!zb0_cb1oNoO1f%DGIZhc+1VBa&egW zg>^pY3k{&>2^-`WKS-9-QJv=9NqLsNOje!);0Rbfh2??`rIa>SjKznuhRDPLMv3!A z=%ZA)o`7lsho9d3liP^_vhm@Ek+r*USl|UQq9V5h#S?y?Q@!^jFH=e{lMXMVhsEri zvn6xFaby0d(5DDnBCi=q53CPgcy}PnLpko{Iq=ZpIr%d53Eu0(iC;8~MCU8_$fLc1_YEeAwL&&1^OV7p`Sk zi}Zad(-3I`5=amJd{Y2y#{#WMFYH+v=&le9dPk@^^Jna(^4t$ntJ6NOU;3(J0jksA z9>RVgk1mChfgJ=w@lGl99mo^jAe+9tqJI1=EU9G3X2roaDfnd2cV#l1`Kdc*Nj0T0 zCtR}NC`?al?Z79ZBHNT@!eB1PIssWhXM2%{MQu|pun{CEzbQ%W0WeXfUt}A72Jkw0 zz%NP$m4yOhQ=!hzlov}aCng;$I;d*7J-1-BpGLuki6coYv0UU!qOCT%i5hRopSJ@7 zow8PeMo@{^s5=)xBg_u1E{bg-`fg5&WjCFgV2-SY#_=T5pfPl$qL1z^G*f}rR+B=X z;L`TwD6Z%VE87|O@%*gVRfzCtghf_>L~ZhT!(==WRq%RVbrsG+nmmApbrsfQd%S^; zI3VhIAuQm(v4`$_#HNWw0mWhOq{+`S&s?IB)R;}q%*Z94GVC1p*;t-%df*LJ7tWu8 z%~#O%Z={~EpR}WydWY+>?Rs&5`Ste{0_nrJd{jXmYv0_rqhwi*9G>-eRTm!Kc1z_I zL3NhTNClsT^OaDZoF{f<@r9eG3q_YuHrTikj&zCDkTbb)cMVvQ9ayc(ujyJ`k?HB! z%Sp4JYBVSIcda*ZANF#RwZ{fNlAGfYu;BqAn?%)9kPIjQK;O3>qzqO?A5>2#hI}2JMJUhJ?aquKft25xJY* zo&x^*tX+)lGEOe__mMqs$9Ewcp^WH&b_&d+dDvWOFjvU(W@&KJdv=CbvgiOR9)%$% zkMCoisd6EO(x=`EuRslaB4;1m6y9N04~XX8Q5+wT6y8zg$N0qw%L4fGr!ue1?_e2g zK><77uhcZWP`@ugUa?(*s+FX=ppq&loKa7pR%!-PS*Kym{)Bx%D}_O_D0Ss3LU~sz ztRMfVR>+MtO-u-}#T0A?4vCxnyu~yzd+c3{SwQh>NEi05O*5L2vWlNTh}PfFd8iQy zX+iV`p8qrJlB5MYSVU2uakOrmi%42z8o$_DaZL))sy&;jjd-VVpJYwUo`9Mxvp3_=Gf~_&No`eoTShPWQGPwj8>R z(S#1{P86zG28d!sb{BTRL}m{+VBB;5YGFbw%`)9hc5g%t%c3mHO8JqSIM^iPbkQE0 z7?X_)$yyWQLW0wlYl?$s<;kQ}H^!WV90oP+TB{a=fKH{D!MwqO0O zEMFQ^`2U_^l(9AYuhEX+L`&N_eU#x(_*4}eqpwDo6*5OEbRG7NNJd5yX^ESJTKziL z>9NG>`i4rBXuU!CJTbF9XnsHZF}I&em@%x<^Yh0JQ=hJMr`P=azTeDqF~wn;4O;@% z8Gk557lgF2xE8ijPc{Lo^u`3}gq)17Akcwu>#Mt&s)xK~5h`((KAp_hy#)|%GSb{y z@Nl3^@$s{q8n+r*Zd}+8$9aA-?BQV&hZqjKIH|b0ZKIrh4}}lyQ{--{hAtUJV6)C9 zR6E7Ff7WJgEvnF2W~Aw)^dA$564QQ$aD;;?s~&H3rT;xcPA6esFtQn1F>M$45Cs%oXTC7X-(u-hDuc>U|T%9ZW!iz`KutD zR~Rih?ZHB8h$LQ!I2<$q>Wb7JA0CBAW)IOp66HocgM5yMR8D^xGiBUD(TN`L*K zE#tFx=BP$7zl{LGnz3cFD)*|K-ymlYZmKZH$gc=81|} znIMISX?yIRsEKx?s+2qwj(XrV>8;1C^A<8gKG031%`Ykf6>1g~si2Vm_!?#BL6udU zsDd=%sVH-#)x(-Ia}SvgNytbjvc1HYW0VyBOOjBFIcg#)J`YFAEUIBx{T`ES5jW4) zrB6aq3~-48eV+uSSYD|5OwlO0l9cdB>ChMhOCL&8Jx}!~HtTleaOW(dMB%8F%HEZt z5K&>1%;t~YW(xay=3m)xTN_Norf)z%`XE3+9RGf=Di}DK3pzTQSQ}cIIFQKL8W~uL zd`)9H01Ta-0Ji_=a!pp!lExN9`Is`TuQ*lRFYjEH&gUC=tP_1*Qi6z3fM}Z86W%5p zjg!%K?KduXsX>7e!hZ#MEs&a)5n@z@@J?ese#*Yi?EmxnzEAGwZ8cF4hED3H&b%dU zr2E@#zd-0_-6GCc&AU&ifP6}{vhGEK^zOaTsg$L6yxpSqcS`Nnh!gph`K+9=SDt7$ zz4IT@gUwfcg6WL#g{XVJ(jSNTkdJE@Pk^~o^Yfq`JY#D~b*s`Jc<|F1O1{W2Btg-Y zqzTA*$>HRZ%MheAt80Yg%jKLCDk(?-@&KwwL87z_t*yzVrr*;DK3XgBzc-U~rRW;ICVO2gb@jlamg$n#TXCQ#bOo(Sy-4fjJ~@a9X@-MAnHFoS@0qF!K;D zV|H{Mw|eul4Lnff<~8#w8SvWLSm_!{IB(z6JFIZFH`_|pRzUNb~UA0t(IAb_qgsVdAs6tb$_1Z|GXdK z2g(}UCdP628bIU)MqVaPvKfg>H4qLaT?f-ZQQ^TA1m#@)B}x^%JE9#UFetm)McQ8+ zQ3VmRi;U={^bxtA&cKJ-TXLHY;pC-SXr|&NcQh?-hol#`+Z1v9g9m>Ioj7b)+Q3UA zshg-@ZE$8;8V#7{S{;lhQBA6pm}GBwq~livu1pJqrSt7vci+cOgJ?babDrk;Ob?Wdin--=Uba zfm0HT(TQ3BdcV&$^0=K+q~ocu5W%ia!+m4^)?)E3(YU}MJc@|9YnG%V@|>@m-x)d) z0q0TgZ=qFPX5+-c?wU%mgP;&ap3>cZj+(ttEV7%rkm1{)kXR#CVXXO%bx&H$7ew9J z+6dOIyFi+uaZ^6a6)YbbF8*G5%%r4{oKXuD{^c9!c%N`Jlq~OOQF=C?qP=A-Q-<-# zpXpu#gB4glf!h`@xIQ(z@Gk`ca;b~p)VXr~CuP@D z$+W{83{QVnxfLmn>lrI{29`8R8Rw}|veVS4hz;MSol^VsZrVb)OU+9o+Fe2Eo?Nb| zPB>PWSvLmtH1K_x4|HhjKckIhwFa@VTu1~9Ua-eisP?_07SWldf7LnnQ*97!$Fwom zS1d=fnr$z2=|lUOCmc$ zwew~1%_L%DXx?2Rtfvjn>bcFcEfDD|Oe2s9cGztVRtjhMR3;O2#}kD5;w zY#{bWjyeMksSP(+Zj{D5&9H1{9K5Mjy@8=)LYL)nPSiZcNI|ZBdCf91C&L&M#|R*} zY}i$lBDA8#aAfT{BPHRqfA|DGg--@eyrN#JqNZ_<+@Dwu!D!mH zq83(S^+!!UF%A)uRFXMl?+V{4Ywin^bj_GN+5-GTJ@mvFTC=ULs&+cpudET}6qoD8 z(;ET##u)D!snQeNTEgc8b&m$YcGwY+sJ%Z^*oepo){%>Ou`8n{?Oi_ z_-lA)&^#)=@6HeV4;T-O5}zy{D}p)s4|zYUeqsgU@r0uqg3bn)Y)H+ywFXJoj~9WB zbt9_eLF4+gzc}gUVnS{hGjXX4?vVx|*AxJ_F=g8-;99X~^V%`04UM(xj#E`w1|1wM z&%N7I_a-vGhkfZul1uDK7Fz_17GAe*CbI4&jw~UG(uuddbnl{_vKDKPC0&!%psde) z{smc|I8MX6zoy^Zz7%H}{ynlPyMGP1TYrtae<{{F0Zbev3~avSZvRVkc2oIFboNsx zp_Y(_h`^8nDw%4Rd2PueT%jT)0S%ZGWx!{Jv~tvNMV1{q-e>h%Jcld$7rgiROuC%e zg*47sL)Wu>o!sTMjSvB$CceW}>+zqd*5i+B2c6q z;)4Pzd^)guz7%I8;0s&GX7Sk!VKM?t{Ferl4EeactzB$PQ;4__+lBTQ%3YM9%~+UG z9!Ak6 zYDm)7=dOlirAFzJTOWXi6w`2{%B`+RiNV*bYqC|W?+%Df%`Tf-M#Ta;}O$TXFm7qOEf zwxo}hM(SkVKe1oIkpV7L-Nl)5q~gru6&`bhX97*ysWS4yCIH}Q(gASlL@Sk4tdXnL z)m`4JOJnad96w53I%0RF;_6i6p-LYXF9!h=*c zr6VH))~v6zGp$;W+FMRhtlqKp1s>Aq_hP1^&0rn{P1dWt=YY+NQS~(_Z{Bv;%hoB) zp&ymKNqtK3gW`1rTB2|<0UZ&U>T34T095Q3b^V>yuaI+Q8oA3zv+w_QOd#yqA z7o|b97tKNT7u6m8$JIl~Qu9K;w$vWnjE%704c1kuS5Gzy6SLNnztqJ#6}kR+VN};S zE{cosp(Wt>6Sr4)vMUmKjYcIW)v*5fK+5|yyzVE>Lbt2>WNRYAiq z_`SmNz+}JPn!8oBf7csOGf2GtBWHFp3AEOdQTJ8bhf^)W7JkqIX-{%=PyPdb_Hc~n zqPC|M>&8sV?-=o*8>`1r_?=@|cj|0rG$*bG^iWc>4+l(qyT?0hTSo%lq|*|HNTdwy z+sQD{FgT217zKDF|1K%gi#4{h^z&{y7Ddk+u~*=8({4eYj|cV`67$=>a6cH!4f;wD z*IBaE%$q(k@2z#I^tw6GtAM5Bx9{osMSe@WZ|0s1KYH?L8=wb5o9)zh=;)MB5ilOX zJHc=Omoz)A?HE+@ZBQIsXV9;u??F$!xzoLqOyAQo6$I z(TL5CT#if*&x*OD_}~aLx6SU=t&W!>KRn0#Ar!n~Fpre_h)7Hjg&53xWJYZnEaBT> zb?=z`iD0G+Py8dq8RL>K*5U)IGmxF{5{kx0t>O{ znbluG;?uB@kWVjk+3{9&;2VSnW)z#gR-vC&4xH0x5*)CM*XOG!XtYesLeX*qPlm@d ze{R!G&(u$<*8b-&jvZ{FN)a} zI>{t6jf}aT`9A0U^6GJVTd(_DF7P;4Vb9!-0q{|RzJO?;IHa4*E>Aibll%w|X?(tXJzWb&B$l?Xndytj(0qoyWa| zfkLk3J|kaI|Kk)R$Yq5il59T3Su{O&HF71x34O9F#(-`EeC^L_&g%DS$Bo)f_>Nhq zXPfK$CAeoC%}8S!4ks`0L(O$dP1#ud2CMVPVR<>L>9y!Tkxg%-02dxPny^fpwcgjF zL^COq-xmNq8!S4VE5fBL7Ah{y537ws%dy|(M?$9-H5N`zYIji-+(eHqIxk_{^@Rmz z`>T#8KF>tuBK@MGG-sDF&9>c6Cs*Ps=seOTjJXiY7*W)C!AF&^UEWdC$GeN&YnnPW zD$h;li&dS=nYI<+#`=M_49%k)HjQ84 z(}j-=b7ly6#U7P}EKr6@ z@T!j&RYC@wvS8@qn-IJ(wmg*}3#PFcq=O!8{wRTCY1sqNC{!FJ=}0aNQw=C(nIO1H zoah40BhV;hTpbb#VIYrMK$=Bd5KLV}Mk5}9`!}X4P{}a@719w9RtY8WRe{M&JOZ$9 zOvo0de;tHKs8@+W`icQCzWNU-{(T(qkF782W@6<0-{Qe)&%VgVn4dpse;@3tGl5&l z5Ji0>T*ZJuP)#s2iqL{ztRoC*IHJkE1kotTlG87liApIZ){&eKaViO%8feg;Z-LC7 zDfYbHNA2Wzfr}sRYn-l z07KfEB99$+6Nk7?x}^gvxwQdn{A$hw&Pu+;1BJ( zG<6HKo<3^#NCo}v**KwB@S^WeOx-xf{`-3wAE6;IFzy=_GGAFiEOg9=gaC~R%9f~_ z(3||AkoV2eUjM6@yxyyiuJ}CvpYa0%zWJ8=cIrYc$1N^Omeb~|w@-!En5(zENN6q@ zs~uG)m%^IGxzgq|ASWxS7k30n$jEQt@E&2*<6)zLZ(>b0+l&b~XUVmD60U|MoGbh@ zD&Z%2KYA!R$0trxtWVq_$8i^-o8kfTDASVfqNLRzVj&ND2XTd)at9<+<6vn~Ttw8Y4;(QJwLbn7$6Yac?`~f!F_T`b|@J8dB1Qd;ng}u6nh?M~% z5|+>FHA3o+K`KJc-fOG%jmu)Cc4fve+XVCOGt;$ZHw>j)Yp+l&*U=+?p(Qr5N5e8n zb7$v>q#k0ULK)-!wm%^fYlYTJ4aBb~D$`_CnMkUi6s}aL+k?;QYbq9e%^JK^x}nPz z9Dd8?9AP%7Smz#~;wswx&yo^r%H_D4x@xx|9r;ADiBvu#31b{c8N7LW;uE($M*sf83LF+n+ukH;pU-(S#WUPzJ8yxK_JYv*Z<=lY~K z@`*~ka4gaj`|>hZl)IJeqpaUqK|aB|Rc^U;-|@uD6pO|)%tKJg@N@jVc{Mg-Bx_38 zU3t7?MgIs6;@^^^`X0$(3@c*#D&DI7ksaDr`2+HqR9iD06($m!#dHxq0_nXL?#5SA z62&MiFCJA{L%Hl2m|&2smUrN^v{cOFQ+9tkYQl$;RXd?P^`^3S`Ak4lMxzR8>Al@t zXCug4&oyQgEwR?IqNKAt7$q=*anx;A%=tWdXod?`$iE*=1SGNE#q7t zbR%e@jO@uDV44Hhpf23HrulD?IWR@GEw4nLbEA_eI=Kws*2)XLb7Sz#J}j+BqhqDl zq2C-ql`#{oEjoVJOR^P*ZKL6{5n0*z*Xs$s!A<+1UUKukahrE<*V`6*rnhZyaiae^Kp;`|hz#5utHwk%)J-+~8;rE-k4MUZRB zr~rx+N@HI2OC#v#;F$XYTxfESv^s+}uC#6lS7@-Bx_YIySYzC;FjE3C411QmN#cq{ zR$@xGBC?E=UZ)_b=lNiu?1_P++Au$KQ@+4U+@8i^cQmsy*;S(}o05fg^djw=^9)u>UlQ>$FZR-GK{ zc6ia=eX!WL>;tQ?){bF1tpcy&H!ME|7n}vYr_8jZS87OqqBrYfzMI+P@+To$m84w} zjBTPkXSd+DGlXxtp?IErbOzO;AKSs}=Dv^zL4!2t#RzlIXmbg(hkW~GC@y67V^)rep{iAUXr%2i75O6S~8KfcV2+c{qC5&g?Ipi+P>4@lDZ(fino5Wpj zo)exbgoPG=N;2@iBOhj(kYh)}lv1IF;JWv%BmTkAS}0Cev|h%gJKW_P7cSwIk*F*l zf*ArKL@4{T;W#VBubU%ID_)-1_K-n`b(|(OQ&sx1nNTjJ+$0T4ZPbm4^81k=FT%sg zzfllZ6Bh4R7-jHU+S#|bBC{XXo$;jbr>%){8%ZjH@80rdUfC3wH!Oty8Y&mYw;P26BLZ`tRVKDz7o*t@UJtx4kk)KjzT`z zhDlMvW8PysR%Mj$7s*6X>Ko}pSw#|qgP+q0UT7LI@XrUy7b$_KU-s*?DGo8TrxX~} z1PD(h`sd{jAVB2@GR;Y%p8^r8a7$GANHmd$u@l{%p;VQF9wkNo#<>j0kD>4^GjF

ZXAnwBU6wIQQH2nhh3!NAAU%O!%D)gr+wGw}*N4Dc4-HH~ibz1r7h^EM(*)Y-?@(Me@i1Y)rlq9hWcK z=RfJ5k$`o``N5 zxSY7|xL_e}i;vnGMq#FoB`|C8+rKbXv8&DS;XSRj8r<5YCCYcTIy}dMu`kGT$@tqr zLJa%SLoRH&38 zX4*s}hV)zQN9EirrFzPp;s%3OCSzU{dYfw1B|r-o21AE7RMO_>S|&WKrS$UBj6^@T zVypxhSnBpn^ln#RMCdw=oxv<6^*!Pg(Mn-qMlI{nwA1TsW1p-TwJbW}#*XLZQgf?k zM3*{5TnRTiW2h=Z3#)}eI`^8t3LC82Zsf+MSG+diFEASHunE7moiiy8bcG|q$!zpX zhxEac1rc)Agd2E5D(*5dD?0DOcs7xj$mWI2K=?S|F2-$MJTfDA0T@}CVQSFetZjaZ zn7Ejho=k0rNCg+9Wcbhu`v!wbK)iDk8JBT3WHeMHniw1XhOWgw5U~$kY5`VO&s)U# zu8%5N=SojVSr^6CEav`glf4dWby6?Pt3%*{Lk~xA937f?;7KgZH%C>S5PD2nKl=&0 zT*)Vky9~wc{2_$$nvWMTLT>LHo|27SOvBzvR+bUYr#aSV+TIs!fp~{9o%F!MYeXMN zYtsDJgkw3dv0nX)c)WlD0^``8HVB2U+I|xbjL&x|)bu^jttWi?rIySn``nk#NO%YxrZ)+YzJ(LWkECN1 zhj!7?JCn*tYt-7&4B&uom?W|n!qi%hu*@)WXd6oEu%0(*N&l5Cl)q#CJ3oFV($vsr z{t-v^Srxb4nBym)1^Q|0r|DtqD%koOdGRg@CODLs&WT;oL3-0514g*1OM^G9;zFem z(K%VrNrcCQCbsZ1@!??bJg}O z#i-*$dxE2k+G+w&jrYacU>8k-8xekjl&nOfiTf-8c}E8vG>gawV)f?bs8ZBsF)+F# z9vNNISbZ^3p`Zr00_^3vCOrAMrjr3h?nI^AT@BdWB1pXCA8)uINEy_b+#y$_1ywX5 ze|%BZ+7qO*q3OjdjW6p8u%Bh{n$*p}c>1l&+)#;O-b*ga-C*~r$QbDMrBQ7O$oHvH zV+FAiz08o?qIkCRfNaYs^tD3D48ONiMXV?r^cHt?RpkfOVCMGuSM&*|!@nhw%BHFy z4zd(&w0aIiA;+me+`IF>Od|%1%17-SG=rl_7-uAaBxoz#`Yalu7V_?8MKvO~4n5PZZ5z z)X@#9Rlh62-Nhp4ixtw#&xAqmjzy1@oSSZe?0=fm<5L(6 zU%&zm#vH|X(}oJ%5;X2da@$346u!nYYL?)Rt7ai4O>$ZC+k)l>rVIAd3GCw{L?kh( zil7#yhM)z7T&X(O^8IB2fdoq&@&v=1-XKEm0Ai?OsT^X!BEl!?;EM?qk;uF~5>)#0 zuzm7CFxiU1-yCoSl|=l&K%>VUFm{8B2C(h zg~uQr!-xBUfEC~O?bIHuW})fhH|Ix?TpZ}x_<>R!d+`V|^Fgrd2>gAJZD1TPk@g?( zWypCL!-wd3i-;3Tk^A){tmZLAhAot?sa@JvXJE1!Wz#TB1`Cc4rY<0v+?B0#5_FeIC+IQHW$ z)Q+I@$F3*I%PabD5S-E7yUb)tqxfxnq}4y@XBtknn5v49^vb0Ct^H`o_MG0R5wU7SND);4<6~$l0g`eot&JP=H2D4<-RmL+3xWX})P_b1G%C76Y zf&S~N)Nk*-Vt%cP+1IL2{!bYBr&ak&BonPDE8Q=E;61-Iv`|w{K25{|CC0;6L?EgN zK|$iSU3^|gx;f&a{JPHb0`f<3k1ZHU21-ZWMmb+BI(NrOgEkUkSxp zPbG|0DlZS@I(r6t;Jy5|)1Q5b*Y{;$J(4-sG zomoPQfS5Oy`CgpPkZy-~?fU0?$4ha?4FR3MipKb1B7E0x^goz%sV&pSTFJw{H9OV067~iriMe$QK?Z4SVpXOt1h3_EHiQYibMG7YW z!V$#6#LS3t?Kh|29D@RgN_ar1^<~vW1S9s9bBu=fq6q75bdiBeiwTC%3bNfd*&IP3 zaTQmWE-(I=U5Z}FGL4lo&z`U6T%^;?2--|8F)Av`JE|+=T%U7Xo$c?)D^+c3DL=gU zt>vz}b})~{z98gJm09sTgtTW$Qh+*_a>YSRs0mF^Sb01*XtH=D70@8^S(lsdpFYxNk#SPLCts{v@dznyBm*)nB4<(8gsM(3g@ zY*u(>X|NHU775I-W$9dZycKSFEBQ%7UWMl#wvw`!57U9m5dUt;+0mmL_2`-*AUMBw z#KTf97TuY+KqH^Pg0JR~X?}DE`Sbn`diI>B=i0$)^2yk)Eh+Ta7MtcB5gtUi z#J8XRhcg~iG@5|RLORXzb}>tZT&k5=Y!dI)K0srw5H(bjQD{^ql#M}ifR5qGFdW$R zZd1TBW5EuH7e!uu8w?Yl(hVb@!R%PBvWI^|I4eOwK&P8vFA1MYoJiJbhk{Q(9Ti9L zJk(P%+}JfKJYRoM!1Qf!%Ue-E%}rwfci+R5Sy5@?mONjGk#W)wk8n`4?x&FdkY>NjU0n_XEeC49 z`;&ok=1B5{B)J9j{2n&<2-DvaXWD@TG$!@|hV#{|5~V z7ze-&H?wUp26a3)1yK~3)Q;db7MEfCFv>x49|_@$Sc!5|@dIlx4cjz=+P+ic>JI8oBV>JSj|U=@Qh-1^m=y?AT$Xk$BgQH>$}4LwJBg0o@1(nQjOd zZEDs#z{MfgD5CZJ53H2ZL~Jd9k+9JGp>=rWB1~u2CSp`PELpieIHad$387-XH~)d5 zq#g5uAds%aVuTf+NL)S+yXZB9;5`~I9l!kBZA`1p=8+UbQ}Z>QA^?K>2?3W1fW|5e;~UzTgXlxY?#_Yy!F$1a2zCb)131;c^3XMLAG>B2DC5q-*V)yivTer^V2d za{m^QuyJR?DG&SKo}3=t<*Cfh9vX(nOYIvf-tI+HCh>P3HDTKC5g4#~@tnu@X|94S zscekj`ox`~b7Tq0mTHp?(>pJxjMB&*YvP4cVx<_To73J(6nr!WEJhXWf6(UCZcIY0Va9s2jxioyMqx7tAI&1@Gbwbp0J)zr5uUy$j4UKa$tKFf!>gLsfELEz5^r#k7Bu z*Zvb={^PF-ntrh>VgLt6CuL{De|-G+{?$R%b2(&1gbz6_4il~1AA%jkszFUN&_o1b zXi1SMP=b;-+jW)Bc^3(nL|p;XyU_aif~1Te%(seO;6_>XTDj`_(1f?soQ|ePUXH8j ztM~WQ%eFvsX{j@QBDl1YkkJwn5`?${^jG!|_8aI4-ym@Uao{;ZNI@Bh1pA0%hH)@M zG|wAVqew8<>O<1y4`khoa>z_t%u%9NCm&4~z3AxC!;q)2BDB?$WM#9YWpeCNcMXq{i!$~zG#b#@ zQl;ZydMS0(CVvHJ)07u3UY{8qP~b!`=i->-#b=C3V4T5v_a?|vHyZ##rWl824rXE~ zku-xI>zLcAy5o_-G^WYXXq4ygg_E=BT&JmPN59ut9f|Hy-JFbLHt}()plMbQaG$Po za}f#DOHsFy%?NL=j4x5(7>A=e1 zgKRrw<+hIjJ3@tNR{WvZ$Uv2j$-9=}Rh5;A8JtM5Di1Z{l8fW>o>#bRSDOtN-Jwv- z9*xLHi;VXtQ32SZM$aBLoE8_MA}y%o_ro!kX5W_&&M5LRI*eD88(wbGn%PgxZg8Qj zwHKI9ayA(Ko+)c`FR94zT+Dyx>fhmP?UPrckM8+QZhHYJ%!+mY5%a*QRgi#T$?v!c z`wjkTJqJjb(p?LjT_JbN|Mddbfq!x#$Xo?0G(@J*Pll+kY)+^PJt~2pY6n(JnISV^ z)FQWpv+#V=FYZb9iuiN-X0SVVDDb*8`|`v8hVDfvisXZ0U2yY>u8=gZIkHEIMy^u- z`Dd-qx{G=X5}KbCxU3c0BuywpX7gq^SIE=m=nmT2D6HTn*_f~(Ibttv-zh;g{slV` zP9vfJs)hrHxRQVWB}{D~%mNezo33fZb>`j)k)5fsBKP-z2tu3Vmi3{NUXwNPQ7aJM zAEB~q$G<SEI{?2E$~2`r?J$=BVw7<)H8J&JYU*n9cE8+=4`gb; z?iZs70*G=>WT`GNCkU4WQLc58IZrbf`O)E#9ceL&$kwkgn#fu~=Dyfi6>+XE-)gn8 zSmd1p7P5dV&heoW693EX`Ibr8VYC8?rv!$2{ZUqnZ$FbxLoTTU%5_|uOA0<((svxd zV0_*AVEy|b`r1vXY+Pu?Ve~LMM7G?S!GXjyE(j;)s-*JX`NL;k_p$XMQ!M1;*Btdn ziWVI@tL0*9Oet-YEdxnQW!b?R8m0#iqTorI$%*CIWNE{R@>ls~1lMp7eRfFo_&WHv z8PBi1aQ;Q(k1_pU%GS5H`SDKLM(TWhxWh(f0emwq#ft(u>|RNet5}iMP%@ zjp304_AjiB@+q0Y7dkdq)z#cHicBWLUWtSdwq*P=-hjgh^t*>ELUjyy6W z`4OU%0|!L?t#-!A`kN!6g5oc=`{80e#!TiH_b3@=E+kXY&!r|eaZ(odSCNvNb&;K`7{s%G%nV8x-eA)i|duUQx z7eo12LaQW>9B4@kYAS?d{pQuXk)WSR0+yj=Z>LG#K-M`bWRGgaNjxsAn-@FN{3RAo zNI75l$u-}6vfU-j%}dRxZx|>;#{6Ee$@_MiWy_z<%jNs=b`A~X=tf`!5lMa$ACK5e zBrX>cM2e}Q4YvnX?cha%HjYCL{PYV3bD5+1yItUZag^4J$PmyH#~o9j`Z0ywhQUd5 zH#62R%#dGRo8^VnDQzl?^O}niHA|MPiyLFu#XjPVSZ&F~4+#cXPpBc-iCZ_zh%xRJ zu&J4A`f;k~T3r2<2I{s;V{OiAv}&VBQ_HN4ZF!m@>djFJx#I{Lv-k_N?`;OG7l!p4 zccZx*OtPDrRprqkaQYKkk$H6+ok#~G+w<;IiPAK|tKdTTUM33WpGUtOHP%M-OCy9TBNgN!@X z_&?*oH%Y3P4{}gtYDXbNsavi$FN#kMA5t(`btiY+@%2?EdUlaB?=e#2Il}zX-7<=c zd@L?foC*uVx$2RDzC_LH4GS>HI%*k@<4P&sJM@HNyWt#0hmu+`4)ZuI+u`dn9&ri1@#3E2C#=>@X|Tx!VIcPA{Nez5>#a8v7=Y^V05Z~<6Ye+VOIdK7gG*t#m%TT1RW@^L z@&!ope}MIP;y3(1$u-|@@Ob4Tc-dn4n*0db{hmkJWITh0DSn4A7g$KMg}~c)9X`^7 z&WIOuL6;>-)Zso<4f}>%W4)t_HFQ_pBP889aC^kP5M#u~!mgEzc{E1Rr<}Azo{qa2zCv*T21Ik|{%Nn;?Ks{XS z>SXxo@fqf`kOiD#vsVfYBi1nK ztgKmlZn3*q3ltbZ+o?*JE@$L=P~^-@*+IV=ix2JSUyC^nR27=CUub&$b-alEUyc@G z14k1{8%GlxM}X6pwV3kP%$|+eSMY0R@YPcJpT_h>%6~VecYXV%(5y5b8bK7MVlIg0 z##kIwDl3z*5L+1ZoQHe1YBV@RMc7^Bg=&q=G`^@K)P(3nYLH*# z_1kM^JJCb@!aa+!IUwf7C0O++i>!W@8WrgXn*fP_^&X%DrE6N%)Ksi&lE5t@t;QU*c@o3O zPw%(1FL6X?6mzGSFk7rebrTd=&P7}=q&e{IX;1lIHXMD!$3VMO_ewQ_Bb)rbx3p08 z#iEeB&d=v;sAxna4gFK2Q79IY^>8HnLCDcQL#LPC0FE92&hr2;` zVHfo2=+42VGovbzP#w3#V6Z8oueh4nM#s|Vb@-YhNzA^tJ)?i-MR5AV`>Y%e`B!XmR3%dzq^0GKON=mb?ps*cLdi}o}7^8 zv45-?#4Ve@2lD|&L=ut|sc}KL@X=dI_625!WJbfd?5^Ule9z2aW5f*Jv8rZq{3QG7 zjSouvCsPnJ1i5U*sQU6Cue|-68|j0s;?4816U$i5r9Ry6dlUDb=l2esldVtli(cO` zy`?0Arhx0i6Tjp6!^gqF;=PHWi@J>cVi=5?k@QZ&ONNg=SQlIftg#1*cL0n(3RE|t zH?UWr1P+6CWR(s)h)*Se#Lt=BAB7XUhru~fIulZt#dE};2XcEq+@%8x_6urT+lMVd z?iV85AEj8G1J?*zdJ9QJZTMS?Z}L<0DAz?qoHJ-SjCD;`&CpWk;}k5^Ye7~B z3KLAERX%Z+)DOnGx<#>ZmSJ25Tbi^gj4qV5EI$KwIlE#2TQ&;%;P=v*`j5BQ!G%@mc>z~6; z76!aH&arWF8dn-)a(}+wznzY^UUA&?GR_5=>{@GiAeGdW6cI3tiXPCDNpo`0&sJ;t z@xzqy{M2OVM2tnzw-y;dD=Qve>sbEy)J?>eGvv&!WkIup`12 zni+H0Ruwqh;qBocJk`BUp>X}Dbapp9MA;&j-i?`rva(hxRc zFB-ajbik^rCcCz$H4E-wx0US|#3ezuneXl~Q~v-7&S|51IynDuM%@gxt>^nvwhbi9Avl_B1xY*O`Z4d*bsvg)4;qBoBk0F*`%XE^3Z=7 z;F3N=Eq%dlBtF!*WUrf%v0@>$k*`JWwMP=>Dkdw<$wzFDSkYsib~@w9?jNez+{R|M z;v0a+LS9<4AYHi#wj=E=3@>S**GtKB)d-`AN=Za$4Lo87A2etS+o5;F$KL_YrI*4d z-*N(KM74LxvRsW<)cQJ=@V!Ac0d78m;YPIx%am*Q~Pi=1#C zTf&w{IZ@#v*RkM+1iJAV6v}7H7s8!M_opap%gDv{o*0`<>=(Kh{4K<{1v2Qv$$jx% z*qhZ}5cbqn75lVQlYVBJ3Vv`VsuB*6W$G&Gd5Ib2HCn>83WIefVyG3Ld;)tcrq1!^ z(oLxfRA4{PeP-E9VRs|Ah75f#o2e_8V5L`mk#9tpj;E|lGal4jiNN>y@`3ZL4kHya zfl;a@PSlQ)LDuA1tff8R_`bz-Z(UyJ94I}8BNFwf%a#dc*1Xm7`vA<*02ky!0{Wo~ zC)*D>HgrB%E?YHrX`JDmhaPzf<0@^lwwb)ag)E&6q|moNPF0te;-4PN#{0Ujw(?b>N?+NPgQ2neorMAB#L77C3hKV*2b_uOESuMDqsWiX$7P1Sw}|Z z%41@2|?5seQp1H)uL z7dt6*fF}Hi#VgY0M{a{uzm2vK>Lg1OnWviG)hJfjpdxXemRE1py>jhZu2MA@FQ2%CHQ5C0$Mv9w+ zwdpmK5|S<~xSC5OAxdjwu_0`*d~}~_&5K7FqVW_F_s7bIsdnD_A}%{W(uSNQ^HnM9 zH-cu1aBc74H&;CT5pT*#Ji5sgB(npVCbQLafW)cw=0CK43$(L~P#KAY!-bz`Bi+_l zu08?3Lbh@;4d4@T9eP+{KO7a{)m~keFc(9Y4z2O^?fB3=`94a!cvt=I3Hk067{e&# zh^>aWl3>)?>ur7J!aRJ`sgSFt#(k( zCZ?4&_J9_K&F4i#c^>|l;hFX5LbKo+UV#wn<1Arpz$y9+M+T+IO0%|P zFWCnAP;V4|fD-p5u@b)oh;MH`@VN(gg;G>>_Uy(rl0iDjria;ta5{Bnl_8DJvXn3B^Wm)D}$f2NdmG`5dkJr4WL6V<%`dw@L&n+S*RsOftLB0Au^PExLU# zwQ4+XlrSSaOXeE{D#i_@$besmD*fFf2;mN1qc@r>T2#~COYvu2+!c+ac)6H}JY>8z zq0QBI&!8i$o&rek%8CKLSe+NpGwwL67Nmd2tF>~%zK+}ifIoNxSULPxubPskc6I;{ z@b?ESVEz10PuC1Um}OQ5AVT7VxxRpmP97X3A+Y?e208|>7VJ!6w z#fl^MGQ#R+_JI!}pbypa4Nv^#TEo~|@%N*#XX&eUp8M6OS)HAp?~?mtBY|z%V~0pY z$w?j5gdtewm&0R2ZQ^&54X4tnCT#_<8qor^AFjlyHRUT@*BH>li};djKG`iYX$Cf@ z5@7ZR?mDbeTCONnA2FtB%$X(9zUv$&h$afPT|`K9%F2m71&vnM4_3@z-($#!QdS#> zmhafcWX?#hU`t!m+L@7abw<11P>VksvKw`vng)v=t$|mnpW+Cs$W#z(8RoG}&KIR= z5|*v0cXCfdwV=l6SCo!jm9xyi8!_bHbJ`{D%_e~Pu+aLU<>S+_=LkGS>Cgoz?9-k;@CoI0GeUj13M>6Y}pdxI{l;5Sj)TgHI3B zno5g=O?i<|-oXu{dg8b6fASyW{VIF4*7L>2t5|>ukluoH-g-+25iABMLK2WH zxgJZ-ON~skk&#lnA8u>}m;oJtB;dYwR)nFFDOz%;MFrRJk|BMKfJktVIC3Nj3ew_W zeI;JmZihhsEylF9iUi74gP}w$S(-VJN~SU2=_&~6X6x~lBiu0r{y8_(=H?Wf0~n2` z?C1l-(S|k-DA?=n3!QJ55c~%7C9*Z5hf5|*p9`G_K0{wY#bf!gkZY*S+-1P0Q1d?y zHyE?sjliIGs4ef{^w&GAGWUP-YpoD%3h1J=Yfm}Ql$U&I_!LXj<^k0!({U5p`8D8u z<8eR@31w>s<4Zc;n4l){8ghOj>s_EeE*xPbp<5@aruBgE>`75X^=-1%6SCZ>lsMYg zRH6v`#Y5AQV!V*}z}e*4sVI32OFfnDwm@|inW|!4@KGo`pOmt;3yEqY@I{KoQqN@F zswDN&b)#f?EIadM;Mj! zIBl1}l@~&z&!Yy4EhQ~;C0lE>5{u*G<5N^PTm$X#h^@EUXXiF%wHpPFwiV{I)Ui2} z1wyzpaPRlr-<8CtJ74+3=ip(y=|!&*^9P$vogs1Tw!cHh^RPwTl73O0QVp%8RABZc z9MuhWHw~ReSDSoynlT3kzYhZSG!|wcuAq?h;5!t=-$B~neTB_A&7Vn1y^YY1PED|L zeUe=nL4K44Ph85aw#*{1iPsKlD6dA(mom6P&>ZwoXjO{4YDK}MINVhTr$39`($Bx1 zTj`c?2f>#Yg+#6ew(K}Vz$*M%%9QZHg&83?Ig$eY+mC5F!C=;M2146B&9J3c;l^a5 zFJrBMNl*%y1T6pN73=p&@Hz;PpzxpDzvtP)v@APTB}9ie)H;M8{YSV^FzRVa)h>g; zz9>V++I%z7Rd}yJ9)B9ow*(0!n2=@8k&xkMCRtJ`i`q^4u4aLebX$k7kV z=Bw7DiDxeF@e)fTS7k6OJay1Nt> z9EIRn@Ee3)1@|r@_n3(lMwdCATN$1OMe#<=PvcIKtvLZ^z{993C{FjV5iOMtuJNH@ zRBr|s^7Ah<2{&kHB{qS_r_>oR8_pV?r%QjVNRF~l-AWg*Y(rLy&dH6NHaqMT40G_T z#CT@Pj}Ga59C)7@lAWT~uDjUF*DyJ0at|_*g?OnkfYM-kQE}(1OTlcYdLJHoir!%S z__caE#SQ;Go<0-}8u3o)fXxn6!4I78oiHelk-AOl$0ngWgT88uw9mBQ9%1i53M`;L z1y*XhB}ND6=5uu#PG|D95RP*vw9A|}Y$Oljc+M47kP*-M<*=CO=;t&**7Xs4D5O2McpDa(*I$cJ7K-*!_8jCu@IUv}|hnPNkVxy#%< zg!sGmq~wza2-KvvZM-M*;6y+gMWtlN=}G10SPyST3y03&At82C44`K<&{?Zm+fs<}gK@rLq4;ec(xRA^7(lP6ARs$Hgt{$_ zu$R3iC1E+_0m1{T5}4-kjYsheLz;JR0AnlaW=iemH|&Y@@89>gP`wG}_>ijLdj^HP z$KNROU8M$HL#0yFSJ&DX4ltKtdpi4zs& zVz;om^rkCHDlZC;74?kc0tP7tqk~GHwn>?DvxbV3ii=ZV-t*mSX31)vqUZCfh%f~W zWR9pm_4mDP0K1lc#9Fu~&dcKKRbVhrr%D=T#b6xb!ug^RJuhyyxBbH%n{1#%J%$Nmc1OUOKC@8Raf$PlgX{xrMRzM+-v z4|Pk}38V*)AQzrKe6y6>gxVzI*Bc;m2kr$w18#;5fW(V`wcPmo#s57n`g@cqLE(oK zAiwJ>@0&h2JrFnLkihz~S`Kq183 z-skx?j_PpcaeQ$TXJZrY*W+Vn-yg5PlX$an9t!eEWc1ZVGKTU|70~AGU{FHWvNIwD zkV4WW^;m3iRBlxSx&bcBC%%`8T&4!aAbyn^kbiraipXP^7dXgMdBMKdoJ?a+ zUgt&i^)5)c4e!vTX)-$0?`w4+C=|`4f|HY{;WFmlY#E#(-d8kR;}HFWorY5Z*$Pe` z@vVesw$|xtYY(a=aEtwBia^@6Kw1JmD2ba%bhia08i z9r)fwRT+F?A3#H6)9y|V9E2*cyEGbl?$usjb3A#7KO8d4b%Q;u*cW`dCH<<4)o!;# z6_^7PN7YtDF^LI1Sc$MM1!GaDG(IP}HAy2Wt(@a1M`N0VOjXE4X5p6|Cbsh4 z6kH8-jGu7q(4*MM2G?9V5N4PbS?9(z*;gy^V`>Q+@sl$H6hRZzP=-kFt`wo1iS(>V zK}bFfW$I?OQ5@*OH{WX9DQ1`$gikcf=v1Inoq9J1+2?cJ)cqucML0hFjKjSq+YQbd z1)c8=ugZmRiHXky!@LDQTT7&Z(mFQDg|0jr^ex1=D?2J zq+c^{!D12U75I#YVL1{QtO*k*Vj$NPP{M4aaA+xfKwyN3pvJ2;0wlEOj)ZZGt>G)h zkfTj}i~84fM`K=_ns|WiI|FS0|A#mH>otK4g-L+Q0@90-<7$8*BytY9+Q+gfIRml} z{_$`kh_S?q5}!nFnByf?H5`Y}5+5qNhSQR?+CJunOn>%@qw83%AVn#^oC4%;rrmek zI~aAkyZwOg!dJ4t(H9D$Mum0(*uTQq@Lo)yOS9y{Tvc{XU$jP}CdZnN%vFHq`}*xu zbtrzO)%wha$UDj(%}aKq3>w!MJe4_5!yTcEG!*cc%VZFN&{0}I{2ptCBv|m+F6QACxr8D#$GBu~ul0_39us(b2r8f&d2OWf zeG<~XKt-4`Fy?qpFZt-Z^c0Z|Wg|L{z+|B1ia{lVO14vxpRTu72?TXOfk`xe93??o za{C)EVVFU^GJ={*-Z7eL+0@Ih<9BoI$xAK7}0trRHPbG0KaVc64w+lg9&_9tw zQlD?8HDDEfm}(6#3bC0ee2aR13_r+iw`A8xiX=kv4*C6YnX7Bq0D9D*Dp4CBGn4u= zutlWj{(;#K!8nB=KDB{3n;51VPLy~}8k53zO}klL9um_XzM$~jK|wJY1CllR@#n&C zdv_&8oLiy_$+Cm1&;M%fI+C7>zyNb^0<4A6{~N#eZ*P1F@|#vOGJt$0G9*Yl@6|ar zWOx=d%=vcoaAE{Wi6JB3<=zeIsVeHdg*8W^A0*oD(|w>n-uxilX{JhB|G=ACw=djz zopGMN`tovpjq3fb&cZ;pHin-nYLNNJSxS01lS3S7aio&2KVH;!6|-D7ELyU z$yQS(zGO=I)n#E?*uvdYtoR3Ps?NQi9h3d0>*E}Y5v>K6Bs|I$@aIc%^Rq^E6g9z< z9CO{Qs>U&M&XvwnEa-_fT>(}V&r|=E?X3z%@kn3eRtu_;AM zvwv8yYF8YU18gkha*7r0q5TL3cNq{&DhlSG1PfdpJu^cNQO>7Y)k-LDV^7a7NjyUH zKtr7$Dx3hx)0Kd!4as7;c0i-zC|$_sFM~(DX1}@GIC< zVRFF*?%j}&6FBnH<7v!Dy6cskv_I+G<>ZAN^f`RY5bSNG84a_nmZLY1nv*P8Bs*D( z-*TlGNNgOkmCm0jpAfY>dAGs=J!g?ZM{MRUAhn#9*ez|Y^kv>FS?l^l)T?J?0XR*R zS>C)5Bv2Zt(5##tnZ;_2!R2n4fNjTJ7@OmL#=1PvbNnNh`b2pwj0p_tu(fv{_2np*doZ6(!^SMHfyKSLKs z#`k^j09ic{N9Xee<@*dAHFVDi5C!h5=Y#~}8NV%WvkW9c5BSk= zqrp4823UvN?!id~yHA?^)@)O3ffPOybm0%|*W>|=m*kANMwo)2-|#}~5aV#Zc8;Q_ z7gE#EsitR*LsbOT^ZfO0Y@n7lhk^j8sRu|7BK{XM=XYN_L)BDAqd(?FEH#D2)wRKJ zxrR%}eCyqDB3(n(o;>#(baCdy>KNED71vlHoXOx6_y$(KrXKi6xJVsDARnC|VBcl~ zNS|fNrXGZ$GlZq_8r@|-E^lZ&^EZQop5HgprpFG@L!M=vq@5HU6dmRl=cOyL-R|JL zRj(9q*e|O;y(lwvE>@#tnLPNx&~Q&*lD!n$Ju*Q5DB6<#_5jVjn6m?k?eZYks|!en z*muY3o_0SOq?^ZD^hZ(-dms`XL>r#(%hPXj{=O~W$u$3fESMRonfpYfJ&sn2o?|5R zXo%zx1JoJ5kS{Z6@7|MP7*4G35dQ_S6RA4MrWC!BmAIHV8#q0GH?u4?L`*vDJl>s? zCT{6VF>##3oJcis(NQvmJY!1fFeCQ0VN0YMo22Z-RKCEVo-41I!(2%M24I($#-J_H zg+Bex_en7MrV4-um)3lQA647sC&UZP$#8A zC#q?UiZBDnC<4}=$dNPfNSP6O<*-~QrMx56Vzr`D@wBQZo4U(BmgbRR0rma}c_*U` zVrCHIQH|Qd!yadSlTsY9k1TFTa<$=>4yWY+$c~Z|5#O|kJU&Wj9ZlHT0mOiBh>1j; zgYWRDuT3pC0t>;V48DCxiX)#6~jj$URi!poc)EnPnu=xYPnPn7T^p#!?pz^he%0aQM+N=x<8;K8J^luhFin`_g@0Ec>jz-c(;*q1(T}}(_W>6h!!br=Y`WvcD;drvD)X6;4v-uX(rS@tb-59{8_KYTR57o!xutgfG<5YM zFpz-Hxm4X3k2;k3o*Z$R0>Z(uMeYGD4Yeg@jp7l&#);+_=2oT+z+vS~k}yQOjZn|| zY}9(eq7;tx$-`62_{r+ALW+~ua-pzR9KCW&mt{J4(Lz#~Nc-{I3Yw{1@;<+ zA-}PtI|Y){%I#o3wFg#jFOTCx+E5LOlMcC{`H<$3b}Tu4rUGe35HPg!UbIxs7HTb29cs-R6MC&oXtVoNpxB3M z0(^j@DT0_PurAc}i(8Yl3@6eFOl)@@UB2W>v!8u&V57!(^kRgWLs1h4)3T+AiX$3A zOfjCOEgd}3(*;xrcrR^H%}lhvJtTS18eRK^0;VS4QhC{${Zcr2vCYoy9(wsG#O(P?|1_$|9n1J}qK z(XX(H$e-5U_t)SxjykETGWei2C1SzbCSfHoy&}81g;S|kwn;6 zG1RP^I{*QMN+q_IqtN&}NbU`iq!Q2+2dC+x)y}4H`9Qyz$(2wfH+|?oDj7 zd|38C1J|rr>1Z3mlqhUVecLjH!Vu?K+g(i5pt=F$J)Jxt! z(5#;U(Ek6P;Gln5cn)mz;{}sLx>=W1gfqcmS@_=dnFXhK*cP{2_H@r>J*58-OoU7tbqQ2 zQn9#O6v#;)WqQSs03+L7s4GSpDb&N&Pc*5S!#O@y-Vw-=c*W0f%R!Kc8Tpb^Ml>!l1 zGz_z{!Sc|&@Kar%6Prr8Yt|*IGSOwU8_ja6N~}jvnF&rqU??OGy`N3;=`cibbbJI; z81(~YXc{$nM@=oO_feLb+fvXuPpeIw76YSKifFlx=aYbtjTr)^K9S(pek5=?RyE_c zR6>RE0;!?pvd4&aHI14O(SB+TxLUMj*g$|J;_-ASA$ie2jSD&i-gK-TQ2OIlN+21F~?hzdJ?a-k%25}3~*+)v3)t>ds$ld;! zJ>3wvXn4&YR5j#dYKzyCJK!ik(WwZ>wi$KCJz7_+zr+unR)x_g7CZT)88ZMez7 z;Lz6^2sUroh&>O|FOWTm7dN_fI*n}=LIJ*=J;`cThFz;+)vbHg-i=kos5_2%*|N_nL>cxe_Fd<4i;Q?MnrZFWK{vFfShE?BlZf=D0}lZyDvzc|CawuSuocd?*P^ zZ^>%kb#RXj<+Kivsd5_&i*vN?4OPrjijC*AmDb=AAt|V-EQHw#YHWl-%uAQufkRpeoFnq$Q3}3;aDFC&>b$c><_X8kHaBG< zb!MfYw zyM`U0n{0GtJ%U*sM12beoB&T}u55vrcDBihZg;gq9_H45u_-&`UV` z5PdpNfk@6z_2X){cAK2W3Zcqmhj_N`2N#c~%SB70Y`90eswX=`pKHicJg`m&qEc)S zcL_LsvuIh0MxGwXn8y3{hwU4SJqMXV1T8s7@%gD4-nhO!;KGtQYRS9ob) zM4G{3@TaK2H;4}AAl;Tig;%Cy&a4&;PHHeqUoE@%`o@SUo<#|#^m2E>Ijl`E6EtN+ za6`4eBh)}DGscg5Y@$2RtUdf`Q}ISOGP`=_JzjXFJ#DcNp1dH<#G2EyUyYASW{*73 z_brmIlG`QY2HL$%tWo=IKKmUp#)UT-lH(*fQ*7hc4~ybZfKs;dDlO=sRFt<2h>sVP zR8pBaT(gtt4uSGpY!fm!rU!Eu>4E#p5bKv9h1jySPq6yHdN;#{9fWyO7M>^0xsp>k%^^Hv1py{hq_BHKGCPqG zS-%RLSg}eXQcXIa5*z}fx|r67$(S0g2rTnOsO$9NbwAOzcMh@6_$sAnys!=~hXmZrgj=@-o zZ#wynu^`x_xx?DeBA&&sZFxte4n1mfjJ54chys+>$dKGY{Gc;9?W{ODQEe5jS#lQd zS7ZOf`Z#o37Q0=~fpxrrWh->;AL08KZ!-r6)7i#!*aEY{BDLYfwc~->2x~8ED7z_Y zpen^UJjpmNQ`Z6)_lVl?Dz_zFGMt@?5|#+a+}KWuMr;O!gJ&k`L7n7UvvyF!H(+U@ zX>6GQ+FQO!lr2p8%cEEqHn_cfA`z_`!AT*0sSpbWTGlt>Z38kBaj2Ip&Kat3(M6XO zSNnR2Yhf%Z>@LNZ*UoXO`}H+KduipvmGY_2RCMDTTj#9CqX*K%4{omVhKtZ>E?D(a zj_n1y6$v_>x@tboZ9tD-K%kix9xTERz)%qQVRsDhzsgF~UD!*U*{~0%^+RA8j_VUq zGC27M4DM=rpxGg<933In$Y**CYz%rD@}#2EV`6fKjcEH+TJM$CaGQ%&rqQmKq+>$Th-s@dPMQh#z@+g2RZde zd@nXtn!kGMk3mkkB;LtRtXTN5PycYQL_}}WO^GSq%%w9>km~|(V4Glr7`ImD2N%~p zxLLOJbRf^RZG~n858WuYaikaT?zdPMH&zD-{b}8Ga;=KUswDlff^I)R6?mM&44~;U(H#Zb*Vkj0}WDC=ZwC53u`26-JdXQq8RQ(`Aj_>2(~- zqvrb)1~1ajNsx9@cfCCs-#Rs&5_M0;hMuzK5KTksfq)@6+^REBeT;EAo27L>={|gL zCJZmFTcR>(tbv+kjanO3oi)il!QI`FwoR?u z8JFI=bnk3`<+x2h5oPSf_C&Gd<#$Z5tUdgac2aq@dr`+>1ASO&=!Jc#gh-}!x%d55 zWCV#>G>?yQ5bk!GjMC2H!AgQmO zxQ7<|=2EUPGA)|5L4JE+fQ(9_ETl|ffiy1$u`!-n#$-9i+;o}c9e6ZStd6ZpNB0)T z=EFc%c6xg9zK1cP_(Bt%@c!7^{e(x>{l-2u!{cB(&kK6j+h=ND$7J#=_y?T@ypS;R zGBDqX+z-?Zp?bg$kw(oph)oJnaZQxe6y%x8mwdpIv;-!7B*&Nd)FlrJTv|P&x^21J z^I(l0?7k@OLj7j&_B~KLwc9Vvv|6b5EdKrb{S!2EJ7Go|~iz2Pu&exgPOmRsg+W|6{?F-Tv z%&SCMTHA&^as^y&{Ut`1e6<~$Y~UKe%lNWLf)7UcXhT7i>ftuRWr;~?SIQU?@J)wg zDOD9eL5juzj^Y$fB`vEiW}+vx8Pxp*Iaqia8Nc)7MPZouKGZSUr3A12jK)-LZA${Y znX>A>^#p-G?|2GCUEA&$xDc;O(%NR&*QgyI`M+`|}*@y8XEN!P7IJZllIt`Pm4_C3|*pC>)T_AFhu+eP_XR z@f0wN0kZ=EzW(8`NlV2b%NPTDfbawSz+>Fm~JaU)%4LwE+iq8VQ(w7V%pzD{cNXO)a`tvln!ph$R z5n9N*NwDNBIfiKtB`wz0dqbX&VD9X=o(AClkn*%o4pqwk}8eCixR)_*P!JZ>u8dY?Ic z0aLLMtKN&%n&(KJ5Kr$RXPOp1fSaVxCa+Ydcq=n36D~TDVQNcpUsdvst+^Oy2PUR2 zvdVZ=zNi^u_FLVOY(cSFEsjfY`fAWl{g-+SX;{8}Ni$KBfo$Pk?(Z8PFwc>=vRG_O zB4U)edKx@S{87i!%Z&wY%5mtxfYjTJNZ(0AvbVG|y96xDa9uQI)rzrZibWXKBBjp^ z_CC{F><8Jsk)1x4q3ue5J>N;C($d1+eVi&}Qg3T}fje^H3FjT*_oj#8QTVhWvKAQI z^ltOn;=v04Bb^AHJ>pRyTA_+Coh{<6huIX81kqv@e>ZY^{#5ptN|W>sKjIZeSdd36 zyp}*vBq$IL-wI6$k(nw(()b51AncsTE)2Er$?BRv7LOh|SPY_juXub1%t(~WpF6Db z<|ZaDGoZflz923Ipq~Ura5*ExZZY*;u!~}BMe;wx>gK(T(nG>i!|u{|PA5ea!0v^A zKW0rAN%DpayPt06T6YBFJn}1R0b=|;UX|3uGkGjIvBJxF7nl8K6NTx%d9SJO+6ad- z9K&4cByF)O>08S{?d)*2zHvFt5VBK!`0LSGR zOsVWS@AI*j^hOW=;;85n&2a&$E+JeFs?CCKEV6SnFDxChep$y(zO!#CnVh!RM=ZBX zc;XMF&lGVUsXV-n$#)FSp)bNsPf$iF@e#mapV?U6mS1f0D+m_xwVdM@=I&|QH-&q} z3#Bho?~oKoN-BvS|Or&ws6G z1=*u;9w{OtV}_;J31MzV#}d*H`fDGY0kw$6Ni;H<9?toQ23wgS)b<%VFg0YEL3Y{jcqw}0=(ryO5%OxevG5OYtMK&@kccI zwV-Cqw3$?6P>nK?&q|KU`e=Dq;ZW2~!E7w$Zlx-zDE-OtJ5@=-H&zd;AIRW#N{3<^ zC?`c(!S?L#hGYvxM4@PL%hL#X_0H|W7ucRc-R^X>O)nBZlWWN;+8201gX7La=cYCv zT!&mfO=9kavk~2KlAJ)=?+bSDX>E(|?Ab0j+QIM~B`)bKPxBsX3Gxc6$bierKqMLr zHNQ@Pb=d^5UjZ!83Ig`HQ2u?H>2J=!@7ue6cL^c@X|8i0k+{|?&xCypzsUgWBKsT4 zmKHy}#Sbu%I_l6&8JT6l`ERdiv#9R{0R3@kcl>=l10rLLn&c$qVsvg$U; zwZ8@^w7Ti_Cb=h$v4*rXS%kn>4+6U>vv4mkbeQml5uquin@WPnM!n;_xQSwO*0#B5 zyCW170zrMu+*+Bpp>DV#dE>&rB+37HoKz$sK$os^%Nm4$D$Cffc9@c%#x1fcJcW&1 zbPPh=*$yH%;8GE!TCi2DADc+`GAUw(-ASv~Ar8MeR#W#Yxt%{}P!JBbq zi?gp(tD{y`RGG#LjvJj=aW#NKdqIHWd7ra+BuKt~Q|rAESl{I>6fcSTI6K)uK7WCB zje$a#GyMbeoGd=!R#FQOc2znm@GP9d5$HV8v|wvQmXbv~AUnP+a;8h|J+fz9$LAi+ z()o4^jT|phaUL{ii58J_YIeb?2-eeKTQwRP0~pzrx69ky@!NR#W>3MMf+w|E9x+Pa zw0+6-6lR4vsD=iKZk`_AHDT$U`m4LmnkOzu}C=7_jU!}dG^x?8ofZ-WYNifptCKDuNgOVOFQ27 zb+i(-b)IH7_1uzh&6nBg8}A(f#6gX?Iad5-7jU_YknkU`l7ciG2l(6p8(<*;8(`Ug zF-L5zJ{juU{~N*Ij978$o{vZYlOVtX?mRNRxhGw~sb~&61hU~2xrAu<{2YmN7C}(& zz<-D-6H`CGeIeaJDy3Tz*r7Z-OlRA?aV)etr>T5%SFQdL1=Gd+YjuJ!SGxkn6ZhFgzCizoPvy5=LSuKT2g}&y z0tYDv6Hy05ixLlOMDd6V?;{Z|BLgC_$b+;)iuc-iZWn^tN%4a?eOCPSyu{`s?!Zy8 zE6SHb?XQfEwaCTqo93}0HtP$aRTMWaFqGP{!}HcmX>sVhhRmRQy1if`tXcgsoIus8 zH(3>Ejn8;6&}D9P3n$W2EaJl_PPzOt$M_*YkyNyT)$n@XTZIW?eEw0b?LZ2 zy$-#S%j>7Bpv!0NdQI-eppEBJaxLLlp9h&rRyDJ!g?y0aVms}sK-q4; z$c_SqOyq9YV5E>ECfGgEe9Tld1{Wj$s$UIbh)iMUH$p+z0WlAJMrd8waZ~5b+?kU? zV^0M#PMOP5VgmR}ovQ{>%58R{_TyD*JnWBNWk3<2-(G9~QzQHj#d^RV^?z2Y-+Qf5 z4+aJXfZzpuel|3~p`L@OxdD}}p}wK1wf&!U@5QOeC&nhPDaIwmsA`zz0sEw7#6iK1 zFPShA0jB{n^#R}C0wKKt^zKj40L%MN1L&>2YLbEqyi}r6LNu>WEMszo|n1Y-s>n{|7ige~oPo?d&7~1lpaAHX1~YpHAezX+mF{3%@k10Dz% z@V^>I=!aht2;1rcs(&g1L>fh`EDR;Atjrv&e?2szwVQzTSHMPCz(A1y>^R^^3ScY0 z7@FUq|0px|o9+D?_i-ETYbgL_74X6T3vL+oFL6cxYjwZIv;Mess}Ha)aJbia4m7{U zv$V6PSo}%$f z^goOg;KaNx1Nj>Ibz!}q(DB;;1pU`Cd#_PnmzDX6dSU_)>;BIi{l_9RuK|Ap09e_7 z+HZwqel~H_zjX7rT(#FHzb-5D^JLe*pZwp-9{<}Hd=3A)RLDJ|7-ZyTJ=BSC2ao*{*|lz8uqnB`%l<1yMKcHa}44&=4-*; zpO{xJ|AhH_F2n!2Grrcw{fWBn`Y%xbw^Huw?!Q*|{7K~R`EQASAD%z7K3@~O)>8aQ z;NwA?@io(HX|0b%0z2wMy$BzJ&! zkUzN@zumF_bhG+nivIq4e*f|(wUyT_uM<~)vRtSBYnI!b&TsL&uIVOcwW1m zf0}sz$7B3;+~p@t=HUNx>Hb#~=D&UZZ_MS-K@-61*Pq%s0PKH7U}VHW0PmW=r7hCG O(FVj@M23F*_WuFL)$*MH literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..75b8c7c8c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..af6708ff2 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..6d57edc70 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 61c1dad4516fe2174f78954352b48ad8f635841b Mon Sep 17 00:00:00 2001 From: "breandan.considine" Date: Thu, 27 Dec 2018 23:06:01 -0500 Subject: [PATCH 08/70] clean up readme --- README.md | 63 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8e07b546a..9dd62a21e 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,54 @@ # KMath -Kotlin MATHematics library is intended as a kotlin based analog of numpy python library. Contrary to `numpy` -and `scipy` it is modular and has a lightweight core. +The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's `numpy` library. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. ## Features * **Algebra** - * Mathematical operation entities like rings, spaces and fields with (**TODO** add example to wiki) - * Basic linear algebra operations (summs products, etc) backed by `Space` API. - * Complex numbers backed by `Field` API (meaning that they will be useable in any structures like vectors and NDArrays). - * [In progress] advanced linear algebra operations like matrix inversions. -* **Array-like structures** Full support of numpy-like ndarray including mixed arithmetic operations and function operations -on arrays and numbers just like it works in python (with benefit of static type checking). + * Algebraic structures like rings, spaces and field (**TODO** add example to wiki) + * Basic linear algebra operations (sums, products, etc.), backed by the `Space` API. + * Complex numbers backed by the `Field` API (meaning that they will be usable in any structure like vectors and N-dimensional arrays). + * [In progress] advanced linear algebra operations like matrix inversion and LU decomposition. +* **Array-like structures** Full support of [numpy-like ndarrays](https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.ndarray.html) including mixed arithmetic operations and function operations over arrays and numbers just like in Python (with the added benefit of static type checking). -* **Expressions** Expressions are one of the ultimate goals of kmath. It is planned to be able to write some mathematical -expression once an then apply it to different types of objects by providing different context. Exception could be used -for a wide variety of purposes from high performance calculations to code generation. +* **Expressions** Expressions are one of the ultimate goals of KMath. By writing a single mathematical expression +once, users will be able to apply different types of objects to the expression by providing a context. Exceptions +can be used for a wide variety of purposes from high performance calculations to code generation. ## Planned features * **Common mathematics** It is planned to gradually wrap most parts of [Apache commons-math](http://commons.apache.org/proper/commons-math/) -library in kotlin code and maybe rewrite some parts to better suite kotlin programming paradigm. There is no fixed priority list for that. Feel free -to submit a future request if you want something to be done first. +library in Kotlin code and maybe rewrite some parts to better suit the Kotlin programming paradigm, however there is no fixed roadmap for that. Feel free +to submit a feature request if you want something to be done first. * **Messaging** A mathematical notation to support multi-language and multi-node communication for mathematical tasks. ## Multi-platform support -KMath is developed as a multi-platform library, which means that most of interfaces are declared in common module. -Implementation is also done in common module wherever it is possible. In some cases features are delegated to -platform even if they could be done in common module because of platform performance optimization. -Currently the main focus of development is the JVM platform, contribution of implementations for Kotlin - Native and -Kotlin - JS is welcome. + +KMath is developed as a multi-platform library, which means that most of interfaces are declared in the [common module](kmath-core/src/commonMain). +Implementation is also done in the common module wherever possible. In some cases, features are delegated to +platform-specific implementations even if they could be done in the common module for performance reasons. +Currently, the JVM is the main focus of development, however Kotlin/Native and Kotlin/JS contributions are also welcome. ## Performance -The calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve -both performance and flexibility. We expect to firstly focus on creating convenient universal API and then work on -increasing performance for specific cases. We expect the worst KMath performance still be better than natural python, -but worse than optimized native/scipy (mostly due to boxing operations on primitive numbers). The best performance -of optimized parts should be better than scipy. + +Calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve +both performance and flexibility. We expect to focus on creating convenient universal API first and then work on +increasing performance for specific cases. We expect the worst KMath benchmarks will perform better than native Python, +but worse than optimized native/SciPy (mostly due to boxing operations on primitive numbers). The best performance +of optimized parts should be better than SciPy. ## Releases -The project is currently in pre-release stage. Nightly builds could be used by adding additional repository to (groovy) gradle config: +The project is currently in pre-release stage. Nightly builds can be used by adding an additional repository to the Gradle config like so: + ```groovy repositories { maven { url = "http://npm.mipt.ru:8081/artifactory/gradle-dev" } mavenCentral() } ``` -or for kotlin gradle dsl: + +or for the Gradle Kotlin DSL: ```kotlin repositories { @@ -56,16 +57,20 @@ repositories { } ``` -Then use regular dependency like +Then use a regular dependency like so: + ```groovy compile(group: 'scientifik', name: 'kmath-core', version: '0.0.1-SNAPSHOT') ``` -or in kotlin + +or in the Gradle Kotlin DSL: + ```kotlin compile(group = "scientifik", name = "kmath-core", version = "0.0.1-SNAPSHOT") ``` -Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). +Working builds can be obtained here: [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). ## Contributing -The project requires a lot of additional work. Please fill free to contribute in any way and propose new features. + +The project requires a lot of additional work. Please fill free to contribute in any way and propose new features. \ No newline at end of file From 32154a4536d0dcb04d034d6bae256b3a12d6d884 Mon Sep 17 00:00:00 2001 From: "breandan.considine" Date: Thu, 27 Dec 2018 23:14:20 -0500 Subject: [PATCH 09/70] clean up operations doc --- doc/operations.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/doc/operations.md b/doc/operations.md index 99c8eacac..e00b38ecc 100644 --- a/doc/operations.md +++ b/doc/operations.md @@ -1,38 +1,40 @@ ## Spaces and fields -An obvious first choice of mathematical objects to implement in context-oriented style are algebra elements like spaces, -rings and fields. Those are located in a `scientifik.kmath.operations.Algebra.kt` file. Alongside algebric context -themselves, the file includes definitions for algebra elements such as `FieldElement`. A `FieldElement` object -stores a reference to the `Field` which contains a additive and multiplicative operations for it, meaning -it has one fixed context attached to it and does not require explicit external context. So those `MathElements` could be -operated without context: +An obvious first choice of mathematical objects to implement in a context-oriented style are algebraic elements like spaces, +rings and fields. Those are located in the `scientifik.kmath.operations.Algebra.kt` file. Alongside common contexts, the file includes definitions for algebra elements like `FieldElement`. A `FieldElement` object +stores a reference to the `Field` which contains additive and multiplicative operations, meaning +it has one fixed context attached and does not require explicit external context. So those `MathElements` can be operated without context: + ```kotlin val c1 = Complex(1.0, 2.0) val c2 = ComplexField.i val c3 = c1 + c2 ``` -`ComplexField` also features special operations to mix complex numbers with real numbers like: + +`ComplexField` also features special operations to mix complex and real numbers, for example: + ```kotlin val c1 = Complex(1.0,2.0) -val c2 = ComplexField.run{ c1 - 1.0} //returns [re:0.0, im: 2.0] +val c2 = ComplexField.run{ c1 - 1.0} // Returns: [re:0.0, im: 2.0] val c3 = ComplexField.run{ c1 - i*2.0} ``` **Note**: In theory it is possible to add behaviors directly to the context, but currently kotlin syntax does not support -that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) for news. +that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) for updates. ## Nested fields -Algebra contexts allow to create more complex structures. For example, it is possible to create a `Matrix` from complex -elements like this: +Contexts allow one to build more complex structures. For example, it is possible to create a `Matrix` from complex elements like so: + ```kotlin val element = NDElements.create(field = ComplexField, shape = intArrayOf(2,2)){index: IntArray -> Complex(index[0] - index[1], index[0] + index[1]) } ``` -The `element` in this example is a member of `Field` of 2-d structures, each element of which is a member of its own -`ComplexField`. The important thing is that one does not need to create a special nd-structure to hold complex -numbers and implements operations on it, one need just to provide a field for its elements. -**Note**: Fields themselves do not solve problem of JVM boxing, but it is possible to solve with special contexts like +The `element` in this example is a member of the `Field` of 2-d structures, each element of which is a member of its own +`ComplexField`. The important thing is one does not need to create a special n-d class to hold complex +numbers and implement operations on it, one just needs to provide a field for its elements. + +**Note**: Fields themselves do not solve the problem of JVM boxing, but it is possible to solve with special contexts like `BufferSpec`. This feature is in development phase. \ No newline at end of file From 940420398237d63eca36fa5aa0a61f2697f67814 Mon Sep 17 00:00:00 2001 From: "breandan.considine" Date: Thu, 27 Dec 2018 23:26:52 -0500 Subject: [PATCH 10/70] clean up context doc --- doc/contexts.md | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/doc/contexts.md b/doc/contexts.md index b6b6be44c..ac17d9e52 100644 --- a/doc/contexts.md +++ b/doc/contexts.md @@ -1,50 +1,57 @@ # Context-oriented mathematics ## The problem -A known problem for implementing mathematics in statically-typed languages (and not only in them) is that different -sets of mathematical operation could be defined on the same mathematical objects. Sometimes there is not single way to -treat some operations like basic arithmetic operations on Java/Kotlin `Number`. Sometimes there are different ways to do -the same thing like Euclidean and elliptic geometry vector spaces defined over real vectors. Another problem arises when -one wants to add some kind of behavior to existing entity. In dynamic languages those problems are usually solved -by adding dynamic context-specific behaviors in runtime, but this solution has a lot of drawbacks. +A known problem for implementing mathematics in statically-typed languages (but not only in them) is that different +sets of mathematical operators can be defined on the same mathematical objects. Sometimes there is no single way to +treat some operations, including basic arithmetic operations, on a Java/Kotlin `Number`. Sometimes there are different ways to +define the same structure, such as Euclidean and elliptic geometry vector spaces over real vectors. Another problem arises when +one wants to add some kind of behavior to an existing entity. In dynamic languages those problems are usually solved +by adding dynamic context-specific behaviors at runtime, but this solution has a lot of drawbacks. ## Context-oriented approach -One of possible solutions to those problems is to completely separate object numerical representations from behaviors. -In terms of kotlin it means to have separate class to represent some entity without any operations, + +One possible solution to these problems is to completely separate numerical representations from behaviors. +One solution in Kotlin, is to define a separate class which represents some entity without any operations, for example a complex number: ```kotlin data class Complex(val re: Double, val im: Double) ``` -And a separate class or singleton, representing operation on those complex numbers: + +And then define a separate class or singleton, representing an operation on those complex numbers: + ```kotlin -object: ComplexOperations{ +object ComplexOperations { operator fun Complex.plus(other: Complex) = Complex(re + other.re, im + other.im) operator fun Complex.minus(other: Complex) = Complex(re - other.re, im - other.im) } ``` -In Java, application of such external operations could be very cumbersome, but Kotlin has a unique feature which allows -to treat this situation: blocks with receivers. So in kotlin, operation on complex number could beimplemented as: +In Java, applying such external operations could be very cumbersome, but Kotlin has a unique feature which allows +to treat this situation: [extensions with receivers](https://kotlinlang.org/docs/reference/extensions.html#extension-functions). +So in Kotlin, an operation on complex number could be implemented as: + ```kotlin -with(ComplexOperations){c1 + c2 - c3} +with(ComplexOperations) { c1 + c2 - c3 } ``` + Kotlin also allows to create functions with receivers: + ```kotlin fun ComplexOperations.doSomethingWithComplex(c1: Complex, c2: Complex, c3: Complex) = c1 + c2 - c3 ComplexOperations.doComethingWithComplex(c1,c2,c3) ``` -In fact, whole parts of program could run in a mathematical context or even multiple nested contexts. +In fact, whole parts of a program may be run within a mathematical context or even multiple nested contexts. -In `kmath` contexts are responsible not only for operations, but also for raw object creation and advanced features. +In KMath, contexts are responsible not only for operations, but also for raw object creation and advanced features. ## Other possibilities -An obvious candidate to get more or less the same functionality is type-class feature. It allows to bind a behavior to -a specific type without modifying the type itself. On a plus side, type-classes do not require explicit context +An obvious candidate to get more or less the same functionality is the type-class, which allows one to bind a behavior to +a specific type without modifying the type itself. On the plus side, type-classes do not require explicit context declaration, so the code looks cleaner. On the minus side, if there are different sets of behaviors for the same types, -it is impossible to combine them in the single module. Also, unlike type-classes, context could have parameters or even -state. For example in `kmath`, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize -performance in case of large amount of structures. \ No newline at end of file +it is impossible to combine them into one module. Also, unlike type-classes, context can have parameters or even +state. For example in KMath, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize +performance in case of a large amount of structures. \ No newline at end of file From 350151b62a6e2ab6594fd44fbe005d97b2ad2611 Mon Sep 17 00:00:00 2001 From: "breandan.considine" Date: Fri, 28 Dec 2018 00:01:57 -0500 Subject: [PATCH 11/70] more adjustments to contexts doc --- doc/contexts.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/contexts.md b/doc/contexts.md index ac17d9e52..c3ce959bf 100644 --- a/doc/contexts.md +++ b/doc/contexts.md @@ -1,6 +1,7 @@ # Context-oriented mathematics ## The problem + A known problem for implementing mathematics in statically-typed languages (but not only in them) is that different sets of mathematical operators can be defined on the same mathematical objects. Sometimes there is no single way to treat some operations, including basic arithmetic operations, on a Java/Kotlin `Number`. Sometimes there are different ways to @@ -10,15 +11,15 @@ by adding dynamic context-specific behaviors at runtime, but this solution has a ## Context-oriented approach -One possible solution to these problems is to completely separate numerical representations from behaviors. -One solution in Kotlin, is to define a separate class which represents some entity without any operations, -for example a complex number: +One possible solution to these problems is to divorce numerical representations from behaviors. +For example in Kotlin one can define a separate class which represents some entity without any operations, +ex. a complex number: ```kotlin data class Complex(val re: Double, val im: Double) ``` -And then define a separate class or singleton, representing an operation on those complex numbers: +And then to define a separate class or singleton, representing an operation on those complex numbers: ```kotlin object ComplexOperations { @@ -27,31 +28,31 @@ object ComplexOperations { } ``` -In Java, applying such external operations could be very cumbersome, but Kotlin has a unique feature which allows -to treat this situation: [extensions with receivers](https://kotlinlang.org/docs/reference/extensions.html#extension-functions). -So in Kotlin, an operation on complex number could be implemented as: +In Java, applying such external operations could be very cumbersome, but Kotlin has a unique feature which allows us +implement this naturally: [extensions with receivers](https://kotlinlang.org/docs/reference/extensions.html#extension-functions). +In Kotlin, an operation on complex number could be implemented as: ```kotlin with(ComplexOperations) { c1 + c2 - c3 } ``` -Kotlin also allows to create functions with receivers: +Kotlin also allows the creation of functions with receivers: ```kotlin fun ComplexOperations.doSomethingWithComplex(c1: Complex, c2: Complex, c3: Complex) = c1 + c2 - c3 -ComplexOperations.doComethingWithComplex(c1,c2,c3) +ComplexOperations.doComethingWithComplex(c1, c2, c3) ``` In fact, whole parts of a program may be run within a mathematical context or even multiple nested contexts. -In KMath, contexts are responsible not only for operations, but also for raw object creation and advanced features. +In KMath, contexts are not only responsible for operations, but also for raw object creation and advanced features. ## Other possibilities -An obvious candidate to get more or less the same functionality is the type-class, which allows one to bind a behavior to -a specific type without modifying the type itself. On the plus side, type-classes do not require explicit context +An obvious candidate to get more or less the same functionality is the type class, which allows one to bind a behavior to +a specific type without modifying the type itself. On the plus side, type classes do not require explicit context declaration, so the code looks cleaner. On the minus side, if there are different sets of behaviors for the same types, -it is impossible to combine them into one module. Also, unlike type-classes, context can have parameters or even +it is impossible to combine them into one module. Also, unlike type classes, context can have parameters or even state. For example in KMath, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize performance in case of a large amount of structures. \ No newline at end of file From a76dac633b0980119b975c562c27b09e224f867b Mon Sep 17 00:00:00 2001 From: "breandan.considine" Date: Fri, 28 Dec 2018 23:50:04 -0500 Subject: [PATCH 12/70] add another alternative for specifying context --- doc/contexts.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/contexts.md b/doc/contexts.md index c3ce959bf..58b198046 100644 --- a/doc/contexts.md +++ b/doc/contexts.md @@ -50,9 +50,24 @@ In KMath, contexts are not only responsible for operations, but also for raw obj ## Other possibilities +### Type classes + An obvious candidate to get more or less the same functionality is the type class, which allows one to bind a behavior to a specific type without modifying the type itself. On the plus side, type classes do not require explicit context declaration, so the code looks cleaner. On the minus side, if there are different sets of behaviors for the same types, it is impossible to combine them into one module. Also, unlike type classes, context can have parameters or even state. For example in KMath, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize -performance in case of a large amount of structures. \ No newline at end of file +performance in case of a large amount of structures. + +### Wildcard imports and importing-on-demand + +Sometimes, one may wish to use a single context throughout a file. In this case, is possible to import all members +from a package or file, via `import context.complex.*`. Effectively, this is the same as enclosing an entire file +with a single context. However when using multiple contexts, this technique can introduce operator ambiguity, due to +namespace pollution. If there are multiple scoped contexts which define the same operation, it is still possible to +to import specific operations as needed, without using an explicit context with extension functions, for example: + +``` +import context.complex.op1 +import context.quaternion.op2 +``` From 334dadc6bfdd2d1a66d60985a281f74937b49528 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 30 Dec 2018 19:48:32 +0300 Subject: [PATCH 13/70] Specialized BufferNDField --- {kmath-jmh => benchmarks}/build.gradle | 0 .../kmath/structures/ArrayBenchmark.kt | 0 .../kmath/structures/BufferBenchmark.kt | 0 .../structures/StructureReadBenchmark.kt | 36 +++ .../structures/StructureWriteBenchmark.kt | 39 ++++ .../kotlin/scientifik/kmath/misc/Grids.kt | 2 +- .../kmath/structures/BufferNDField.kt | 22 ++ .../kmath/structures/ExtendedNDField.kt | 9 +- .../scientifik/kmath/structures/NDField.kt | 206 +++++++++--------- .../kmath/structures/NDStructure.kt | 17 +- .../kmath/structures/GenericNDFieldTest.kt | 16 -- .../kmath/structures/NDFieldTest.kt | 13 ++ .../kmath/structures/NumberNDFieldTest.kt | 13 +- .../scientifik/kmath/structures/LazyField.kt | 7 + .../kmath/structures/LazyNDField.kt | 14 +- .../kmath/structures/LazyNDFieldTest.kt | 2 +- settings.gradle.kts | 2 +- 17 files changed, 259 insertions(+), 139 deletions(-) rename {kmath-jmh => benchmarks}/build.gradle (100%) rename {kmath-jmh => benchmarks}/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt (100%) rename {kmath-jmh => benchmarks}/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt (100%) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt delete mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt create mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt diff --git a/kmath-jmh/build.gradle b/benchmarks/build.gradle similarity index 100% rename from kmath-jmh/build.gradle rename to benchmarks/build.gradle diff --git a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt similarity index 100% rename from kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt rename to benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt diff --git a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt similarity index 100% rename from kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt rename to benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt new file mode 100644 index 000000000..ecfb4ab20 --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureReadBenchmark.kt @@ -0,0 +1,36 @@ +package scientifik.kmath.structures + +import kotlin.system.measureTimeMillis + +fun main(args: Array) { + val n = 6000 + + val array = DoubleArray(n * n) { 1.0 } + val buffer = DoubleBuffer(array) + val strides = DefaultStrides(intArrayOf(n, n)) + + val structure = BufferNDStructure(strides, buffer) + + measureTimeMillis { + var res: Double = 0.0 + strides.indices().forEach { res = structure[it] } + } // warmup + + val time1 = measureTimeMillis { + var res: Double = 0.0 + strides.indices().forEach { res = structure[it] } + } + println("Structure reading finished in $time1 millis") + + val time2 = measureTimeMillis { + var res: Double = 0.0 + strides.indices().forEach { res = buffer[strides.offset(it)] } + } + println("Buffer reading finished in $time2 millis") + + val time3 = measureTimeMillis { + var res: Double = 0.0 + strides.indices().forEach { res = array[strides.offset(it)] } + } + println("Array reading finished in $time3 millis") +} \ No newline at end of file diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt new file mode 100644 index 000000000..538993d8d --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt @@ -0,0 +1,39 @@ +package scientifik.kmath.structures + +import kotlin.system.measureTimeMillis + + + +fun main(args: Array) { + + val n = 6000 + + val structure = NdStructure(intArrayOf(n, n), DoubleBufferFactory) { 1.0 } + + structure.map { it + 1 } // warm-up + + val time1 = measureTimeMillis { + val res = structure.map { it + 1 } + } + println("Structure mapping finished in $time1 millis") + + val array = DoubleArray(n*n){1.0} + + val time2 = measureTimeMillis { + val target = DoubleArray(n*n) + val res = array.forEachIndexed{index, value -> + target[index] = value + 1 + } + } + println("Array mapping finished in $time2 millis") + + val buffer = DoubleBuffer(DoubleArray(n*n){1.0}) + + val time3 = measureTimeMillis { + val target = DoubleBuffer(DoubleArray(n*n)) + val res = array.forEachIndexed{index, value -> + target[index] = value + 1 + } + } + println("Buffer mapping finished in $time3 millis") +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt index b48afbdcc..c16b03608 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt @@ -32,6 +32,6 @@ fun ClosedFloatingPointRange.toSequence(step: Double): Sequence * Convert double range to array of evenly spaced doubles, where the size of array equals [numPoints] */ fun ClosedFloatingPointRange.toGrid(numPoints: Int): DoubleArray { - if (numPoints < 2) error("Can't create grid with less than two points") + if (numPoints < 2) error("Can't generic grid with less than two points") return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt new file mode 100644 index 000000000..edf34c7b4 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -0,0 +1,22 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field + +class BufferNDField>(override val shape: IntArray, override val field: F, val bufferFactory: BufferFactory) : NDField { + val strides = DefaultStrides(shape) + + override inline fun produce(crossinline initializer: F.(IntArray) -> T): NDElement { + return BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) + } +} + +class BufferNDElement>(override val context: BufferNDField, private val buffer: Buffer) : NDElement { + + override val self: NDStructure get() = this + override val shape: IntArray get() = context.shape + + override fun get(index: IntArray): T = buffer[context.strides.offset(index)] + + override fun elements(): Sequence> = context.strides.indices().map { it to get(it) } + +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 915b09419..5fb9cc8c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -9,14 +9,15 @@ import scientifik.kmath.operations.TrigonometricOperations /** * NDField that supports [ExtendedField] operations on its elements */ -class ExtendedNDField>(shape: IntArray, field: F) : NDField(shape, field), +inline class ExtendedNDField>(private val ndField: NDField) : NDField, TrigonometricOperations>, PowerOperations>, ExponentialOperations> { - override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure { - return NdStructure(shape, ::boxingBuffer) { field.initializer(it) } - } + override val shape: IntArray get() = ndField.shape + override val field: F get() = ndField.field + + override fun produce(initializer: F.(IntArray) -> T): NDElement = ndField.produce(initializer) override fun power(arg: NDStructure, pow: Double): NDElement { return produce { with(field) { power(arg[it], pow) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index c06df4938..dedb0e6d3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -15,28 +15,25 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run * @param field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ -abstract class NDField>(val shape: IntArray, val field: F) : Field> { +interface NDField> : Field> { - abstract fun produceStructure(initializer: F.(IntArray) -> T): NDStructure + val shape: IntArray + val field: F /** * Create new instance of NDArray using field shape and given initializer * The producer takes list of indices as argument and returns contained value */ - fun produce(initializer: F.(IntArray) -> T): NDElement = NDStructureElement(this, produceStructure(initializer)) + fun produce(initializer: F.(IntArray) -> T): NDElement - override val zero: NDElement by lazy { - produce { zero } - } + override val zero: NDElement get() = produce { zero } - override val one: NDElement by lazy { - produce { one } - } + override val one: NDElement get() = produce { one } /** * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field */ - private fun checkShape(vararg elements: NDStructure) { + fun checkShape(vararg elements: NDStructure) { elements.forEach { if (!shape.contentEquals(it.shape)) { throw ShapeMismatchException(shape, it.shape) @@ -49,7 +46,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie */ override fun add(a: NDStructure, b: NDStructure): NDElement { checkShape(a, b) - return produce { with(field) { a[it] + b[it] } } + return produce { field.run { a[it] + b[it] } } } /** @@ -57,7 +54,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie */ override fun multiply(a: NDStructure, k: Double): NDElement { checkShape(a) - return produce { with(field) { a[it] * k } } + return produce { field.run { a[it] * k } } } /** @@ -65,7 +62,7 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie */ override fun multiply(a: NDStructure, b: NDStructure): NDElement { checkShape(a) - return produce { with(field) { a[it] * b[it] } } + return produce { field.run { a[it] * b[it] } } } /** @@ -73,55 +70,72 @@ abstract class NDField>(val shape: IntArray, val field: F) : Fie */ override fun divide(a: NDStructure, b: NDStructure): NDElement { checkShape(a) - return produce { with(field) { a[it] / b[it] } } + return produce { field.run { a[it] / b[it] } } } -// /** -// * Reverse sum operation -// */ -// operator fun T.plus(arg: NDElement): NDElement = arg + this -// -// /** -// * Reverse minus operation -// */ -// operator fun T.minus(arg: NDElement): NDElement = arg.transformIndexed { _, value -> -// with(arg.context.field) { -// this@minus - value -// } -// } -// -// /** -// * Reverse product operation -// */ -// operator fun T.times(arg: NDElement): NDElement = arg * this -// -// /** -// * Reverse division operation -// */ -// operator fun T.div(arg: NDElement): NDElement = arg.transformIndexed { _, value -> -// with(arg.context.field) { -// this@div / value -// } -// } + + companion object { + /** + * Create a nd-field for [Double] values + */ + fun real(shape: IntArray) = ExtendedNDField(BufferNDField(shape, DoubleField, DoubleBufferFactory)) + + /** + * Create a nd-field with boxing generic buffer + */ + fun > generic(shape: IntArray, field: F) = BufferNDField(shape, field, ::boxingBuffer) + + /** + * Create a most suitable implementation for nd-field using reified class + */ + inline fun > inline(shape: IntArray, field: F) = BufferNDField(shape, field, ::inlineBuffer) + } } -interface NDElement> : FieldElement, NDField>, NDStructure +interface NDElement> : FieldElement, NDField>, NDStructure { + companion object { + /** + * Create a platform-optimized NDArray of doubles + */ + fun real(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement { + return NDField.real(shape).produce(initializer) + } + + fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { + return real(intArrayOf(dim)) { initializer(it[0]) } + } + + fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { + return real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + } + + fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement { + return real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } + } + +// inline fun real(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { +// val field = NDField.real(shape) +// return GenericNDElement(field, field.run(block)) +// } + + /** + * Simple boxing NDArray + */ + fun > generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): NDElement { + return NDField.generic(shape,field).produce(initializer) + } + + inline fun > inline(shape: IntArray, field: F, crossinline initializer: F.(IntArray) -> T): NDElement { + return NDField.inline(shape,field).produce(initializer) + } + } +} inline fun > NDElement.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement = context.produce { action(it, get(*it)) } inline fun > NDElement.transform(crossinline action: F.(T) -> T): NDElement = context.produce { action(get(*it)) } -/** - * Read-only [NDStructure] coupled to the context. - */ -class NDStructureElement>(override val context: NDField, private val structure: NDStructure) : NDElement, NDStructure by structure { - - //TODO ensure structure is immutable - - override val self: NDElement get() = this -} - /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ @@ -133,18 +147,14 @@ operator fun > Function1.invoke(ndElement: NDElement * Summation operation for [NDElement] and single element */ operator fun > NDElement.plus(arg: T): NDElement = transform { value -> - with(context.field) { - arg + value - } + context.field.run { arg + value } } /** * Subtraction operation between [NDElement] and single element */ operator fun > NDElement.minus(arg: T): NDElement = transform { value -> - with(context.field) { - arg - value - } + context.field.run { arg - value } } /* prod and div */ @@ -153,55 +163,53 @@ operator fun > NDElement.minus(arg: T): NDElement = * Product operation for [NDElement] and single element */ operator fun > NDElement.times(arg: T): NDElement = transform { value -> - with(context.field) { - arg * value - } + context.field.run { arg * value } } /** * Division operation between [NDElement] and single element */ operator fun > NDElement.div(arg: T): NDElement = transform { value -> - with(context.field) { - arg / value - } + context.field.run { arg / value } } -class GenericNDField>(shape: IntArray, field: F) : NDField(shape, field) { - override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = NdStructure(shape, ::boxingBuffer) { field.initializer(it) } + +// /** +// * Reverse sum operation +// */ +// operator fun T.plus(arg: NDStructure): NDElement = produce { index -> +// field.run { this@plus + arg[index] } +// } +// +// /** +// * Reverse minus operation +// */ +// operator fun T.minus(arg: NDStructure): NDElement = produce { index -> +// field.run { this@minus - arg[index] } +// } +// +// /** +// * Reverse product operation +// */ +// operator fun T.times(arg: NDStructure): NDElement = produce { index -> +// field.run { this@times * arg[index] } +// } +// +// /** +// * Reverse division operation +// */ +// operator fun T.div(arg: NDStructure): NDElement = produce { index -> +// field.run { this@div / arg[index] } +// } + +class GenericNDField>(override val shape: IntArray, override val field: F) : NDField { + override fun produce(initializer: F.(IntArray) -> T): NDElement = GenericNDElement(this, produceStructure(initializer)) + private inline fun produceStructure(crossinline initializer: F.(IntArray) -> T): NDStructure = NdStructure(shape, ::boxingBuffer) { field.initializer(it) } } -//typealias NDFieldFactory = (IntArray)->NDField - -object NDElements { - /** - * Create a platform-optimized NDArray of doubles - */ - fun realNDElement(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement { - return ExtendedNDField(shape, DoubleField).produce(initializer) - } - - fun real1DElement(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { - return realNDElement(intArrayOf(dim)) { initializer(it[0]) } - } - - fun real2DElement(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { - return realNDElement(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } - } - - fun real3DElement(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement { - return realNDElement(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - } - - inline fun real(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { - val field = ExtendedNDField(shape, DoubleField) - return NDStructureElement(field, field.run(block)) - } - - /** - * Simple boxing NDArray - */ - fun > create(field: F, shape: IntArray, initializer: (IntArray) -> T): NDElement { - return GenericNDField(shape, field).produce { initializer(it) } - } +/** + * Read-only [NDStructure] coupled to the context. + */ +class GenericNDElement>(override val context: NDField, private val structure: NDStructure) : NDElement, NDStructure by structure { + override val self: NDElement get() = this } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 8900d0d9b..765d7148b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -19,7 +19,7 @@ interface MutableNDStructure : NDStructure { operator fun set(index: IntArray, value: T) } -fun MutableNDStructure.transformInPlace(action: (IntArray, T) -> T) { +fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T) { elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } @@ -113,8 +113,8 @@ class DefaultStrides private constructor(override val shape: IntArray) : Strides } abstract class GenericNDStructure> : NDStructure { - protected abstract val buffer: B - protected abstract val strides: Strides + abstract val buffer: B + abstract val strides: Strides override fun get(index: IntArray): T = buffer[strides.offset(index)] @@ -153,7 +153,18 @@ class BufferNDStructure( result = 31 * result + buffer.hashCode() return result } +} +/** + * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] + */ +inline fun NDStructure.map(factory: BufferFactory = ::inlineBuffer, crossinline transform: (T) -> R): BufferNDStructure { + return if (this is BufferNDStructure) { + BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) + } else { + val strides = DefaultStrides(shape) + BufferNDStructure(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) + } } /** diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt deleted file mode 100644 index 0bc118e3b..000000000 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.DoubleField -import scientifik.kmath.structures.NDElements.create -import kotlin.test.Test -import kotlin.test.assertEquals - - -class GenericNDFieldTest{ - @Test - fun testStrides(){ - val ndArray = create(DoubleField, intArrayOf(10,10)){(it[0]+it[1]).toDouble()} - assertEquals(ndArray[5,5], 10.0) - } - -} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt new file mode 100644 index 000000000..39cce5c67 --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt @@ -0,0 +1,13 @@ +package scientifik.kmath.structures + +import kotlin.test.Test +import kotlin.test.assertEquals + + +class NDFieldTest { + @Test + fun testStrides() { + val ndArray = NDElement.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() } + assertEquals(ndArray[5, 5], 10.0) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 5d5ee97cf..3c4160329 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,16 +1,15 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Norm -import scientifik.kmath.structures.NDElements.real -import scientifik.kmath.structures.NDElements.real2DElement +import scientifik.kmath.structures.NDElement.Companion.real2D import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test import kotlin.test.assertEquals class NumberNDFieldTest { - val array1 = real2DElement(3, 3) { i, j -> (i + j).toDouble() } - val array2 = real2DElement(3, 3) { i, j -> (i - j).toDouble() } + val array1 = real2D(3, 3) { i, j -> (i + j).toDouble() } + val array2 = real2D(3, 3) { i, j -> (i - j).toDouble() } @Test fun testSum() { @@ -27,7 +26,7 @@ class NumberNDFieldTest { @Test fun testGeneration() { - val array = real2DElement(3, 3) { i, j -> (i * 10 + j).toDouble() } + val array = real2D(3, 3) { i, j -> (i * 10 + j).toDouble() } for (i in 0..2) { for (j in 0..2) { @@ -52,7 +51,7 @@ class NumberNDFieldTest { } @Test - fun combineTest(){ + fun combineTest() { val division = array1.combine(array2, Double::div) } @@ -64,7 +63,7 @@ class NumberNDFieldTest { @Test fun testInternalContext() { - real(array1.shape) { + NDField.real(array1.shape).run { with(L2Norm) { 1 + norm(array1) + exp(array2) } diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt new file mode 100644 index 000000000..d534d3eb3 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt @@ -0,0 +1,7 @@ +package scientifik.kmath.structures + +// +//class LazyField: Field { +//} +// +//class LazyValue(): \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index 823245685..adc2b4ed5 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -8,7 +8,7 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = LazyNDStructure(this) { initializer(field, it) } - override fun add(a: NDElement, b: NDElement): NDElement { + override fun add(a: NDStructure, b: NDStructure): NDElement { return LazyNDStructure(this) { index -> val aDeferred = a.deferred(index) val bDeferred = b.deferred(index) @@ -16,11 +16,11 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } } - override fun multiply(a: NDElement, k: Double): NDElement { + override fun multiply(a: NDStructure, k: Double): NDElement { return LazyNDStructure(this) { index -> a.await(index) * k } } - override fun multiply(a: NDElement, b: NDElement): NDElement { + override fun multiply(a: NDStructure, b: NDStructure): NDElement { return LazyNDStructure(this) { index -> val aDeferred = a.deferred(index) val bDeferred = b.deferred(index) @@ -28,7 +28,7 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } } - override fun divide(a: NDElement, b: NDElement): NDElement { + override fun divide(a: NDStructure, b: NDStructure): NDElement { return LazyNDStructure(this) { index -> val aDeferred = a.deferred(index) val bDeferred = b.deferred(index) @@ -57,15 +57,15 @@ class LazyNDStructure>(override val context: LazyNDField, } } -fun NDElement.deferred(index: IntArray) = if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) +fun NDStructure.deferred(index: IntArray) = if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) -suspend fun NDElement.await(index: IntArray) = if (this is LazyNDStructure) this.await(index) else get(index) +suspend fun NDStructure.await(index: IntArray) = if (this is LazyNDStructure) this.await(index) else get(index) fun > NDElement.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { return if (this is LazyNDStructure) { this } else { - val context = LazyNDField(context.shape, context.field) + val context = LazyNDField(context.shape, context.field, scope) LazyNDStructure(context) { get(it) } } } diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index 6dce9b533..b5594cbdb 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -9,7 +9,7 @@ class LazyNDFieldTest { @Test fun testLazyStructure() { var counter = 0 - val regularStructure = NDElements.create(IntField, intArrayOf(2, 2, 2)) { it[0] + it[1] - it[2] } + val regularStructure = NDElements.generic(intArrayOf(2, 2, 2), IntField) { it[0] + it[1] - it[2] } val result = (regularStructure.lazy() + 2).transform { counter++ it * it diff --git a/settings.gradle.kts b/settings.gradle.kts index 0c91b8446..3b94f15c5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,5 +12,5 @@ include( ":kmath-core", ":kmath-io", ":kmath-coroutines", - ":kmath-jmh" + ":benchmarks" ) From b47cdd12c237c7fdb004dcf1f674cdeb042b230a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 31 Dec 2018 13:48:27 +0300 Subject: [PATCH 14/70] Adjustments to RealNDField --- .../structures/BufferNDStructureBenchmark.kt | 42 +++++++++++ .../kmath/structures/BufferNDField.kt | 64 +++++++++++++++- .../scientifik/kmath/structures/Buffers.kt | 8 +- .../kmath/structures/ExtendedNDField.kt | 12 ++- .../scientifik/kmath/structures/NDField.kt | 9 +-- .../kmath/structures/RealNDField.kt | 73 +++++++++++++++++++ .../kmath/structures/LazyNDField.kt | 5 +- 7 files changed, 192 insertions(+), 21 deletions(-) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt new file mode 100644 index 000000000..de2e6844e --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt @@ -0,0 +1,42 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.DoubleField +import kotlin.system.measureTimeMillis + +fun main(args: Array) { + val dim = 1000 + val n = 1000 + + val genericField = NDField.generic(intArrayOf(dim, dim), DoubleField) + val doubleField = NDField.inline(intArrayOf(dim, dim), DoubleField) + val specializedField = NDField.real(intArrayOf(dim, dim)) + + + val doubleTime = measureTimeMillis { + var res = doubleField.produce { one } + repeat(n) { + res += 1.0 + } + } + + println("Inlined addition completed in $doubleTime millis") + + val specializedTime = measureTimeMillis { + var res = specializedField.produce { one } + repeat(n) { + res += 1.0 + } + } + + println("Specialized addition completed in $specializedTime millis") + + + val genericTime = measureTimeMillis { + var res = genericField.produce { one } + repeat(n) { + res += 1.0 + } + } + + println("Generic addition completed in $genericTime millis") +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index edf34c7b4..ebe2a67cf 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -2,15 +2,36 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -class BufferNDField>(override val shape: IntArray, override val field: F, val bufferFactory: BufferFactory) : NDField { +open class BufferNDField>(final override val shape: IntArray, final override val field: F, val bufferFactory: BufferFactory) : NDField { val strides = DefaultStrides(shape) - override inline fun produce(crossinline initializer: F.(IntArray) -> T): NDElement { + override fun produce(initializer: F.(IntArray) -> T): BufferNDElement { return BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) } + + open fun produceBuffered(initializer: F.(Int) -> T) = + BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(offset) }) + +// override fun add(a: NDStructure, b: NDStructure): NDElement { +// checkShape(a, b) +// return if (a is BufferNDElement && b is BufferNDElement) { +// BufferNDElement(this,bufferFactory(strides.linearSize){i-> field.run { a.buffer[i] + b.buffer[i]}}) +// } else { +// produce { field.run { a[it] + b[it] } } +// } +// } +// +// override fun NDStructure.plus(b: Number): NDElement { +// checkShape(this) +// return if (this is BufferNDElement) { +// BufferNDElement(this@BufferNDField,bufferFactory(strides.linearSize){i-> field.run { this@plus.buffer[i] + b}}) +// } else { +// produce {index -> field.run { this@plus[index] + b } } +// } +// } } -class BufferNDElement>(override val context: BufferNDField, private val buffer: Buffer) : NDElement { +class BufferNDElement>(override val context: BufferNDField, val buffer: Buffer) : NDElement { override val self: NDStructure get() = this override val shape: IntArray get() = context.shape @@ -19,4 +40,39 @@ class BufferNDElement>(override val context: BufferNDField override fun elements(): Sequence> = context.strides.indices().map { it to get(it) } -} \ No newline at end of file +} + + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: BufferNDElement) = + ndElement.context.produceBuffered { i -> invoke(ndElement.buffer[i]) } + +/* plus and minus */ + +/** + * Summation operation for [BufferNDElement] and single element + */ +operator fun > BufferNDElement.plus(arg: T) = + context.produceBuffered { i -> buffer[i] + arg } + +/** + * Subtraction operation between [BufferNDElement] and single element + */ +operator fun > BufferNDElement.minus(arg: T) = + context.produceBuffered { i -> buffer[i] - arg } + +/* prod and div */ + +/** + * Product operation for [BufferNDElement] and single element + */ +operator fun > BufferNDElement.times(arg: T) = + context.produceBuffered { i -> buffer[i] * arg } + +/** + * Division operation between [BufferNDElement] and single element + */ +operator fun > BufferNDElement.div(arg: T) = + context.produceBuffered { i -> buffer[i] / arg } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 442047dca..cffc2bea0 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -133,13 +133,13 @@ fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { /** * Create a boxing buffer of given type */ -fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) +inline fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) /** * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineBuffer(size: Int, noinline initializer: (Int) -> T): Buffer { +inline fun inlineBuffer(size: Int, initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer @@ -151,13 +151,13 @@ inline fun inlineBuffer(size: Int, noinline initializer: (Int) /** * Create a boxing mutable buffer of given type */ -fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) +inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) /** * Create most appropriate mutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineMutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { +inline fun inlineMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 5fb9cc8c6..d274f92bd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -6,13 +6,17 @@ import scientifik.kmath.operations.PowerOperations import scientifik.kmath.operations.TrigonometricOperations +interface ExtendedNDField> : + NDField, + TrigonometricOperations>, + PowerOperations>, + ExponentialOperations> + + /** * NDField that supports [ExtendedField] operations on its elements */ -inline class ExtendedNDField>(private val ndField: NDField) : NDField, - TrigonometricOperations>, - PowerOperations>, - ExponentialOperations> { +inline class ExtendedNDFieldWrapper>(private val ndField: NDField) : ExtendedNDField { override val shape: IntArray get() = ndField.shape override val field: F get() = ndField.field diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index dedb0e6d3..4fd6c3ee5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -73,12 +73,11 @@ interface NDField> : Field> { return produce { field.run { a[it] / b[it] } } } - companion object { /** * Create a nd-field for [Double] values */ - fun real(shape: IntArray) = ExtendedNDField(BufferNDField(shape, DoubleField, DoubleBufferFactory)) + fun real(shape: IntArray) = RealNDField(shape) /** * Create a nd-field with boxing generic buffer @@ -123,11 +122,11 @@ interface NDElement> : FieldElement, NDField> generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): NDElement { - return NDField.generic(shape,field).produce(initializer) + return NDField.generic(shape, field).produce(initializer) } - inline fun > inline(shape: IntArray, field: F, crossinline initializer: F.(IntArray) -> T): NDElement { - return NDField.inline(shape,field).produce(initializer) + inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): NDElement { + return NDField.inline(shape, field).produce(initializer) } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt new file mode 100644 index 000000000..7705c710e --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -0,0 +1,73 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.DoubleField + +typealias RealNDElement = BufferNDElement + +class RealNDField(shape: IntArray) : BufferNDField(shape, DoubleField, DoubleBufferFactory), ExtendedNDField { + + /** + * Inline map an NDStructure to + */ + private inline fun NDStructure.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement { + return if (this is BufferNDElement) { + val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } + BufferNDElement(this@RealNDField, DoubleBuffer(array)) + } else { + produce { index -> DoubleField.operation(get(index)) } + } + } + + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun produce(initializer: DoubleField.(IntArray) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> field.initializer(strides.index(offset)) } + return BufferNDElement(this, DoubleBuffer(array)) + } + + override fun power(arg: NDStructure, pow: Double) = arg.mapInline { power(it, pow) } + + override fun exp(arg: NDStructure) = arg.mapInline { exp(it) } + + override fun ln(arg: NDStructure) = arg.mapInline { ln(it) } + + override fun sin(arg: NDStructure) = arg.mapInline { sin(it) } + + override fun cos(arg: NDStructure) = arg.mapInline { cos(it) } + + override fun NDStructure.times(k: Number) = mapInline { value -> value * k.toDouble() } + + override fun NDStructure.div(k: Number) = mapInline { value -> value / k.toDouble() } + + override fun Number.times(b: NDStructure) = b * this + + override fun Number.div(b: NDStructure) = b * (1.0 / this.toDouble()) +} + +/** + * Fast element production using function inlining + */ +inline fun BufferNDField.produceInline(crossinline initializer: DoubleField.(Int) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> field.initializer(offset) } + return BufferNDElement(this, DoubleBuffer(array)) +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun Function1.invoke(ndElement: RealNDElement) = + ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + +/* plus and minus */ + +/** + * Summation operation for [BufferNDElement] and single element + */ +operator fun RealNDElement.plus(arg: Double) = + context.produceInline { i -> buffer[i] + arg } + +/** + * Subtraction operation between [BufferNDElement] and single element + */ +operator fun RealNDElement.minus(arg: Double) = + context.produceInline { i -> buffer[i] - arg } \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index adc2b4ed5..bf823d64b 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -3,10 +3,7 @@ package scientifik.kmath.structures import kotlinx.coroutines.* import scientifik.kmath.operations.Field -class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : NDField(shape, field) { - - override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = LazyNDStructure(this) { initializer(field, it) } - +class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : BufferNDField(shape,field, ::boxingBuffer) { override fun add(a: NDStructure, b: NDStructure): NDElement { return LazyNDStructure(this) { index -> From 622a6a7756bd7fc95e8df029ada595eb29b0babb Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 31 Dec 2018 14:10:02 +0300 Subject: [PATCH 15/70] Removed empty files and fixed one test --- .../kmath/structures/LazyStructure.kt | 20 ------------------- .../scientifik/kmath/structures/LazyField.kt | 7 ------- .../kmath/structures/LazyNDField.kt | 4 ++-- .../kmath/structures/LazyNDFieldTest.kt | 2 +- 4 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt delete mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt deleted file mode 100644 index 0428535f9..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt +++ /dev/null @@ -1,20 +0,0 @@ -package scientifik.kmath.structures - -// -//class LazyStructureField(val field: Field): Field>{ -// -//} -// -//class LazyStructure : NDStructure { -// -// 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> { -// TODO("not implemented") //To change body of created functions use File | Settings | File Templates. -// } -//} \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt deleted file mode 100644 index d534d3eb3..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyField.kt +++ /dev/null @@ -1,7 +0,0 @@ -package scientifik.kmath.structures - -// -//class LazyField: Field { -//} -// -//class LazyValue(): \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index bf823d64b..ad38100cb 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -67,10 +67,10 @@ fun > NDElement.lazy(scope: CoroutineScope = GlobalScope): } } -inline fun > LazyNDStructure.transformIndexed(crossinline action: suspend F.(IntArray, T) -> T) = LazyNDStructure(context) { index -> +inline fun > LazyNDStructure.mapIndexed(crossinline action: suspend F.(IntArray, T) -> T) = LazyNDStructure(context) { index -> action.invoke(this, index, await(index)) } -inline fun > LazyNDStructure.transform(crossinline action: suspend F.(T) -> T) = LazyNDStructure(context) { index -> +inline fun > LazyNDStructure.map(crossinline action: suspend F.(T) -> T) = LazyNDStructure(context) { index -> action.invoke(this, await(index)) } \ No newline at end of file diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index b5594cbdb..6e712f2a8 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -9,7 +9,7 @@ class LazyNDFieldTest { @Test fun testLazyStructure() { var counter = 0 - val regularStructure = NDElements.generic(intArrayOf(2, 2, 2), IntField) { it[0] + it[1] - it[2] } + val regularStructure = NDField.generic(intArrayOf(2, 2, 2), IntField).produce { it[0] + it[1] - it[2] } val result = (regularStructure.lazy() + 2).transform { counter++ it * it From 55ce9b475431512bbf46f63447f43e1ae30fe88e Mon Sep 17 00:00:00 2001 From: breandan Date: Thu, 3 Jan 2019 10:43:12 -0500 Subject: [PATCH 16/70] use consistent code style and simplify --- .../kmath/expressions/Expression.kt | 5 +- .../scientifik/kmath/histogram/Counters.kt | 18 +++--- .../kmath/histogram/FastHistogram.kt | 60 ++++++++----------- .../scientifik/kmath/histogram/Histogram.kt | 18 +++--- .../kmath/histogram/PhantomHistogram.kt | 9 ++- .../kmath/linear/LUDecomposition.kt | 6 +- .../scientifik/kmath/linear/LinearAlgrebra.kt | 14 ++--- .../kotlin/scientifik/kmath/linear/Matrix.kt | 31 ++++++---- .../kotlin/scientifik/kmath/linear/Vector.kt | 17 ++---- .../scientifik/kmath/misc/Cumulative.kt | 18 +++--- .../kotlin/scientifik/kmath/misc/Grids.kt | 35 ++++++----- .../scientifik/kmath/operations/Algebra.kt | 1 + .../scientifik/kmath/operations/Fields.kt | 4 +- .../kmath/structures/BufferNDField.kt | 22 ++++--- .../scientifik/kmath/structures/Buffers.kt | 30 +++++----- .../kmath/structures/ExtendedNDField.kt | 21 ++----- .../scientifik/kmath/structures/NDField.kt | 43 +++++-------- .../kmath/structures/NDStructure.kt | 52 +++++++--------- .../kmath/structures/RealNDField.kt | 23 +++---- 19 files changed, 191 insertions(+), 236 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt index 0a34b536c..47d07c63e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt @@ -17,9 +17,8 @@ interface ExpressionContext { } internal class VariableExpression(val name: String, val default: T? = null) : Expression { - override fun invoke(arguments: Map): T { - return arguments[name] ?: default ?: error("The parameter not found: $name") - } + override fun invoke(arguments: Map): T = + arguments[name] ?: default ?: error("Parameter not found: $name") } internal class ConstantExpression(val value: T) : Expression { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt index f6cc1f822..9a470014a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -5,16 +5,16 @@ package scientifik.kmath.histogram */ -expect class LongCounter(){ - fun decrement() - fun increment() - fun reset() - fun sum(): Long - fun add(l:Long) +expect class LongCounter() { + fun decrement() + fun increment() + fun reset() + fun sum(): Long + fun add(l: Long) } -expect class DoubleCounter(){ - fun reset() - fun sum(): Double +expect class DoubleCounter() { + fun reset() + fun sum(): Double fun add(d: Double) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt index 705a8e7ca..e3a3ee0d1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -31,7 +31,7 @@ class FastHistogram( // argument checks if (lower.size != upper.size) error("Dimension mismatch in histogram lower and upper limits.") if (lower.size != binNums.size) error("Dimension mismatch in bin count.") - if ((upper - lower).asSequence().any { it <= 0 }) error("Range for one of axis is not strictly positive") + if ((upper - lower).asSequence().any { it <= 0 }) error("Range for one axis is not strictly positive") } @@ -41,23 +41,19 @@ class FastHistogram( /** * Get internal [NDStructure] bin index for given axis */ - private fun getIndex(axis: Int, value: Double): Int { - return when { - value >= upper[axis] -> binNums[axis] + 1 // overflow - value < lower[axis] -> 0 // underflow - else -> floor((value - lower[axis]) / binSize[axis]).toInt() + 1 - } - } + private fun getIndex(axis: Int, value: Double): Int = + when { + value >= upper[axis] -> binNums[axis] + 1 // overflow + value < lower[axis] -> 0 // underflow + else -> floor((value - lower[axis]) / binSize[axis]).toInt() + 1 + } - private fun getIndex(point: Buffer): IntArray = IntArray(dimension) { getIndex(it, point[it]) } + private fun getIndex(point: Buffer): IntArray = + IntArray(dimension) { getIndex(it, point[it]) } - private fun getValue(index: IntArray): Long { - return values[index].sum() - } + private fun getValue(index: IntArray): Long = values[index].sum() - fun getValue(point: Buffer): Long { - return getValue(getIndex(point)) - } + fun getValue(point: Buffer): Long = getValue(getIndex(point)) private fun getTemplate(index: IntArray): BinTemplate { val center = index.mapIndexed { axis, i -> @@ -70,9 +66,7 @@ class FastHistogram( return BinTemplate(center, binSize) } - fun getTemplate(point: Buffer): BinTemplate { - return getTemplate(getIndex(point)) - } + fun getTemplate(point: Buffer): BinTemplate = getTemplate(getIndex(point)) override fun get(point: Buffer): PhantomBin? { val index = getIndex(point) @@ -85,16 +79,16 @@ class FastHistogram( values[index].increment() } - override fun iterator(): Iterator> = values.elements().map { (index, value) -> - PhantomBin(getTemplate(index), value.sum()) - }.iterator() + override fun iterator(): Iterator> = + 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 { - return inlineNdStructure(this.values.shape) { values[it].sum() } - } + fun asNDStructure(): NDStructure = + inlineNdStructure(this.values.shape) { values[it].sum() } /** * Create a phantom lightweight immutable copy of this histogram @@ -115,9 +109,8 @@ class FastHistogram( *) *``` */ - fun fromRanges(vararg ranges: ClosedFloatingPointRange): FastHistogram { - return FastHistogram(ranges.map { it.start }.toVector(), ranges.map { it.endInclusive }.toVector()) - } + fun fromRanges(vararg ranges: ClosedFloatingPointRange): FastHistogram = + FastHistogram(ranges.map { it.start }.toVector(), ranges.map { it.endInclusive }.toVector()) /** * Use it like @@ -128,13 +121,12 @@ class FastHistogram( *) *``` */ - fun fromRanges(vararg ranges: Pair, Int>): FastHistogram { - return FastHistogram( - ListBuffer(ranges.map { it.first.start }), - ListBuffer(ranges.map { it.first.endInclusive }), - ranges.map { it.second }.toIntArray() - ) - } + fun fromRanges(vararg ranges: Pair, Int>): FastHistogram = + FastHistogram( + ListBuffer(ranges.map { it.first.start }), + ListBuffer(ranges.map { it.first.endInclusive }), + ranges.map { it.second }.toIntArray() + ) } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt index 08214142e..90251b9d1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -12,7 +12,7 @@ typealias RealPoint = Buffer * A simple geometric domain * TODO move to geometry module */ -interface Domain { +interface Domain { operator fun contains(vector: Point): Boolean val dimension: Int } @@ -20,7 +20,7 @@ interface Domain { /** * The bin in the histogram. The histogram is by definition always done in the real space */ -interface Bin : Domain { +interface Bin : Domain { /** * The value of this bin */ @@ -28,7 +28,7 @@ interface Bin : Domain { val center: Point } -interface Histogram> : Iterable { +interface Histogram> : Iterable { /** * Find existing bin, corresponding to given coordinates @@ -42,7 +42,7 @@ interface Histogram> : Iterable { } -interface MutableHistogram>: Histogram{ +interface MutableHistogram> : Histogram { /** * Increment appropriate bin @@ -50,14 +50,14 @@ interface MutableHistogram>: Histogram{ fun put(point: Point, weight: Double = 1.0) } -fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) +fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) -fun MutableHistogram.put(vararg point: Number) = put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray())) -fun MutableHistogram.put(vararg point: Double) = put(DoubleBuffer(point)) +fun MutableHistogram.put(vararg point: Number) = put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray())) +fun MutableHistogram.put(vararg point: Double) = put(DoubleBuffer(point)) -fun MutableHistogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } +fun MutableHistogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } /** * Pass a sequence builder into histogram */ -fun MutableHistogram.fill(buider: suspend SequenceScope>.() -> Unit) = fill(sequence(buider).asIterable()) \ No newline at end of file +fun MutableHistogram.fill(buider: suspend SequenceScope>.() -> Unit) = fill(sequence(buider).asIterable()) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt index ffffb0d7d..f9ec68f73 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt @@ -8,8 +8,8 @@ import scientifik.kmath.structures.asSequence data class BinTemplate>(val center: Vector, val sizes: Point) { fun contains(vector: Point): 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} + 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 } @@ -51,9 +51,8 @@ class PhantomHistogram>( override val dimension: Int get() = data.dimension - override fun iterator(): Iterator> { - return bins.asSequence().map { entry -> PhantomBin(entry.key, data[entry.value]) }.iterator() - } + override fun iterator(): Iterator> = + bins.asSequence().map { entry -> PhantomBin(entry.key, data[entry.value]) }.iterator() override fun get(point: Point): PhantomBin? { val template = bins.keys.find { it.contains(point) } 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 3f14787f2..d94d4c7c9 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -86,7 +86,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr } /** - * In-place transformation for [MutableNDArray], using given transformation for each element + * In-place transformation for [MutableNDStructure], using given transformation for each element */ operator fun MutableNDStructure.set(i: Int, j: Int, value: T) { this[intArrayOf(i, j)] = value @@ -174,9 +174,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * @return the pivot permutation vector * @see .getP */ - fun getPivot(): IntArray { - return pivot.copyOf() - } + fun getPivot(): IntArray = pivot.copyOf() } 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 c2e54bf3b..7108f4865 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -28,8 +28,8 @@ fun List.toVector() = Vector.real(this.size) { this[it] } /** * Convert matrix to vector if it is possible */ -fun > Matrix.toVector(): Vector { - return if (this.numCols == 1) { +fun > Matrix.toVector(): Vector = + if (this.numCols == 1) { // if (this is ArrayMatrix) { // //Reuse existing underlying array // ArrayVector(ArrayVectorSpace(rows, context.field, context.ndFactory), array) @@ -37,9 +37,8 @@ fun > Matrix.toVector(): Vector { // //Generic vector // vector(rows, context.field) { get(it, 0) } // } - Vector.generic(numRows, context.ring) { get(it, 0) } - } else error("Can't convert matrix with more than one column to vector") -} + Vector.generic(numRows, context.ring) { get(it, 0) } + } else error("Can't convert matrix with more than one column to vector") fun > Vector.toMatrix(): Matrix { // val context = StructureMatrixContext(size, 1, context.space) @@ -56,9 +55,8 @@ fun > Vector.toMatrix(): Matrix { } object VectorL2Norm : Norm, Double> { - override fun norm(arg: Vector): Double { - return kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) - } + override fun norm(arg: Vector): Double = + kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) } typealias RealVector = Vector diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 13a54cb5d..34ce820ec 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -33,9 +33,11 @@ interface MatrixSpace> : Space> { val one get() = produce { i, j -> if (i == j) ring.one else ring.zero } - override fun add(a: Matrix, b: Matrix): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } + override fun add(a: Matrix, b: Matrix): Matrix = + produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } - override fun multiply(a: Matrix, k: Double): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } + override fun multiply(a: Matrix, k: Double): Matrix = + produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } companion object { /** @@ -120,21 +122,24 @@ data class StructureMatrixSpace>( private val strides = DefaultStrides(shape) - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { - return if (rows == rowNum && columns == colNum) { - val structure = NdStructure(strides, bufferFactory) { initializer(it[0], it[1]) } - StructureMatrix(this, structure) - } else { - val context = StructureMatrixSpace(rows, columns, ring, bufferFactory) - val structure = NdStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) } - StructureMatrix(context, structure) - } - } + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix = + if (rows == rowNum && columns == colNum) { + val structure = NdStructure(strides, bufferFactory) { initializer(it[0], it[1]) } + StructureMatrix(this, structure) + } else { + val context = StructureMatrixSpace(rows, columns, ring, bufferFactory) + val structure = NdStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) } + StructureMatrix(context, structure) + } override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) } -data class StructureMatrix>(override val context: StructureMatrixSpace, val structure: NDStructure) : Matrix { +data class StructureMatrix>( + override val context: StructureMatrixSpace, + val structure: NDStructure +) : Matrix { + init { if (structure.shape.size != 2 || structure.shape[0] != context.rowNum || structure.shape[1] != context.colNum) { error("Dimension mismatch for structure, (${context.rowNum}, ${context.colNum}) expected, but ${structure.shape} found") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 756f85959..37de60d7b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -33,9 +33,8 @@ interface VectorSpace> : Space> { /** * Non-boxing double vector space */ - fun real(size: Int): BufferVectorSpace { - return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, DoubleField, DoubleBufferFactory) } - } + fun real(size: Int): BufferVectorSpace = + realSpaceCache.getOrPut(size) { BufferVectorSpace(size, DoubleField, DoubleBufferFactory) } /** * A structured vector space with custom buffer @@ -69,16 +68,12 @@ interface Vector> : SpaceElement, VectorSpace Double) = VectorSpace.real(size).produce(initializer) - fun ofReal(vararg elements: Double) = VectorSpace.real(elements.size).produce{elements[it]} + fun ofReal(vararg elements: Double) = VectorSpace.real(elements.size).produce { elements[it] } } } -data class BufferVectorSpace>( - override val size: Int, - override val space: S, - val bufferFactory: BufferFactory -) : VectorSpace { +data class BufferVectorSpace>(override val size: Int, override val space: S, val bufferFactory: BufferFactory) : VectorSpace { override fun produce(initializer: (Int) -> T): Vector = BufferVector(this, bufferFactory(size, initializer)) } @@ -91,9 +86,7 @@ data class BufferVector>(override val context: VectorSpace } } - override fun get(index: Int): T { - return buffer[index] - } + override fun get(index: Int): T = buffer[index] override val self: BufferVector get() = this diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt index 4d4f8ced6..a3f5cfbbb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt @@ -32,28 +32,28 @@ fun List.cumulative(initial: R, operation: (T, R) -> R): List = thi //Cumulative sum @JvmName("cumulativeSumOfDouble") -fun Iterable.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element} +fun Iterable.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @JvmName("cumulativeSumOfInt") -fun Iterable.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element} +fun Iterable.cumulativeSum() = this.cumulative(0) { element, sum -> sum + element } @JvmName("cumulativeSumOfLong") -fun Iterable.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element} +fun Iterable.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } @JvmName("cumulativeSumOfDouble") -fun Sequence.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element} +fun Sequence.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @JvmName("cumulativeSumOfInt") -fun Sequence.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element} +fun Sequence.cumulativeSum() = this.cumulative(0) { element, sum -> sum + element } @JvmName("cumulativeSumOfLong") -fun Sequence.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element} +fun Sequence.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } @JvmName("cumulativeSumOfDouble") -fun List.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element} +fun List.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @JvmName("cumulativeSumOfInt") -fun List.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element} +fun List.cumulativeSum() = this.cumulative(0) { element, sum -> sum + element } @JvmName("cumulativeSumOfLong") -fun List.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element} \ No newline at end of file +fun List.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt index c16b03608..90ce5da68 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt @@ -8,30 +8,29 @@ package scientifik.kmath.misc * * If step is negative, the same goes from upper boundary downwards */ -fun ClosedFloatingPointRange.toSequence(step: Double): Sequence { - return when { - step == 0.0 -> error("Zero step in double progression") - step > 0 -> sequence { - var current = start - while (current <= endInclusive) { - yield(current) - current += step +fun ClosedFloatingPointRange.toSequence(step: Double): Sequence = + when { + step == 0.0 -> error("Zero step in double progression") + step > 0 -> sequence { + var current = start + while (current <= endInclusive) { + yield(current) + current += step + } + } + else -> sequence { + var current = endInclusive + while (current >= start) { + yield(current) + current += step + } } } - else -> sequence { - var current = endInclusive - while (current >= start) { - yield(current) - current += step - } - } - } -} /** * Convert double range to array of evenly spaced doubles, where the size of array equals [numPoints] */ fun ClosedFloatingPointRange.toGrid(numPoints: Int): DoubleArray { - if (numPoints < 2) error("Can't generic grid with less than two points") + if (numPoints < 2) error("Can't create generic grid with less than two points") return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 51f3e75b3..4f5b93552 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -53,6 +53,7 @@ interface Space { //TODO move to external extensions when they are available fun Iterable.sum(): T = fold(zero) { left, right -> left + right } + fun Sequence.sum(): T = fold(zero) { left, right -> left + right } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt index eabae8ea4..ca6a48b12 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt @@ -79,11 +79,11 @@ object DoubleField : ExtendedField, Norm { /** * A field for double without boxing. Does not produce appropriate field element */ -object IntField : Field{ +object IntField : Field { 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 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 } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index ebe2a67cf..d0ae6a87b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -2,12 +2,15 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -open class BufferNDField>(final override val shape: IntArray, final override val field: F, val bufferFactory: BufferFactory) : NDField { +open class BufferNDField>( + final override val shape: IntArray, + final override val field: F, + val bufferFactory: BufferFactory +) : NDField { val strides = DefaultStrides(shape) - override fun produce(initializer: F.(IntArray) -> T): BufferNDElement { - return BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) - } + override fun produce(initializer: F.(IntArray) -> T): BufferNDElement = + BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) open fun produceBuffered(initializer: F.(Int) -> T) = BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(offset) }) @@ -31,7 +34,10 @@ open class BufferNDField>(final override val shape: IntArray, fi // } } -class BufferNDElement>(override val context: BufferNDField, val buffer: Buffer) : NDElement { +class BufferNDElement>( + override val context: BufferNDField, + val buffer: Buffer +) : NDElement { override val self: NDStructure get() = this override val shape: IntArray get() = context.shape @@ -60,7 +66,7 @@ operator fun > BufferNDElement.plus(arg: T) = /** * Subtraction operation between [BufferNDElement] and single element */ -operator fun > BufferNDElement.minus(arg: T) = +operator fun > BufferNDElement.minus(arg: T) = context.produceBuffered { i -> buffer[i] - arg } /* prod and div */ @@ -68,11 +74,11 @@ operator fun > BufferNDElement.minus(arg: T) = /** * Product operation for [BufferNDElement] and single element */ -operator fun > BufferNDElement.times(arg: T) = +operator fun > BufferNDElement.times(arg: T) = context.produceBuffered { i -> buffer[i] * arg } /** * Division operation between [BufferNDElement] and single element */ -operator fun > BufferNDElement.div(arg: T) = +operator fun > BufferNDElement.div(arg: T) = context.produceBuffered { i -> buffer[i] / arg } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index cffc2bea0..74e7af8d9 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -139,14 +139,13 @@ inline fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = Lis * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineBuffer(size: Int, initializer: (Int) -> T): Buffer { - return when (T::class) { - Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer - Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer - Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer - else -> boxingBuffer(size, initializer) - } -} +inline fun inlineBuffer(size: Int, initializer: (Int) -> T): Buffer = + when (T::class) { + Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer + Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer + else -> boxingBuffer(size, initializer) + } /** * Create a boxing mutable buffer of given type @@ -157,14 +156,13 @@ inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): Mu * Create most appropriate mutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer { - return when (T::class) { - Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer - Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer - Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer - else -> boxingMutableBuffer(size, initializer) - } -} +inline fun inlineMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = + when (T::class) { + Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer + Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer + else -> boxingMutableBuffer(size, initializer) + } typealias BufferFactory = (Int, (Int) -> T) -> Buffer typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index d274f92bd..cf6a1ca0c 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -23,25 +23,16 @@ inline class ExtendedNDFieldWrapper>(private val n override fun produce(initializer: F.(IntArray) -> T): NDElement = ndField.produce(initializer) - override fun power(arg: NDStructure, pow: Double): NDElement { - return produce { with(field) { power(arg[it], pow) } } - } + override fun power(arg: NDStructure, pow: Double): NDElement = + produce { with(field) { power(arg[it], pow) } } - override fun exp(arg: NDStructure): NDElement { - return produce { with(field) { exp(arg[it]) } } - } + override fun exp(arg: NDStructure): NDElement = produce { with(field) { exp(arg[it]) } } - override fun ln(arg: NDStructure): NDElement { - return produce { with(field) { ln(arg[it]) } } - } + override fun ln(arg: NDStructure): NDElement = produce { with(field) { ln(arg[it]) } } - override fun sin(arg: NDStructure): NDElement { - return produce { with(field) { sin(arg[it]) } } - } + override fun sin(arg: NDStructure): NDElement = produce { with(field) { sin(arg[it]) } } - override fun cos(arg: NDStructure): NDElement { - return produce { with(field) { cos(arg[it]) } } - } + override fun cos(arg: NDStructure): NDElement = produce { with(field) { cos(arg[it]) } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 4fd6c3ee5..1fb0328b2 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -11,8 +11,8 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run /** * Field for n-dimensional arrays. - * @param shape - the list of dimensions of the array - * @param field - operations field defined on individual array element + * @property shape - the list of dimensions of the array + * @property field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ interface NDField> : Field> { @@ -33,13 +33,8 @@ interface NDField> : Field> { /** * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field */ - fun checkShape(vararg elements: NDStructure) { - elements.forEach { - if (!shape.contentEquals(it.shape)) { - throw ShapeMismatchException(shape, it.shape) - } - } - } + fun checkShape(vararg elements: NDStructure) = + elements.forEach { if (!shape.contentEquals(it.shape)) throw ShapeMismatchException(shape, it.shape) } /** * Element-by-element addition @@ -97,21 +92,17 @@ interface NDElement> : FieldElement, NDField Double = { 0.0 }): NDElement { - return NDField.real(shape).produce(initializer) - } + fun real(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement = + NDField.real(shape).produce(initializer) - fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { - return real(intArrayOf(dim)) { initializer(it[0]) } - } + fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement = + real(intArrayOf(dim)) { initializer(it[0]) } - fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { - return real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } - } + fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement = + real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } - fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement { - return real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - } + fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement = + real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } // inline fun real(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { // val field = NDField.real(shape) @@ -121,13 +112,11 @@ interface NDElement> : FieldElement, NDField> generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): NDElement { - return NDField.generic(shape, field).produce(initializer) - } + fun > generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): NDElement = + NDField.generic(shape, field).produce(initializer) - inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): NDElement { - return NDField.inline(shape, field).produce(initializer) - } + inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): NDElement = + NDField.inline(shape, field).produce(initializer) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 765d7148b..6c5d81713 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -19,11 +19,8 @@ interface MutableNDStructure : NDStructure { operator fun set(index: IntArray, value: T) } -fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T) { - elements().forEach { (index, oldValue) -> - this[index] = action(index, oldValue) - } -} +fun MutableNDStructure.mapInPlace(action: (IntArray, T) -> T) = + elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } /** * A way to convert ND index to linear one and back @@ -56,11 +53,11 @@ interface Strides { /** * Iterate over ND indices in a natural order + * + * TODO: introduce a fast way to calculate index of the next element? */ - fun indices(): Sequence { - //TODO introduce a fast way to calculate index of the next element? - return (0 until linearSize).asSequence().map { index(it) } - } + fun indices(): Sequence = + (0 until linearSize).asSequence().map { index(it) } } class DefaultStrides private constructor(override val shape: IntArray) : Strides { @@ -128,25 +125,21 @@ abstract class GenericNDStructure> : NDStructure { /** * Boxing generic [NDStructure] */ -class BufferNDStructure( - override val strides: Strides, - override val buffer: Buffer -) : GenericNDStructure>() { - +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}") } } - override fun equals(other: Any?): Boolean { - return when { - this === other -> true - other is BufferNDStructure<*> -> this.strides == other.strides && this.buffer.contentEquals(other.buffer) - other is NDStructure<*> -> elements().all { (index, value) -> value == other[index] } - else -> false - } - } + override fun equals(other: Any?): Boolean = + when { + this === other -> true + other is BufferNDStructure<*> -> this.strides == other.strides && this.buffer.contentEquals(other.buffer) + other is NDStructure<*> -> elements().all { (index, value) -> value == other[index] } + else -> false + } override fun hashCode(): Int { var result = strides.hashCode() @@ -158,14 +151,13 @@ class BufferNDStructure( /** * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] */ -inline fun NDStructure.map(factory: BufferFactory = ::inlineBuffer, crossinline transform: (T) -> R): BufferNDStructure { - return if (this is BufferNDStructure) { - BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) - } else { - val strides = DefaultStrides(shape) - BufferNDStructure(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) - } -} +inline fun NDStructure.map(factory: BufferFactory = ::inlineBuffer, crossinline transform: (T) -> R): BufferNDStructure = + if (this is BufferNDStructure) { + BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) + } else { + val strides = DefaultStrides(shape) + BufferNDStructure(strides, factory.invoke(strides.linearSize) { transform(get(strides.index(it))) }) + } /** * Create a NDStructure with explicit buffer factory diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 7705c710e..f30460f5a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -9,14 +9,13 @@ class RealNDField(shape: IntArray) : BufferNDField(shape, D /** * Inline map an NDStructure to */ - private inline fun NDStructure.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement { - return if (this is BufferNDElement) { - val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } - BufferNDElement(this@RealNDField, DoubleBuffer(array)) - } else { - produce { index -> DoubleField.operation(get(index)) } - } - } + private inline fun NDStructure.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement = + if (this is BufferNDElement) { + val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } + BufferNDElement(this@RealNDField, DoubleBuffer(array)) + } else { + produce { index -> DoubleField.operation(get(index)) } + } @Suppress("OVERRIDE_BY_INLINE") @@ -58,16 +57,12 @@ inline fun BufferNDField.produceInline(crossinline initiali operator fun Function1.invoke(ndElement: RealNDElement) = ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } -/* plus and minus */ - /** * Summation operation for [BufferNDElement] and single element */ -operator fun RealNDElement.plus(arg: Double) = - context.produceInline { i -> buffer[i] + arg } +operator fun RealNDElement.plus(arg: Double) = context.produceInline { i -> buffer[i] + arg } /** * Subtraction operation between [BufferNDElement] and single element */ -operator fun RealNDElement.minus(arg: Double) = - context.produceInline { i -> buffer[i] - arg } \ No newline at end of file +operator fun RealNDElement.minus(arg: Double) = context.produceInline { i -> buffer[i] - arg } \ No newline at end of file From 53d752dee8267f0b11b1ede2c1807c8634ffab15 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 3 Jan 2019 20:48:10 +0300 Subject: [PATCH 17/70] Major refactoring of types for algebra elements. Element type could differ from field type. --- .../structures/StructureWriteBenchmark.kt | 6 +- .../kmath/linear/LUDecomposition.kt | 2 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 4 +- .../kotlin/scientifik/kmath/linear/Vector.kt | 28 ++- .../scientifik/kmath/operations/Algebra.kt | 48 +---- .../kmath/operations/AlgebraElements.kt | 49 +++++ .../scientifik/kmath/operations/Complex.kt | 12 +- .../scientifik/kmath/operations/Fields.kt | 58 +++--- .../kmath/structures/BufferNDField.kt | 90 ++++---- .../kmath/structures/ExtendedNDField.kt | 24 +-- .../scientifik/kmath/structures/NDElement.kt | 126 +++++++++++ .../scientifik/kmath/structures/NDField.kt | 197 ++++-------------- .../kmath/structures/NDStructure.kt | 36 ++-- .../kmath/structures/RealNDField.kt | 33 ++- .../kmath/structures/NDFieldTest.kt | 2 +- .../kmath/structures/NumberNDFieldTest.kt | 1 - .../kmath/structures/LazyNDField.kt | 14 +- 17 files changed, 378 insertions(+), 352 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt index 538993d8d..4402264f8 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt @@ -8,12 +8,12 @@ fun main(args: Array) { val n = 6000 - val structure = NdStructure(intArrayOf(n, n), DoubleBufferFactory) { 1.0 } + val structure = ndStructure(intArrayOf(n, n), DoubleBufferFactory) { 1.0 } - structure.map { it + 1 } // warm-up + structure.mapToBuffer { it + 1 } // warm-up val time1 = measureTimeMillis { - val res = structure.map { it + 1 } + val res = structure.mapToBuffer { it + 1 } } println("Structure mapping finished in $time1 millis") 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 3f14787f2..311e590b6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -104,7 +104,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr val m = matrix.numCols val pivot = IntArray(matrix.numRows) //TODO fix performance - val lu: MutableNDStructure = MutableNdStructure(intArrayOf(matrix.numRows, matrix.numCols), ::boxingMutableBuffer) { index: IntArray -> matrix[index[0], index[1]] } + val lu: MutableNDStructure = mutableNdStructure(intArrayOf(matrix.numRows, matrix.numCols), ::boxingMutableBuffer) { index: IntArray -> matrix[index[0], index[1]] } with(matrix.context.ring) { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 13a54cb5d..f5269c74b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -122,11 +122,11 @@ data class StructureMatrixSpace>( override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { return if (rows == rowNum && columns == colNum) { - val structure = NdStructure(strides, bufferFactory) { initializer(it[0], it[1]) } + val structure = ndStructure(strides, bufferFactory) { initializer(it[0], it[1]) } StructureMatrix(this, structure) } else { val context = StructureMatrixSpace(rows, columns, ring, bufferFactory) - val structure = NdStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) } + val structure = ndStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) } StructureMatrix(context, structure) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 756f85959..12758d887 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -16,13 +16,18 @@ interface VectorSpace> : Space> { val space: S - fun produce(initializer: (Int) -> T): Vector + fun produce(initializer: (Int) -> T): Point - override val zero: Vector get() = produce { space.zero } + /** + * Produce a space-element of this vector space for expressions + */ + fun produceElement(initializer: (Int) -> T): Vector - override fun add(a: Point, b: Point): Vector = produce { with(space) { a[it] + b[it] } } + override val zero: Point get() = produce { space.zero } - override fun multiply(a: Point, k: Double): Vector = produce { with(space) { a[it] * k } } + override fun add(a: Point, b: Point): Point = produce { with(space) { a[it] + b[it] } } + + override fun multiply(a: Point, k: Double): Point = produce { with(space) { a[it] * k } } //TODO add basis @@ -53,7 +58,7 @@ interface VectorSpace> : Space> { /** * A point coupled to the linear space */ -interface Vector> : SpaceElement, VectorSpace>, Point { +interface Vector> : SpaceElement, VectorSpace>, Point { override val size: Int get() = context.size override operator fun plus(b: Point): Vector = context.add(self, b) @@ -65,11 +70,11 @@ interface Vector> : SpaceElement, VectorSpace> generic(size: Int, field: F, initializer: (Int) -> T) = - VectorSpace.buffered(size, field).produce(initializer) + fun > generic(size: Int, field: S, initializer: (Int) -> T): Vector = + VectorSpace.buffered(size, field).produceElement(initializer) - fun real(size: Int, initializer: (Int) -> Double) = VectorSpace.real(size).produce(initializer) - fun ofReal(vararg elements: Double) = VectorSpace.real(elements.size).produce{elements[it]} + fun real(size: Int, initializer: (Int) -> Double): Vector = VectorSpace.real(size).produceElement(initializer) + fun ofReal(vararg elements: Double): Vector = VectorSpace.real(elements.size).produceElement { elements[it] } } } @@ -79,7 +84,8 @@ data class BufferVectorSpace>( override val space: S, val bufferFactory: BufferFactory ) : VectorSpace { - override fun produce(initializer: (Int) -> T): Vector = BufferVector(this, bufferFactory(size, initializer)) + override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer) + override fun produceElement(initializer: (Int) -> T): Vector = BufferVector(this, produce(initializer)) } @@ -95,7 +101,7 @@ data class BufferVector>(override val context: VectorSpace return buffer[index] } - override val self: BufferVector get() = this + override fun getSelf(): BufferVector = (0 until size).map { buffer[it] }.iterator() diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 51f3e75b3..659ab9d07 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,23 +1,6 @@ package scientifik.kmath.operations -/** - * The generic mathematics elements which is able to store its context - * @param T the self type of the element - * @param S the type of mathematical context for this element - */ -interface MathElement { - /** - * Self value. Needed for static type checking. - */ - val self: T - - /** - * The context this element belongs to - */ - val context: S -} - /** * A general interface representing linear context of some kind. * The context defines sum operation for its elements and multiplication by real value. @@ -53,19 +36,8 @@ interface Space { //TODO move to external extensions when they are available fun Iterable.sum(): T = fold(zero) { left, right -> left + right } - fun Sequence.sum(): T = fold(zero) { left, right -> left + right } -} -/** - * The element of linear context - * @param T self type of the element. Needed for static type checking - * @param S the type of space - */ -interface SpaceElement> : MathElement { - operator fun plus(b: T): T = context.add(self, b) - operator fun minus(b: T): T = context.add(self, context.multiply(b, -1.0)) - operator fun times(k: Number): T = context.multiply(self, k.toDouble()) - operator fun div(k: Number): T = context.multiply(self, 1.0 / k.toDouble()) + fun Sequence.sum(): T = fold(zero) { left, right -> left + right } } /** @@ -86,15 +58,6 @@ interface Ring : Space { } -/** - * Ring element - */ -interface RingElement> : SpaceElement { - override val context: S - - operator fun times(b: T): T = context.multiply(self, b) -} - /** * Four operations algebra */ @@ -109,13 +72,4 @@ interface Field : Ring { operator fun T.minus(b: Number) = this.minus(b * one) operator fun Number.minus(b: T) = -b + this -} - -/** - * Field element - */ -interface FieldElement> : RingElement { - override val context: F - - operator fun div(b: T): T = context.divide(self, b) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt new file mode 100644 index 000000000..4e5854453 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt @@ -0,0 +1,49 @@ +package scientifik.kmath.operations + +/** + * The generic mathematics elements which is able to store its context + * @param S the type of mathematical context for this element + */ +interface MathElement { + /** + * The context this element belongs to + */ + val context: S +} + +/** + * The element of linear context + * @param T the type of space operation results + * @param I self type of the element. Needed for static type checking + * @param S the type of space + */ +interface SpaceElement, S : Space> : MathElement { + /** + * Self value. Needed for static type checking. + */ + fun unwrap(): T + + fun T.wrap(): I + + operator fun plus(b: T) = context.add(unwrap(), b).wrap() + operator fun minus(b: T) = context.add(unwrap(), context.multiply(b, -1.0)).wrap() + operator fun times(k: Number) = context.multiply(unwrap(), k.toDouble()).wrap() + operator fun div(k: Number) = context.multiply(unwrap(), 1.0 / k.toDouble()).wrap() +} + +/** + * Ring element + */ +interface RingElement, R : Ring> : SpaceElement { + override val context: R + operator fun times(b: T) = context.multiply(unwrap(), b).wrap() +} + +/** + * Field element + */ +interface FieldElement, F : Field> : RingElement { + override val context: F + + operator fun div(b: T) = context.divide(unwrap(), b).wrap() +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index e8a80a451..6f30e8cfc 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -16,15 +16,18 @@ object ComplexField : Field { override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) - override fun divide(a: Complex, b: Complex): Complex = Complex(a.re * b.re + a.im * b.im, a.re * b.im - a.im * b.re) / b.square + override fun divide(a: Complex, b: Complex): Complex { + val norm = b.square + return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm) + } - operator fun Double.plus(c: Complex) = this.toComplex() + c + operator fun Double.plus(c: Complex) = add(this.toComplex(), c) - operator fun Double.minus(c: Complex) = this.toComplex() - c + operator fun Double.minus(c: Complex) = add(this.toComplex(), -c) operator fun Complex.plus(d: Double) = d + this - operator fun Complex.minus(d: Double) = this - d.toComplex() + operator fun Complex.minus(d: Double) = add(this, -d.toComplex()) operator fun Double.times(c: Complex) = Complex(c.re * this, c.im * this) } @@ -34,6 +37,7 @@ object ComplexField : Field { */ data class Complex(val re: Double, val im: Double) : FieldElement { override val self: Complex get() = this + override val context: ComplexField get() = ComplexField diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt index eabae8ea4..3cc6ed377 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt @@ -12,42 +12,18 @@ interface ExtendedField : ExponentialOperations -/** - * Field for real values - */ -object RealField : ExtendedField, Norm { - override val zero: Real = Real(0.0) - override fun add(a: Real, b: Real): Real = Real(a.value + b.value) - override val one: Real = Real(1.0) - override fun multiply(a: Real, b: Real): Real = Real(a.value * b.value) - override fun multiply(a: Real, k: Double): Real = Real(a.value * k) - override fun divide(a: Real, b: Real): Real = Real(a.value / b.value) - - override fun sin(arg: Real): Real = Real(kotlin.math.sin(arg.value)) - override fun cos(arg: Real): Real = Real(kotlin.math.cos(arg.value)) - - override fun power(arg: Real, pow: Double): Real = Real(arg.value.pow(pow)) - - override fun exp(arg: Real): Real = Real(kotlin.math.exp(arg.value)) - - override fun ln(arg: Real): Real = Real(kotlin.math.ln(arg.value)) - - override fun norm(arg: Real): Real = Real(kotlin.math.abs(arg.value)) -} /** * Real field element wrapping double. * * TODO inline does not work due to compiler bug. Waiting for fix for KT-27586 */ -inline class Real(val value: Double) : FieldElement { +inline class Real(val value: Double) : FieldElement { + override fun unwrap(): Double = value - //values are dynamically calculated to save memory - override val self - get() = this + override fun Double.wrap(): Real = Real(value) - override val context - get() = RealField + override val context get() = DoubleField companion object { @@ -79,11 +55,31 @@ object DoubleField : ExtendedField, Norm { /** * A field for double without boxing. Does not produce appropriate field element */ -object IntField : Field{ +object IntField : Field { 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 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 -} \ No newline at end of file +} + +//interface FieldAdapter : Field { +// +// val field: Field +// +// abstract fun T.evolve(): R +// abstract fun R.devolve(): T +// +// override val zero get() = field.zero.evolve() +// override val one get() = field.zero.evolve() +// +// override fun add(a: R, b: R): R = field.add(a.devolve(), b.devolve()).evolve() +// +// override fun multiply(a: R, k: Double): R = field.multiply(a.devolve(), k).evolve() +// +// +// override fun multiply(a: R, b: R): R = field.multiply(a.devolve(), b.devolve()).evolve() +// +// override fun divide(a: R, b: R): R = field.divide(a.devolve(), b.devolve()).evolve() +//} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index ebe2a67cf..87035b9fa 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -1,45 +1,65 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement -open class BufferNDField>(final override val shape: IntArray, final override val field: F, val bufferFactory: BufferFactory) : NDField { +open class BufferNDField>(final override val shape: IntArray, final override val field: F, val bufferFactory: BufferFactory) : NDField> { val strides = DefaultStrides(shape) - override fun produce(initializer: F.(IntArray) -> T): BufferNDElement { - return BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) - } + override fun produce(initializer: F.(IntArray) -> T) = + BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) - open fun produceBuffered(initializer: F.(Int) -> T) = - BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(offset) }) + open fun NDBuffer.map(transform: F.(T) -> T) = + BufferNDElement(this@BufferNDField, bufferFactory(strides.linearSize) { offset -> field.transform(buffer[offset]) }) -// override fun add(a: NDStructure, b: NDStructure): NDElement { -// checkShape(a, b) -// return if (a is BufferNDElement && b is BufferNDElement) { -// BufferNDElement(this,bufferFactory(strides.linearSize){i-> field.run { a.buffer[i] + b.buffer[i]}}) -// } else { -// produce { field.run { a[it] + b[it] } } -// } -// } -// -// override fun NDStructure.plus(b: Number): NDElement { -// checkShape(this) -// return if (this is BufferNDElement) { -// BufferNDElement(this@BufferNDField,bufferFactory(strides.linearSize){i-> field.run { this@plus.buffer[i] + b}}) -// } else { -// produce {index -> field.run { this@plus[index] + b } } -// } -// } + open fun NDBuffer.mapIndexed(transform: F.(index: IntArray, T) -> T) = + BufferNDElement(this@BufferNDField, bufferFactory(strides.linearSize) { offset -> field.transform(strides.index(offset), buffer[offset]) }) + + open fun combine(a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T) = + BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.transform(a[offset], b[offset]) }) + + /** + * Convert any [NDStructure] to buffered structure using strides from this context. + * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes + */ + fun NDStructure.toBuffer(): NDBuffer = + this as? NDBuffer ?: produce { index -> get(index) } + + override val zero: NDBuffer by lazy { produce { field.zero } } + + override fun add(a: NDBuffer, b: NDBuffer): NDBuffer = combine(a, b) { aValue, bValue -> add(aValue, bValue) } + + override fun multiply(a: NDBuffer, k: Double): NDBuffer = a.map { it * k } + + override val one: NDBuffer by lazy { produce { field.one } } + + override fun multiply(a: NDBuffer, b: NDBuffer): NDBuffer = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } + + override fun divide(a: NDBuffer, b: NDBuffer): NDBuffer = combine(a, b) { aValue, bValue -> divide(aValue, bValue) } } -class BufferNDElement>(override val context: BufferNDField, val buffer: Buffer) : NDElement { +class BufferNDElement>(override val context: BufferNDField, override val buffer: Buffer) : + NDBuffer, + FieldElement, BufferNDElement, BufferNDField>, + NDElement { + + override val elementField: F get() = context.field + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferNDElement = BufferNDElement(context, this.buffer) + + override val strides get() = context.strides - override val self: NDStructure get() = this override val shape: IntArray get() = context.shape - override fun get(index: IntArray): T = buffer[context.strides.offset(index)] + override fun get(index: IntArray): T = buffer[strides.offset(index)] - override fun elements(): Sequence> = context.strides.indices().map { it to get(it) } + override fun elements(): Sequence> = strides.indices().map { it to get(it) } + override fun map(action: F.(T) -> T): BufferNDElement = context.run { map(action) } + + override fun mapIndexed(transform: F.(index: IntArray, T) -> T): BufferNDElement = context.run { mapIndexed(transform) } } @@ -47,7 +67,7 @@ class BufferNDElement>(override val context: BufferNDField * Element by element application of any operation on elements to the whole array. Just like in numpy */ operator fun > Function1.invoke(ndElement: BufferNDElement) = - ndElement.context.produceBuffered { i -> invoke(ndElement.buffer[i]) } + ndElement.context.run { ndElement.map { invoke(it) } } /* plus and minus */ @@ -55,24 +75,24 @@ operator fun > Function1.invoke(ndElement: BufferNDE * Summation operation for [BufferNDElement] and single element */ operator fun > BufferNDElement.plus(arg: T) = - context.produceBuffered { i -> buffer[i] + arg } + context.run { map { it + arg } } /** * Subtraction operation between [BufferNDElement] and single element */ -operator fun > BufferNDElement.minus(arg: T) = - context.produceBuffered { i -> buffer[i] - arg } +operator fun > BufferNDElement.minus(arg: T) = + context.run { map { it - arg } } /* prod and div */ /** * Product operation for [BufferNDElement] and single element */ -operator fun > BufferNDElement.times(arg: T) = - context.produceBuffered { i -> buffer[i] * arg } +operator fun > BufferNDElement.times(arg: T) = + context.run { map { it * arg } } /** * Division operation between [BufferNDElement] and single element */ -operator fun > BufferNDElement.div(arg: T) = - context.produceBuffered { i -> buffer[i] / arg } \ No newline at end of file +operator fun > BufferNDElement.div(arg: T) = + context.run { map { it / arg } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index d274f92bd..7c1b63c23 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -6,40 +6,40 @@ import scientifik.kmath.operations.PowerOperations import scientifik.kmath.operations.TrigonometricOperations -interface ExtendedNDField> : - NDField, - TrigonometricOperations>, - PowerOperations>, - ExponentialOperations> +interface ExtendedNDField, N : NDStructure> : + NDField, + TrigonometricOperations, + PowerOperations, + ExponentialOperations /** * NDField that supports [ExtendedField] operations on its elements */ -inline class ExtendedNDFieldWrapper>(private val ndField: NDField) : ExtendedNDField { +class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : ExtendedNDField, NDField by ndField { override val shape: IntArray get() = ndField.shape override val field: F get() = ndField.field - override fun produce(initializer: F.(IntArray) -> T): NDElement = ndField.produce(initializer) + override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer) - override fun power(arg: NDStructure, pow: Double): NDElement { + override fun power(arg: N, pow: Double): N { return produce { with(field) { power(arg[it], pow) } } } - override fun exp(arg: NDStructure): NDElement { + override fun exp(arg: N): N { return produce { with(field) { exp(arg[it]) } } } - override fun ln(arg: NDStructure): NDElement { + override fun ln(arg: N): N { return produce { with(field) { ln(arg[it]) } } } - override fun sin(arg: NDStructure): NDElement { + override fun sin(arg: N): N { return produce { with(field) { sin(arg[it]) } } } - override fun cos(arg: NDStructure): NDElement { + override fun cos(arg: N): N { return produce { with(field) { cos(arg[it]) } } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt new file mode 100644 index 000000000..7f9f77d4e --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -0,0 +1,126 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement + + +interface NDElement> : NDStructure { + val elementField: F + + fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement + fun map(action: F.(T) -> T) = mapIndexed { _, value -> action(value) } +} + + +object NDElements { + /** + * Create a optimized NDArray of doubles + */ + fun real(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }) = + NDField.real(shape).produce(initializer) + + + fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }) = + real(intArrayOf(dim)) { initializer(it[0]) } + + + fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }) = + real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + + fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }) = + real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } + + + /** + * Simple boxing NDArray + */ + fun > generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): GenericNDElement { + val ndField = GenericNDField(shape, field) + val structure = ndStructure(shape) { index -> field.initializer(index) } + return GenericNDElement(ndField, structure) + } + + inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): GenericNDElement { + val ndField = GenericNDField(shape, field) + val structure = ndStructure(shape, ::inlineBuffer) { index -> field.initializer(index) } + return GenericNDElement(ndField, structure) + } +} + + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: NDElement) = ndElement.map { value -> this@invoke(value) } + +/* plus and minus */ + +/** + * Summation operation for [NDElements] and single element + */ +operator fun > NDElement.plus(arg: T): NDElement = this.map { value -> elementField.run { arg + value } } + +/** + * Subtraction operation between [NDElements] and single element + */ +operator fun > NDElement.minus(arg: T): NDElement = this.map { value -> elementField.run { arg - value } } + +/* prod and div */ + +/** + * Product operation for [NDElements] and single element + */ +operator fun > NDElement.times(arg: T): NDElement = this.map { value -> elementField.run { arg * value } } + +/** + * Division operation between [NDElements] and single element + */ +operator fun > NDElement.div(arg: T): NDElement = this.map { value -> elementField.run { arg / value } } + + +// /** +// * Reverse sum operation +// */ +// operator fun T.plus(arg: NDStructure): NDElement = produce { index -> +// field.run { this@plus + arg[index] } +// } +// +// /** +// * Reverse minus operation +// */ +// operator fun T.minus(arg: NDStructure): NDElement = produce { index -> +// field.run { this@minus - arg[index] } +// } +// +// /** +// * Reverse product operation +// */ +// operator fun T.times(arg: NDStructure): NDElement = produce { index -> +// field.run { this@times * arg[index] } +// } +// +// /** +// * Reverse division operation +// */ +// operator fun T.div(arg: NDStructure): NDElement = produce { index -> +// field.run { this@div / arg[index] } +// } + + +/** + * Read-only [NDStructure] coupled to the context. + */ +class GenericNDElement>(override val context: NDField>, private val structure: NDStructure) : + NDStructure by structure, + NDElement, + FieldElement, GenericNDElement, NDField>> { + override val elementField: F get() = context.field + + override fun unwrap(): NDStructure = structure + + override fun NDStructure.wrap() = GenericNDElement(context, this) + + override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = + ndStructure(context.shape) { index: IntArray -> context.field.transform(index, get(index)) }.wrap() +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 4fd6c3ee5..e42cebfed 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -1,8 +1,6 @@ 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 @@ -13,27 +11,19 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run * Field for n-dimensional arrays. * @param shape - the list of dimensions of the array * @param field - operations field defined on individual array element - * @param T the type of the element contained in NDArray + * @param T - the type of the element contained in ND structure + * @param F - field over structure elements + * @param R - actual nd-element type of this field */ -interface NDField> : Field> { +interface NDField, N : NDStructure> : Field { val shape: IntArray val field: F - /** - * Create new instance of NDArray using field shape and given initializer - * The producer takes list of indices as argument and returns contained value - */ - fun produce(initializer: F.(IntArray) -> T): NDElement - - override val zero: NDElement get() = produce { zero } - - override val one: NDElement get() = produce { one } - /** * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field */ - fun checkShape(vararg elements: NDStructure) { + fun checkShape(vararg elements: N) { elements.forEach { if (!shape.contentEquals(it.shape)) { throw ShapeMismatchException(shape, it.shape) @@ -41,37 +31,8 @@ interface NDField> : Field> { } } - /** - * Element-by-element addition - */ - override fun add(a: NDStructure, b: NDStructure): NDElement { - checkShape(a, b) - return produce { field.run { a[it] + b[it] } } - } + fun produce(initializer: F.(IntArray) -> T): N - /** - * Multiply all elements by cinstant - */ - override fun multiply(a: NDStructure, k: Double): NDElement { - checkShape(a) - return produce { field.run { a[it] * k } } - } - - /** - * Element-by-element multiplication - */ - override fun multiply(a: NDStructure, b: NDStructure): NDElement { - checkShape(a) - return produce { field.run { a[it] * b[it] } } - } - - /** - * Element-by-element division - */ - override fun divide(a: NDStructure, b: NDStructure): NDElement { - checkShape(a) - return produce { field.run { a[it] / b[it] } } - } companion object { /** @@ -82,7 +43,7 @@ interface NDField> : Field> { /** * Create a nd-field with boxing generic buffer */ - fun > generic(shape: IntArray, field: F) = BufferNDField(shape, field, ::boxingBuffer) + fun > generic(shape: IntArray, field: F) = GenericNDField(shape, field) /** * Create a most suitable implementation for nd-field using reified class @@ -92,123 +53,43 @@ interface NDField> : Field> { } -interface NDElement> : FieldElement, NDField>, NDStructure { - companion object { - /** - * Create a platform-optimized NDArray of doubles - */ - fun real(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement { - return NDField.real(shape).produce(initializer) - } +class GenericNDField>(override val shape: IntArray, override val field: F, val bufferFactory: BufferFactory = ::boxingBuffer) : NDField> { + override fun produce(initializer: F.(IntArray) -> T): NDStructure = ndStructure(shape, bufferFactory) { field.initializer(it) } - fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { - return real(intArrayOf(dim)) { initializer(it[0]) } - } + override val zero: NDStructure by lazy { produce { zero } } - fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { - return real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } - } + override val one: NDStructure by lazy { produce { one } } - fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDElement { - return real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - } -// inline fun real(shape: IntArray, block: ExtendedNDField.() -> NDStructure): NDElement { -// val field = NDField.real(shape) -// return GenericNDElement(field, field.run(block)) -// } - - /** - * Simple boxing NDArray - */ - fun > generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): NDElement { - return NDField.generic(shape, field).produce(initializer) - } - - inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): NDElement { - return NDField.inline(shape, field).produce(initializer) - } + /** + * Element-by-element addition + */ + override fun add(a: NDStructure, b: NDStructure): NDStructure { + checkShape(a, b) + return produce { field.run { a[it] + b[it] } } } -} -inline fun > NDElement.transformIndexed(crossinline action: F.(IntArray, T) -> T): NDElement = context.produce { action(it, get(*it)) } -inline fun > NDElement.transform(crossinline action: F.(T) -> T): NDElement = context.produce { action(get(*it)) } + /** + * Multiply all elements by cinstant + */ + override fun multiply(a: NDStructure, k: Double): NDStructure { + checkShape(a) + return produce { field.run { a[it] * k } } + } + /** + * Element-by-element multiplication + */ + override fun multiply(a: NDStructure, b: NDStructure): NDStructure { + checkShape(a) + return produce { field.run { a[it] * b[it] } } + } -/** - * Element by element application of any operation on elements to the whole array. Just like in numpy - */ -operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.transform { value -> this@invoke(value) } - -/* plus and minus */ - -/** - * Summation operation for [NDElement] and single element - */ -operator fun > NDElement.plus(arg: T): NDElement = transform { value -> - context.field.run { arg + value } -} - -/** - * Subtraction operation between [NDElement] and single element - */ -operator fun > NDElement.minus(arg: T): NDElement = transform { value -> - context.field.run { arg - value } -} - -/* prod and div */ - -/** - * Product operation for [NDElement] and single element - */ -operator fun > NDElement.times(arg: T): NDElement = transform { value -> - context.field.run { arg * value } -} - -/** - * Division operation between [NDElement] and single element - */ -operator fun > NDElement.div(arg: T): NDElement = transform { value -> - context.field.run { arg / value } -} - - -// /** -// * Reverse sum operation -// */ -// operator fun T.plus(arg: NDStructure): NDElement = produce { index -> -// field.run { this@plus + arg[index] } -// } -// -// /** -// * Reverse minus operation -// */ -// operator fun T.minus(arg: NDStructure): NDElement = produce { index -> -// field.run { this@minus - arg[index] } -// } -// -// /** -// * Reverse product operation -// */ -// operator fun T.times(arg: NDStructure): NDElement = produce { index -> -// field.run { this@times * arg[index] } -// } -// -// /** -// * Reverse division operation -// */ -// operator fun T.div(arg: NDStructure): NDElement = produce { index -> -// field.run { this@div / arg[index] } -// } - -class GenericNDField>(override val shape: IntArray, override val field: F) : NDField { - override fun produce(initializer: F.(IntArray) -> T): NDElement = GenericNDElement(this, produceStructure(initializer)) - private inline fun produceStructure(crossinline initializer: F.(IntArray) -> T): NDStructure = NdStructure(shape, ::boxingBuffer) { field.initializer(it) } -} - -/** - * Read-only [NDStructure] coupled to the context. - */ -class GenericNDElement>(override val context: NDField, private val structure: NDStructure) : NDElement, NDStructure by structure { - override val self: NDElement get() = this -} + /** + * Element-by-element division + */ + override fun divide(a: NDStructure, b: NDStructure): NDStructure { + checkShape(a) + return produce { field.run { a[it] / b[it] } } + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 765d7148b..5e55bea78 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -112,26 +112,24 @@ class DefaultStrides private constructor(override val shape: IntArray) : Strides } } -abstract class GenericNDStructure> : NDStructure { - abstract val buffer: B - abstract val strides: Strides +interface NDBuffer : NDStructure { + val buffer: Buffer + val strides: Strides override fun get(index: IntArray): T = buffer[strides.offset(index)] - override val shape: IntArray - get() = strides.shape + override val shape: IntArray get() = strides.shape - override fun elements() = - strides.indices().map { it to this[it] } + override fun elements() = strides.indices().map { it to this[it] } } /** * Boxing generic [NDStructure] */ -class BufferNDStructure( +data class BufferNDStructure( override val strides: Strides, override val buffer: Buffer -) : GenericNDStructure>() { +) : NDBuffer { init { if (strides.linearSize != buffer.size) { @@ -158,7 +156,7 @@ class BufferNDStructure( /** * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] */ -inline fun NDStructure.map(factory: BufferFactory = ::inlineBuffer, crossinline transform: (T) -> R): BufferNDStructure { +inline fun NDStructure.mapToBuffer(factory: BufferFactory = ::inlineBuffer, crossinline transform: (T) -> R): BufferNDStructure { return if (this is BufferNDStructure) { BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) } else { @@ -172,8 +170,7 @@ inline fun NDStructure.map(factory: BufferFactory = : * * Strides should be reused if possible */ -@Suppress("FunctionName") -fun NdStructure(strides: Strides, bufferFactory: BufferFactory, initializer: (IntArray) -> T) = +fun ndStructure(strides: Strides, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** @@ -182,9 +179,8 @@ fun NdStructure(strides: Strides, bufferFactory: BufferFactory, ini inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = BufferNDStructure(strides, inlineBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) -@Suppress("FunctionName") -fun NdStructure(shape: IntArray, bufferFactory: BufferFactory, initializer: (IntArray) -> T) = - NdStructure(DefaultStrides(shape), bufferFactory, initializer) +fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = + ndStructure(DefaultStrides(shape), bufferFactory, initializer) inline fun inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = inlineNDStructure(DefaultStrides(shape), initializer) @@ -195,7 +191,7 @@ inline fun inlineNdStructure(shape: IntArray, crossinline init class MutableBufferNDStructure( override val strides: Strides, override val buffer: MutableBuffer -) : GenericNDStructure>(), MutableNDStructure { +) : NDBuffer, MutableNDStructure { init { if (strides.linearSize != buffer.size) { @@ -209,16 +205,14 @@ class MutableBufferNDStructure( /** * The same as [inlineNDStructure], but mutable */ -@Suppress("FunctionName") -fun MutableNdStructure(strides: Strides, bufferFactory: MutableBufferFactory, initializer: (IntArray) -> T) = +fun mutableNdStructure(strides: Strides, bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, initializer: (IntArray) -> T) = MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) inline fun inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = MutableBufferNDStructure(strides, inlineMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) -@Suppress("FunctionName") -fun MutableNdStructure(shape: IntArray, bufferFactory: MutableBufferFactory, initializer: (IntArray) -> T) = - MutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) +fun mutableNdStructure(shape: IntArray, bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, initializer: (IntArray) -> T) = + mutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) inline fun inlineMutableNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = inlineMutableNdStructure(DefaultStrides(shape), initializer) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 7705c710e..3158c38c1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -4,44 +4,41 @@ import scientifik.kmath.operations.DoubleField typealias RealNDElement = BufferNDElement -class RealNDField(shape: IntArray) : BufferNDField(shape, DoubleField, DoubleBufferFactory), ExtendedNDField { +class RealNDField(shape: IntArray) : + BufferNDField(shape, DoubleField, DoubleBufferFactory), + ExtendedNDField> { /** * Inline map an NDStructure to */ - private inline fun NDStructure.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement { - return if (this is BufferNDElement) { - val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } - BufferNDElement(this@RealNDField, DoubleBuffer(array)) - } else { - produce { index -> DoubleField.operation(get(index)) } - } + private inline fun NDBuffer.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } + return BufferNDElement(this@RealNDField, DoubleBuffer(array)) } - @Suppress("OVERRIDE_BY_INLINE") override inline fun produce(initializer: DoubleField.(IntArray) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> field.initializer(strides.index(offset)) } return BufferNDElement(this, DoubleBuffer(array)) } - override fun power(arg: NDStructure, pow: Double) = arg.mapInline { power(it, pow) } + override fun power(arg: NDBuffer, pow: Double) = arg.mapInline { power(it, pow) } - override fun exp(arg: NDStructure) = arg.mapInline { exp(it) } + override fun exp(arg: NDBuffer) = arg.mapInline { exp(it) } - override fun ln(arg: NDStructure) = arg.mapInline { ln(it) } + override fun ln(arg: NDBuffer) = arg.mapInline { ln(it) } - override fun sin(arg: NDStructure) = arg.mapInline { sin(it) } + override fun sin(arg: NDBuffer) = arg.mapInline { sin(it) } - override fun cos(arg: NDStructure) = arg.mapInline { cos(it) } + override fun cos(arg: NDBuffer) = arg.mapInline { cos(it) } - override fun NDStructure.times(k: Number) = mapInline { value -> value * k.toDouble() } + override fun NDBuffer.times(k: Number) = mapInline { value -> value * k.toDouble() } - override fun NDStructure.div(k: Number) = mapInline { value -> value / k.toDouble() } + override fun NDBuffer.div(k: Number) = mapInline { value -> value / k.toDouble() } - override fun Number.times(b: NDStructure) = b * this + override fun Number.times(b: NDBuffer) = b * this - override fun Number.div(b: NDStructure) = b * (1.0 / this.toDouble()) + override fun Number.div(b: NDBuffer) = b * (1.0 / this.toDouble()) } /** diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt index 39cce5c67..637147629 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertEquals class NDFieldTest { @Test fun testStrides() { - val ndArray = NDElement.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() } + val ndArray = NDElements.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() } assertEquals(ndArray[5, 5], 10.0) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 3c4160329..7c5227293 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,7 +1,6 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Norm -import scientifik.kmath.structures.NDElement.Companion.real2D import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index ad38100cb..8b790da99 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -5,7 +5,7 @@ import scientifik.kmath.operations.Field class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : BufferNDField(shape,field, ::boxingBuffer) { - override fun add(a: NDStructure, b: NDStructure): NDElement { + override fun add(a: NDStructure, b: NDStructure): NDElements { return LazyNDStructure(this) { index -> val aDeferred = a.deferred(index) val bDeferred = b.deferred(index) @@ -13,11 +13,11 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } } - override fun multiply(a: NDStructure, k: Double): NDElement { + override fun multiply(a: NDStructure, k: Double): NDElements { return LazyNDStructure(this) { index -> a.await(index) * k } } - override fun multiply(a: NDStructure, b: NDStructure): NDElement { + override fun multiply(a: NDStructure, b: NDStructure): NDElements { return LazyNDStructure(this) { index -> val aDeferred = a.deferred(index) val bDeferred = b.deferred(index) @@ -25,7 +25,7 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } } - override fun divide(a: NDStructure, b: NDStructure): NDElement { + override fun divide(a: NDStructure, b: NDStructure): NDElements { return LazyNDStructure(this) { index -> val aDeferred = a.deferred(index) val bDeferred = b.deferred(index) @@ -34,8 +34,8 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } } -class LazyNDStructure>(override val context: LazyNDField, val function: suspend F.(IntArray) -> T) : NDElement, NDStructure { - override val self: NDElement get() = this +class LazyNDStructure>(override val context: LazyNDField, val function: suspend F.(IntArray) -> T) : NDElements, NDStructure { + override val self: NDElements get() = this override val shape: IntArray get() = context.shape private val cache = HashMap>() @@ -58,7 +58,7 @@ fun NDStructure.deferred(index: IntArray) = if (this is LazyNDStructure NDStructure.await(index: IntArray) = if (this is LazyNDStructure) this.await(index) else get(index) -fun > NDElement.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { +fun > NDElements.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { return if (this is LazyNDStructure) { this } else { From c0a43c1bd1638b70d38c271912868567bad2aedd Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 4 Jan 2019 18:12:28 +0300 Subject: [PATCH 18/70] Code style update + RealNDField tweaks --- .../kmath/structures/ArrayBenchmark.kt | 2 +- .../kmath/structures/BufferBenchmark.kt | 6 +- .../structures/BufferNDStructureBenchmark.kt | 49 +++++-- .../structures/StructureWriteBenchmark.kt | 13 +- build.gradle.kts | 14 +- .../kmath/expressions/Expression.kt | 28 +++- .../scientifik/kmath/histogram/Counters.kt | 18 +-- .../kmath/histogram/FastHistogram.kt | 18 +-- .../scientifik/kmath/histogram/Histogram.kt | 23 +-- .../kmath/histogram/PhantomHistogram.kt | 9 +- .../kmath/linear/LUDecomposition.kt | 11 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 44 ++++-- .../kotlin/scientifik/kmath/linear/Vector.kt | 47 ++++--- .../scientifik/kmath/misc/Cumulative.kt | 21 +-- .../scientifik/kmath/operations/Algebra.kt | 36 +++-- .../scientifik/kmath/operations/Complex.kt | 9 +- .../scientifik/kmath/operations/Fields.kt | 11 +- .../kmath/operations/OptionalOperations.kt | 23 +-- .../kmath/structures/BufferNDField.kt | 133 ++++++++++++------ .../scientifik/kmath/structures/Buffers.kt | 6 +- .../kmath/structures/ExtendedNDField.kt | 23 +-- .../scientifik/kmath/structures/NDElement.kt | 50 ++++--- .../scientifik/kmath/structures/NDField.kt | 130 ++++++++++++----- .../kmath/structures/NDStructure.kt | 52 ++++--- .../kmath/structures/RealNDField.kt | 76 +++++++--- ...nContextTest.kt => ExpressionFieldTest.kt} | 18 +-- .../histogram/MultivariateHistogramTest.kt | 18 +-- .../scientifik/kmath/linear/MatrixTest.kt | 4 +- .../kmath/linear/RealLUSolverTest.kt | 4 +- .../kmath/operations/RealFieldTest.kt | 7 +- .../kmath/structures/NumberNDFieldTest.kt | 1 + .../scientifik/kmath/histogram/Counters.kt | 33 +++-- .../kmath/histogram/UnivariateHistogram.kt | 5 +- .../scientifik/kmath/structures/BufferSpec.kt | 4 +- .../kmath/structures/ObjectBuffer.kt | 5 +- .../kmath/structures/CoroutinesExtra.kt | 5 +- .../kmath/structures/LazyNDField.kt | 37 +++-- .../kmath/structures/LazyNDFieldTest.kt | 2 +- .../kmath/structures/_CoroutinesExtra.kt | 3 +- settings.gradle.kts | 8 +- 40 files changed, 665 insertions(+), 341 deletions(-) rename kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/{FieldExpressionContextTest.kt => ExpressionFieldTest.kt} (66%) diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt index fe10fbd75..098c61cf6 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt @@ -19,7 +19,7 @@ open class ArrayBenchmark { arrayBuffer = IntBuffer.wrap(array) nativeBuffer = IntBuffer.allocate(10000) for (i in 0 until 10000) { - nativeBuffer.put(i,i) + nativeBuffer.put(i, i) } } diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt index fd27ae3e0..90f9bc372 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt @@ -22,12 +22,12 @@ open class BufferBenchmark { @Benchmark fun complexBufferReadWrite() { - val buffer = Complex.createBuffer(size/2) - (0 until size/2).forEach { + val buffer = Complex.createBuffer(size / 2) + (0 until size / 2).forEach { buffer[it] = Complex(it.toDouble(), -it.toDouble()) } - (0 until size/2).forEach { + (0 until size / 2).forEach { buffer[it] } } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt index de2e6844e..99111f6b3 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt @@ -5,26 +5,50 @@ import kotlin.system.measureTimeMillis fun main(args: Array) { val dim = 1000 - val n = 1000 + val n = 10000 - val genericField = NDField.generic(intArrayOf(dim, dim), DoubleField) - val doubleField = NDField.inline(intArrayOf(dim, dim), DoubleField) + val bufferedField = NDField.buffered(intArrayOf(dim, dim), DoubleField) val specializedField = NDField.real(intArrayOf(dim, dim)) + val genericField = NDField.generic(intArrayOf(dim, dim), DoubleField) + +// val action: NDField>.() -> Unit = { +// var res = one +// repeat(n) { +// res += 1.0 +// } +// } + val doubleTime = measureTimeMillis { - var res = doubleField.produce { one } + + bufferedField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } + } + } + + println("Buffered addition completed in $doubleTime millis") + + + val elementTime = measureTimeMillis { + var res = bufferedField.produce { one } repeat(n) { res += 1.0 } } - println("Inlined addition completed in $doubleTime millis") + println("Element addition completed in $elementTime millis") val specializedTime = measureTimeMillis { - var res = specializedField.produce { one } - repeat(n) { - res += 1.0 + //specializedField.run(action) + specializedField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } } } @@ -32,9 +56,12 @@ fun main(args: Array) { val genericTime = measureTimeMillis { - var res = genericField.produce { one } - repeat(n) { - res += 1.0 + //genericField.run(action) + genericField.run { + var res = one + repeat(n) { + res += 1.0 + } } } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt index 4402264f8..7bb4c8267 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt @@ -3,7 +3,6 @@ package scientifik.kmath.structures import kotlin.system.measureTimeMillis - fun main(args: Array) { val n = 6000 @@ -17,21 +16,21 @@ fun main(args: Array) { } println("Structure mapping finished in $time1 millis") - val array = DoubleArray(n*n){1.0} + val array = DoubleArray(n * n) { 1.0 } val time2 = measureTimeMillis { - val target = DoubleArray(n*n) - val res = array.forEachIndexed{index, value -> + val target = DoubleArray(n * n) + val res = array.forEachIndexed { index, value -> target[index] = value + 1 } } println("Array mapping finished in $time2 millis") - val buffer = DoubleBuffer(DoubleArray(n*n){1.0}) + val buffer = DoubleBuffer(DoubleArray(n * n) { 1.0 }) val time3 = measureTimeMillis { - val target = DoubleBuffer(DoubleArray(n*n)) - val res = array.forEachIndexed{index, value -> + val target = DoubleBuffer(DoubleArray(n * n)) + val res = array.forEachIndexed { index, value -> target[index] = value + 1 } } diff --git a/build.gradle.kts b/build.gradle.kts index 409bbe606..5fa89bc1a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ buildscript { extra["kotlinVersion"] = "1.3.20-eap-52" - extra["ioVersion"] = "0.1.2-dev-2" + extra["ioVersion"] = "0.1.2" extra["coroutinesVersion"] = "1.1.0" val kotlinVersion: String by extra @@ -8,7 +8,7 @@ buildscript { val coroutinesVersion: String by extra repositories { - maven ("https://dl.bintray.com/kotlin/kotlin-eap") + maven("https://dl.bintray.com/kotlin/kotlin-eap") jcenter() } @@ -19,7 +19,7 @@ buildscript { } plugins { - id("com.jfrog.artifactory") version "4.8.1" apply false + id("com.jfrog.artifactory") version "4.8.1" apply false // id("org.jetbrains.kotlin.multiplatform") apply false } @@ -28,14 +28,14 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.2-dev-1" + version = "0.0.3-dev-1" - repositories{ - maven ("https://dl.bintray.com/kotlin/kotlin-eap") + repositories { + maven("https://dl.bintray.com/kotlin/kotlin-eap") jcenter() } } -if(file("artifactory.gradle").exists()){ +if (file("artifactory.gradle").exists()) { apply(from = "artifactory.gradle") } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt index 0a34b536c..c3a6caf58 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt @@ -26,23 +26,28 @@ internal class ConstantExpression(val value: T) : Expression { override fun invoke(arguments: Map): T = value } -internal class SumExpression(val context: Space, val first: Expression, val second: Expression) : Expression { +internal class SumExpression(val context: Space, val first: Expression, val second: Expression) : + Expression { override fun invoke(arguments: Map): T = context.add(first.invoke(arguments), second.invoke(arguments)) } -internal class ProductExpression(val context: Field, val first: Expression, val second: Expression) : Expression { - override fun invoke(arguments: Map): T = context.multiply(first.invoke(arguments), second.invoke(arguments)) +internal class ProductExpression(val context: Field, val first: Expression, val second: Expression) : + Expression { + override fun invoke(arguments: Map): T = + context.multiply(first.invoke(arguments), second.invoke(arguments)) } -internal class ConstProductExpession(val context: Field, val expr: Expression, val const: Double) : Expression { +internal class ConstProductExpession(val context: Field, val expr: Expression, val const: Double) : + Expression { override fun invoke(arguments: Map): T = context.multiply(expr.invoke(arguments), const) } -internal class DivExpession(val context: Field, val expr: Expression, val second: Expression) : Expression { +internal class DivExpession(val context: Field, val expr: Expression, val second: Expression) : + Expression { override fun invoke(arguments: Map): T = context.divide(expr.invoke(arguments), second.invoke(arguments)) } -class FieldExpressionContext(val field: Field) : Field>, ExpressionContext { +class ExpressionField(val field: Field) : Field>, ExpressionContext { override val zero: Expression = ConstantExpression(field.zero) @@ -59,4 +64,15 @@ class FieldExpressionContext(val field: Field) : Field>, Exp override fun multiply(a: Expression, b: Expression): Expression = ProductExpression(field, a, b) override fun divide(a: Expression, b: Expression): Expression = DivExpession(field, a, b) + + + operator fun Expression.plus(arg: T) = this + const(arg) + operator fun Expression.minus(arg: T) = this - const(arg) + operator fun Expression.times(arg: T) = this * const(arg) + operator fun Expression.div(arg: T) = this / const(arg) + + operator fun T.plus(arg: Expression) = arg + this + operator fun T.minus(arg: Expression) = arg - this + operator fun T.times(arg: Expression) = arg * this + operator fun T.div(arg: Expression) = arg / this } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt index f6cc1f822..9a470014a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -5,16 +5,16 @@ package scientifik.kmath.histogram */ -expect class LongCounter(){ - fun decrement() - fun increment() - fun reset() - fun sum(): Long - fun add(l:Long) +expect class LongCounter() { + fun decrement() + fun increment() + fun reset() + fun sum(): Long + fun add(l: Long) } -expect class DoubleCounter(){ - fun reset() - fun sum(): Double +expect class DoubleCounter() { + fun reset() + fun sum(): Double fun add(d: Double) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt index 705a8e7ca..d9d4da66a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -6,15 +6,16 @@ import kotlin.math.floor private operator fun RealPoint.minus(other: RealPoint) = ListBuffer((0 until size).map { get(it) - other[it] }) -private inline fun Buffer.mapIndexed(crossinline mapper: (Int, Double) -> T): Sequence = (0 until size).asSequence().map { mapper(it, get(it)) } +private inline fun Buffer.mapIndexed(crossinline mapper: (Int, Double) -> T): Sequence = + (0 until size).asSequence().map { mapper(it, get(it)) } /** * Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions. */ class FastHistogram( - private val lower: RealPoint, - private val upper: RealPoint, - private val binNums: IntArray = IntArray(lower.size) { 20 } + private val lower: RealPoint, + private val upper: RealPoint, + private val binNums: IntArray = IntArray(lower.size) { 20 } ) : MutableHistogram> { @@ -25,7 +26,8 @@ class FastHistogram( //private val weight: NDStructure = ndStructure(strides){null} //TODO optimize binSize performance if needed - private val binSize: RealPoint = ListBuffer((upper - lower).mapIndexed { index, value -> value / binNums[index] }.toList()) + private val binSize: RealPoint = + ListBuffer((upper - lower).mapIndexed { index, value -> value / binNums[index] }.toList()) init { // argument checks @@ -130,9 +132,9 @@ class FastHistogram( */ fun fromRanges(vararg ranges: Pair, Int>): FastHistogram { return FastHistogram( - ListBuffer(ranges.map { it.first.start }), - ListBuffer(ranges.map { it.first.endInclusive }), - ranges.map { it.second }.toIntArray() + ListBuffer(ranges.map { it.first.start }), + ListBuffer(ranges.map { it.first.endInclusive }), + ranges.map { it.second }.toIntArray() ) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt index 08214142e..a71ab7207 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -1,10 +1,10 @@ package scientifik.kmath.histogram +import scientifik.kmath.linear.Point import scientifik.kmath.structures.ArrayBuffer import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.DoubleBuffer -typealias Point = Buffer typealias RealPoint = Buffer @@ -12,7 +12,7 @@ typealias RealPoint = Buffer * A simple geometric domain * TODO move to geometry module */ -interface Domain { +interface Domain { operator fun contains(vector: Point): Boolean val dimension: Int } @@ -20,7 +20,7 @@ interface Domain { /** * The bin in the histogram. The histogram is by definition always done in the real space */ -interface Bin : Domain { +interface Bin : Domain { /** * The value of this bin */ @@ -28,7 +28,7 @@ interface Bin : Domain { val center: Point } -interface Histogram> : Iterable { +interface Histogram> : Iterable { /** * Find existing bin, corresponding to given coordinates @@ -42,7 +42,7 @@ interface Histogram> : Iterable { } -interface MutableHistogram>: Histogram{ +interface MutableHistogram> : Histogram { /** * Increment appropriate bin @@ -50,14 +50,17 @@ interface MutableHistogram>: Histogram{ fun put(point: Point, weight: Double = 1.0) } -fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) +fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) -fun MutableHistogram.put(vararg point: Number) = put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray())) -fun MutableHistogram.put(vararg point: Double) = put(DoubleBuffer(point)) +fun MutableHistogram.put(vararg point: Number) = + put(DoubleBuffer(point.map { it.toDouble() }.toDoubleArray())) -fun MutableHistogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } +fun MutableHistogram.put(vararg point: Double) = put(DoubleBuffer(point)) + +fun MutableHistogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } /** * Pass a sequence builder into histogram */ -fun MutableHistogram.fill(buider: suspend SequenceScope>.() -> Unit) = fill(sequence(buider).asIterable()) \ No newline at end of file +fun MutableHistogram.fill(buider: suspend SequenceScope>.() -> Unit) = + fill(sequence(buider).asIterable()) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt index ffffb0d7d..1d04a8c19 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt @@ -1,5 +1,6 @@ package scientifik.kmath.histogram +import scientifik.kmath.linear.Point import scientifik.kmath.linear.Vector import scientifik.kmath.operations.Space import scientifik.kmath.structures.NDStructure @@ -8,8 +9,8 @@ import scientifik.kmath.structures.asSequence data class BinTemplate>(val center: Vector, val sizes: Point) { fun contains(vector: Point): 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} + 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 } @@ -44,8 +45,8 @@ class PhantomBin>(val template: BinTemplate, override val v * @param bins map a template into structure index */ class PhantomHistogram>( - val bins: Map, IntArray>, - val data: NDStructure + val bins: Map, IntArray>, + val data: NDStructure ) : Histogram> { override val dimension: Int 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 311e590b6..3db4809be 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -104,7 +104,10 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr val m = matrix.numCols val pivot = IntArray(matrix.numRows) //TODO fix performance - val lu: MutableNDStructure = mutableNdStructure(intArrayOf(matrix.numRows, matrix.numCols), ::boxingMutableBuffer) { index: IntArray -> matrix[index[0], index[1]] } + val lu: MutableNDStructure = mutableNdStructure( + intArrayOf(matrix.numRows, matrix.numCols), + ::boxingMutableBuffer + ) { index: IntArray -> matrix[index[0], index[1]] } with(matrix.context.ring) { @@ -180,7 +183,8 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr } -class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : LUDecomposition(matrix) { +class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : + LUDecomposition(matrix) { override fun isSingular(value: Double): Boolean { return value.absoluteValue < singularityThreshold } @@ -195,7 +199,8 @@ class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: /** Specialized solver. */ object RealLUSolver : LinearSolver { - fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) + fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = + RealLUDecomposition(mat, threshold) override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix { val decomposition = decompose(a, a.context.ring.zero) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index f5269c74b..e9a45f90a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -1,6 +1,5 @@ package scientifik.kmath.linear -import scientifik.kmath.histogram.Point import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space @@ -33,25 +32,34 @@ interface MatrixSpace> : Space> { val one get() = produce { i, j -> if (i == j) ring.one else ring.zero } - override fun add(a: Matrix, b: Matrix): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } + override fun add(a: Matrix, b: Matrix): Matrix = + produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } - override fun multiply(a: Matrix, k: Double): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } + override fun multiply(a: Matrix, k: Double): Matrix = + produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } companion object { /** * Non-boxing double matrix */ - fun real(rows: Int, columns: Int): MatrixSpace = StructureMatrixSpace(rows, columns, DoubleField, DoubleBufferFactory) + fun real(rows: Int, columns: Int): MatrixSpace = + StructureMatrixSpace(rows, columns, DoubleField, DoubleBufferFactory) /** * A structured matrix with custom buffer */ - fun > buffered(rows: Int, columns: Int, ring: R, bufferFactory: BufferFactory = ::boxingBuffer): MatrixSpace = StructureMatrixSpace(rows, columns, ring, bufferFactory) + fun > buffered( + rows: Int, + columns: Int, + ring: R, + bufferFactory: BufferFactory = ::boxingBuffer + ): MatrixSpace = StructureMatrixSpace(rows, columns, ring, bufferFactory) /** * Automatic buffered matrix, unboxed if it is possible */ - inline fun > smart(rows: Int, columns: Int, ring: R): MatrixSpace = buffered(rows, columns, ring, ::inlineBuffer) + inline fun > smart(rows: Int, columns: Int, ring: R): MatrixSpace = + buffered(rows, columns, ring, ::inlineBuffer) } } @@ -59,7 +67,7 @@ interface MatrixSpace> : Space> { /** * Specialized 2-d structure */ -interface Matrix> : NDStructure, SpaceElement, MatrixSpace> { +interface Matrix> : NDStructure, SpaceElement, Matrix, MatrixSpace> { operator fun get(i: Int, j: Int): T override fun get(index: IntArray): T = get(index[0], index[1]) @@ -82,7 +90,7 @@ interface Matrix> : NDStructure, SpaceElement Double) = - MatrixSpace.real(rows, columns).produce(rows, columns, initializer) + MatrixSpace.real(rows, columns).produce(rows, columns, initializer) } } @@ -110,10 +118,10 @@ infix fun > Matrix.dot(vector: Point): Point { } data class StructureMatrixSpace>( - override val rowNum: Int, - override val colNum: Int, - override val ring: R, - private val bufferFactory: BufferFactory + override val rowNum: Int, + override val colNum: Int, + override val ring: R, + private val bufferFactory: BufferFactory ) : MatrixSpace { override val shape: IntArray = intArrayOf(rowNum, colNum) @@ -134,15 +142,21 @@ data class StructureMatrixSpace>( override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) } -data class StructureMatrix>(override val context: StructureMatrixSpace, val structure: NDStructure) : Matrix { +data class StructureMatrix>( + override val context: StructureMatrixSpace, + val structure: NDStructure +) : Matrix { init { if (structure.shape.size != 2 || structure.shape[0] != context.rowNum || structure.shape[1] != context.colNum) { error("Dimension mismatch for structure, (${context.rowNum}, ${context.colNum}) expected, but ${structure.shape} found") } } + override fun unwrap(): Matrix = this + + override fun Matrix.wrap(): Matrix = this + override val shape: IntArray get() = structure.shape - override val self: Matrix get() = this override fun get(index: IntArray): T = structure[index] @@ -153,4 +167,4 @@ data class StructureMatrix>(override val context: Structure //TODO produce transposed matrix via reference without creating new space and structure fun > Matrix.transpose(): Matrix = - context.produce(numCols, numRows) { i, j -> get(j, i) } \ No newline at end of file + context.produce(numCols, numRows) { i, j -> get(j, i) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 12758d887..818a65733 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -1,11 +1,12 @@ package scientifik.kmath.linear -import scientifik.kmath.histogram.Point import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement import scientifik.kmath.structures.* +typealias Point = Buffer + /** * A linear space for vectors. * Could be used on any point-like structure @@ -45,12 +46,17 @@ interface VectorSpace> : Space> { /** * A structured vector space with custom buffer */ - fun > buffered(size: Int, space: S, bufferFactory: BufferFactory = ::boxingBuffer): VectorSpace = BufferVectorSpace(size, space, bufferFactory) + fun > buffered( + size: Int, + space: S, + bufferFactory: BufferFactory = ::boxingBuffer + ): VectorSpace = BufferVectorSpace(size, space, bufferFactory) /** * Automatic buffered vector, unboxed if it is possible */ - inline fun > smart(size: Int, space: S): VectorSpace = buffered(size, space, ::inlineBuffer) + inline fun > smart(size: Int, space: S): VectorSpace = + buffered(size, space, ::inlineBuffer) } } @@ -58,38 +64,42 @@ interface VectorSpace> : Space> { /** * A point coupled to the linear space */ -interface Vector> : SpaceElement, VectorSpace>, Point { +interface Vector> : SpaceElement, Vector, VectorSpace>, Point { override val size: Int get() = context.size - override operator fun plus(b: Point): Vector = context.add(self, b) - override operator fun minus(b: Point): Vector = context.add(self, context.multiply(b, -1.0)) - override operator fun times(k: Number): Vector = context.multiply(self, k.toDouble()) - override operator fun div(k: Number): Vector = context.multiply(self, 1.0 / k.toDouble()) + override operator fun plus(b: Point): Vector = context.add(this, b).wrap() + override operator fun minus(b: Point): Vector = context.add(this, context.multiply(b, -1.0)).wrap() + override operator fun times(k: Number): Vector = context.multiply(this, k.toDouble()).wrap() + override operator fun div(k: Number): Vector = context.multiply(this, 1.0 / k.toDouble()).wrap() companion object { /** * Create vector with custom field */ fun > generic(size: Int, field: S, initializer: (Int) -> T): Vector = - VectorSpace.buffered(size, field).produceElement(initializer) + VectorSpace.buffered(size, field).produceElement(initializer) - fun real(size: Int, initializer: (Int) -> Double): Vector = VectorSpace.real(size).produceElement(initializer) - fun ofReal(vararg elements: Double): Vector = VectorSpace.real(elements.size).produceElement { elements[it] } + fun real(size: Int, initializer: (Int) -> Double): Vector = + VectorSpace.real(size).produceElement(initializer) + + fun ofReal(vararg elements: Double): Vector = + VectorSpace.real(elements.size).produceElement { elements[it] } } } data class BufferVectorSpace>( - override val size: Int, - override val space: S, - val bufferFactory: BufferFactory + override val size: Int, + override val space: S, + val bufferFactory: BufferFactory ) : VectorSpace { override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer) override fun produceElement(initializer: (Int) -> T): Vector = BufferVector(this, produce(initializer)) } -data class BufferVector>(override val context: VectorSpace, val buffer: Buffer) : Vector { +data class BufferVector>(override val context: VectorSpace, val buffer: Buffer) : + Vector { init { if (context.size != buffer.size) { @@ -101,10 +111,13 @@ data class BufferVector>(override val context: VectorSpace return buffer[index] } - override fun getSelf(): BufferVector = this + + override fun Point.wrap(): Vector = BufferVector(context, this) override fun iterator(): Iterator = (0 until size).map { buffer[it] }.iterator() - override fun toString(): String = this.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } + override fun toString(): String = + this.asSequence().joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt index 4d4f8ced6..77a1d54bc 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt @@ -27,33 +27,34 @@ fun Sequence.cumulative(initial: R, operation: (T, R) -> R): Sequence< override fun iterator(): Iterator = this@cumulative.iterator().cumulative(initial, operation) } -fun List.cumulative(initial: R, operation: (T, R) -> R): List = this.iterator().cumulative(initial, operation).asSequence().toList() +fun List.cumulative(initial: R, operation: (T, R) -> R): List = + this.iterator().cumulative(initial, operation).asSequence().toList() //Cumulative sum @JvmName("cumulativeSumOfDouble") -fun Iterable.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element} +fun Iterable.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @JvmName("cumulativeSumOfInt") -fun Iterable.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element} +fun Iterable.cumulativeSum() = this.cumulative(0) { element, sum -> sum + element } @JvmName("cumulativeSumOfLong") -fun Iterable.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element} +fun Iterable.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } @JvmName("cumulativeSumOfDouble") -fun Sequence.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element} +fun Sequence.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @JvmName("cumulativeSumOfInt") -fun Sequence.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element} +fun Sequence.cumulativeSum() = this.cumulative(0) { element, sum -> sum + element } @JvmName("cumulativeSumOfLong") -fun Sequence.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element} +fun Sequence.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } @JvmName("cumulativeSumOfDouble") -fun List.cumulativeSum() = this.cumulative(0.0){ element, sum -> sum + element} +fun List.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @JvmName("cumulativeSumOfInt") -fun List.cumulativeSum() = this.cumulative(0){ element, sum -> sum + element} +fun List.cumulativeSum() = this.cumulative(0) { element, sum -> sum + element } @JvmName("cumulativeSumOfLong") -fun List.cumulativeSum() = this.cumulative(0L){ element, sum -> sum + element} \ No newline at end of file +fun List.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 659ab9d07..51d0815ec 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -33,13 +33,25 @@ interface Space { operator fun T.times(k: Number) = multiply(this, k.toDouble()) operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun Number.times(b: T) = b * this - - //TODO move to external extensions when they are available fun Iterable.sum(): T = fold(zero) { left, right -> left + right } - fun Sequence.sum(): T = fold(zero) { left, right -> left + right } } +abstract class AbstractSpace : Space { + //TODO move to external extensions when they are available + final override operator fun T.unaryMinus(): T = multiply(this, -1.0) + + final override operator fun T.plus(b: T): T = add(this, b) + final override operator fun T.minus(b: T): T = add(this, -b) + final override operator fun T.times(k: Number) = multiply(this, k.toDouble()) + final override operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) + final override operator fun Number.times(b: T) = b * this + + final override fun Iterable.sum(): T = fold(zero) { left, right -> left + right } + + final override fun Sequence.sum(): T = fold(zero) { left, right -> left + right } +} + /** * The same as {@link Space} but with additional multiplication operation */ @@ -56,6 +68,15 @@ interface Ring : Space { operator fun T.times(b: T): T = multiply(this, b) +// operator fun T.plus(b: Number) = this.plus(b * one) +// operator fun Number.plus(b: T) = b + this +// +// operator fun T.minus(b: Number) = this.minus(b * one) +// operator fun Number.minus(b: T) = -b + this +} + +abstract class AbstractRing : AbstractSpace(), Ring { + final override operator fun T.times(b: T): T = multiply(this, b) } /** @@ -66,10 +87,9 @@ interface Field : Ring { operator fun T.div(b: T): T = divide(this, b) operator fun Number.div(b: T) = this * divide(one, b) +} - operator fun T.plus(b: Number) = this.plus(b * one) - operator fun Number.plus(b: T) = b + this - - operator fun T.minus(b: Number) = this.minus(b * one) - operator fun Number.minus(b: T) = -b + this +abstract class AbstractField : AbstractRing(), Field { + final override operator fun T.div(b: T): T = divide(this, b) + final override operator fun Number.div(b: T) = this * divide(one, b) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 6f30e8cfc..a97afdb6b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -14,7 +14,8 @@ object ComplexField : Field { override fun multiply(a: Complex, k: Double): Complex = Complex(a.re * k, a.im * k) - override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) + override fun multiply(a: Complex, b: Complex): Complex = + Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) override fun divide(a: Complex, b: Complex): Complex { val norm = b.square @@ -35,8 +36,10 @@ object ComplexField : Field { /** * Complex number class */ -data class Complex(val re: Double, val im: Double) : FieldElement { - override val self: Complex get() = this +data class Complex(val re: Double, val im: Double) : FieldElement { + override fun unwrap(): Complex = this + + override fun Complex.wrap(): Complex = this override val context: ComplexField get() = ComplexField diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt index 3cc6ed377..97ff3c6f5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt @@ -6,11 +6,10 @@ import kotlin.math.pow * Advanced Number-like field that implements basic operations */ interface ExtendedField : - Field, - TrigonometricOperations, - PowerOperations, - ExponentialOperations - + Field, + TrigonometricOperations, + PowerOperations, + ExponentialOperations /** @@ -33,7 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ -object DoubleField : ExtendedField, Norm { +object DoubleField : AbstractField(),ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt index 9e5c8a801..7a7866966 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -19,10 +19,10 @@ interface TrigonometricOperations : Field { fun ctg(arg: T): T = cos(arg) / sin(arg) } -fun >> sin(arg: T): T = arg.context.sin(arg) -fun >> cos(arg: T): T = arg.context.cos(arg) -fun >> tg(arg: T): T = arg.context.tg(arg) -fun >> ctg(arg: T): T = arg.context.ctg(arg) +fun >> sin(arg: T): T = arg.context.sin(arg) +fun >> cos(arg: T): T = arg.context.cos(arg) +fun >> tg(arg: T): T = arg.context.tg(arg) +fun >> ctg(arg: T): T = arg.context.ctg(arg) /* Power and roots */ @@ -31,11 +31,12 @@ fun >> ctg(arg: T): T = arg.c */ interface PowerOperations { fun power(arg: T, pow: Double): T + fun sqrt(arg: T) = power(arg, 0.5) } -infix fun >> T.pow(power: Double): T = context.power(this, power) -fun >> sqrt(arg: T): T = arg pow 0.5 -fun >> sqr(arg: T): T = arg pow 2.0 +infix fun >> T.pow(power: Double): T = context.power(this, power) +fun >> sqrt(arg: T): T = arg pow 0.5 +fun >> sqr(arg: T): T = arg pow 2.0 /* Exponential */ @@ -44,11 +45,11 @@ interface ExponentialOperations { fun ln(arg: T): T } -fun >> exp(arg: T): T = arg.context.exp(arg) -fun >> ln(arg: T): T = arg.context.ln(arg) +fun >> exp(arg: T): T = arg.context.exp(arg) +fun >> ln(arg: T): T = arg.context.ln(arg) -interface Norm { +interface Norm { fun norm(arg: T): R } -fun >, R> norm(arg: T): R = arg.context.norm(arg) \ No newline at end of file +fun >, R> norm(arg: T): R = arg.context.norm(arg) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index 87035b9fa..59fba75df 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -3,71 +3,120 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement -open class BufferNDField>(final override val shape: IntArray, final override val field: F, val bufferFactory: BufferFactory) : NDField> { +abstract class StridedNDField>(shape: IntArray, elementField: F) : + AbstractNDField>(shape, elementField) { + + abstract val bufferFactory: BufferFactory val strides = DefaultStrides(shape) +} - override fun produce(initializer: F.(IntArray) -> T) = - BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.initializer(strides.index(offset)) }) - open fun NDBuffer.map(transform: F.(T) -> T) = - BufferNDElement(this@BufferNDField, bufferFactory(strides.linearSize) { offset -> field.transform(buffer[offset]) }) +class BufferNDField>( + shape: IntArray, + elementField: F, + override val bufferFactory: BufferFactory +) : + StridedNDField(shape, elementField) { - open fun NDBuffer.mapIndexed(transform: F.(index: IntArray, T) -> T) = - BufferNDElement(this@BufferNDField, bufferFactory(strides.linearSize) { offset -> field.transform(strides.index(offset), buffer[offset]) }) + override fun check(vararg elements: NDBuffer) { + if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") + } - open fun combine(a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T) = - BufferNDElement(this, bufferFactory(strides.linearSize) { offset -> field.transform(a[offset], b[offset]) }) + override val zero by lazy { produce { zero } } + override val one by lazy { produce { one } } + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun produce(crossinline initializer: F.(IntArray) -> T): BufferNDElement = + BufferNDElement( + this, + bufferFactory(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun NDBuffer.map(crossinline transform: F.(T) -> T): BufferNDElement { + check(this) + return BufferNDElement( + this@BufferNDField, + bufferFactory(strides.linearSize) { offset -> elementField.transform(buffer[offset]) }) + } + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun NDBuffer.mapIndexed(crossinline transform: F.(index: IntArray, T) -> T): BufferNDElement { + check(this) + return BufferNDElement( + this@BufferNDField, + bufferFactory(strides.linearSize) { offset -> + elementField.transform( + strides.index(offset), + buffer[offset] + ) + }) + } + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun combine( + a: NDBuffer, + b: NDBuffer, + crossinline transform: F.(T, T) -> T + ): BufferNDElement { + check(a, b) + return BufferNDElement( + this, + bufferFactory(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + } /** * Convert any [NDStructure] to buffered structure using strides from this context. * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes + * + * If the argument is [NDBuffer] with different strides structure, the new element will be produced. */ - fun NDStructure.toBuffer(): NDBuffer = - this as? NDBuffer ?: produce { index -> get(index) } - - override val zero: NDBuffer by lazy { produce { field.zero } } - - override fun add(a: NDBuffer, b: NDBuffer): NDBuffer = combine(a, b) { aValue, bValue -> add(aValue, bValue) } - - override fun multiply(a: NDBuffer, k: Double): NDBuffer = a.map { it * k } - - override val one: NDBuffer by lazy { produce { field.one } } - - override fun multiply(a: NDBuffer, b: NDBuffer): NDBuffer = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } - - override fun divide(a: NDBuffer, b: NDBuffer): NDBuffer = combine(a, b) { aValue, bValue -> divide(aValue, bValue) } + fun NDStructure.toBuffer(): NDBuffer { + return if (this is NDBuffer && this.strides == this@BufferNDField.strides) { + this + } else { + produce { index -> get(index) } + } + } } -class BufferNDElement>(override val context: BufferNDField, override val buffer: Buffer) : - NDBuffer, - FieldElement, BufferNDElement, BufferNDField>, - NDElement { +class BufferNDElement>(override val context: StridedNDField, override val buffer: Buffer) : + NDBuffer, + FieldElement, BufferNDElement, StridedNDField>, + NDElement { - override val elementField: F get() = context.field + override val elementField: F + get() = context.elementField - override fun unwrap(): NDBuffer = this + override fun unwrap(): NDBuffer = + this - override fun NDBuffer.wrap(): BufferNDElement = BufferNDElement(context, this.buffer) + override fun NDBuffer.wrap(): BufferNDElement = + BufferNDElement(context, this.buffer) - override val strides get() = context.strides + override val strides + get() = context.strides - override val shape: IntArray get() = context.shape + override val shape: IntArray + get() = context.shape - override fun get(index: IntArray): T = buffer[strides.offset(index)] + override fun get(index: IntArray): T = + buffer[strides.offset(index)] - override fun elements(): Sequence> = strides.indices().map { it to get(it) } + override fun elements(): Sequence> = + strides.indices().map { it to get(it) } - override fun map(action: F.(T) -> T): BufferNDElement = context.run { map(action) } + override fun map(action: F.(T) -> T): BufferNDElement = + context.run { map(action) } - override fun mapIndexed(transform: F.(index: IntArray, T) -> T): BufferNDElement = context.run { mapIndexed(transform) } + override fun mapIndexed(transform: F.(index: IntArray, T) -> T): BufferNDElement = + context.run { mapIndexed(transform) } } - /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ operator fun > Function1.invoke(ndElement: BufferNDElement) = - ndElement.context.run { ndElement.map { invoke(it) } } + ndElement.context.run { ndElement.map { invoke(it) } } /* plus and minus */ @@ -75,13 +124,13 @@ operator fun > Function1.invoke(ndElement: BufferNDE * Summation operation for [BufferNDElement] and single element */ operator fun > BufferNDElement.plus(arg: T) = - context.run { map { it + arg } } + context.run { map { it + arg } } /** * Subtraction operation between [BufferNDElement] and single element */ operator fun > BufferNDElement.minus(arg: T) = - context.run { map { it - arg } } + context.run { map { it - arg } } /* prod and div */ @@ -89,10 +138,10 @@ operator fun > BufferNDElement.minus(arg: T) = * Product operation for [BufferNDElement] and single element */ operator fun > BufferNDElement.times(arg: T) = - context.run { map { it * arg } } + context.run { map { it * arg } } /** * Division operation between [BufferNDElement] and single element */ operator fun > BufferNDElement.div(arg: T) = - context.run { map { it / arg } } \ No newline at end of file + context.run { map { it / arg } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index cffc2bea0..e3ca79558 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -12,7 +12,8 @@ interface Buffer { operator fun iterator(): Iterator - fun contentEquals(other: Buffer<*>): Boolean = asSequence().mapIndexed { index, value -> value == other[index] }.all { it } + fun contentEquals(other: Buffer<*>): Boolean = + asSequence().mapIndexed { index, value -> value == other[index] }.all { it } } fun Buffer.asSequence(): Sequence = iterator().asSequence() @@ -151,7 +152,8 @@ inline fun inlineBuffer(size: Int, initializer: (Int) -> T): B /** * Create a boxing mutable buffer of given type */ -inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) +inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = + MutableListBuffer(MutableList(size, initializer)) /** * Create most appropriate mutable buffer for given type avoiding boxing wherever possible diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 7c1b63c23..50ecd3a57 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -7,40 +7,41 @@ import scientifik.kmath.operations.TrigonometricOperations interface ExtendedNDField, N : NDStructure> : - NDField, - TrigonometricOperations, - PowerOperations, - ExponentialOperations + NDField, + TrigonometricOperations, + PowerOperations, + ExponentialOperations /** * NDField that supports [ExtendedField] operations on its elements */ -class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : ExtendedNDField, NDField by ndField { +class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : + ExtendedNDField, NDField by ndField { override val shape: IntArray get() = ndField.shape - override val field: F get() = ndField.field + override val elementField: F get() = ndField.elementField override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer) override fun power(arg: N, pow: Double): N { - return produce { with(field) { power(arg[it], pow) } } + return produce { with(elementField) { power(arg[it], pow) } } } override fun exp(arg: N): N { - return produce { with(field) { exp(arg[it]) } } + return produce { with(elementField) { exp(arg[it]) } } } override fun ln(arg: N): N { - return produce { with(field) { ln(arg[it]) } } + return produce { with(elementField) { ln(arg[it]) } } } override fun sin(arg: N): N { - return produce { with(field) { sin(arg[it]) } } + return produce { with(elementField) { sin(arg[it]) } } } override fun cos(arg: N): N { - return produce { with(field) { cos(arg[it]) } } + return produce { with(elementField) { cos(arg[it]) } } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 7f9f77d4e..223e45b9d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -18,30 +18,38 @@ object NDElements { * Create a optimized NDArray of doubles */ fun real(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }) = - NDField.real(shape).produce(initializer) + NDField.real(shape).produce(initializer) fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }) = - real(intArrayOf(dim)) { initializer(it[0]) } + real(intArrayOf(dim)) { initializer(it[0]) } fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }) = - real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }) = - real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } + real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } /** * Simple boxing NDArray */ - fun > generic(shape: IntArray, field: F, initializer: F.(IntArray) -> T): GenericNDElement { + fun > generic( + shape: IntArray, + field: F, + initializer: F.(IntArray) -> T + ): GenericNDElement { val ndField = GenericNDField(shape, field) val structure = ndStructure(shape) { index -> field.initializer(index) } return GenericNDElement(ndField, structure) } - inline fun > inline(shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T): GenericNDElement { + inline fun > inline( + shape: IntArray, + field: F, + noinline initializer: F.(IntArray) -> T + ): GenericNDElement { val ndField = GenericNDField(shape, field) val structure = ndStructure(shape, ::inlineBuffer) { index -> field.initializer(index) } return GenericNDElement(ndField, structure) @@ -52,31 +60,36 @@ object NDElements { /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ -operator fun > Function1.invoke(ndElement: NDElement) = ndElement.map { value -> this@invoke(value) } +operator fun > Function1.invoke(ndElement: NDElement) = + ndElement.map { value -> this@invoke(value) } /* plus and minus */ /** * Summation operation for [NDElements] and single element */ -operator fun > NDElement.plus(arg: T): NDElement = this.map { value -> elementField.run { arg + value } } +operator fun > NDElement.plus(arg: T): NDElement = + this.map { value -> elementField.run { arg + value } } /** * Subtraction operation between [NDElements] and single element */ -operator fun > NDElement.minus(arg: T): NDElement = this.map { value -> elementField.run { arg - value } } +operator fun > NDElement.minus(arg: T): NDElement = + this.map { value -> elementField.run { arg - value } } /* prod and div */ /** * Product operation for [NDElements] and single element */ -operator fun > NDElement.times(arg: T): NDElement = this.map { value -> elementField.run { arg * value } } +operator fun > NDElement.times(arg: T): NDElement = + this.map { value -> elementField.run { arg * value } } /** * Division operation between [NDElements] and single element */ -operator fun > NDElement.div(arg: T): NDElement = this.map { value -> elementField.run { arg / value } } +operator fun > NDElement.div(arg: T): NDElement = + this.map { value -> elementField.run { arg / value } } // /** @@ -111,16 +124,19 @@ operator fun > NDElement.div(arg: T): NDElement = th /** * Read-only [NDStructure] coupled to the context. */ -class GenericNDElement>(override val context: NDField>, private val structure: NDStructure) : - NDStructure by structure, - NDElement, - FieldElement, GenericNDElement, NDField>> { - override val elementField: F get() = context.field +class GenericNDElement>( + override val context: NDField>, + private val structure: NDStructure +) : + NDStructure by structure, + NDElement, + FieldElement, GenericNDElement, NDField>> { + override val elementField: F get() = context.elementField override fun unwrap(): NDStructure = structure override fun NDStructure.wrap() = GenericNDElement(context, this) override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - ndStructure(context.shape) { index: IntArray -> context.field.transform(index, get(index)) }.wrap() + ndStructure(context.shape) { index: IntArray -> context.elementField.transform(index, get(index)) }.wrap() } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index e42cebfed..5cda16b25 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -1,5 +1,6 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.AbstractField import scientifik.kmath.operations.Field /** @@ -10,29 +11,53 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run /** * Field for n-dimensional arrays. * @param shape - the list of dimensions of the array - * @param field - operations field defined on individual array element + * @param elementField - operations field defined on individual array element * @param T - the type of the element contained in ND structure * @param F - field over structure elements * @param R - actual nd-element type of this field */ -interface NDField, N : NDStructure> : Field { +interface NDField, N : NDStructure> : Field { val shape: IntArray - val field: F - - /** - * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field - */ - fun checkShape(vararg elements: N) { - elements.forEach { - if (!shape.contentEquals(it.shape)) { - throw ShapeMismatchException(shape, it.shape) - } - } - } + val elementField: F fun produce(initializer: F.(IntArray) -> T): N + fun N.map(transform: F.(T) -> T): N + + fun N.mapIndexed(transform: F.(index: IntArray, T) -> T): N + + fun combine(a: N, b: N, transform: F.(T, T) -> T): N + + /** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ + operator fun Function1.invoke(structure: N): N + + /** + * Summation operation for [NDElements] and single element + */ + operator fun N.plus(arg: T): N + + /** + * Subtraction operation between [NDElements] and single element + */ + operator fun N.minus(arg: T): N + + /** + * Product operation for [NDElements] and single element + */ + operator fun N.times(arg: T): N + + /** + * Division operation between [NDElements] and single element + */ + operator fun N.div(arg: T): N + + operator fun T.plus(arg: N): N + operator fun T.minus(arg: N): N + operator fun T.times(arg: N): N + operator fun T.div(arg: N): N companion object { /** @@ -48,48 +73,85 @@ interface NDField, N : NDStructure> : Field { /** * Create a most suitable implementation for nd-field using reified class */ - inline fun > inline(shape: IntArray, field: F) = BufferNDField(shape, field, ::inlineBuffer) + inline fun > buffered(shape: IntArray, field: F) = + BufferNDField(shape, field, ::inlineBuffer) } } -class GenericNDField>(override val shape: IntArray, override val field: F, val bufferFactory: BufferFactory = ::boxingBuffer) : NDField> { - override fun produce(initializer: F.(IntArray) -> T): NDStructure = ndStructure(shape, bufferFactory) { field.initializer(it) } +abstract class AbstractNDField, N : NDStructure>( + override val shape: IntArray, + override val elementField: F +) : AbstractField(), NDField { + override val zero: N by lazy { produce { zero } } - override val zero: NDStructure by lazy { produce { zero } } + override val one: N by lazy { produce { one } } - override val one: NDStructure by lazy { produce { one } } + final override operator fun Function1.invoke(structure: N) = structure.map { value -> this@invoke(value) } + final override operator fun N.plus(arg: T) = this.map { value -> elementField.run { arg + value } } + final override operator fun N.minus(arg: T) = this.map { value -> elementField.run { arg - value } } + final override operator fun N.times(arg: T) = this.map { value -> elementField.run { arg * value } } + final override operator fun N.div(arg: T) = this.map { value -> elementField.run { arg / value } } + + final override operator fun T.plus(arg: N) = arg + this + final override operator fun T.minus(arg: N) = arg - this + final override operator fun T.times(arg: N) = arg * this + final override operator fun T.div(arg: N) = arg / this /** * Element-by-element addition */ - override fun add(a: NDStructure, b: NDStructure): NDStructure { - checkShape(a, b) - return produce { field.run { a[it] + b[it] } } - } + override fun add(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue + bValue } /** * Multiply all elements by cinstant */ - override fun multiply(a: NDStructure, k: Double): NDStructure { - checkShape(a) - return produce { field.run { a[it] * k } } - } + override fun multiply(a: N, k: Double): N = + a.map { it * k } + /** * Element-by-element multiplication */ - override fun multiply(a: NDStructure, b: NDStructure): NDStructure { - checkShape(a) - return produce { field.run { a[it] * b[it] } } - } + override fun multiply(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue * bValue } /** * Element-by-element division */ - override fun divide(a: NDStructure, b: NDStructure): NDStructure { - checkShape(a) - return produce { field.run { a[it] / b[it] } } + override fun divide(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue / bValue } + + /** + * Check if given objects are compatible with this context. Throw exception if they are not + */ + open fun check(vararg elements: N) { + elements.forEach { + if (!shape.contentEquals(it.shape)) { + throw ShapeMismatchException(shape, it.shape) + } + } } +} + +class GenericNDField>( + shape: IntArray, + elementField: F, + val bufferFactory: BufferFactory = ::boxingBuffer +) : + AbstractNDField>(shape, elementField) { + + override fun produce(initializer: F.(IntArray) -> T): NDStructure = + ndStructure(shape, bufferFactory) { elementField.initializer(it) } + + override fun NDStructure.map(transform: F.(T) -> T): NDStructure = + produce { index -> transform(get(index)) } + + override fun NDStructure.mapIndexed(transform: F.(index: IntArray, T) -> T): NDStructure = + produce { index -> transform(index, get(index)) } + + override fun combine(a: NDStructure, b: NDStructure, transform: F.(T, T) -> T): NDStructure = + produce { index -> transform(a[index], b[index]) } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 5e55bea78..617c99a9d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -127,8 +127,8 @@ interface NDBuffer : NDStructure { * Boxing generic [NDStructure] */ data class BufferNDStructure( - override val strides: Strides, - override val buffer: Buffer + override val strides: Strides, + override val buffer: Buffer ) : NDBuffer { init { @@ -137,6 +137,12 @@ data class BufferNDStructure( } } + override fun get(index: IntArray): T = buffer[strides.offset(index)] + + override val shape: IntArray get() = strides.shape + + override fun elements() = strides.indices().map { it to this[it] } + override fun equals(other: Any?): Boolean { return when { this === other -> true @@ -156,7 +162,10 @@ data class BufferNDStructure( /** * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] */ -inline fun NDStructure.mapToBuffer(factory: BufferFactory = ::inlineBuffer, crossinline transform: (T) -> R): BufferNDStructure { +inline fun NDStructure.mapToBuffer( + factory: BufferFactory = ::inlineBuffer, + crossinline transform: (T) -> R +): BufferNDStructure { return if (this is BufferNDStructure) { BufferNDStructure(this.strides, factory.invoke(strides.linearSize) { transform(buffer[it]) }) } else { @@ -171,26 +180,26 @@ inline fun NDStructure.mapToBuffer(factory: BufferFactor * Strides should be reused if possible */ fun ndStructure(strides: Strides, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = - BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) + BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** * Inline create NDStructure with non-boxing buffer implementation if it is possible */ inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, inlineBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + BufferNDStructure(strides, inlineBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = - ndStructure(DefaultStrides(shape), bufferFactory, initializer) + ndStructure(DefaultStrides(shape), bufferFactory, initializer) inline fun inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = - inlineNDStructure(DefaultStrides(shape), initializer) + inlineNDStructure(DefaultStrides(shape), initializer) /** * Mutable ND buffer based on linear [inlineBuffer] */ class MutableBufferNDStructure( - override val strides: Strides, - override val buffer: MutableBuffer + override val strides: Strides, + override val buffer: MutableBuffer ) : NDBuffer, MutableNDStructure { init { @@ -205,19 +214,30 @@ class MutableBufferNDStructure( /** * The same as [inlineNDStructure], but mutable */ -fun mutableNdStructure(strides: Strides, bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) +fun mutableNdStructure( + strides: Strides, + bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, + initializer: (IntArray) -> T +) = + MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) inline fun inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, inlineMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + MutableBufferNDStructure(strides, inlineMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) -fun mutableNdStructure(shape: IntArray, bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, initializer: (IntArray) -> T) = - mutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) +fun mutableNdStructure( + shape: IntArray, + bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, + initializer: (IntArray) -> T +) = + mutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) inline fun inlineMutableNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = - inlineMutableNdStructure(DefaultStrides(shape), initializer) + inlineMutableNdStructure(DefaultStrides(shape), initializer) -inline fun NDStructure.combine(struct: NDStructure, crossinline block: (T, T) -> T): NDStructure { +inline fun NDStructure.combine( + struct: NDStructure, + crossinline block: (T, T) -> T +): NDStructure { if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination") return inlineNdStructure(shape) { block(this[it], struct[it]) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 3158c38c1..344154ceb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -5,47 +5,78 @@ import scientifik.kmath.operations.DoubleField typealias RealNDElement = BufferNDElement class RealNDField(shape: IntArray) : - BufferNDField(shape, DoubleField, DoubleBufferFactory), - ExtendedNDField> { + StridedNDField(shape, DoubleField), + ExtendedNDField> { + + override val bufferFactory: BufferFactory + get() = DoubleBufferFactory /** * Inline map an NDStructure to */ - private inline fun NDBuffer.mapInline(crossinline operation: DoubleField.(Double) -> Double): RealNDElement { - val array = DoubleArray(strides.linearSize) { offset -> DoubleField.operation(buffer[offset]) } + @Suppress("OVERRIDE_BY_INLINE") + override inline fun NDBuffer.map(crossinline transform: DoubleField.(Double) -> Double): RealNDElement { + check(this) + val array = DoubleArray(strides.linearSize) { offset -> DoubleField.transform(buffer[offset]) } return BufferNDElement(this@RealNDField, DoubleBuffer(array)) } @Suppress("OVERRIDE_BY_INLINE") - override inline fun produce(initializer: DoubleField.(IntArray) -> Double): RealNDElement { - val array = DoubleArray(strides.linearSize) { offset -> field.initializer(strides.index(offset)) } + override inline fun produce(crossinline initializer: DoubleField.(IntArray) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } return BufferNDElement(this, DoubleBuffer(array)) } - override fun power(arg: NDBuffer, pow: Double) = arg.mapInline { power(it, pow) } + @Suppress("OVERRIDE_BY_INLINE") + override inline fun NDBuffer.mapIndexed(crossinline transform: DoubleField.(index: IntArray, Double) -> Double): BufferNDElement { + check(this) + return BufferNDElement( + this@RealNDField, + bufferFactory(strides.linearSize) { offset -> + elementField.transform( + strides.index(offset), + buffer[offset] + ) + }) + } - override fun exp(arg: NDBuffer) = arg.mapInline { exp(it) } + @Suppress("OVERRIDE_BY_INLINE") + override inline fun combine( + a: NDBuffer, + b: NDBuffer, + crossinline transform: DoubleField.(Double, Double) -> Double + ): BufferNDElement { + check(a, b) + return BufferNDElement( + this, + bufferFactory(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + } - override fun ln(arg: NDBuffer) = arg.mapInline { ln(it) } + override fun power(arg: NDBuffer, pow: Double) = arg.map { power(it, pow) } - override fun sin(arg: NDBuffer) = arg.mapInline { sin(it) } + override fun exp(arg: NDBuffer) = arg.map { exp(it) } - override fun cos(arg: NDBuffer) = arg.mapInline { cos(it) } + override fun ln(arg: NDBuffer) = arg.map { ln(it) } - override fun NDBuffer.times(k: Number) = mapInline { value -> value * k.toDouble() } + override fun sin(arg: NDBuffer) = arg.map { sin(it) } - override fun NDBuffer.div(k: Number) = mapInline { value -> value / k.toDouble() } - - override fun Number.times(b: NDBuffer) = b * this - - override fun Number.div(b: NDBuffer) = b * (1.0 / this.toDouble()) + override fun cos(arg: NDBuffer) = arg.map { cos(it) } +// +// override fun NDBuffer.times(k: Number) = mapInline { value -> value * k.toDouble() } +// +// override fun NDBuffer.div(k: Number) = mapInline { value -> value / k.toDouble() } +// +// override fun Number.times(b: NDBuffer) = b * this +// +// override fun Number.div(b: NDBuffer) = b * (1.0 / this.toDouble()) } + /** * Fast element production using function inlining */ -inline fun BufferNDField.produceInline(crossinline initializer: DoubleField.(Int) -> Double): RealNDElement { - val array = DoubleArray(strides.linearSize) { offset -> field.initializer(offset) } +inline fun StridedNDField.produceInline(crossinline initializer: DoubleField.(Int) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(offset) } return BufferNDElement(this, DoubleBuffer(array)) } @@ -53,7 +84,8 @@ inline fun BufferNDField.produceInline(crossinline initiali * Element by element application of any operation on elements to the whole array. Just like in numpy */ operator fun Function1.invoke(ndElement: RealNDElement) = - ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + /* plus and minus */ @@ -61,10 +93,10 @@ operator fun Function1.invoke(ndElement: RealNDElement) = * Summation operation for [BufferNDElement] and single element */ operator fun RealNDElement.plus(arg: Double) = - context.produceInline { i -> buffer[i] + arg } + context.produceInline { i -> buffer[i] + arg } /** * Subtraction operation between [BufferNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = - context.produceInline { i -> buffer[i] - arg } \ No newline at end of file + context.produceInline { i -> buffer[i] - arg } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt similarity index 66% rename from kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt index 8e2845b9e..9266b9394 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt @@ -6,10 +6,10 @@ import scientifik.kmath.operations.DoubleField import kotlin.test.Test import kotlin.test.assertEquals -class FieldExpressionContextTest { +class ExpressionFieldTest { @Test fun testExpression() { - val context = FieldExpressionContext(DoubleField) + val context = ExpressionField(DoubleField) val expression = with(context) { val x = variable("x", 2.0) x * x + 2 * x + 1.0 @@ -20,10 +20,10 @@ class FieldExpressionContextTest { @Test fun testComplex() { - val context = FieldExpressionContext(ComplexField) + val context = ExpressionField(ComplexField) val expression = with(context) { val x = variable("x", Complex(2.0, 0.0)) - x * x + 2 * x + 1.0 + x * x + 2 * x + one } assertEquals(expression("x" to Complex(1.0, 0.0)), Complex(4.0, 0.0)) assertEquals(expression(), Complex(9.0, 0.0)) @@ -31,23 +31,23 @@ class FieldExpressionContextTest { @Test fun separateContext() { - fun FieldExpressionContext.expression(): Expression{ + fun ExpressionField.expression(): Expression { val x = variable("x") - return x * x + 2 * x + 1.0 + return x * x + 2 * x + one } - val expression = FieldExpressionContext(DoubleField).expression() + val expression = ExpressionField(DoubleField).expression() assertEquals(expression("x" to 1.0), 4.0) } @Test fun valueExpression() { - val expressionBuilder: FieldExpressionContext.()->Expression = { + val expressionBuilder: ExpressionField.() -> Expression = { val x = variable("x") x * x + 2 * x + 1.0 } - val expression = FieldExpressionContext(DoubleField).expressionBuilder() + val expression = ExpressionField(DoubleField).expressionBuilder() assertEquals(expression("x" to 1.0), 4.0) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt index 842a12ae4..94ec6666c 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt @@ -11,8 +11,8 @@ class MultivariateHistogramTest { @Test fun testSinglePutHistogram() { val histogram = FastHistogram.fromRanges( - (-1.0..1.0), - (-1.0..1.0) + (-1.0..1.0), + (-1.0..1.0) ) histogram.put(0.55, 0.55) val bin = histogram.find { it.value.toInt() > 0 }!! @@ -22,21 +22,21 @@ class MultivariateHistogramTest { } @Test - fun testSequentialPut(){ + fun testSequentialPut() { val histogram = FastHistogram.fromRanges( - (-1.0..1.0), - (-1.0..1.0), - (-1.0..1.0) + (-1.0..1.0), + (-1.0..1.0), + (-1.0..1.0) ) val random = Random(1234) - fun nextDouble() = random.nextDouble(-1.0,1.0) + fun nextDouble() = random.nextDouble(-1.0, 1.0) val n = 10000 histogram.fill { - repeat(n){ - yield(Vector.ofReal(nextDouble(),nextDouble(),nextDouble())) + repeat(n) { + yield(Vector.ofReal(nextDouble(), nextDouble(), nextDouble())) } } assertEquals(n, histogram.sumBy { it.value.toInt() }) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 87aad3d1d..d02cb2c6f 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -21,8 +21,8 @@ class MatrixTest { } @Test - fun testTranspose(){ - val matrix = MatrixSpace.real(3,3).one + fun testTranspose() { + val matrix = MatrixSpace.real(3, 3).one val transposed = matrix.transpose() assertEquals(matrix.context, transposed.context) assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index bd1eb8147..569b9e450 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -6,9 +6,9 @@ import kotlin.test.assertEquals class RealLUSolverTest { @Test fun testInvertOne() { - val matrix = MatrixSpace.real(2,2).one + val matrix = MatrixSpace.real(2, 2).one val inverted = RealLUSolver.inverse(matrix) - assertEquals(matrix,inverted) + assertEquals(matrix, inverted) } // @Test diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt index a705fc8f9..df474c53b 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -6,10 +6,9 @@ import kotlin.test.assertEquals class RealFieldTest { @Test fun testSqrt() { - //fails because KT-27586 - val sqrt = with(RealField) { - sqrt( 25 * one) + val sqrt = with(DoubleField) { + sqrt(25 * one) } - assertEquals(5.0, sqrt.value) + assertEquals(5.0, sqrt) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 7c5227293..3bbd620fa 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,6 +1,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Norm +import scientifik.kmath.structures.NDElements.real2D import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test diff --git a/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt index 3c2915587..3765220b9 100644 --- a/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt +++ b/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -1,16 +1,33 @@ package scientifik.kmath.histogram -actual class LongCounter{ +actual class LongCounter { private var sum: Long = 0 - actual fun decrement() {sum--} - actual fun increment() {sum++} - actual fun reset() {sum = 0} + actual fun decrement() { + sum-- + } + + actual fun increment() { + sum++ + } + + actual fun reset() { + sum = 0 + } + actual fun sum(): Long = sum - actual fun add(l: Long) {sum+=l} + actual fun add(l: Long) { + sum += l + } } -actual class DoubleCounter{ + +actual class DoubleCounter { private var sum: Double = 0.0 - actual fun reset() {sum = 0.0} + actual fun reset() { + sum = 0.0 + } + actual fun sum(): Double = sum - actual fun add(d: Double) {sum+=d} + actual fun add(d: Double) { + sum += d + } } \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt index 26ad520e6..4d18b714d 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -18,7 +18,7 @@ class UnivariateBin(val position: Double, val size: Double, val counter: LongCou override fun contains(vector: Buffer): Boolean = contains(vector[0]) - internal operator fun inc() = this.also { counter.increment()} + internal operator fun inc() = this.also { counter.increment() } override val dimension: Int get() = 1 } @@ -26,7 +26,8 @@ class UnivariateBin(val position: Double, val size: Double, val counter: LongCou /** * Univariate histogram with log(n) bin search speed */ -class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : MutableHistogram { +class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : + MutableHistogram { private val bins: TreeMap = TreeMap() diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt index a5cc8c5de..9f95360fd 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt @@ -31,7 +31,7 @@ interface FixedSizeBufferSpec : BufferSpec { */ fun ByteBuffer.readObject(index: Int): T { val dup = duplicate() - dup.position(index*unitSize) + dup.position(index * unitSize) return dup.readObject() } @@ -49,7 +49,7 @@ interface FixedSizeBufferSpec : BufferSpec { */ fun ByteBuffer.writeObject(index: Int, obj: T) { val dup = duplicate() - dup.position(index*unitSize) + dup.position(index * unitSize) dup.writeObject(obj) } } \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt index b50ed9674..fceda1b25 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -2,7 +2,8 @@ package scientifik.kmath.structures import java.nio.ByteBuffer -class ObjectBuffer(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec) : MutableBuffer { +class ObjectBuffer(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec) : + MutableBuffer { override val size: Int get() = buffer.limit() / spec.unitSize @@ -23,6 +24,6 @@ class ObjectBuffer(private val buffer: ByteBuffer, private val spec: Fi companion object { fun create(spec: FixedSizeBufferSpec, size: Int) = - ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec) + ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec) } } \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt index a837cde01..6bb8682ff 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt @@ -6,6 +6,9 @@ import kotlinx.coroutines.Dispatchers import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -expect fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, function: suspend CoroutineScope.()->R): R +expect fun runBlocking( + context: CoroutineContext = EmptyCoroutineContext, + function: suspend CoroutineScope.() -> R +): R val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index 8b790da99..f1752b483 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -3,7 +3,8 @@ package scientifik.kmath.structures import kotlinx.coroutines.* import scientifik.kmath.operations.Field -class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : BufferNDField(shape,field, ::boxingBuffer) { +class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : + BufferNDField(shape, field, ::boxingBuffer) { override fun add(a: NDStructure, b: NDStructure): NDElements { return LazyNDStructure(this) { index -> @@ -34,13 +35,23 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } } -class LazyNDStructure>(override val context: LazyNDField, val function: suspend F.(IntArray) -> T) : NDElements, NDStructure { +class LazyNDStructure>( + override val context: LazyNDField, + val function: suspend F.(IntArray) -> T +) : NDElements, NDStructure { override val self: NDElements get() = this override val shape: IntArray get() = context.shape private val cache = HashMap>() - fun deferred(index: IntArray) = cache.getOrPut(index) { context.scope.async(context = Dispatchers.Math) { function.invoke(context.field, index) } } + fun deferred(index: IntArray) = cache.getOrPut(index) { + context.scope.async(context = Dispatchers.Math) { + function.invoke( + context.elementField, + index + ) + } + } suspend fun await(index: IntArray): T = deferred(index).await() @@ -54,9 +65,11 @@ class LazyNDStructure>(override val context: LazyNDField, } } -fun NDStructure.deferred(index: IntArray) = if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) +fun NDStructure.deferred(index: IntArray) = + if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) -suspend fun NDStructure.await(index: IntArray) = if (this is LazyNDStructure) this.await(index) else get(index) +suspend fun NDStructure.await(index: IntArray) = + if (this is LazyNDStructure) this.await(index) else get(index) fun > NDElements.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { return if (this is LazyNDStructure) { @@ -67,10 +80,12 @@ fun > NDElements.lazy(scope: CoroutineScope = GlobalScope) } } -inline fun > LazyNDStructure.mapIndexed(crossinline action: suspend F.(IntArray, T) -> T) = LazyNDStructure(context) { index -> - action.invoke(this, index, await(index)) -} +inline fun > LazyNDStructure.mapIndexed(crossinline action: suspend F.(IntArray, T) -> T) = + LazyNDStructure(context) { index -> + action.invoke(this, index, await(index)) + } -inline fun > LazyNDStructure.map(crossinline action: suspend F.(T) -> T) = LazyNDStructure(context) { index -> - action.invoke(this, await(index)) -} \ No newline at end of file +inline fun > LazyNDStructure.map(crossinline action: suspend F.(T) -> T) = + LazyNDStructure(context) { index -> + action.invoke(this, await(index)) + } \ No newline at end of file diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index 6e712f2a8..1eee26f29 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -14,7 +14,7 @@ class LazyNDFieldTest { counter++ it * it } - assertEquals(4, result[0,0,0]) + assertEquals(4, result[0, 0, 0]) assertEquals(1, counter) } } \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt index 72a764729..0bc3b0dea 100644 --- a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt @@ -3,4 +3,5 @@ package scientifik.kmath.structures import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext -actual fun runBlocking(context: CoroutineContext, function: suspend CoroutineScope.() -> R): R = kotlinx.coroutines.runBlocking(context, function) \ No newline at end of file +actual fun runBlocking(context: CoroutineContext, function: suspend CoroutineScope.() -> R): R = + kotlinx.coroutines.runBlocking(context, function) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3b94f15c5..3a8aef730 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,8 +9,8 @@ pluginManagement { rootProject.name = "kmath" include( - ":kmath-core", - ":kmath-io", - ":kmath-coroutines", - ":benchmarks" + ":kmath-core", + ":kmath-io", + ":kmath-coroutines", + ":benchmarks" ) From 600d8a64b89b2aa37b44232f755151787039f668 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 4 Jan 2019 20:23:32 +0300 Subject: [PATCH 19/70] Fixes to tests and lazy structures --- benchmarks/build.gradle | 3 +- ...reBenchmark.kt => NDStructureBenchmark.kt} | 26 ++++- .../kmath/histogram/PhantomHistogram.kt | 2 +- .../kmath/linear/LUDecomposition.kt | 8 +- .../scientifik/kmath/linear/LinearAlgrebra.kt | 6 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 6 +- .../kotlin/scientifik/kmath/linear/Vector.kt | 12 +- .../scientifik/kmath/operations/Fields.kt | 6 +- .../kmath/structures/BufferNDField.kt | 42 +++---- .../kmath/structures/ExtendedNDField.kt | 4 +- .../scientifik/kmath/structures/NDElement.kt | 4 +- .../scientifik/kmath/structures/NDField.kt | 27 +++-- .../kmath/structures/RealNDField.kt | 60 +++++----- .../kmath/expressions/ExpressionFieldTest.kt | 8 +- .../kmath/operations/RealFieldTest.kt | 2 +- .../kmath/structures/LazyNDField.kt | 105 +++++++++++------- .../kmath/structures/LazyNDFieldTest.kt | 2 +- 17 files changed, 185 insertions(+), 138 deletions(-) rename benchmarks/src/main/kotlin/scientifik/kmath/structures/{BufferNDStructureBenchmark.kt => NDStructureBenchmark.kt} (68%) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 1876d86d7..23f967102 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -5,6 +5,7 @@ plugins { } dependencies { - compile project(':kmath-core') + compile project(":kmath-core") + compile project(":kmath-coroutines") //jmh project(':kmath-core') } \ No newline at end of file diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDStructureBenchmark.kt similarity index 68% rename from benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt rename to benchmarks/src/main/kotlin/scientifik/kmath/structures/NDStructureBenchmark.kt index 99111f6b3..77f0a2737 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/BufferNDStructureBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDStructureBenchmark.kt @@ -1,15 +1,16 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.RealField import kotlin.system.measureTimeMillis fun main(args: Array) { val dim = 1000 - val n = 10000 + val n = 100 - val bufferedField = NDField.buffered(intArrayOf(dim, dim), DoubleField) + val bufferedField = NDField.buffered(intArrayOf(dim, dim), RealField) val specializedField = NDField.real(intArrayOf(dim, dim)) - val genericField = NDField.generic(intArrayOf(dim, dim), DoubleField) + val genericField = NDField.generic(intArrayOf(dim, dim), RealField) + val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) // val action: NDField>.() -> Unit = { // var res = one @@ -55,6 +56,23 @@ fun main(args: Array) { println("Specialized addition completed in $specializedTime millis") + val lazyTime = measureTimeMillis { + val tr : RealField.(Double)->Double = {arg-> + var r = arg + repeat(n) { + r += 1.0 + } + r + } + lazyNDField.run { + val res = one.map(tr) + + res.elements().sumByDouble { it.second } + } + } + + println("Lazy addition completed in $lazyTime millis") + val genericTime = measureTimeMillis { //genericField.run(action) genericField.run { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt index 1d04a8c19..75d9ebd74 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt @@ -42,7 +42,7 @@ class PhantomBin>(val template: BinTemplate, override val v /** * 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 + * @param bins transform a template into structure index */ class PhantomHistogram>( val bins: Map, IntArray>, 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 3db4809be..5618b3100 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -1,7 +1,7 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field +import scientifik.kmath.operations.RealField import scientifik.kmath.structures.* import kotlin.math.absoluteValue @@ -184,7 +184,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr } class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : - LUDecomposition(matrix) { + LUDecomposition(matrix) { override fun isSingular(value: Double): Boolean { return value.absoluteValue < singularityThreshold } @@ -197,9 +197,9 @@ class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: /** Specialized solver. */ -object RealLUSolver : LinearSolver { +object RealLUSolver : LinearSolver { - fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = + fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix { 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 c2e54bf3b..11f1f7dd8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -1,8 +1,8 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm +import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.asSequence import scientifik.kmath.structures.boxingBuffer @@ -61,5 +61,5 @@ object VectorL2Norm : Norm, Double> { } } -typealias RealVector = Vector -typealias RealMatrix = Matrix +typealias RealVector = Vector +typealias RealMatrix = Matrix diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index e9a45f90a..63c78973a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -1,6 +1,6 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement @@ -42,8 +42,8 @@ interface MatrixSpace> : Space> { /** * Non-boxing double matrix */ - fun real(rows: Int, columns: Int): MatrixSpace = - StructureMatrixSpace(rows, columns, DoubleField, DoubleBufferFactory) + fun real(rows: Int, columns: Int): MatrixSpace = + StructureMatrixSpace(rows, columns, RealField, DoubleBufferFactory) /** * A structured matrix with custom buffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 818a65733..9aaa28203 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -1,6 +1,6 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement import scientifik.kmath.structures.* @@ -34,13 +34,13 @@ interface VectorSpace> : Space> { companion object { - private val realSpaceCache = HashMap>() + private val realSpaceCache = HashMap>() /** * Non-boxing double vector space */ - fun real(size: Int): BufferVectorSpace { - return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, DoubleField, DoubleBufferFactory) } + fun real(size: Int): BufferVectorSpace { + return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, RealField, DoubleBufferFactory) } } /** @@ -79,10 +79,10 @@ interface Vector> : SpaceElement, Vector, V fun > generic(size: Int, field: S, initializer: (Int) -> T): Vector = VectorSpace.buffered(size, field).produceElement(initializer) - fun real(size: Int, initializer: (Int) -> Double): Vector = + fun real(size: Int, initializer: (Int) -> Double): Vector = VectorSpace.real(size).produceElement(initializer) - fun ofReal(vararg elements: Double): Vector = + fun ofReal(vararg elements: Double): Vector = VectorSpace.real(elements.size).produceElement { elements[it] } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt index 97ff3c6f5..fd8bcef11 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt @@ -17,12 +17,12 @@ interface ExtendedField : * * TODO inline does not work due to compiler bug. Waiting for fix for KT-27586 */ -inline class Real(val value: Double) : FieldElement { +inline class Real(val value: Double) : FieldElement { override fun unwrap(): Double = value override fun Double.wrap(): Real = Real(value) - override val context get() = DoubleField + override val context get() = RealField companion object { @@ -32,7 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ -object DoubleField : AbstractField(),ExtendedField, Norm { +object RealField : AbstractField(),ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index 59fba75df..ec9268b21 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -5,18 +5,19 @@ import scientifik.kmath.operations.FieldElement abstract class StridedNDField>(shape: IntArray, elementField: F) : AbstractNDField>(shape, elementField) { - - abstract val bufferFactory: BufferFactory val strides = DefaultStrides(shape) + + abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer } class BufferNDField>( shape: IntArray, elementField: F, - override val bufferFactory: BufferFactory -) : - StridedNDField(shape, elementField) { + val bufferFactory: BufferFactory +) : StridedNDField(shape, elementField) { + + override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) override fun check(vararg elements: NDBuffer) { if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") @@ -32,22 +33,25 @@ class BufferNDField>( bufferFactory(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) @Suppress("OVERRIDE_BY_INLINE") - override inline fun NDBuffer.map(crossinline transform: F.(T) -> T): BufferNDElement { - check(this) + override inline fun map(arg: NDBuffer, crossinline transform: F.(T) -> T): BufferNDElement { + check(arg) return BufferNDElement( - this@BufferNDField, - bufferFactory(strides.linearSize) { offset -> elementField.transform(buffer[offset]) }) + this, + bufferFactory(arg.strides.linearSize) { offset -> elementField.transform(arg.buffer[offset]) }) } @Suppress("OVERRIDE_BY_INLINE") - override inline fun NDBuffer.mapIndexed(crossinline transform: F.(index: IntArray, T) -> T): BufferNDElement { - check(this) + override inline fun mapIndexed( + arg: NDBuffer, + crossinline transform: F.(index: IntArray, T) -> T + ): BufferNDElement { + check(arg) return BufferNDElement( - this@BufferNDField, - bufferFactory(strides.linearSize) { offset -> + this, + bufferFactory(arg.strides.linearSize) { offset -> elementField.transform( - strides.index(offset), - buffer[offset] + arg.strides.index(offset), + arg.buffer[offset] ) }) } @@ -105,11 +109,11 @@ class BufferNDElement>(override val context: StridedNDField> = strides.indices().map { it to get(it) } - override fun map(action: F.(T) -> T): BufferNDElement = - context.run { map(action) } + override fun map(action: F.(T) -> T) = + context.run { map(this@BufferNDElement, action) }.wrap() - override fun mapIndexed(transform: F.(index: IntArray, T) -> T): BufferNDElement = - context.run { mapIndexed(transform) } + override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = + context.run { mapIndexed(this@BufferNDElement, transform) }.wrap() } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 50ecd3a57..7648efef8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -6,7 +6,7 @@ import scientifik.kmath.operations.PowerOperations import scientifik.kmath.operations.TrigonometricOperations -interface ExtendedNDField, N : NDStructure> : +interface ExtendedNDField, N : NDStructure> : NDField, TrigonometricOperations, PowerOperations, @@ -16,7 +16,7 @@ interface ExtendedNDField, N : NDStructure> /** * NDField that supports [ExtendedField] operations on its elements */ -class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : +class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : ExtendedNDField, NDField by ndField { override val shape: IntArray get() = ndField.shape diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 223e45b9d..4bed26510 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -1,8 +1,8 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement +import scientifik.kmath.operations.RealField interface NDElement> : NDStructure { @@ -17,7 +17,7 @@ object NDElements { /** * Create a optimized NDArray of doubles */ - fun real(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }) = + fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }) = NDField.real(shape).produce(initializer) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 5cda16b25..dde406c51 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -23,9 +23,9 @@ interface NDField, N : NDStructure> : Field { fun produce(initializer: F.(IntArray) -> T): N - fun N.map(transform: F.(T) -> T): N + fun map(arg: N, transform: F.(T) -> T): N - fun N.mapIndexed(transform: F.(index: IntArray, T) -> T): N + fun mapIndexed(arg: N, transform: F.(index: IntArray, T) -> T): N fun combine(a: N, b: N, transform: F.(T, T) -> T): N @@ -87,11 +87,11 @@ abstract class AbstractNDField, N : NDStructure>( override val one: N by lazy { produce { one } } - final override operator fun Function1.invoke(structure: N) = structure.map { value -> this@invoke(value) } - final override operator fun N.plus(arg: T) = this.map { value -> elementField.run { arg + value } } - final override operator fun N.minus(arg: T) = this.map { value -> elementField.run { arg - value } } - final override operator fun N.times(arg: T) = this.map { value -> elementField.run { arg * value } } - final override operator fun N.div(arg: T) = this.map { value -> elementField.run { arg / value } } + final override operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } + final override operator fun N.plus(arg: T) = map(this) { value -> elementField.run { arg + value } } + final override operator fun N.minus(arg: T) = map(this) { value -> elementField.run { arg - value } } + final override operator fun N.times(arg: T) = map(this) { value -> elementField.run { arg * value } } + final override operator fun N.div(arg: T) = map(this) { value -> elementField.run { arg / value } } final override operator fun T.plus(arg: N) = arg + this final override operator fun T.minus(arg: N) = arg - this @@ -109,7 +109,7 @@ abstract class AbstractNDField, N : NDStructure>( * Multiply all elements by cinstant */ override fun multiply(a: N, k: Double): N = - a.map { it * k } + map(a) { it * k } /** @@ -140,17 +140,16 @@ class GenericNDField>( shape: IntArray, elementField: F, val bufferFactory: BufferFactory = ::boxingBuffer -) : - AbstractNDField>(shape, elementField) { +) : AbstractNDField>(shape, elementField) { override fun produce(initializer: F.(IntArray) -> T): NDStructure = ndStructure(shape, bufferFactory) { elementField.initializer(it) } - override fun NDStructure.map(transform: F.(T) -> T): NDStructure = - produce { index -> transform(get(index)) } + override fun map(arg: NDStructure, transform: F.(T) -> T): NDStructure = + produce { index -> transform(arg.get(index)) } - override fun NDStructure.mapIndexed(transform: F.(index: IntArray, T) -> T): NDStructure = - produce { index -> transform(index, get(index)) } + override fun mapIndexed(arg: NDStructure, transform: F.(index: IntArray, T) -> T): NDStructure = + produce { index -> transform(index, arg.get(index)) } override fun combine(a: NDStructure, b: NDStructure, transform: F.(T, T) -> T): NDStructure = produce { index -> transform(a[index], b[index]) } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 344154ceb..5bd58eb6e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -1,41 +1,47 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.RealField -typealias RealNDElement = BufferNDElement +typealias RealNDElement = BufferNDElement class RealNDField(shape: IntArray) : - StridedNDField(shape, DoubleField), - ExtendedNDField> { + StridedNDField(shape, RealField), + ExtendedNDField> { - override val bufferFactory: BufferFactory - get() = DoubleBufferFactory + override fun buildBuffer(size: Int, initializer: (Int) -> Double): Buffer = + DoubleBuffer(DoubleArray(size, initializer)) /** - * Inline map an NDStructure to + * Inline transform an NDStructure to */ @Suppress("OVERRIDE_BY_INLINE") - override inline fun NDBuffer.map(crossinline transform: DoubleField.(Double) -> Double): RealNDElement { - check(this) - val array = DoubleArray(strides.linearSize) { offset -> DoubleField.transform(buffer[offset]) } - return BufferNDElement(this@RealNDField, DoubleBuffer(array)) + override inline fun map( + arg: NDBuffer, + crossinline transform: RealField.(Double) -> Double + ): RealNDElement { + check(arg) + val array = DoubleArray(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } + return BufferNDElement(this, DoubleBuffer(array)) } @Suppress("OVERRIDE_BY_INLINE") - override inline fun produce(crossinline initializer: DoubleField.(IntArray) -> Double): RealNDElement { + override inline fun produce(crossinline initializer: RealField.(IntArray) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } return BufferNDElement(this, DoubleBuffer(array)) } @Suppress("OVERRIDE_BY_INLINE") - override inline fun NDBuffer.mapIndexed(crossinline transform: DoubleField.(index: IntArray, Double) -> Double): BufferNDElement { - check(this) + override inline fun mapIndexed( + arg: NDBuffer, + crossinline transform: RealField.(index: IntArray, Double) -> Double + ): BufferNDElement { + check(arg) return BufferNDElement( - this@RealNDField, - bufferFactory(strides.linearSize) { offset -> + this, + buildBuffer(arg.strides.linearSize) { offset -> elementField.transform( - strides.index(offset), - buffer[offset] + arg.strides.index(offset), + arg.buffer[offset] ) }) } @@ -44,23 +50,23 @@ class RealNDField(shape: IntArray) : override inline fun combine( a: NDBuffer, b: NDBuffer, - crossinline transform: DoubleField.(Double, Double) -> Double - ): BufferNDElement { + crossinline transform: RealField.(Double, Double) -> Double + ): BufferNDElement { check(a, b) return BufferNDElement( this, - bufferFactory(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) } - override fun power(arg: NDBuffer, pow: Double) = arg.map { power(it, pow) } + override fun power(arg: NDBuffer, pow: Double) = map(arg) { power(it, pow) } - override fun exp(arg: NDBuffer) = arg.map { exp(it) } + override fun exp(arg: NDBuffer) = map(arg) { exp(it) } - override fun ln(arg: NDBuffer) = arg.map { ln(it) } + override fun ln(arg: NDBuffer) = map(arg) { ln(it) } - override fun sin(arg: NDBuffer) = arg.map { sin(it) } + override fun sin(arg: NDBuffer) = map(arg) { sin(it) } - override fun cos(arg: NDBuffer) = arg.map { cos(it) } + override fun cos(arg: NDBuffer) = map(arg) { cos(it) } // // override fun NDBuffer.times(k: Number) = mapInline { value -> value * k.toDouble() } // @@ -75,7 +81,7 @@ class RealNDField(shape: IntArray) : /** * Fast element production using function inlining */ -inline fun StridedNDField.produceInline(crossinline initializer: DoubleField.(Int) -> Double): RealNDElement { +inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(offset) } return BufferNDElement(this, DoubleBuffer(array)) } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt index 9266b9394..5c5002e06 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt @@ -2,14 +2,14 @@ package scientifik.kmath.expressions import scientifik.kmath.operations.Complex import scientifik.kmath.operations.ComplexField -import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.RealField import kotlin.test.Test import kotlin.test.assertEquals class ExpressionFieldTest { @Test fun testExpression() { - val context = ExpressionField(DoubleField) + val context = ExpressionField(RealField) val expression = with(context) { val x = variable("x", 2.0) x * x + 2 * x + 1.0 @@ -36,7 +36,7 @@ class ExpressionFieldTest { return x * x + 2 * x + one } - val expression = ExpressionField(DoubleField).expression() + val expression = ExpressionField(RealField).expression() assertEquals(expression("x" to 1.0), 4.0) } @@ -47,7 +47,7 @@ class ExpressionFieldTest { x * x + 2 * x + 1.0 } - val expression = ExpressionField(DoubleField).expressionBuilder() + val expression = ExpressionField(RealField).expressionBuilder() assertEquals(expression("x" to 1.0), 4.0) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt index df474c53b..aedbf6655 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class RealFieldTest { @Test fun testSqrt() { - val sqrt = with(DoubleField) { + val sqrt = with(RealField) { sqrt(25 * one) } assertEquals(5.0, sqrt) diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index f1752b483..edda12888 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -2,54 +2,80 @@ package scientifik.kmath.structures import kotlinx.coroutines.* import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : - BufferNDField(shape, field, ::boxingBuffer) { + AbstractNDField>(shape, field) { - override fun add(a: NDStructure, b: NDStructure): NDElements { - return LazyNDStructure(this) { index -> - val aDeferred = a.deferred(index) - val bDeferred = b.deferred(index) - aDeferred.await() + bDeferred.await() + override val zero by lazy { produce { zero } } + + override val one by lazy { produce { one } } + + override fun produce(initializer: F.(IntArray) -> T) = + LazyNDStructure(this) { elementField.initializer(it) } + + override fun mapIndexed( + arg: NDStructure, + transform: F.(index: IntArray, T) -> T + ): LazyNDStructure { + check(arg) + return if (arg is LazyNDStructure) { + LazyNDStructure(this) { index -> + this.elementField.transform(index, arg.function(index)) + } + } else { + LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } } } - override fun multiply(a: NDStructure, k: Double): NDElements { - return LazyNDStructure(this) { index -> a.await(index) * k } - } + override fun map(arg: NDStructure, transform: F.(T) -> T) = + mapIndexed(arg) { _, t -> transform(t) } - override fun multiply(a: NDStructure, b: NDStructure): NDElements { - return LazyNDStructure(this) { index -> - val aDeferred = a.deferred(index) - val bDeferred = b.deferred(index) - aDeferred.await() * bDeferred.await() + override fun combine(a: NDStructure, b: NDStructure, transform: F.(T, T) -> T): LazyNDStructure { + check(a, b) + return if (a is LazyNDStructure && b is LazyNDStructure) { + LazyNDStructure(this@LazyNDField) { index -> + elementField.transform( + a.function(index), + b.function(index) + ) + } + } else { + LazyNDStructure(this@LazyNDField) { elementField.transform(a.await(it), b.await(it)) } } } - override fun divide(a: NDStructure, b: NDStructure): NDElements { - return LazyNDStructure(this) { index -> - val aDeferred = a.deferred(index) - val bDeferred = b.deferred(index) - aDeferred.await() / bDeferred.await() + fun NDStructure.lazy(): LazyNDStructure { + check(this) + return if (this is LazyNDStructure) { + LazyNDStructure(this@LazyNDField, function) + } else { + LazyNDStructure(this@LazyNDField) { get(it) } } } } class LazyNDStructure>( override val context: LazyNDField, - val function: suspend F.(IntArray) -> T -) : NDElements, NDStructure { - override val self: NDElements get() = this + val function: suspend (IntArray) -> T +) : FieldElement, LazyNDStructure, LazyNDField>, NDElement { + + + override fun unwrap(): NDStructure = this + + override fun NDStructure.wrap(): LazyNDStructure = LazyNDStructure(context) { await(it) } + override val shape: IntArray get() = context.shape + override val elementField: F get() = context.elementField + + override fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement = + context.run { mapIndexed(this@LazyNDStructure, transform) } private val cache = HashMap>() fun deferred(index: IntArray) = cache.getOrPut(index) { context.scope.async(context = Dispatchers.Math) { - function.invoke( - context.elementField, - index - ) + function(index) } } @@ -61,7 +87,10 @@ class LazyNDStructure>( override fun elements(): Sequence> { val strides = DefaultStrides(shape) - return strides.indices().map { index -> index to runBlocking { await(index) } } + val res = runBlocking { + strides.indices().toList().map { index -> index to await(index) } + } + return res.asSequence() } } @@ -71,21 +100,11 @@ fun NDStructure.deferred(index: IntArray) = suspend fun NDStructure.await(index: IntArray) = if (this is LazyNDStructure) this.await(index) else get(index) -fun > NDElements.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { - return if (this is LazyNDStructure) { - this - } else { - val context = LazyNDField(context.shape, context.field, scope) - LazyNDStructure(context) { get(it) } - } -} -inline fun > LazyNDStructure.mapIndexed(crossinline action: suspend F.(IntArray, T) -> T) = - LazyNDStructure(context) { index -> - action.invoke(this, index, await(index)) - } +fun > NDField.Companion.lazy(shape: IntArray, field: F, scope: CoroutineScope = GlobalScope) = + LazyNDField(shape, field, scope) -inline fun > LazyNDStructure.map(crossinline action: suspend F.(T) -> T) = - LazyNDStructure(context) { index -> - action.invoke(this, await(index)) - } \ No newline at end of file +fun > NDStructure.lazy(field: F, scope: CoroutineScope = GlobalScope): LazyNDStructure { + val context: LazyNDField = LazyNDField(shape, field, scope) + return LazyNDStructure(context) { get(it) } +} \ No newline at end of file diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index 1eee26f29..51e1db5ae 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -10,7 +10,7 @@ class LazyNDFieldTest { fun testLazyStructure() { var counter = 0 val regularStructure = NDField.generic(intArrayOf(2, 2, 2), IntField).produce { it[0] + it[1] - it[2] } - val result = (regularStructure.lazy() + 2).transform { + val result = (regularStructure.lazy(IntField) + 2).map { counter++ it * it } From b883455bd3c2724f9011addba48b928195c8fa48 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 5 Jan 2019 12:31:47 +0300 Subject: [PATCH 20/70] Cleanup after nd-fields upgrade --- ...uctureBenchmark.kt => NDFieldBenchmark.kt} | 5 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 2 +- .../kotlin/scientifik/kmath/linear/Vector.kt | 2 +- .../kmath/structures/BufferNDField.kt | 52 +++++++++---------- .../scientifik/kmath/structures/Buffers.kt | 4 +- .../scientifik/kmath/structures/NDElement.kt | 2 +- .../scientifik/kmath/structures/NDField.kt | 7 +-- .../kmath/structures/NDStructure.kt | 8 +-- .../kmath/structures/RealNDField.kt | 33 ++++++------ 9 files changed, 54 insertions(+), 61 deletions(-) rename benchmarks/src/main/kotlin/scientifik/kmath/structures/{NDStructureBenchmark.kt => NDFieldBenchmark.kt} (95%) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDStructureBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt similarity index 95% rename from benchmarks/src/main/kotlin/scientifik/kmath/structures/NDStructureBenchmark.kt rename to benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 77f0a2737..fcacc44f5 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDStructureBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -5,9 +5,9 @@ import kotlin.system.measureTimeMillis fun main(args: Array) { val dim = 1000 - val n = 100 + val n = 1000 - val bufferedField = NDField.buffered(intArrayOf(dim, dim), RealField) + val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) val specializedField = NDField.real(intArrayOf(dim, dim)) val genericField = NDField.generic(intArrayOf(dim, dim), RealField) val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) @@ -20,7 +20,6 @@ fun main(args: Array) { // } - val doubleTime = measureTimeMillis { bufferedField.run { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 63c78973a..1fc48264a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -59,7 +59,7 @@ interface MatrixSpace> : Space> { * Automatic buffered matrix, unboxed if it is possible */ inline fun > smart(rows: Int, columns: Int, ring: R): MatrixSpace = - buffered(rows, columns, ring, ::inlineBuffer) + buffered(rows, columns, ring, ::autoBuffer) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 9aaa28203..4cbed0b42 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -56,7 +56,7 @@ interface VectorSpace> : Space> { * Automatic buffered vector, unboxed if it is possible */ inline fun > smart(size: Int, space: S): VectorSpace = - buffered(size, space, ::inlineBuffer) + buffered(size, space, ::autoBuffer) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index ec9268b21..60b0e9869 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -8,6 +8,20 @@ abstract class StridedNDField>(shape: IntArray, elementField: F) val strides = DefaultStrides(shape) abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer + + /** + * Convert any [NDStructure] to buffered structure using strides from this context. + * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes + * + * If the argument is [NDBuffer] with different strides structure, the new element will be produced. + */ + fun NDStructure.toBuffer(): NDBuffer { + return if (this is NDBuffer && this.strides == this@StridedNDField.strides) { + this + } else { + produce { index -> get(index) } + } + } } @@ -26,29 +40,26 @@ class BufferNDField>( override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun produce(crossinline initializer: F.(IntArray) -> T): BufferNDElement = + override fun produce(initializer: F.(IntArray) -> T): BufferNDElement = BufferNDElement( this, - bufferFactory(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) + buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) - @Suppress("OVERRIDE_BY_INLINE") - override inline fun map(arg: NDBuffer, crossinline transform: F.(T) -> T): BufferNDElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferNDElement { check(arg) return BufferNDElement( this, - bufferFactory(arg.strides.linearSize) { offset -> elementField.transform(arg.buffer[offset]) }) + buildBuffer(arg.strides.linearSize) { offset -> elementField.transform(arg.buffer[offset]) }) } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun mapIndexed( + override fun mapIndexed( arg: NDBuffer, - crossinline transform: F.(index: IntArray, T) -> T + transform: F.(index: IntArray, T) -> T ): BufferNDElement { check(arg) return BufferNDElement( this, - bufferFactory(arg.strides.linearSize) { offset -> + buildBuffer(arg.strides.linearSize) { offset -> elementField.transform( arg.strides.index(offset), arg.buffer[offset] @@ -56,30 +67,15 @@ class BufferNDField>( }) } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun combine( + override fun combine( a: NDBuffer, b: NDBuffer, - crossinline transform: F.(T, T) -> T + transform: F.(T, T) -> T ): BufferNDElement { check(a, b) return BufferNDElement( this, - bufferFactory(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) - } - - /** - * Convert any [NDStructure] to buffered structure using strides from this context. - * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes - * - * If the argument is [NDBuffer] with different strides structure, the new element will be produced. - */ - fun NDStructure.toBuffer(): NDBuffer { - return if (this is NDBuffer && this.strides == this@BufferNDField.strides) { - this - } else { - produce { index -> get(index) } - } + buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index e3ca79558..2c08427b3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -140,7 +140,7 @@ inline fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = Lis * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineBuffer(size: Int, initializer: (Int) -> T): Buffer { +inline fun autoBuffer(size: Int, initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer @@ -159,7 +159,7 @@ inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): Mu * Create most appropriate mutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") -inline fun inlineMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer { +inline fun autoMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 4bed26510..6beea1a04 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -51,7 +51,7 @@ object NDElements { noinline initializer: F.(IntArray) -> T ): GenericNDElement { val ndField = GenericNDField(shape, field) - val structure = ndStructure(shape, ::inlineBuffer) { index -> field.initializer(index) } + val structure = ndStructure(shape, ::autoBuffer) { index -> field.initializer(index) } return GenericNDElement(ndField, structure) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index dde406c51..9ed82faa1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -71,10 +71,11 @@ interface NDField, N : NDStructure> : Field { fun > generic(shape: IntArray, field: F) = GenericNDField(shape, field) /** - * Create a most suitable implementation for nd-field using reified class + * Create a most suitable implementation for nd-field using reified class. */ - inline fun > buffered(shape: IntArray, field: F) = - BufferNDField(shape, field, ::inlineBuffer) + inline fun > auto(shape: IntArray, field: F): BufferNDField { + return BufferNDField(shape, field, ::autoBuffer) + } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 617c99a9d..0852271e4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -163,7 +163,7 @@ data class BufferNDStructure( * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] */ inline fun NDStructure.mapToBuffer( - factory: BufferFactory = ::inlineBuffer, + factory: BufferFactory = ::autoBuffer, crossinline transform: (T) -> R ): BufferNDStructure { return if (this is BufferNDStructure) { @@ -186,7 +186,7 @@ fun ndStructure(strides: Strides, bufferFactory: BufferFactory = ::boxing * Inline create NDStructure with non-boxing buffer implementation if it is possible */ inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, inlineBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + BufferNDStructure(strides, autoBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = ndStructure(DefaultStrides(shape), bufferFactory, initializer) @@ -195,7 +195,7 @@ inline fun inlineNdStructure(shape: IntArray, crossinline init inlineNDStructure(DefaultStrides(shape), initializer) /** - * Mutable ND buffer based on linear [inlineBuffer] + * Mutable ND buffer based on linear [autoBuffer] */ class MutableBufferNDStructure( override val strides: Strides, @@ -222,7 +222,7 @@ fun mutableNdStructure( MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) inline fun inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, inlineMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + MutableBufferNDStructure(strides, autoMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) fun mutableNdStructure( shape: IntArray, diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 5bd58eb6e..82d399436 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -8,32 +8,30 @@ class RealNDField(shape: IntArray) : StridedNDField(shape, RealField), ExtendedNDField> { - override fun buildBuffer(size: Int, initializer: (Int) -> Double): Buffer = - DoubleBuffer(DoubleArray(size, initializer)) + @Suppress("OVERRIDE_BY_INLINE") + override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = + DoubleBuffer(DoubleArray(size){initializer(it)}) /** * Inline transform an NDStructure to */ - @Suppress("OVERRIDE_BY_INLINE") - override inline fun map( + override fun map( arg: NDBuffer, - crossinline transform: RealField.(Double) -> Double + transform: RealField.(Double) -> Double ): RealNDElement { check(arg) - val array = DoubleArray(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } - return BufferNDElement(this, DoubleBuffer(array)) + val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } + return BufferNDElement(this, array) } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun produce(crossinline initializer: RealField.(IntArray) -> Double): RealNDElement { - val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } - return BufferNDElement(this, DoubleBuffer(array)) + override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { + val array = buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } + return BufferNDElement(this, array) } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun mapIndexed( + override fun mapIndexed( arg: NDBuffer, - crossinline transform: RealField.(index: IntArray, Double) -> Double + transform: RealField.(index: IntArray, Double) -> Double ): BufferNDElement { check(arg) return BufferNDElement( @@ -46,11 +44,10 @@ class RealNDField(shape: IntArray) : }) } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun combine( + override fun combine( a: NDBuffer, b: NDBuffer, - crossinline transform: RealField.(Double, Double) -> Double + transform: RealField.(Double, Double) -> Double ): BufferNDElement { check(a, b) return BufferNDElement( @@ -105,4 +102,4 @@ operator fun RealNDElement.plus(arg: Double) = * Subtraction operation between [BufferNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = - context.produceInline { i -> buffer[i] - arg } + context.produceInline { i -> buffer[i] - arg } \ No newline at end of file From 12b343e905a96ee998c59d400576f01638135dd1 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 5 Jan 2019 12:43:51 +0300 Subject: [PATCH 21/70] Removed GenericNDField. Additional cleanup --- .../kmath/structures/NDFieldBenchmark.kt | 4 +- .../kotlin/scientifik/kmath/misc/Grids.kt | 2 +- .../scientifik/kmath/structures/NDElement.kt | 18 ++--- .../scientifik/kmath/structures/NDField.kt | 78 ++++--------------- .../kmath/structures/LazyNDFieldTest.kt | 2 +- 5 files changed, 25 insertions(+), 79 deletions(-) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index fcacc44f5..85150ad32 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -9,7 +9,7 @@ fun main(args: Array) { val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) val specializedField = NDField.real(intArrayOf(dim, dim)) - val genericField = NDField.generic(intArrayOf(dim, dim), RealField) + val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) // val action: NDField>.() -> Unit = { @@ -75,7 +75,7 @@ fun main(args: Array) { val genericTime = measureTimeMillis { //genericField.run(action) genericField.run { - var res = one + var res: NDBuffer = one repeat(n) { res += 1.0 } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt index c16b03608..e586bca71 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Grids.kt @@ -32,6 +32,6 @@ fun ClosedFloatingPointRange.toSequence(step: Double): Sequence * Convert double range to array of evenly spaced doubles, where the size of array equals [numPoints] */ fun ClosedFloatingPointRange.toGrid(numPoints: Int): DoubleArray { - if (numPoints < 2) error("Can't generic grid with less than two points") + if (numPoints < 2) error("Can't buffered grid with less than two points") return DoubleArray(numPoints) { i -> start + (endInclusive - start) / (numPoints - 1) * i } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 6beea1a04..022a4e61f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -35,24 +35,22 @@ object NDElements { /** * Simple boxing NDArray */ - fun > generic( + fun > buffered( shape: IntArray, field: F, initializer: F.(IntArray) -> T - ): GenericNDElement { - val ndField = GenericNDField(shape, field) - val structure = ndStructure(shape) { index -> field.initializer(index) } - return GenericNDElement(ndField, structure) + ): BufferNDElement { + val ndField = BufferNDField(shape, field, ::boxingBuffer) + return ndField.produce(initializer) } - inline fun > inline( + inline fun > auto( shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T - ): GenericNDElement { - val ndField = GenericNDField(shape, field) - val structure = ndStructure(shape, ::autoBuffer) { index -> field.initializer(index) } - return GenericNDElement(ndField, structure) + ): BufferNDElement { + val ndField = NDField.auto(shape, field) + return ndField.produce(initializer) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 9ed82faa1..c1abf8acd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -22,43 +22,10 @@ interface NDField, N : NDStructure> : Field { val elementField: F fun produce(initializer: F.(IntArray) -> T): N - fun map(arg: N, transform: F.(T) -> T): N - fun mapIndexed(arg: N, transform: F.(index: IntArray, T) -> T): N - fun combine(a: N, b: N, transform: F.(T, T) -> T): N - /** - * Element by element application of any operation on elements to the whole array. Just like in numpy - */ - operator fun Function1.invoke(structure: N): N - - /** - * Summation operation for [NDElements] and single element - */ - operator fun N.plus(arg: T): N - - /** - * Subtraction operation between [NDElements] and single element - */ - operator fun N.minus(arg: T): N - - /** - * Product operation for [NDElements] and single element - */ - operator fun N.times(arg: T): N - - /** - * Division operation between [NDElements] and single element - */ - operator fun N.div(arg: T): N - - operator fun T.plus(arg: N): N - operator fun T.minus(arg: N): N - operator fun T.times(arg: N): N - operator fun T.div(arg: N): N - companion object { /** * Create a nd-field for [Double] values @@ -68,14 +35,14 @@ interface NDField, N : NDStructure> : Field { /** * Create a nd-field with boxing generic buffer */ - fun > generic(shape: IntArray, field: F) = GenericNDField(shape, field) + fun > buffered(shape: IntArray, field: F) = + BufferNDField(shape, field, ::boxingBuffer) /** * Create a most suitable implementation for nd-field using reified class. */ - inline fun > auto(shape: IntArray, field: F): BufferNDField { - return BufferNDField(shape, field, ::autoBuffer) - } + inline fun > auto(shape: IntArray, field: F) = + BufferNDField(shape, field, ::autoBuffer) } } @@ -88,16 +55,16 @@ abstract class AbstractNDField, N : NDStructure>( override val one: N by lazy { produce { one } } - final override operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } - final override operator fun N.plus(arg: T) = map(this) { value -> elementField.run { arg + value } } - final override operator fun N.minus(arg: T) = map(this) { value -> elementField.run { arg - value } } - final override operator fun N.times(arg: T) = map(this) { value -> elementField.run { arg * value } } - final override operator fun N.div(arg: T) = map(this) { value -> elementField.run { arg / value } } + operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } + operator fun N.plus(arg: T) = map(this) { value -> elementField.run { arg + value } } + operator fun N.minus(arg: T) = map(this) { value -> elementField.run { arg - value } } + operator fun N.times(arg: T) = map(this) { value -> elementField.run { arg * value } } + operator fun N.div(arg: T) = map(this) { value -> elementField.run { arg / value } } - final override operator fun T.plus(arg: N) = arg + this - final override operator fun T.minus(arg: N) = arg - this - final override operator fun T.times(arg: N) = arg * this - final override operator fun T.div(arg: N) = arg / this + operator fun T.plus(arg: N) = arg + this + operator fun T.minus(arg: N) = arg - this + operator fun T.times(arg: N) = arg * this + operator fun T.div(arg: N) = arg / this /** @@ -135,23 +102,4 @@ abstract class AbstractNDField, N : NDStructure>( } } } -} - -class GenericNDField>( - shape: IntArray, - elementField: F, - val bufferFactory: BufferFactory = ::boxingBuffer -) : AbstractNDField>(shape, elementField) { - - override fun produce(initializer: F.(IntArray) -> T): NDStructure = - ndStructure(shape, bufferFactory) { elementField.initializer(it) } - - override fun map(arg: NDStructure, transform: F.(T) -> T): NDStructure = - produce { index -> transform(arg.get(index)) } - - override fun mapIndexed(arg: NDStructure, transform: F.(index: IntArray, T) -> T): NDStructure = - produce { index -> transform(index, arg.get(index)) } - - override fun combine(a: NDStructure, b: NDStructure, transform: F.(T, T) -> T): NDStructure = - produce { index -> transform(a[index], b[index]) } } \ No newline at end of file diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index 51e1db5ae..d5f43d3eb 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -9,7 +9,7 @@ class LazyNDFieldTest { @Test fun testLazyStructure() { var counter = 0 - val regularStructure = NDField.generic(intArrayOf(2, 2, 2), IntField).produce { it[0] + it[1] - it[2] } + val regularStructure = NDField.auto(intArrayOf(2, 2, 2), IntField).produce { it[0] + it[1] - it[2] } val result = (regularStructure.lazy(IntField) + 2).map { counter++ it * it From 696a916ade4e7c3fa0c5dd4a66deb873c7f0cbef Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 5 Jan 2019 20:15:36 +0300 Subject: [PATCH 22/70] Buffer factories removed from global scope --- .../kmath/structures/NDFieldBenchmark.kt | 6 +- .../structures/StructureWriteBenchmark.kt | 1 + doc/algebra.md | 70 ++++++++++++++ doc/nd-performance.md | 13 +++ doc/operations.md | 2 +- .../kmath/linear/LUDecomposition.kt | 8 +- .../scientifik/kmath/linear/LinearAlgrebra.kt | 5 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 7 +- .../kotlin/scientifik/kmath/linear/Vector.kt | 10 +- .../scientifik/kmath/operations/Algebra.kt | 14 +-- .../kmath/structures/BufferNDField.kt | 49 +++++----- .../scientifik/kmath/structures/Buffers.kt | 95 ++++++++++--------- .../scientifik/kmath/structures/NDElement.kt | 67 +++++++------ .../scientifik/kmath/structures/NDField.kt | 12 ++- .../kmath/structures/NDStructure.kt | 14 +-- .../kmath/structures/RealNDField.kt | 20 ++-- .../kmath/expressions/ExpressionFieldTest.kt | 4 +- .../kmath/structures/NDFieldTest.kt | 2 +- .../kmath/structures/NumberNDFieldTest.kt | 2 +- .../kmath/structures/ComplexBufferSpec.kt | 1 + 20 files changed, 255 insertions(+), 147 deletions(-) create mode 100644 doc/algebra.md create mode 100644 doc/nd-performance.md diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 85150ad32..d1101d503 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -25,7 +25,7 @@ fun main(args: Array) { bufferedField.run { var res: NDBuffer = one repeat(n) { - res += 1.0 + res += one } } } @@ -34,7 +34,7 @@ fun main(args: Array) { val elementTime = measureTimeMillis { - var res = bufferedField.produce { one } + var res = bufferedField.run{one.toElement()} repeat(n) { res += 1.0 } @@ -77,7 +77,7 @@ fun main(args: Array) { genericField.run { var res: NDBuffer = one repeat(n) { - res += 1.0 + res += one } } } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt index 7bb4c8267..a2be2aa9c 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt @@ -1,5 +1,6 @@ package scientifik.kmath.structures +import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory import kotlin.system.measureTimeMillis diff --git a/doc/algebra.md b/doc/algebra.md new file mode 100644 index 000000000..2888f9484 --- /dev/null +++ b/doc/algebra.md @@ -0,0 +1,70 @@ +# Algebra and algebra elements + +The mathematical operations in `kmath` are generally separated from mathematical objects. +This means that in order to perform an operation, say `+`, one needs two objects of a type `T` and +and algebra context which defines appropriate operation, say `Space`. Next one needs to run actual operation +in the context: + +```kotlin +val a: T +val b: T +val space: Space + +val c = space.run{a + b} +``` + +From the first glance, this distinction seems to be a needless complication, but in fact one needs +to remember that in mathematics, one could define different operations on the same objects. For example, +one could use different types of geometry for vectors. + +## Algebra hierarchy + +Mathematical contexts have the following hierarchy: + +**Space** <- **Ring** <- **Field** + +All classes follow abstract mathematical constructs. +[Space](http://mathworld.wolfram.com/Space.html) defines `zero` element, addition operation and multiplication by constant, +[Ring](http://mathworld.wolfram.com/Ring.html) adds multiplication and unit `one` element, +[Field](http://mathworld.wolfram.com/Field.html) adds division operation. + +Typical case of `Field` is the `RealField` which works on doubles. And typical case of `Space` is a `VectorSpace`. + +In some cases algebra context could hold additional operation like `exp` or `sin`, in this case it inherits appropriate +interface. Also a context could have an operation which produces an element outside of its context. For example +`Matrix` `dot` operation produces a matrix with new dimensions which could not be compatible with initial matrix in +terms of linear operations. + +## Algebra element + +In order to achieve more familiar behavior (where you apply operations directly to mathematica objects), without involving contexts +`kmath` introduces special type objects called `MathElement`. A `MathElement` is basically some object coupled to +a mathematical context. For example `Complex` is the pair of real numbers representing real and imaginary parts, +but it also holds reference to the `ComplexField` singleton which allows to perform direct operations on `Complex` +numbers without explicit involving the context like: + +```kotlin + val c1 = Complex(1.0, 1.0) + val c2 = Complex(1.0, -1.0) + val c3 = c1 + c2 + 3.0.toComplex() + //or with field notation: + val c4 = ComplexField.run{c1 + i - 2.0} +``` + +Both notations have their pros and cons. + +The hierarchy for algebra elements follows the hierarchy for the corresponding algebra. + +**MathElement** <- **SpaceElement** <- **RingElement** <- **FieldElement** + +**MathElement** is the generic common ancestor of the class with context. + +One important distinction between algebra elements and algebra contexts is that algebra element has three type parameters: + +1. The type of elements, field operates on. +2. The self-type of the element returned from operation (must be algebra element). +3. The type of the algebra over first type-parameter. + +The middle type is needed in case algebra members do not store context. For example, it is not possible to add +a context to regular `Double`. The element performs automatic conversions from context types and back. +One should used context operations in all important places. The performance of element operations is not guaranteed. diff --git a/doc/nd-performance.md b/doc/nd-performance.md new file mode 100644 index 000000000..8ddfc355b --- /dev/null +++ b/doc/nd-performance.md @@ -0,0 +1,13 @@ +# Performance for n-dimensional structures operations + +One of the most sought after features of mathematical libraries is the high-performance operations on n-dimensional +structures. In `kmath` performance depends on which particular context was used for operation. + +Let us consider following contexts: +```kotlin + // automatically build context + val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) + val specializedField = NDField.real(intArrayOf(dim, dim)) + val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) + val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) +``` \ No newline at end of file diff --git a/doc/operations.md b/doc/operations.md index e00b38ecc..ff4a407ee 100644 --- a/doc/operations.md +++ b/doc/operations.md @@ -20,7 +20,7 @@ val c3 = ComplexField.run{ c1 - i*2.0} ``` **Note**: In theory it is possible to add behaviors directly to the context, but currently kotlin syntax does not support -that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) for updates. +that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) and [KEEP-176](https://github.com/Kotlin/KEEP/pull/176) for updates. ## Nested fields 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 5618b3100..402947288 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -2,7 +2,11 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.RealField -import scientifik.kmath.structures.* +import scientifik.kmath.structures.MutableBuffer.Companion.boxing +import scientifik.kmath.structures.MutableNDStructure +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.get +import scientifik.kmath.structures.mutableNdStructure import kotlin.math.absoluteValue /** @@ -106,7 +110,7 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr //TODO fix performance val lu: MutableNDStructure = mutableNdStructure( intArrayOf(matrix.numRows, matrix.numCols), - ::boxingMutableBuffer + ::boxing ) { index: IntArray -> matrix[index[0], index[1]] } 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 11f1f7dd8..72a859111 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -4,8 +4,9 @@ import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.Buffer.Companion.boxing import scientifik.kmath.structures.asSequence -import scientifik.kmath.structures.boxingBuffer + /** @@ -52,7 +53,7 @@ fun > Vector.toMatrix(): Matrix { // matrix(size, 1, context.field) { i, j -> get(i) } // } //return Matrix.of(size, 1, context.space) { i, _ -> get(i) } - return StructureMatrixSpace(size, 1, context.space, ::boxingBuffer).produce { i, _ -> get(i) } + return StructureMatrixSpace(size, 1, context.space, ::boxing).produce { i, _ -> get(i) } } object VectorL2Norm : Norm, Double> { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 1fc48264a..2a5765e30 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -5,6 +5,9 @@ import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement import scientifik.kmath.structures.* +import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory +import scientifik.kmath.structures.Buffer.Companion.auto +import scientifik.kmath.structures.Buffer.Companion.boxing interface MatrixSpace> : Space> { @@ -52,14 +55,14 @@ interface MatrixSpace> : Space> { rows: Int, columns: Int, ring: R, - bufferFactory: BufferFactory = ::boxingBuffer + bufferFactory: BufferFactory = ::boxing ): MatrixSpace = StructureMatrixSpace(rows, columns, ring, bufferFactory) /** * Automatic buffered matrix, unboxed if it is possible */ inline fun > smart(rows: Int, columns: Int, ring: R): MatrixSpace = - buffered(rows, columns, ring, ::autoBuffer) + buffered(rows, columns, ring, ::auto) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index 4cbed0b42..a673c0197 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -3,7 +3,9 @@ package scientifik.kmath.linear import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement -import scientifik.kmath.structures.* +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.asSequence typealias Point = Buffer @@ -40,7 +42,7 @@ interface VectorSpace> : Space> { * Non-boxing double vector space */ fun real(size: Int): BufferVectorSpace { - return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, RealField, DoubleBufferFactory) } + return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, RealField, Buffer.DoubleBufferFactory) } } /** @@ -49,14 +51,14 @@ interface VectorSpace> : Space> { fun > buffered( size: Int, space: S, - bufferFactory: BufferFactory = ::boxingBuffer + bufferFactory: BufferFactory = Buffer.Companion::boxing ): VectorSpace = BufferVectorSpace(size, space, bufferFactory) /** * Automatic buffered vector, unboxed if it is possible */ inline fun > smart(size: Int, space: S): VectorSpace = - buffered(size, space, ::autoBuffer) + buffered(size, space, Buffer.Companion::auto) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 51d0815ec..219af01fd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -68,14 +68,14 @@ interface Ring : Space { operator fun T.times(b: T): T = multiply(this, b) -// operator fun T.plus(b: Number) = this.plus(b * one) -// operator fun Number.plus(b: T) = b + this -// -// operator fun T.minus(b: Number) = this.minus(b * one) -// operator fun Number.minus(b: T) = -b + this + operator fun T.plus(b: Number) = this.plus(b * one) + operator fun Number.plus(b: T) = b + this + + operator fun T.minus(b: Number) = this.minus(b * one) + operator fun Number.minus(b: T) = -b + this } -abstract class AbstractRing : AbstractSpace(), Ring { +abstract class AbstractRing : AbstractSpace(), Ring { final override operator fun T.times(b: T): T = multiply(this, b) } @@ -89,7 +89,7 @@ interface Field : Ring { operator fun Number.div(b: T) = this * divide(one, b) } -abstract class AbstractField : AbstractRing(), Field { +abstract class AbstractField : AbstractRing(), Field { final override operator fun T.div(b: T): T = divide(this, b) final override operator fun Number.div(b: T) = this * divide(one, b) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index 60b0e9869..018675be3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -22,6 +22,9 @@ abstract class StridedNDField>(shape: IntArray, elementField: F) produce { index -> get(index) } } } + + fun NDBuffer.toElement(): StridedNDElement = + StridedNDElement(this@StridedNDField, buffer) } @@ -40,14 +43,14 @@ class BufferNDField>( override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - override fun produce(initializer: F.(IntArray) -> T): BufferNDElement = - BufferNDElement( + override fun produce(initializer: F.(IntArray) -> T): StridedNDElement = + StridedNDElement( this, buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) - override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferNDElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDElement { check(arg) - return BufferNDElement( + return StridedNDElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementField.transform(arg.buffer[offset]) }) } @@ -55,9 +58,9 @@ class BufferNDField>( override fun mapIndexed( arg: NDBuffer, transform: F.(index: IntArray, T) -> T - ): BufferNDElement { + ): StridedNDElement { check(arg) - return BufferNDElement( + return StridedNDElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementField.transform( @@ -71,17 +74,17 @@ class BufferNDField>( a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T - ): BufferNDElement { + ): StridedNDElement { check(a, b) - return BufferNDElement( + return StridedNDElement( this, buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) } } -class BufferNDElement>(override val context: StridedNDField, override val buffer: Buffer) : +class StridedNDElement>(override val context: StridedNDField, override val buffer: Buffer) : NDBuffer, - FieldElement, BufferNDElement, StridedNDField>, + FieldElement, StridedNDElement, StridedNDField>, NDElement { override val elementField: F @@ -90,8 +93,8 @@ class BufferNDElement>(override val context: StridedNDField = this - override fun NDBuffer.wrap(): BufferNDElement = - BufferNDElement(context, this.buffer) + override fun NDBuffer.wrap(): StridedNDElement = + StridedNDElement(context, this.buffer) override val strides get() = context.strides @@ -106,42 +109,42 @@ class BufferNDElement>(override val context: StridedNDField T) = - context.run { map(this@BufferNDElement, action) }.wrap() + context.run { map(this@StridedNDElement, action) }.wrap() override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - context.run { mapIndexed(this@BufferNDElement, transform) }.wrap() + context.run { mapIndexed(this@StridedNDElement, transform) }.wrap() } /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ -operator fun > Function1.invoke(ndElement: BufferNDElement) = +operator fun > Function1.invoke(ndElement: StridedNDElement) = ndElement.context.run { ndElement.map { invoke(it) } } /* plus and minus */ /** - * Summation operation for [BufferNDElement] and single element + * Summation operation for [StridedNDElement] and single element */ -operator fun > BufferNDElement.plus(arg: T) = +operator fun > StridedNDElement.plus(arg: T) = context.run { map { it + arg } } /** - * Subtraction operation between [BufferNDElement] and single element + * Subtraction operation between [StridedNDElement] and single element */ -operator fun > BufferNDElement.minus(arg: T) = +operator fun > StridedNDElement.minus(arg: T) = context.run { map { it - arg } } /* prod and div */ /** - * Product operation for [BufferNDElement] and single element + * Product operation for [StridedNDElement] and single element */ -operator fun > BufferNDElement.times(arg: T) = +operator fun > StridedNDElement.times(arg: T) = context.run { map { it * arg } } /** - * Division operation between [BufferNDElement] and single element + * Division operation between [StridedNDElement] and single element */ -operator fun > BufferNDElement.div(arg: T) = +operator fun > StridedNDElement.div(arg: T) = context.run { map { it / arg } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 2c08427b3..d93a0d46e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -1,6 +1,10 @@ package scientifik.kmath.structures +typealias BufferFactory = (Int, (Int) -> T) -> Buffer +typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer + + /** * A generic random access structure for both primitives and objects */ @@ -14,6 +18,31 @@ interface Buffer { fun contentEquals(other: Buffer<*>): Boolean = asSequence().mapIndexed { index, value -> value == other[index] }.all { it } + + companion object { + + /** + * Create a boxing buffer of given type + */ + inline fun boxing(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) + + /** + * Create most appropriate immutable buffer for given type avoiding boxing wherever possible + */ + @Suppress("UNCHECKED_CAST") + inline fun auto(size: Int, initializer: (Int) -> T): Buffer { + return when (T::class) { + Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer + Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer + else -> boxing(size, initializer) + } + } + + val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } + val IntBufferFactory: BufferFactory = { size, initializer -> IntBuffer(IntArray(size, initializer)) } + val LongBufferFactory: BufferFactory = { size, initializer -> LongBuffer(LongArray(size, initializer)) } + } } fun Buffer.asSequence(): Sequence = iterator().asSequence() @@ -27,6 +56,27 @@ interface MutableBuffer : Buffer { * A shallow copy of the buffer */ fun copy(): MutableBuffer + + companion object { + /** + * Create a boxing mutable buffer of given type + */ + inline fun boxing(size: Int, initializer: (Int) -> T): MutableBuffer = + MutableListBuffer(MutableList(size, initializer)) + + /** + * Create most appropriate mutable buffer for given type avoiding boxing wherever possible + */ + @Suppress("UNCHECKED_CAST") + inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer { + return when (T::class) { + Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer + Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer + Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer + else -> boxing(size, initializer) + } + } + } } @@ -130,48 +180,3 @@ fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { } else { this } - -/** - * Create a boxing buffer of given type - */ -inline fun boxingBuffer(size: Int, initializer: (Int) -> T): Buffer = ListBuffer(List(size, initializer)) - -/** - * Create most appropriate immutable buffer for given type avoiding boxing wherever possible - */ -@Suppress("UNCHECKED_CAST") -inline fun autoBuffer(size: Int, initializer: (Int) -> T): Buffer { - return when (T::class) { - Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer - Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer - Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer - else -> boxingBuffer(size, initializer) - } -} - -/** - * Create a boxing mutable buffer of given type - */ -inline fun boxingMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer = - MutableListBuffer(MutableList(size, initializer)) - -/** - * Create most appropriate mutable buffer for given type avoiding boxing wherever possible - */ -@Suppress("UNCHECKED_CAST") -inline fun autoMutableBuffer(size: Int, initializer: (Int) -> T): MutableBuffer { - return when (T::class) { - Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer - Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer - Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer - else -> boxingMutableBuffer(size, initializer) - } -} - -typealias BufferFactory = (Int, (Int) -> T) -> Buffer -typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer - -val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } -val IntBufferFactory: BufferFactory = { size, initializer -> IntBuffer(IntArray(size, initializer)) } -val LongBufferFactory: BufferFactory = { size, initializer -> LongBuffer(LongArray(size, initializer)) } - diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 022a4e61f..18ada3449 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -10,47 +10,46 @@ interface NDElement> : NDStructure { fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement fun map(action: F.(T) -> T) = mapIndexed { _, value -> action(value) } -} + + companion object { + /** + * Create a optimized NDArray of doubles + */ + fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }) = + NDField.real(shape).produce(initializer) -object NDElements { - /** - * Create a optimized NDArray of doubles - */ - fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }) = - NDField.real(shape).produce(initializer) + fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }) = + real(intArrayOf(dim)) { initializer(it[0]) } - fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }) = - real(intArrayOf(dim)) { initializer(it[0]) } + fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }) = + real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + + fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }) = + real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - fun real2D(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }) = - real(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + /** + * Simple boxing NDArray + */ + fun > buffered( + shape: IntArray, + field: F, + initializer: F.(IntArray) -> T + ): StridedNDElement { + val ndField = BufferNDField(shape, field, Buffer.Companion::boxing) + return ndField.produce(initializer) + } - fun real3D(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }) = - real(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - - - /** - * Simple boxing NDArray - */ - fun > buffered( - shape: IntArray, - field: F, - initializer: F.(IntArray) -> T - ): BufferNDElement { - val ndField = BufferNDField(shape, field, ::boxingBuffer) - return ndField.produce(initializer) - } - - inline fun > auto( - shape: IntArray, - field: F, - noinline initializer: F.(IntArray) -> T - ): BufferNDElement { - val ndField = NDField.auto(shape, field) - return ndField.produce(initializer) + inline fun > auto( + shape: IntArray, + field: F, + noinline initializer: F.(IntArray) -> T + ): StridedNDElement { + val ndField = NDField.auto(shape, field) + return StridedNDElement(ndField, ndField.produce(initializer).buffer) + } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index c1abf8acd..b859b6dd3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -2,6 +2,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.AbstractField import scientifik.kmath.operations.Field +import scientifik.kmath.structures.Buffer.Companion.boxing /** * An exception is thrown when the expected ans actual shape of NDArray differs @@ -36,13 +37,18 @@ interface NDField, N : NDStructure> : Field { * Create a nd-field with boxing generic buffer */ fun > buffered(shape: IntArray, field: F) = - BufferNDField(shape, field, ::boxingBuffer) + BufferNDField(shape, field, Buffer.Companion::boxing) /** * Create a most suitable implementation for nd-field using reified class. */ - inline fun > auto(shape: IntArray, field: F) = - BufferNDField(shape, field, ::autoBuffer) + inline fun > auto(shape: IntArray, field: F): StridedNDField = + if (T::class == Double::class) { + @Suppress("UNCHECKED_CAST") + real(shape) as StridedNDField + } else { + BufferNDField(shape, field, Buffer.Companion::auto) + } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 0852271e4..74e3a59a2 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -163,7 +163,7 @@ data class BufferNDStructure( * Transform structure to a new structure using provided [BufferFactory] and optimizing if argument is [BufferNDStructure] */ inline fun NDStructure.mapToBuffer( - factory: BufferFactory = ::autoBuffer, + factory: BufferFactory = Buffer.Companion::auto, crossinline transform: (T) -> R ): BufferNDStructure { return if (this is BufferNDStructure) { @@ -179,16 +179,16 @@ inline fun NDStructure.mapToBuffer( * * Strides should be reused if possible */ -fun ndStructure(strides: Strides, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = +fun ndStructure(strides: Strides, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T) = BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** * Inline create NDStructure with non-boxing buffer implementation if it is possible */ inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, autoBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + BufferNDStructure(strides, Buffer.Companion.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) -fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = ::boxingBuffer, initializer: (IntArray) -> T) = +fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T) = ndStructure(DefaultStrides(shape), bufferFactory, initializer) inline fun inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = @@ -216,17 +216,17 @@ class MutableBufferNDStructure( */ fun mutableNdStructure( strides: Strides, - bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, + bufferFactory: MutableBufferFactory = MutableBuffer.Companion::boxing, initializer: (IntArray) -> T ) = MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) inline fun inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, autoMutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + MutableBufferNDStructure(strides, MutableBuffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) fun mutableNdStructure( shape: IntArray, - bufferFactory: MutableBufferFactory = ::boxingMutableBuffer, + bufferFactory: MutableBufferFactory = MutableBuffer.Companion::boxing, initializer: (IntArray) -> T ) = mutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 82d399436..b8bab7c07 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -2,7 +2,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.RealField -typealias RealNDElement = BufferNDElement +typealias RealNDElement = StridedNDElement class RealNDField(shape: IntArray) : StridedNDField(shape, RealField), @@ -21,20 +21,20 @@ class RealNDField(shape: IntArray) : ): RealNDElement { check(arg) val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } - return BufferNDElement(this, array) + return StridedNDElement(this, array) } override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { val array = buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } - return BufferNDElement(this, array) + return StridedNDElement(this, array) } override fun mapIndexed( arg: NDBuffer, transform: RealField.(index: IntArray, Double) -> Double - ): BufferNDElement { + ): StridedNDElement { check(arg) - return BufferNDElement( + return StridedNDElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementField.transform( @@ -48,9 +48,9 @@ class RealNDField(shape: IntArray) : a: NDBuffer, b: NDBuffer, transform: RealField.(Double, Double) -> Double - ): BufferNDElement { + ): StridedNDElement { check(a, b) - return BufferNDElement( + return StridedNDElement( this, buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) } @@ -80,7 +80,7 @@ class RealNDField(shape: IntArray) : */ inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(offset) } - return BufferNDElement(this, DoubleBuffer(array)) + return StridedNDElement(this, DoubleBuffer(array)) } /** @@ -93,13 +93,13 @@ operator fun Function1.invoke(ndElement: RealNDElement) = /* plus and minus */ /** - * Summation operation for [BufferNDElement] and single element + * Summation operation for [StridedNDElement] and single element */ operator fun RealNDElement.plus(arg: Double) = context.produceInline { i -> buffer[i] + arg } /** - * Subtraction operation between [BufferNDElement] and single element + * Subtraction operation between [StridedNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = context.produceInline { i -> buffer[i] - arg } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt index 5c5002e06..033b2792f 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/ExpressionFieldTest.kt @@ -12,7 +12,7 @@ class ExpressionFieldTest { val context = ExpressionField(RealField) val expression = with(context) { val x = variable("x", 2.0) - x * x + 2 * x + 1.0 + x * x + 2 * x + one } assertEquals(expression("x" to 1.0), 4.0) assertEquals(expression(), 9.0) @@ -44,7 +44,7 @@ class ExpressionFieldTest { fun valueExpression() { val expressionBuilder: ExpressionField.() -> Expression = { val x = variable("x") - x * x + 2 * x + 1.0 + x * x + 2 * x + one } val expression = ExpressionField(RealField).expressionBuilder() diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt index 637147629..39cce5c67 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NDFieldTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertEquals class NDFieldTest { @Test fun testStrides() { - val ndArray = NDElements.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() } + val ndArray = NDElement.real(intArrayOf(10, 10)) { (it[0] + it[1]).toDouble() } assertEquals(ndArray[5, 5], 10.0) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 3bbd620fa..3c4160329 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,7 +1,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Norm -import scientifik.kmath.structures.NDElements.real2D +import scientifik.kmath.structures.NDElement.Companion.real2D import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index 68e989e81..35ee5e6b7 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -23,3 +23,4 @@ object ComplexBufferSpec : FixedSizeBufferSpec { */ fun Complex.Companion.createBuffer(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size) + From 798512517dd87ddec7f8daee60089b32322a6473 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 5 Jan 2019 21:01:30 +0300 Subject: [PATCH 23/70] Added factories for Complex number buffers and nd-structures --- .../kmath/structures/ComplexBufferSpec.kt | 15 +++++++++++++-- .../scientifik/kmath/structures/ObjectBuffer.kt | 10 ++++++++-- .../kmath/structures/ComplexBufferSpecTest.kt | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index 35ee5e6b7..516653817 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -1,6 +1,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField import java.nio.ByteBuffer object ComplexBufferSpec : FixedSizeBufferSpec { @@ -19,8 +20,18 @@ object ComplexBufferSpec : FixedSizeBufferSpec { } /** - * Create a mutable buffer which ignores boxing + * Create a read-only/mutable buffer which ignores boxing */ -fun Complex.Companion.createBuffer(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size) +fun Buffer.Companion.complex(size: Int): Buffer = + ObjectBuffer.create(ComplexBufferSpec, size) + +fun MutableBuffer.Companion.complex(size: Int) = + ObjectBuffer.create(ComplexBufferSpec, size) + +fun NDField.Companion.complex(shape: IntArray) = + BufferNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } + +fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = + NDField.complex(shape).produce(initializer) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt index fceda1b25..8d7bece0c 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -23,7 +23,13 @@ class ObjectBuffer(private val buffer: ByteBuffer, private val spec: Fi } companion object { - fun create(spec: FixedSizeBufferSpec, size: Int) = - ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec) + fun create(spec: FixedSizeBufferSpec, size: Int, initializer: ((Int) -> T)? = null) = + ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec).also { buffer -> + if (initializer != null) { + (0 until size).forEach { + buffer[it] = initializer(it) + } + } + } } } \ No newline at end of file diff --git a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt index 350f06848..1a3aea10f 100644 --- a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt +++ b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertEquals class ComplexBufferSpecTest { @Test fun testComplexBuffer() { - val buffer = Complex.createBuffer(20) + val buffer = MutableBuffer.complex(20) (0 until 20).forEach { buffer[it] = Complex(it.toDouble(), -it.toDouble()) } From c161ef0b57f9b5354ebda34ccb563e69f36aea0d Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 7 Jan 2019 17:18:31 +0300 Subject: [PATCH 24/70] Documentation for nd-performance --- benchmarks/build.gradle | 8 +- .../kmath/structures/ArrayBenchmark.kt | 45 ++++--- .../kmath/structures/BufferBenchmark.kt | 10 +- .../kmath/structures/NDFieldBenchmark.kt | 69 ++++++++++ .../kmath/structures/NDFieldBenchmark.kt | 52 ++++---- build.gradle.kts | 2 +- doc/nd-performance.md | 122 +++++++++++++++++- .../scientifik/kmath/operations/Algebra.kt | 10 +- .../kmath/structures/RealNDField.kt | 4 +- .../kmath/structures/LazyNDField.kt | 5 +- 10 files changed, 256 insertions(+), 71 deletions(-) create mode 100644 benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 23f967102..a9450a974 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -8,4 +8,10 @@ dependencies { compile project(":kmath-core") compile project(":kmath-coroutines") //jmh project(':kmath-core') -} \ No newline at end of file +} + +jmh{ + warmupIterations = 1 +} + +jmhClasses.dependsOn(compileKotlin) \ No newline at end of file diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt index 098c61cf6..393d7b06a 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt @@ -1,49 +1,48 @@ package scientifik.kmath.structures -import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State import java.nio.IntBuffer -@Warmup(iterations = 1) -@Measurement(iterations = 5) @State(Scope.Benchmark) open class ArrayBenchmark { - lateinit var array: IntArray - lateinit var arrayBuffer: IntBuffer - lateinit var nativeBuffer: IntBuffer - - @Setup - fun setup() { - array = IntArray(10000) { it } - arrayBuffer = IntBuffer.wrap(array) - nativeBuffer = IntBuffer.allocate(10000) - for (i in 0 until 10000) { - nativeBuffer.put(i, i) - } - } - @Benchmark fun benchmarkArrayRead() { var res = 0 - for (i in 1..10000) { - res += array[10000 - i] + for (i in 1..size) { + res += array[size - i] } } @Benchmark fun benchmarkBufferRead() { var res = 0 - for (i in 1..10000) { - res += arrayBuffer.get(10000 - i) + for (i in 1..size) { + res += arrayBuffer.get(size - i) } } @Benchmark fun nativeBufferRead() { var res = 0 - for (i in 1..10000) { - res += nativeBuffer.get(10000 - i) + for (i in 1..size) { + res += nativeBuffer.get(size - i) + } + } + + companion object { + val size = 1000 + + val array = IntArray(size) { it } + val arrayBuffer = IntBuffer.wrap(array) + val nativeBuffer = IntBuffer.allocate(size).also { + for (i in 0 until size) { + it.put(i, i) + } + } } } \ No newline at end of file diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt index 90f9bc372..5ca05d451 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt @@ -1,10 +1,10 @@ package scientifik.kmath.structures -import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State import scientifik.kmath.operations.Complex -@Warmup(iterations = 1) -@Measurement(iterations = 5) @State(Scope.Benchmark) open class BufferBenchmark { @@ -22,7 +22,7 @@ open class BufferBenchmark { @Benchmark fun complexBufferReadWrite() { - val buffer = Complex.createBuffer(size / 2) + val buffer = MutableBuffer.complex(size / 2) (0 until size / 2).forEach { buffer[it] = Complex(it.toDouble(), -it.toDouble()) } @@ -33,6 +33,6 @@ open class BufferBenchmark { } companion object { - const val size = 1000 + const val size = 100 } } \ No newline at end of file diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt new file mode 100644 index 000000000..421b5fb6c --- /dev/null +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -0,0 +1,69 @@ +package scientifik.kmath.structures + +import org.openjdk.jmh.annotations.Benchmark +import scientifik.kmath.operations.RealField + +open class NDFieldBenchmark { + + @Benchmark + fun autoFieldAdd() { + bufferedField.run { + var res: NDBuffer = one + repeat(n) { + res += one + } + } + } + + @Benchmark + fun autoElementAdd() { + var res = bufferedField.run { one.toElement() } + repeat(n) { + res += 1.0 + } + } + + @Benchmark + fun specializedFieldAdd() { + specializedField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } + } + } + + + @Benchmark + fun lazyFieldAdd() { + lazyNDField.run { + var res = one + repeat(n) { + res += one + } + + res.elements().sumByDouble { it.second } + } + } + + + @Benchmark + fun boxingFieldAdd() { + genericField.run { + var res: NDBuffer = one + repeat(n) { + res += one + } + } + } + + companion object { + val dim = 1000 + val n = 100 + + val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) + val specializedField = NDField.real(intArrayOf(dim, dim)) + val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) + val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) + } +} \ No newline at end of file diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index d1101d503..bc5db1e2f 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -7,34 +7,29 @@ fun main(args: Array) { val dim = 1000 val n = 1000 - val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) + // automatically build context most suited for given type. + val autoField = NDField.auto(intArrayOf(dim, dim), RealField) + // specialized nd-field for Double. It works as generic Double field as well val specializedField = NDField.real(intArrayOf(dim, dim)) + //A field implementing lazy computations. All elements are computed on-demand + val lazyField = NDField.lazy(intArrayOf(dim, dim), RealField) + //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) - val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) - -// val action: NDField>.() -> Unit = { -// var res = one -// repeat(n) { -// res += 1.0 -// } -// } - val doubleTime = measureTimeMillis { - - bufferedField.run { - var res: NDBuffer = one + val autoTime = measureTimeMillis { + autoField.run { + var res = one repeat(n) { - res += one + res += 1.0 } } } - println("Buffered addition completed in $doubleTime millis") - + println("Buffered addition completed in $autoTime millis") val elementTime = measureTimeMillis { - var res = bufferedField.run{one.toElement()} + var res = genericField.one repeat(n) { res += 1.0 } @@ -43,9 +38,8 @@ fun main(args: Array) { println("Element addition completed in $elementTime millis") val specializedTime = measureTimeMillis { - //specializedField.run(action) specializedField.run { - var res: NDBuffer = one + var res = one repeat(n) { res += 1.0 } @@ -56,17 +50,16 @@ fun main(args: Array) { val lazyTime = measureTimeMillis { - val tr : RealField.(Double)->Double = {arg-> - var r = arg - repeat(n) { - r += 1.0 + lazyField.run { + val res = one.map { + var c = 0.0 + repeat(n) { + c += 1.0 + } + c } - r - } - lazyNDField.run { - val res = one.map(tr) - res.elements().sumByDouble { it.second } + res.elements().forEach { it.second } } } @@ -77,10 +70,11 @@ fun main(args: Array) { genericField.run { var res: NDBuffer = one repeat(n) { - res += one + res += 1.0 } } } println("Generic addition completed in $genericTime millis") + } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5fa89bc1a..37e61237e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.3-dev-1" + version = "0.0.3-dev-2" repositories { maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/doc/nd-performance.md b/doc/nd-performance.md index 8ddfc355b..47653e3e8 100644 --- a/doc/nd-performance.md +++ b/doc/nd-performance.md @@ -5,9 +5,123 @@ structures. In `kmath` performance depends on which particular context was used Let us consider following contexts: ```kotlin - // automatically build context - val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) + // specialized nd-field for Double. It works as generic Double field as well val specializedField = NDField.real(intArrayOf(dim, dim)) + + // automatically build context most suited for given type. + val autoField = NDField.auto(intArrayOf(dim, dim), RealField) + + //A field implementing lazy computations. All elements are computed on-demand + val lazyField = NDField.lazy(intArrayOf(dim, dim), RealField) + + //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) - val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) -``` \ No newline at end of file +``` +Now let us perform several tests and see which implementation is best suited for each case: + +## Test case + +In order to test performance we will take 2d-structures with `dim = 1000` and add a structure filled with `1.0` +to it `n = 1000` times. + +## Specialized +The code to run this looks like: +```kotlin + specializedField.run { + var res = one + repeat(n) { + res += 1.0 + } + } +``` +The performance of this code is the best of all tests since it inlines all operations and is specialized for operation +with doubles. We will measure everything else relative to this one, so time for this test will be `1x` (real time +on my computer is about 4.5 seconds). The only problem with this approach is that it requires to specify type +from the beginning. Everyone do so anyway, so it is the recommended approach. + +## Automatic +Let's do the same with automatic field inference: +```kotlin + autoField.run { + var res = one + repeat(n) { + res += 1.0 + } + } +``` +Ths speed of this operation is approximately the same as for specialized case since `NDField.auto` just +returns the same `RealNDField` in this case. Of course it is usually better to use specialized method to be sure. + +## Lazy +Lazy field does not produce a structure when asked, instead it generates an empty structure and fills it on-demand +using coroutines to parallelize computations. +When one calls +```kotlin + lazyField.run { + var res = one + repeat(n) { + res += 1.0 + } + } +``` +The result will be calculated almost immediately but the result will be empty. In order to get the full result +structure one needs to call all its elements. In this case computation overhead will be huge. So this field never +should be used if one expects to use the full result structure. Though if one wants only small fraction, it could +save a lot of time. + +This field still could be used with reasonable performance if call code is changed: +```kotlin + lazyField.run { + val res = one.map { + var c = 0.0 + repeat(n) { + c += 1.0 + } + c + } + + res.elements().forEach { it.second } + } +``` +In this case it completes in about `4x-5x` time due to boxing. + +## Boxing +The boxing field produced by +```kotlin + genericField.run { + var res = one + repeat(n) { + res += 1.0 + } + } +``` +obviously is the slowest one, because it requires to box and unbox the `double` on each operation. It takes about +`15x` time (**TODO: there seems to be a problem here, it should be slow, but not that slow**). This field should +never be used for primitives. + +## Element operation +Let us also check the speed for direct operations on elements: +```kotlin + var res = genericField.one + repeat(n) { + res += 1.0 + } +``` +One would expect to be at least as slow as field operation, but in fact, this one takes only `2x` time to complete. +It happens, because in this particular case it does not use actual `NDField` but instead calculated directly +via extension function. + +## What about python? + +Usually it is bad idea to compare the direct numerical operation performance in different languages, but it hard to +work completely without frame of reference. In this case, simple numpy code: +```python +res = np.ones((1000,1000)) +for i in range(1000): + res = res + 1.0 +``` +gives the completion time of about `1.1x`, which means that specialized kotlin code in fact is working faster (I think it is +because better memory management). Of course if one writes `res += 1.0`, the performance will be different, +but it would be differenc case, because numpy overrides `+=` with in-place operations. In-place operations are +available in `kmath` with `MutableNDStructure` but there is no field for it (one can still work with mapping +functions). \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index a6bd8d78a..ff647504a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -69,11 +69,11 @@ interface Ring : Space { operator fun T.times(b: T): T = multiply(this, b) - operator fun T.plus(b: Number) = this.plus(b * one) - operator fun Number.plus(b: T) = b + this - - operator fun T.minus(b: Number) = this.minus(b * one) - operator fun Number.minus(b: T) = -b + this +// operator fun T.plus(b: Number) = this.plus(b * one) +// operator fun Number.plus(b: T) = b + this +// +// operator fun T.minus(b: Number) = this.minus(b * one) +// operator fun Number.minus(b: T) = -b + this } abstract class AbstractRing : AbstractSpace(), Ring { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index b8bab7c07..9b3e306d5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -79,7 +79,7 @@ class RealNDField(shape: IntArray) : * Fast element production using function inlining */ inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { - val array = DoubleArray(strides.linearSize) { offset -> elementField.initializer(offset) } + val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } return StridedNDElement(this, DoubleBuffer(array)) } @@ -102,4 +102,4 @@ operator fun RealNDElement.plus(arg: Double) = * Subtraction operation between [StridedNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = - context.produceInline { i -> buffer[i] - arg } \ No newline at end of file + context.produceInline { i -> buffer[i] - arg } diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index edda12888..451ef9fdd 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -21,11 +21,13 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti check(arg) return if (arg is LazyNDStructure) { LazyNDStructure(this) { index -> - this.elementField.transform(index, arg.function(index)) + //FIXME if value of arg is already calculated, it should be used + elementField.transform(index, arg.function(index)) } } else { LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } } +// return LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } } override fun map(arg: NDStructure, transform: F.(T) -> T) = @@ -43,6 +45,7 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti } else { LazyNDStructure(this@LazyNDField) { elementField.transform(a.await(it), b.await(it)) } } +// return LazyNDStructure(this) { elementField.transform(a.await(it), b.await(it)) } } fun NDStructure.lazy(): LazyNDStructure { From 83e24acdc5c17a6e876fe3698e25d639b0b798df Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 7 Jan 2019 17:24:10 +0300 Subject: [PATCH 25/70] Added features to Matrix --- .../kotlin/scientifik/kmath/linear/Matrix.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 2a5765e30..35ad86379 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -66,6 +66,11 @@ interface MatrixSpace> : Space> { } } +/** + * A marker interface representing some matrix feature like diagonal, sparce, zero, etc. Features used to optimize matrix + * operations performance in some cases. + */ +interface MatrixFeature /** * Specialized 2-d structure @@ -91,6 +96,8 @@ interface Matrix> : NDStructure, SpaceElement get(i, j) } } + val features: Set + companion object { fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = MatrixSpace.real(rows, columns).produce(rows, columns, initializer) @@ -147,7 +154,8 @@ data class StructureMatrixSpace>( data class StructureMatrix>( override val context: StructureMatrixSpace, - val structure: NDStructure + val structure: NDStructure, + override val features: Set = emptySet() ) : Matrix { init { if (structure.shape.size != 2 || structure.shape[0] != context.rowNum || structure.shape[1] != context.colNum) { From 2bf34f2a0738d17d38b84fff9bb66f9d3e613d52 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 7 Jan 2019 17:44:10 +0300 Subject: [PATCH 26/70] Added Rings for Int and Short --- .../scientifik/kmath/operations/Fields.kt | 18 ++++++++++++++---- .../scientifik/kmath/structures/NDField.kt | 10 ++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt index fd8bcef11..ef0d91aab 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt @@ -32,7 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ -object RealField : AbstractField(),ExtendedField, Norm { +object RealField : AbstractField(), ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b @@ -52,15 +52,25 @@ object RealField : AbstractField(),ExtendedField, Norm { +object IntRing : Ring { 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 +} + +/** + * A field for [Short] without boxing. Does not produce appropriate field element + */ +object ShortRing : Ring { + override val zero: Short = 0 + override fun add(a: Short, b: Short): Short = (a + b).toShort() + override fun multiply(a: Short, b: Short): Short = (a * b).toShort() + override fun multiply(a: Short, k: Double): Short = (a * k).toShort() + override val one: Short = 1 } //interface FieldAdapter : Field { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index b859b6dd3..00f19ec7e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -32,7 +32,6 @@ interface NDField, N : NDStructure> : Field { * Create a nd-field for [Double] values */ fun real(shape: IntArray) = RealNDField(shape) - /** * Create a nd-field with boxing generic buffer */ @@ -42,12 +41,11 @@ interface NDField, N : NDStructure> : Field { /** * Create a most suitable implementation for nd-field using reified class. */ + @Suppress("UNCHECKED_CAST") inline fun > auto(shape: IntArray, field: F): StridedNDField = - if (T::class == Double::class) { - @Suppress("UNCHECKED_CAST") - real(shape) as StridedNDField - } else { - BufferNDField(shape, field, Buffer.Companion::auto) + when { + T::class == Double::class -> real(shape) as StridedNDField + else -> BufferNDField(shape, field, Buffer.Companion::auto) } } } From 05d15d5b34595b1c70bc575717f4ed3de158a5a8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jan 2019 10:35:00 +0300 Subject: [PATCH 27/70] Cleanup after nd optimization phase I --- .../kmath/structures/NDFieldBenchmark.kt | 2 +- .../scientifik/kmath/operations/Algebra.kt | 24 ---- .../{Fields.kt => NumberAlgebra.kt} | 54 ++++--- .../kmath/structures/BufferNDField.kt | 122 ++-------------- .../scientifik/kmath/structures/Buffers.kt | 17 +++ .../kmath/structures/ExtendedNDField.kt | 62 ++++---- .../structures/{NDField.kt => NDAlgebra.kt} | 134 +++++++++--------- .../scientifik/kmath/structures/NDElement.kt | 25 ++-- .../kmath/structures/NDStructure.kt | 17 +-- .../kmath/structures/RealNDField.kt | 43 +++--- .../kmath/structures/ShortNDRing.kt | 86 +++++++++++ .../kmath/structures/StridedContext.kt | 97 +++++++++++++ .../kmath/structures/LazyNDField.kt | 20 +-- .../kmath/structures/LazyNDFieldTest.kt | 26 ++-- 14 files changed, 403 insertions(+), 326 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/{Fields.kt => NumberAlgebra.kt} (67%) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/{NDField.kt => NDAlgebra.kt} (68%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index bc5db1e2f..a0cbd66ca 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -39,7 +39,7 @@ fun main(args: Array) { val specializedTime = measureTimeMillis { specializedField.run { - var res = one + var res:NDBuffer = one repeat(n) { res += 1.0 } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index ff647504a..771c86389 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -38,21 +38,6 @@ interface Space { fun Sequence.sum(): T = fold(zero) { left, right -> left + right } } -abstract class AbstractSpace : Space { - //TODO move to external extensions when they are available - final override operator fun T.unaryMinus(): T = multiply(this, -1.0) - - final override operator fun T.plus(b: T): T = add(this, b) - final override operator fun T.minus(b: T): T = add(this, -b) - final override operator fun T.times(k: Number) = multiply(this, k.toDouble()) - final override operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) - final override operator fun Number.times(b: T) = b * this - - final override fun Iterable.sum(): T = fold(zero) { left, right -> left + right } - - final override fun Sequence.sum(): T = fold(zero) { left, right -> left + right } -} - /** * The same as {@link Space} but with additional multiplication operation */ @@ -76,10 +61,6 @@ interface Ring : Space { // operator fun Number.minus(b: T) = -b + this } -abstract class AbstractRing : AbstractSpace(), Ring { - final override operator fun T.times(b: T): T = multiply(this, b) -} - /** * Four operations algebra */ @@ -89,8 +70,3 @@ interface Field : Ring { operator fun T.div(b: T): T = divide(this, b) operator fun Number.div(b: T) = this * divide(one, b) } - -abstract class AbstractField : AbstractRing(), Field { - final override operator fun T.div(b: T): T = divide(this, b) - final override operator fun Number.div(b: T) = this * divide(one, b) -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt similarity index 67% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index ef0d91aab..fa89cd793 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Fields.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -32,7 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ -object RealField : AbstractField(), ExtendedField, Norm { +object RealField : ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b @@ -54,41 +54,51 @@ object RealField : AbstractField(), ExtendedField, Norm { +object IntRing : Ring, Norm { 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 norm(arg: Int): Int = arg } /** * A field for [Short] without boxing. Does not produce appropriate field element */ -object ShortRing : Ring { +object ShortRing : Ring, Norm{ override val zero: Short = 0 override fun add(a: Short, b: Short): Short = (a + b).toShort() override fun multiply(a: Short, b: Short): Short = (a * b).toShort() override fun multiply(a: Short, k: Double): Short = (a * k).toShort() override val one: Short = 1 + + override fun norm(arg: Short): Short = arg } -//interface FieldAdapter : Field { -// -// val field: Field -// -// abstract fun T.evolve(): R -// abstract fun R.devolve(): T -// -// override val zero get() = field.zero.evolve() -// override val one get() = field.zero.evolve() -// -// override fun add(a: R, b: R): R = field.add(a.devolve(), b.devolve()).evolve() -// -// override fun multiply(a: R, k: Double): R = field.multiply(a.devolve(), k).evolve() -// -// -// override fun multiply(a: R, b: R): R = field.multiply(a.devolve(), b.devolve()).evolve() -// -// override fun divide(a: R, b: R): R = field.divide(a.devolve(), b.devolve()).evolve() -//} \ No newline at end of file +/** + * A field for [Byte] values + */ +object ByteRing : Ring, Norm { + override val zero: Byte = 0 + override fun add(a: Byte, b: Byte): Byte = (a + b).toByte() + override fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte() + override fun multiply(a: Byte, k: Double): Byte = (a * k).toByte() + override val one: Byte = 1 + + override fun norm(arg: Byte): Byte = arg +} + +/** + * A field for [Long] values + */ +object LongRing : Ring, Norm { + override val zero: Long = 0 + override fun add(a: Long, b: Long): Long = (a + b) + override fun multiply(a: Long, b: Long): Long = (a * b) + override fun multiply(a: Long, k: Double): Long = (a * k).toLong() + override val one: Long = 1 + + override fun norm(arg: Long): Long = arg +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt index 018675be3..752663712 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt @@ -1,38 +1,13 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -import scientifik.kmath.operations.FieldElement - -abstract class StridedNDField>(shape: IntArray, elementField: F) : - AbstractNDField>(shape, elementField) { - val strides = DefaultStrides(shape) - - abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer - - /** - * Convert any [NDStructure] to buffered structure using strides from this context. - * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes - * - * If the argument is [NDBuffer] with different strides structure, the new element will be produced. - */ - fun NDStructure.toBuffer(): NDBuffer { - return if (this is NDBuffer && this.strides == this@StridedNDField.strides) { - this - } else { - produce { index -> get(index) } - } - } - - fun NDBuffer.toElement(): StridedNDElement = - StridedNDElement(this@StridedNDField, buffer) -} class BufferNDField>( shape: IntArray, - elementField: F, + override val elementContext: F, val bufferFactory: BufferFactory -) : StridedNDField(shape, elementField) { +) : StridedNDField(shape), NDField> { override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) @@ -43,27 +18,27 @@ class BufferNDField>( override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - override fun produce(initializer: F.(IntArray) -> T): StridedNDElement = - StridedNDElement( + override fun produce(initializer: F.(IntArray) -> T): StridedNDFieldElement = + StridedNDFieldElement( this, - buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) }) + buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDFieldElement { check(arg) - return StridedNDElement( + return StridedNDFieldElement( this, - buildBuffer(arg.strides.linearSize) { offset -> elementField.transform(arg.buffer[offset]) }) + buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) } override fun mapIndexed( arg: NDBuffer, transform: F.(index: IntArray, T) -> T - ): StridedNDElement { + ): StridedNDFieldElement { check(arg) - return StridedNDElement( + return StridedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> - elementField.transform( + elementContext.transform( arg.strides.index(offset), arg.buffer[offset] ) @@ -74,77 +49,10 @@ class BufferNDField>( a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T - ): StridedNDElement { + ): StridedNDFieldElement { check(a, b) - return StridedNDElement( + return StridedNDFieldElement( this, - buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } -} - -class StridedNDElement>(override val context: StridedNDField, override val buffer: Buffer) : - NDBuffer, - FieldElement, StridedNDElement, StridedNDField>, - NDElement { - - override val elementField: F - get() = context.elementField - - override fun unwrap(): NDBuffer = - this - - override fun NDBuffer.wrap(): StridedNDElement = - StridedNDElement(context, this.buffer) - - override val strides - get() = context.strides - - override val shape: IntArray - get() = context.shape - - override fun get(index: IntArray): T = - buffer[strides.offset(index)] - - override fun elements(): Sequence> = - strides.indices().map { it to get(it) } - - override fun map(action: F.(T) -> T) = - context.run { map(this@StridedNDElement, action) }.wrap() - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - context.run { mapIndexed(this@StridedNDElement, transform) }.wrap() -} - -/** - * Element by element application of any operation on elements to the whole array. Just like in numpy - */ -operator fun > Function1.invoke(ndElement: StridedNDElement) = - ndElement.context.run { ndElement.map { invoke(it) } } - -/* plus and minus */ - -/** - * Summation operation for [StridedNDElement] and single element - */ -operator fun > StridedNDElement.plus(arg: T) = - context.run { map { it + arg } } - -/** - * Subtraction operation between [StridedNDElement] and single element - */ -operator fun > StridedNDElement.minus(arg: T) = - context.run { map { it - arg } } - -/* prod and div */ - -/** - * Product operation for [StridedNDElement] and single element - */ -operator fun > StridedNDElement.times(arg: T) = - context.run { map { it * arg } } - -/** - * Division operation between [StridedNDElement] and single element - */ -operator fun > StridedNDElement.div(arg: T) = - context.run { map { it / arg } } \ No newline at end of file +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index d93a0d46e..8c112bb87 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -33,6 +33,7 @@ interface Buffer { inline fun auto(size: Int, initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer + Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as Buffer Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as Buffer else -> boxing(size, initializer) @@ -40,6 +41,7 @@ interface Buffer { } val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } + val ShortBufferFactory: BufferFactory = { size, initializer -> ShortBuffer(ShortArray(size, initializer)) } val IntBufferFactory: BufferFactory = { size, initializer -> IntBuffer(IntArray(size, initializer)) } val LongBufferFactory: BufferFactory = { size, initializer -> LongBuffer(LongArray(size, initializer)) } } @@ -71,6 +73,7 @@ interface MutableBuffer : Buffer { inline fun auto(size: Int, initializer: (Int) -> T): MutableBuffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as MutableBuffer + Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as MutableBuffer Int::class -> IntBuffer(IntArray(size) { initializer(it) as Int }) as MutableBuffer Long::class -> LongBuffer(LongArray(size) { initializer(it) as Long }) as MutableBuffer else -> boxing(size, initializer) @@ -136,6 +139,20 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer = DoubleBuffer(array.copyOf()) } +inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { + override val size: Int get() = array.size + + override fun get(index: Int): Short = array[index] + + override fun set(index: Int, value: Short) { + array[index] = value + } + + override fun iterator(): Iterator = array.iterator() + + override fun copy(): MutableBuffer = ShortBuffer(array.copyOf()) +} + inline class IntBuffer(private val array: IntArray) : MutableBuffer { override val size: Int get() = array.size diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 7648efef8..0a8f1de88 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -13,36 +13,36 @@ interface ExtendedNDField, N : NDStructure> : ExponentialOperations -/** - * NDField that supports [ExtendedField] operations on its elements - */ -class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : - ExtendedNDField, NDField by ndField { - - override val shape: IntArray get() = ndField.shape - override val elementField: F get() = ndField.elementField - - override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer) - - override fun power(arg: N, pow: Double): N { - return produce { with(elementField) { power(arg[it], pow) } } - } - - override fun exp(arg: N): N { - return produce { with(elementField) { exp(arg[it]) } } - } - - override fun ln(arg: N): N { - return produce { with(elementField) { ln(arg[it]) } } - } - - override fun sin(arg: N): N { - return produce { with(elementField) { sin(arg[it]) } } - } - - override fun cos(arg: N): N { - return produce { with(elementField) { cos(arg[it]) } } - } -} +///** +// * NDField that supports [ExtendedField] operations on its elements +// */ +//class ExtendedNDFieldWrapper, N : NDStructure>(private val ndField: NDField) : +// ExtendedNDField, NDField by ndField { +// +// override val shape: IntArray get() = ndField.shape +// override val elementContext: F get() = ndField.elementContext +// +// override fun produce(initializer: F.(IntArray) -> T) = ndField.produce(initializer) +// +// override fun power(arg: N, pow: Double): N { +// return produce { with(elementContext) { power(arg[it], pow) } } +// } +// +// override fun exp(arg: N): N { +// return produce { with(elementContext) { exp(arg[it]) } } +// } +// +// override fun ln(arg: N): N { +// return produce { with(elementContext) { ln(arg[it]) } } +// } +// +// override fun sin(arg: N): N { +// return produce { with(elementContext) { sin(arg[it]) } } +// } +// +// override fun cos(arg: N): N { +// return produce { with(elementContext) { cos(arg[it]) } } +// } +//} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt similarity index 68% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 00f19ec7e..4a560107f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -1,14 +1,62 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.AbstractField import scientifik.kmath.operations.Field -import scientifik.kmath.structures.Buffer.Companion.boxing +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space + /** * An exception is thrown when the expected ans actual shape of NDArray differs */ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() + +interface NDSpace, N : NDStructure> : Space { + val shape: IntArray + val elementContext: S + + /** + * Produce a new [N] structure using given initializer function + */ + fun produce(initializer: S.(IntArray) -> T): N + + fun map(arg: N, transform: S.(T) -> T): N + fun mapIndexed(arg: N, transform: S.(index: IntArray, T) -> T): N + fun combine(a: N, b: N, transform: S.(T, T) -> T): N + + /** + * Element-by-element addition + */ + override fun add(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue + bValue } + + /** + * Multiply all elements by constant + */ + override fun multiply(a: N, k: Double): N = + map(a) { it * k } + + operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } + operator fun N.plus(arg: T) = map(this) { value -> elementContext.run { arg + value } } + operator fun N.minus(arg: T) = map(this) { value -> elementContext.run { arg - value } } + + operator fun T.plus(arg: N) = arg + this + operator fun T.minus(arg: N) = arg - this + +} + +interface NDRing, N : NDStructure> : Ring, NDSpace { + + /** + * Element-by-element multiplication + */ + override fun multiply(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue * bValue } + + operator fun N.times(arg: T) = map(this) { value -> elementContext.run { arg * value } } + operator fun T.times(arg: N) = arg * this +} + /** * Field for n-dimensional arrays. * @param shape - the list of dimensions of the array @@ -17,21 +65,31 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run * @param F - field over structure elements * @param R - actual nd-element type of this field */ -interface NDField, N : NDStructure> : Field { +interface NDField, N : NDStructure> : Field, NDRing { - val shape: IntArray - val elementField: F + /** + * Element-by-element division + */ + override fun divide(a: N, b: N): N = + combine(a, b) { aValue, bValue -> aValue / bValue } - fun produce(initializer: F.(IntArray) -> T): N - fun map(arg: N, transform: F.(T) -> T): N - fun mapIndexed(arg: N, transform: F.(index: IntArray, T) -> T): N - fun combine(a: N, b: N, transform: F.(T, T) -> T): N + operator fun N.div(arg: T) = map(this) { value -> elementContext.run { arg / value } } + operator fun T.div(arg: N) = arg / this + + fun check(vararg elements: N) { + elements.forEach { + if (!shape.contentEquals(it.shape)) { + throw ShapeMismatchException(shape, it.shape) + } + } + } companion object { /** * Create a nd-field for [Double] values */ fun real(shape: IntArray) = RealNDField(shape) + /** * Create a nd-field with boxing generic buffer */ @@ -49,61 +107,3 @@ interface NDField, N : NDStructure> : Field { } } } - - -abstract class AbstractNDField, N : NDStructure>( - override val shape: IntArray, - override val elementField: F -) : AbstractField(), NDField { - override val zero: N by lazy { produce { zero } } - - override val one: N by lazy { produce { one } } - - operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } - operator fun N.plus(arg: T) = map(this) { value -> elementField.run { arg + value } } - operator fun N.minus(arg: T) = map(this) { value -> elementField.run { arg - value } } - operator fun N.times(arg: T) = map(this) { value -> elementField.run { arg * value } } - operator fun N.div(arg: T) = map(this) { value -> elementField.run { arg / value } } - - operator fun T.plus(arg: N) = arg + this - operator fun T.minus(arg: N) = arg - this - operator fun T.times(arg: N) = arg * this - operator fun T.div(arg: N) = arg / this - - - /** - * Element-by-element addition - */ - override fun add(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue + bValue } - - /** - * Multiply all elements by cinstant - */ - override fun multiply(a: N, k: Double): N = - map(a) { it * k } - - - /** - * Element-by-element multiplication - */ - override fun multiply(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue * bValue } - - /** - * Element-by-element division - */ - override fun divide(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue / bValue } - - /** - * Check if given objects are compatible with this context. Throw exception if they are not - */ - open fun check(vararg elements: N) { - elements.forEach { - if (!shape.contentEquals(it.shape)) { - throw ShapeMismatchException(shape, it.shape) - } - } - } -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 18ada3449..1f4bddf7b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -3,13 +3,14 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Space -interface NDElement> : NDStructure { - val elementField: F +interface NDElement> : NDStructure { + val elementField: S - fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement - fun map(action: F.(T) -> T) = mapIndexed { _, value -> action(value) } + fun mapIndexed(transform: S.(index: IntArray, T) -> T): NDElement + fun map(action: S.(T) -> T) = mapIndexed { _, value -> action(value) } companion object { /** @@ -37,7 +38,7 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, initializer: F.(IntArray) -> T - ): StridedNDElement { + ): StridedNDFieldElement { val ndField = BufferNDField(shape, field, Buffer.Companion::boxing) return ndField.produce(initializer) } @@ -46,9 +47,9 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T - ): StridedNDElement { + ): StridedNDFieldElement { val ndField = NDField.auto(shape, field) - return StridedNDElement(ndField, ndField.produce(initializer).buffer) + return StridedNDFieldElement(ndField, ndField.produce(initializer).buffer) } } } @@ -121,19 +122,19 @@ operator fun > NDElement.div(arg: T): NDElement = /** * Read-only [NDStructure] coupled to the context. */ -class GenericNDElement>( +class GenericNDFieldElement>( override val context: NDField>, private val structure: NDStructure ) : NDStructure by structure, NDElement, - FieldElement, GenericNDElement, NDField>> { - override val elementField: F get() = context.elementField + FieldElement, GenericNDFieldElement, NDField>> { + override val elementField: F get() = context.elementContext override fun unwrap(): NDStructure = structure - override fun NDStructure.wrap() = GenericNDElement(context, this) + override fun NDStructure.wrap() = GenericNDFieldElement(context, this) override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - ndStructure(context.shape) { index: IntArray -> context.elementField.transform(index, get(index)) }.wrap() + ndStructure(context.shape) { index: IntArray -> context.elementContext.transform(index, get(index)) }.wrap() } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 74e3a59a2..3c422797c 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -240,19 +240,4 @@ inline fun NDStructure.combine( ): NDStructure { if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination") return inlineNdStructure(shape) { block(this[it], struct[it]) } -} - - -///** -// * 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 = MutableListBuffer(sequence.toMutableList()) -// return MutableBufferNDStructure(strides, buffer) -//} +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 9b3e306d5..934583002 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -2,15 +2,19 @@ package scientifik.kmath.structures import scientifik.kmath.operations.RealField -typealias RealNDElement = StridedNDElement +typealias RealNDElement = StridedNDFieldElement class RealNDField(shape: IntArray) : - StridedNDField(shape, RealField), + StridedNDField(shape), ExtendedNDField> { + override val elementContext: RealField get() = RealField + override val zero by lazy { produce { zero } } + override val one by lazy { produce { one } } + @Suppress("OVERRIDE_BY_INLINE") override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = - DoubleBuffer(DoubleArray(size){initializer(it)}) + DoubleBuffer(DoubleArray(size) { initializer(it) }) /** * Inline transform an NDStructure to @@ -21,23 +25,23 @@ class RealNDField(shape: IntArray) : ): RealNDElement { check(arg) val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } - return StridedNDElement(this, array) + return StridedNDFieldElement(this, array) } override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { - val array = buildBuffer(strides.linearSize) { offset -> elementField.initializer(strides.index(offset)) } - return StridedNDElement(this, array) + val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } + return StridedNDFieldElement(this, array) } override fun mapIndexed( arg: NDBuffer, transform: RealField.(index: IntArray, Double) -> Double - ): StridedNDElement { + ): StridedNDFieldElement { check(arg) - return StridedNDElement( + return StridedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> - elementField.transform( + elementContext.transform( arg.strides.index(offset), arg.buffer[offset] ) @@ -48,11 +52,11 @@ class RealNDField(shape: IntArray) : a: NDBuffer, b: NDBuffer, transform: RealField.(Double, Double) -> Double - ): StridedNDElement { + ): StridedNDFieldElement { check(a, b) - return StridedNDElement( + return StridedNDFieldElement( this, - buildBuffer(strides.linearSize) { offset -> elementField.transform(a.buffer[offset], b.buffer[offset]) }) + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } override fun power(arg: NDBuffer, pow: Double) = map(arg) { power(it, pow) } @@ -64,14 +68,7 @@ class RealNDField(shape: IntArray) : override fun sin(arg: NDBuffer) = map(arg) { sin(it) } override fun cos(arg: NDBuffer) = map(arg) { cos(it) } -// -// override fun NDBuffer.times(k: Number) = mapInline { value -> value * k.toDouble() } -// -// override fun NDBuffer.div(k: Number) = mapInline { value -> value / k.toDouble() } -// -// override fun Number.times(b: NDBuffer) = b * this -// -// override fun Number.div(b: NDBuffer) = b * (1.0 / this.toDouble()) + } @@ -80,7 +77,7 @@ class RealNDField(shape: IntArray) : */ inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } - return StridedNDElement(this, DoubleBuffer(array)) + return StridedNDFieldElement(this, DoubleBuffer(array)) } /** @@ -93,13 +90,13 @@ operator fun Function1.invoke(ndElement: RealNDElement) = /* plus and minus */ /** - * Summation operation for [StridedNDElement] and single element + * Summation operation for [StridedNDFieldElement] and single element */ operator fun RealNDElement.plus(arg: Double) = context.produceInline { i -> buffer[i] + arg } /** - * Subtraction operation between [StridedNDElement] and single element + * Subtraction operation between [StridedNDFieldElement] and single element */ operator fun RealNDElement.minus(arg: Double) = context.produceInline { i -> buffer[i] - arg } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt new file mode 100644 index 000000000..e167516c8 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -0,0 +1,86 @@ +//package scientifik.kmath.structures +// +//import scientifik.kmath.operations.ShortRing +// +// +////typealias ShortNDElement = StridedNDFieldElement +// +//class ShortNDRing(shape: IntArray) : +// NDRing> { +// +// inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = +// ShortBuffer(ShortArray(size) { initializer(it) }) +// +// /** +// * Inline transform an NDStructure to +// */ +// override fun map( +// arg: NDBuffer, +// transform: ShortRing.(Short) -> Short +// ): ShortNDElement { +// check(arg) +// val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) } +// return StridedNDFieldElement(this, array) +// } +// +// override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement { +// val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } +// return StridedNDFieldElement(this, array) +// } +// +// override fun mapIndexed( +// arg: NDBuffer, +// transform: ShortRing.(index: IntArray, Short) -> Short +// ): StridedNDFieldElement { +// check(arg) +// return StridedNDFieldElement( +// this, +// buildBuffer(arg.strides.linearSize) { offset -> +// elementContext.transform( +// arg.strides.index(offset), +// arg.buffer[offset] +// ) +// }) +// } +// +// override fun combine( +// a: NDBuffer, +// b: NDBuffer, +// transform: ShortRing.(Short, Short) -> Short +// ): StridedNDFieldElement { +// check(a, b) +// return StridedNDFieldElement( +// this, +// buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) +// } +//} +// +// +///** +// * Fast element production using function inlining +// */ +//inline fun StridedNDField.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { +// val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) } +// return StridedNDFieldElement(this, ShortBuffer(array)) +//} +// +///** +// * Element by element application of any operation on elements to the whole array. Just like in numpy +// */ +//operator fun Function1.invoke(ndElement: ShortNDElement) = +// ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } +// +// +///* plus and minus */ +// +///** +// * Summation operation for [StridedNDFieldElement] and single element +// */ +//operator fun ShortNDElement.plus(arg: Short) = +// context.produceInline { i -> buffer[i] + arg } +// +///** +// * Subtraction operation between [StridedNDFieldElement] and single element +// */ +//operator fun ShortNDElement.minus(arg: Short) = +// context.produceInline { i -> buffer[i] - arg } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt new file mode 100644 index 000000000..653f56013 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt @@ -0,0 +1,97 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement + +abstract class StridedNDField>(final override val shape: IntArray) : NDField> { + val strides = DefaultStrides(shape) + + abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer + + /** + * Convert any [NDStructure] to buffered structure using strides from this context. + * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes + * + * If the argument is [NDBuffer] with different strides structure, the new element will be produced. + */ + fun NDStructure.toBuffer(): NDBuffer { + return if (this is NDBuffer && this.strides == this@StridedNDField.strides) { + this + } else { + produce { index -> get(index) } + } + } + + fun NDBuffer.toElement(): StridedNDFieldElement = + StridedNDFieldElement(this@StridedNDField, buffer) +} + +class StridedNDFieldElement>( + override val context: StridedNDField, + override val buffer: Buffer +) : + NDBuffer, + FieldElement, StridedNDFieldElement, StridedNDField>, + NDElement { + + override val elementField: F + get() = context.elementContext + + override fun unwrap(): NDBuffer = + this + + override fun NDBuffer.wrap(): StridedNDFieldElement = + StridedNDFieldElement(context, this.buffer) + + override val strides + get() = context.strides + + override val shape: IntArray + get() = context.shape + + override fun get(index: IntArray): T = + buffer[strides.offset(index)] + + override fun elements(): Sequence> = + strides.indices().map { it to get(it) } + + override fun map(action: F.(T) -> T) = + context.run { map(this@StridedNDFieldElement, action) }.wrap() + + override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = + context.run { mapIndexed(this@StridedNDFieldElement, transform) }.wrap() +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: StridedNDFieldElement) = + ndElement.context.run { ndElement.map { invoke(it) } } + +/* plus and minus */ + +/** + * Summation operation for [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.plus(arg: T) = + context.run { map { it + arg } } + +/** + * Subtraction operation between [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.minus(arg: T) = + context.run { map { it - arg } } + +/* prod and div */ + +/** + * Product operation for [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.times(arg: T) = + context.run { map { it * arg } } + +/** + * Division operation between [StridedNDFieldElement] and single element + */ +operator fun > StridedNDFieldElement.div(arg: T) = + context.run { map { it / arg } } \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index 451ef9fdd..7840586ef 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -4,15 +4,19 @@ import kotlinx.coroutines.* import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement -class LazyNDField>(shape: IntArray, field: F, val scope: CoroutineScope = GlobalScope) : - AbstractNDField>(shape, field) { +class LazyNDField>( + override val shape: IntArray, + override val elementContext: F, + val scope: CoroutineScope = GlobalScope +) : + NDField> { override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } override fun produce(initializer: F.(IntArray) -> T) = - LazyNDStructure(this) { elementField.initializer(it) } + LazyNDStructure(this) { elementContext.initializer(it) } override fun mapIndexed( arg: NDStructure, @@ -22,10 +26,10 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti return if (arg is LazyNDStructure) { LazyNDStructure(this) { index -> //FIXME if value of arg is already calculated, it should be used - elementField.transform(index, arg.function(index)) + elementContext.transform(index, arg.function(index)) } } else { - LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } + LazyNDStructure(this) { elementContext.transform(it, arg.await(it)) } } // return LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } } @@ -37,13 +41,13 @@ class LazyNDField>(shape: IntArray, field: F, val scope: Corouti check(a, b) return if (a is LazyNDStructure && b is LazyNDStructure) { LazyNDStructure(this@LazyNDField) { index -> - elementField.transform( + elementContext.transform( a.function(index), b.function(index) ) } } else { - LazyNDStructure(this@LazyNDField) { elementField.transform(a.await(it), b.await(it)) } + LazyNDStructure(this@LazyNDField) { elementContext.transform(a.await(it), b.await(it)) } } // return LazyNDStructure(this) { elementField.transform(a.await(it), b.await(it)) } } @@ -69,7 +73,7 @@ class LazyNDStructure>( override fun NDStructure.wrap(): LazyNDStructure = LazyNDStructure(context) { await(it) } override val shape: IntArray get() = context.shape - override val elementField: F get() = context.elementField + override val elementField: F get() = context.elementContext override fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement = context.run { mapIndexed(this@LazyNDStructure, transform) } diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt index d5f43d3eb..ea3cb9494 100644 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -1,20 +1,16 @@ 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 = NDField.auto(intArrayOf(2, 2, 2), IntField).produce { it[0] + it[1] - it[2] } - val result = (regularStructure.lazy(IntField) + 2).map { - counter++ - it * it - } - assertEquals(4, result[0, 0, 0]) - assertEquals(1, counter) - } +// @Test +// fun testLazyStructure() { +// var counter = 0 +// val regularStructure = NDField.auto(intArrayOf(2, 2, 2), IntRing).produce { it[0] + it[1] - it[2] } +// val result = (regularStructure.lazy(IntRing) + 2).map { +// counter++ +// it * it +// } +// assertEquals(4, result[0, 0, 0]) +// assertEquals(1, counter) +// } } \ No newline at end of file From 876a363e0ba1a7b653e96d6216d473a0d4e83a9f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jan 2019 19:10:08 +0300 Subject: [PATCH 28/70] Cleanup after nd optimization phase II --- .../kmath/structures/NDFieldBenchmark.kt | 2 +- .../kmath/structures/NDFieldBenchmark.kt | 96 ++++----- .../scientifik/kmath/operations/Algebra.kt | 4 +- .../kmath/operations/AlgebraElements.kt | 23 ++- .../kmath/operations/NumberAlgebra.kt | 6 +- .../{BufferNDField.kt => BoxingNDField.kt} | 31 +-- .../kmath/structures/BufferedNDAlgebra.kt | 45 +++++ .../kmath/structures/BufferedNDElement.kt | 88 +++++++++ .../scientifik/kmath/structures/Buffers.kt | 8 +- .../scientifik/kmath/structures/NDAlgebra.kt | 93 ++++++--- .../scientifik/kmath/structures/NDElement.kt | 73 +++---- .../kmath/structures/RealNDField.kt | 32 +-- .../kmath/structures/ShortNDRing.kt | 182 +++++++++--------- .../kmath/structures/StridedContext.kt | 97 ---------- .../kmath/structures/ComplexBufferSpec.kt | 2 +- .../kmath/structures/LazyNDField.kt | 10 +- 16 files changed, 433 insertions(+), 359 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/{BufferNDField.kt => BoxingNDField.kt} (63%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 421b5fb6c..ad180b727 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -17,7 +17,7 @@ open class NDFieldBenchmark { @Benchmark fun autoElementAdd() { - var res = bufferedField.run { one.toElement() } + var res = genericField.one repeat(n) { res += 1.0 } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index a0cbd66ca..f4ffccd14 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -16,54 +16,54 @@ fun main(args: Array) { //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) - - val autoTime = measureTimeMillis { - autoField.run { - var res = one - repeat(n) { - res += 1.0 - } - } - } - - println("Buffered addition completed in $autoTime millis") - - val elementTime = measureTimeMillis { - var res = genericField.one - repeat(n) { - res += 1.0 - } - } - - println("Element addition completed in $elementTime millis") - - val specializedTime = measureTimeMillis { - specializedField.run { - var res:NDBuffer = one - repeat(n) { - res += 1.0 - } - } - } - - println("Specialized addition completed in $specializedTime millis") - - - val lazyTime = measureTimeMillis { - lazyField.run { - val res = one.map { - var c = 0.0 - repeat(n) { - c += 1.0 - } - c - } - - res.elements().forEach { it.second } - } - } - - println("Lazy addition completed in $lazyTime millis") +// +// val autoTime = measureTimeMillis { +// autoField.run { +// var res = one +// repeat(n) { +// res += 1.0 +// } +// } +// } +// +// println("Buffered addition completed in $autoTime millis") +// +// val elementTime = measureTimeMillis { +// var res = genericField.one +// repeat(n) { +// res += 1.0 +// } +// } +// +// println("Element addition completed in $elementTime millis") +// +// val specializedTime = measureTimeMillis { +// specializedField.run { +// var res:NDBuffer = one +// repeat(n) { +// res += 1.0 +// } +// } +// } +// +// println("Specialized addition completed in $specializedTime millis") +// +// +// val lazyTime = measureTimeMillis { +// lazyField.run { +// val res = one.map { +// var c = 0.0 +// repeat(n) { +// c += 1.0 +// } +// c +// } +// +// res.elements().forEach { it.second } +// } +// } +// +// println("Lazy addition completed in $lazyTime millis") val genericTime = measureTimeMillis { //genericField.run(action) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 771c86389..d77df4b7f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -33,9 +33,9 @@ interface Space { operator fun T.times(k: Number) = multiply(this, k.toDouble()) operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun Number.times(b: T) = b * this - fun Iterable.sum(): T = fold(zero) { left, right -> left + right } + fun Iterable.sum(): T = fold(zero) { left, right -> add(left,right) } - fun Sequence.sum(): T = fold(zero) { left, right -> left + right } + fun Sequence.sum(): T = fold(zero) { left, right -> add(left, right) } } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt index 4e5854453..093021ae3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraElements.kt @@ -2,13 +2,20 @@ package scientifik.kmath.operations /** * The generic mathematics elements which is able to store its context - * @param S the type of mathematical context for this element + * @param T the type of space operation results + * @param I self type of the element. Needed for static type checking + * @param C the type of mathematical context for this element */ -interface MathElement { +interface MathElement { /** * The context this element belongs to */ - val context: S + val context: C +} + +interface MathWrapper { + fun unwrap(): T + fun T.wrap(): I } /** @@ -17,13 +24,7 @@ interface MathElement { * @param I self type of the element. Needed for static type checking * @param S the type of space */ -interface SpaceElement, S : Space> : MathElement { - /** - * Self value. Needed for static type checking. - */ - fun unwrap(): T - - fun T.wrap(): I +interface SpaceElement, S : Space> : MathElement, MathWrapper { operator fun plus(b: T) = context.add(unwrap(), b).wrap() operator fun minus(b: T) = context.add(unwrap(), context.multiply(b, -1.0)).wrap() @@ -35,7 +36,6 @@ interface SpaceElement, S : Space> : MathElement * Ring element */ interface RingElement, R : Ring> : SpaceElement { - override val context: R operator fun times(b: T) = context.multiply(unwrap(), b).wrap() } @@ -44,6 +44,5 @@ interface RingElement, R : Ring> : SpaceElement, F : Field> : RingElement { override val context: F - operator fun div(b: T) = context.divide(unwrap(), b).wrap() } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index fa89cd793..2dc6d3228 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -32,6 +32,7 @@ inline class Real(val value: Double) : FieldElement { /** * A field for double without boxing. Does not produce appropriate field element */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") object RealField : ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b @@ -45,10 +46,13 @@ object RealField : ExtendedField, Norm { override fun power(arg: Double, pow: Double): Double = arg.pow(pow) override fun exp(arg: Double): Double = kotlin.math.exp(arg) - override fun ln(arg: Double): Double = kotlin.math.ln(arg) override fun norm(arg: Double): Double = kotlin.math.abs(arg) + + override fun Double.unaryMinus(): Double = -this + + override fun Double.minus(b: Double): Double = this - b } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt similarity index 63% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt index 752663712..e06119766 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt @@ -1,15 +1,19 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field +import scientifik.kmath.operations.FieldElement -class BufferNDField>( - shape: IntArray, +class BoxingNDField>( + override val shape: IntArray, override val elementContext: F, val bufferFactory: BufferFactory -) : StridedNDField(shape), NDField> { +) : BufferedNDField { - override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) + override val strides: Strides = DefaultStrides(shape) + + override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = + bufferFactory(size, initializer) override fun check(vararg elements: NDBuffer) { if (!elements.all { it.strides == this.strides }) error("Element strides are not the same as context strides") @@ -18,14 +22,14 @@ class BufferNDField>( override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - override fun produce(initializer: F.(IntArray) -> T): StridedNDFieldElement = - StridedNDFieldElement( + override fun produce(initializer: F.(IntArray) -> T) = + BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }) - override fun map(arg: NDBuffer, transform: F.(T) -> T): StridedNDFieldElement { + override fun map(arg: NDBuffer, transform: F.(T) -> T): BufferedNDFieldElement { check(arg) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) } @@ -33,9 +37,9 @@ class BufferNDField>( override fun mapIndexed( arg: NDBuffer, transform: F.(index: IntArray, T) -> T - ): StridedNDFieldElement { + ): BufferedNDFieldElement { check(arg) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform( @@ -49,10 +53,13 @@ class BufferNDField>( a: NDBuffer, b: NDBuffer, transform: F.(T, T) -> T - ): StridedNDFieldElement { + ): BufferedNDFieldElement { check(a, b) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } + + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + BufferedNDFieldElement(this@BoxingNDField, buffer) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt new file mode 100644 index 000000000..03b5407f5 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt @@ -0,0 +1,45 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.* + +interface BufferedNDAlgebra: NDAlgebra>{ + val strides: Strides + + fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer + + override fun check(vararg elements: NDBuffer) { + if (!elements.all { it.strides == this.strides }) error("Strides mismatch") + } + + /** + * Convert any [NDStructure] to buffered structure using strides from this context. + * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes + * + * If the argument is [NDBuffer] with different strides structure, the new element will be produced. + */ + fun NDStructure.toBuffer(): NDBuffer { + return if (this is NDBuffer && this.strides == this@BufferedNDAlgebra.strides) { + this + } else { + produce { index -> get(index) } + } + } + + /** + * Convert a buffer to element of this algebra + */ + fun NDBuffer.toElement(): MathElement> +} + + +interface BufferedNDSpace> : NDSpace>, BufferedNDAlgebra { + override fun NDBuffer.toElement(): SpaceElement, *, out BufferedNDSpace> +} + +interface BufferedNDRing> : NDRing>, BufferedNDSpace { + override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> +} + +interface BufferedNDField> : NDField>, BufferedNDRing { + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt new file mode 100644 index 000000000..04049368a --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDElement.kt @@ -0,0 +1,88 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.* + +/** + * Base interface for an element with context, containing strides + */ +interface BufferedNDElement : NDBuffer, NDElement> { + override val context: BufferedNDAlgebra + + override val strides get() = context.strides + + override val shape: IntArray get() = context.shape +} + +class BufferedNDSpaceElement>( + override val context: BufferedNDSpace, + override val buffer: Buffer +) : BufferedNDElement, SpaceElement, BufferedNDSpaceElement, BufferedNDSpace> { + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferedNDSpaceElement { + context.check(this) + return BufferedNDSpaceElement(context, buffer) + } +} + +class BufferedNDRingElement>( + override val context: BufferedNDRing, + override val buffer: Buffer +) : BufferedNDElement, RingElement, BufferedNDRingElement, BufferedNDRing> { + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferedNDRingElement { + context.check(this) + return BufferedNDRingElement(context, buffer) + } +} + +class BufferedNDFieldElement>( + override val context: BufferedNDField, + override val buffer: Buffer +) : BufferedNDElement, FieldElement, BufferedNDFieldElement, BufferedNDField> { + + override fun unwrap(): NDBuffer = this + + override fun NDBuffer.wrap(): BufferedNDFieldElement { + context.check(this) + return BufferedNDFieldElement(context, buffer) + } +} + + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun > Function1.invoke(ndElement: BufferedNDElement) = + ndElement.context.run { map(ndElement) { invoke(it) }.toElement() } + +/* plus and minus */ + +/** + * Summation operation for [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.plus(arg: T) = + context.map(this) { it + arg }.wrap() + +/** + * Subtraction operation between [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.minus(arg: T) = + context.map(this) { it - arg }.wrap() + +/* prod and div */ + +/** + * Product operation for [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.times(arg: T) = + context.map(this) { it * arg }.wrap() + +/** + * Division operation between [BufferedNDElement] and single element + */ +operator fun > BufferedNDElement.div(arg: T) = + context.map(this) { it / arg }.wrap() \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 8c112bb87..f7c04bdbe 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -30,7 +30,7 @@ interface Buffer { * Create most appropriate immutable buffer for given type avoiding boxing wherever possible */ @Suppress("UNCHECKED_CAST") - inline fun auto(size: Int, initializer: (Int) -> T): Buffer { + inline fun auto(size: Int, crossinline initializer: (Int) -> T): Buffer { return when (T::class) { Double::class -> DoubleBuffer(DoubleArray(size) { initializer(it) as Double }) as Buffer Short::class -> ShortBuffer(ShortArray(size) { initializer(it) as Short }) as Buffer @@ -40,8 +40,10 @@ interface Buffer { } } - val DoubleBufferFactory: BufferFactory = { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } - val ShortBufferFactory: BufferFactory = { size, initializer -> ShortBuffer(ShortArray(size, initializer)) } + val DoubleBufferFactory: BufferFactory = + { size, initializer -> DoubleBuffer(DoubleArray(size, initializer)) } + val ShortBufferFactory: BufferFactory = + { size, initializer -> ShortBuffer(ShortArray(size, initializer)) } val IntBufferFactory: BufferFactory = { size, initializer -> IntBuffer(IntArray(size, initializer)) } val LongBufferFactory: BufferFactory = { size, initializer -> LongBuffer(LongArray(size, initializer)) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 4a560107f..d8da8c937 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -11,40 +11,79 @@ import scientifik.kmath.operations.Space class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() -interface NDSpace, N : NDStructure> : Space { +/** + * The base interface for all nd-algebra implementations + * @param T the type of nd-structure element + * @param C the type of the context + * @param N the type of the structure + */ +interface NDAlgebra> { val shape: IntArray - val elementContext: S + val elementContext: C /** * Produce a new [N] structure using given initializer function */ - fun produce(initializer: S.(IntArray) -> T): N + fun produce(initializer: C.(IntArray) -> T): N - fun map(arg: N, transform: S.(T) -> T): N - fun mapIndexed(arg: N, transform: S.(index: IntArray, T) -> T): N - fun combine(a: N, b: N, transform: S.(T, T) -> T): N + /** + * Map elements from one structure to another one + */ + fun map(arg: N, transform: C.(T) -> T): N + /** + * Map indexed elements + */ + fun mapIndexed(arg: N, transform: C.(index: IntArray, T) -> T): N + + /** + * Combine two structures into one + */ + fun combine(a: N, b: N, transform: C.(T, T) -> T): N + + /** + * Check if given elements are consistent with this context + */ + fun check(vararg elements: N) { + elements.forEach { + if (!shape.contentEquals(it.shape)) { + throw ShapeMismatchException(shape, it.shape) + } + } + } + + /** + * element-by-element invoke a function working on [T] on a [NDStructure] + */ + operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } +} + +/** + * An nd-space over element space + */ +interface NDSpace, N : NDStructure> : Space, NDAlgebra { /** * Element-by-element addition */ override fun add(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue + bValue } + combine(a, b) { aValue, bValue -> add(aValue, bValue) } /** * Multiply all elements by constant */ override fun multiply(a: N, k: Double): N = - map(a) { it * k } + map(a) { multiply(it, k) } - operator fun Function1.invoke(structure: N) = map(structure) { value -> this@invoke(value) } - operator fun N.plus(arg: T) = map(this) { value -> elementContext.run { arg + value } } - operator fun N.minus(arg: T) = map(this) { value -> elementContext.run { arg - value } } - - operator fun T.plus(arg: N) = arg + this - operator fun T.minus(arg: N) = arg - this + operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) } + operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) } + operator fun T.plus(arg: N) = map(arg) { value -> add(this@plus, value) } + operator fun T.minus(arg: N) = map(arg) { value -> add(-this@minus, value) } } +/** + * An nd-ring over element ring + */ interface NDRing, N : NDStructure> : Ring, NDSpace { /** @@ -53,16 +92,16 @@ interface NDRing, N : NDStructure> : Ring, NDSpace override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> aValue * bValue } - operator fun N.times(arg: T) = map(this) { value -> elementContext.run { arg * value } } - operator fun T.times(arg: N) = arg * this + operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) } + operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) } } /** - * Field for n-dimensional arrays. + * Field for n-dimensional structures. * @param shape - the list of dimensions of the array * @param elementField - operations field defined on individual array element * @param T - the type of the element contained in ND structure - * @param F - field over structure elements + * @param F - field of structure elements * @param R - actual nd-element type of this field */ interface NDField, N : NDStructure> : Field, NDRing { @@ -73,17 +112,9 @@ interface NDField, N : NDStructure> : Field, NDRing aValue / bValue } - operator fun N.div(arg: T) = map(this) { value -> elementContext.run { arg / value } } + operator fun N.div(arg: T) = map(this) { value -> arg / value } operator fun T.div(arg: N) = arg / this - fun check(vararg elements: N) { - elements.forEach { - if (!shape.contentEquals(it.shape)) { - throw ShapeMismatchException(shape, it.shape) - } - } - } - companion object { /** * Create a nd-field for [Double] values @@ -94,16 +125,16 @@ interface NDField, N : NDStructure> : Field, NDRing> buffered(shape: IntArray, field: F) = - BufferNDField(shape, field, Buffer.Companion::boxing) + BoxingNDField(shape, field, Buffer.Companion::boxing) /** * Create a most suitable implementation for nd-field using reified class. */ @Suppress("UNCHECKED_CAST") - inline fun > auto(shape: IntArray, field: F): StridedNDField = + inline fun > auto(shape: IntArray, field: F): BufferedNDField = when { - T::class == Double::class -> real(shape) as StridedNDField - else -> BufferNDField(shape, field, Buffer.Companion::auto) + T::class == Double::class -> real(shape) as BufferedNDField + else -> BoxingNDField(shape, field, Buffer.Companion::auto) } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 1f4bddf7b..6a09f1cb1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -1,16 +1,21 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field -import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space -interface NDElement> : NDStructure { - val elementField: S +interface NDElement> : NDStructure { - fun mapIndexed(transform: S.(index: IntArray, T) -> T): NDElement - fun map(action: S.(T) -> T) = mapIndexed { _, value -> action(value) } + val context: NDAlgebra + + fun unwrap(): N + + fun N.wrap(): NDElement + + fun mapIndexed(transform: C.(index: IntArray, T) -> T) = context.mapIndexed(unwrap(), transform).wrap() + fun map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap() companion object { /** @@ -38,8 +43,8 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, initializer: F.(IntArray) -> T - ): StridedNDFieldElement { - val ndField = BufferNDField(shape, field, Buffer.Companion::boxing) + ): BufferedNDElement { + val ndField = BoxingNDField(shape, field, Buffer.Companion::boxing) return ndField.produce(initializer) } @@ -47,47 +52,46 @@ interface NDElement> : NDStructure { shape: IntArray, field: F, noinline initializer: F.(IntArray) -> T - ): StridedNDFieldElement { + ): BufferedNDFieldElement { val ndField = NDField.auto(shape, field) - return StridedNDFieldElement(ndField, ndField.produce(initializer).buffer) + return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer) } } } - /** - * 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 [NDElement] */ -operator fun > Function1.invoke(ndElement: NDElement) = +operator fun Function1.invoke(ndElement: NDElement) = ndElement.map { value -> this@invoke(value) } /* plus and minus */ /** - * Summation operation for [NDElements] and single element + * Summation operation for [NDElement] and single element */ -operator fun > NDElement.plus(arg: T): NDElement = - this.map { value -> elementField.run { arg + value } } +operator fun > NDElement.plus(arg: T) = + map { value -> arg + value } /** - * Subtraction operation between [NDElements] and single element + * Subtraction operation between [NDElement] and single element */ -operator fun > NDElement.minus(arg: T): NDElement = - this.map { value -> elementField.run { arg - value } } +operator fun > NDElement.minus(arg: T) = + map { value -> arg - value } /* prod and div */ /** - * Product operation for [NDElements] and single element + * Product operation for [NDElement] and single element */ -operator fun > NDElement.times(arg: T): NDElement = - this.map { value -> elementField.run { arg * value } } +operator fun > NDElement.times(arg: T) = + map { value -> arg * value } /** - * Division operation between [NDElements] and single element + * Division operation between [NDElement] and single element */ -operator fun > NDElement.div(arg: T): NDElement = - this.map { value -> elementField.run { arg / value } } +operator fun > NDElement.div(arg: T) = + map { value -> arg / value } // /** @@ -117,24 +121,3 @@ operator fun > NDElement.div(arg: T): NDElement = // operator fun T.div(arg: NDStructure): NDElement = produce { index -> // field.run { this@div / arg[index] } // } - - -/** - * Read-only [NDStructure] coupled to the context. - */ -class GenericNDFieldElement>( - override val context: NDField>, - private val structure: NDStructure -) : - NDStructure by structure, - NDElement, - FieldElement, GenericNDFieldElement, NDField>> { - override val elementField: F get() = context.elementContext - - override fun unwrap(): NDStructure = structure - - override fun NDStructure.wrap() = GenericNDFieldElement(context, this) - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - ndStructure(context.shape) { index: IntArray -> context.elementContext.transform(index, get(index)) }.wrap() -} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 934583002..62c2338c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -1,13 +1,16 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.FieldElement import scientifik.kmath.operations.RealField -typealias RealNDElement = StridedNDFieldElement +typealias RealNDElement = BufferedNDFieldElement -class RealNDField(shape: IntArray) : - StridedNDField(shape), +class RealNDField(override val shape: IntArray) : + BufferedNDField, ExtendedNDField> { + override val strides: Strides = DefaultStrides(shape) + override val elementContext: RealField get() = RealField override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } @@ -25,20 +28,20 @@ class RealNDField(shape: IntArray) : ): RealNDElement { check(arg) val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) } - return StridedNDFieldElement(this, array) + return BufferedNDFieldElement(this, array) } override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement { val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } - return StridedNDFieldElement(this, array) + return BufferedNDFieldElement(this, array) } override fun mapIndexed( arg: NDBuffer, transform: RealField.(index: IntArray, Double) -> Double - ): StridedNDFieldElement { + ): RealNDElement { check(arg) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform( @@ -52,13 +55,16 @@ class RealNDField(shape: IntArray) : a: NDBuffer, b: NDBuffer, transform: RealField.(Double, Double) -> Double - ): StridedNDFieldElement { + ): RealNDElement { check(a, b) - return StridedNDFieldElement( + return BufferedNDFieldElement( this, buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) } + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + BufferedNDFieldElement(this@RealNDField, buffer) + override fun power(arg: NDBuffer, pow: Double) = map(arg) { power(it, pow) } override fun exp(arg: NDBuffer) = map(arg) { exp(it) } @@ -75,9 +81,9 @@ class RealNDField(shape: IntArray) : /** * Fast element production using function inlining */ -inline fun StridedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { +inline fun BufferedNDField.produceInline(crossinline initializer: RealField.(Int) -> Double): RealNDElement { val array = DoubleArray(strides.linearSize) { offset -> RealField.initializer(offset) } - return StridedNDFieldElement(this, DoubleBuffer(array)) + return BufferedNDFieldElement(this, DoubleBuffer(array)) } /** @@ -90,13 +96,13 @@ operator fun Function1.invoke(ndElement: RealNDElement) = /* plus and minus */ /** - * Summation operation for [StridedNDFieldElement] and single element + * Summation operation for [BufferedNDElement] and single element */ operator fun RealNDElement.plus(arg: Double) = context.produceInline { i -> buffer[i] + arg } /** - * Subtraction operation between [StridedNDFieldElement] and single element + * Subtraction operation between [BufferedNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = context.produceInline { i -> buffer[i] - arg } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt index e167516c8..5c887f343 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -1,86 +1,96 @@ -//package scientifik.kmath.structures -// -//import scientifik.kmath.operations.ShortRing -// -// -////typealias ShortNDElement = StridedNDFieldElement -// -//class ShortNDRing(shape: IntArray) : -// NDRing> { -// -// inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = -// ShortBuffer(ShortArray(size) { initializer(it) }) -// -// /** -// * Inline transform an NDStructure to -// */ -// override fun map( -// arg: NDBuffer, -// transform: ShortRing.(Short) -> Short -// ): ShortNDElement { -// check(arg) -// val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) } -// return StridedNDFieldElement(this, array) -// } -// -// override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement { -// val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } -// return StridedNDFieldElement(this, array) -// } -// -// override fun mapIndexed( -// arg: NDBuffer, -// transform: ShortRing.(index: IntArray, Short) -> Short -// ): StridedNDFieldElement { -// check(arg) -// return StridedNDFieldElement( -// this, -// buildBuffer(arg.strides.linearSize) { offset -> -// elementContext.transform( -// arg.strides.index(offset), -// arg.buffer[offset] -// ) -// }) -// } -// -// override fun combine( -// a: NDBuffer, -// b: NDBuffer, -// transform: ShortRing.(Short, Short) -> Short -// ): StridedNDFieldElement { -// check(a, b) -// return StridedNDFieldElement( -// this, -// buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) -// } -//} -// -// -///** -// * Fast element production using function inlining -// */ -//inline fun StridedNDField.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { -// val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) } -// return StridedNDFieldElement(this, ShortBuffer(array)) -//} -// -///** -// * Element by element application of any operation on elements to the whole array. Just like in numpy -// */ -//operator fun Function1.invoke(ndElement: ShortNDElement) = -// ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } -// -// -///* plus and minus */ -// -///** -// * Summation operation for [StridedNDFieldElement] and single element -// */ -//operator fun ShortNDElement.plus(arg: Short) = -// context.produceInline { i -> buffer[i] + arg } -// -///** -// * Subtraction operation between [StridedNDFieldElement] and single element -// */ -//operator fun ShortNDElement.minus(arg: Short) = -// context.produceInline { i -> buffer[i] - arg } \ No newline at end of file +package scientifik.kmath.structures + +import scientifik.kmath.operations.RingElement +import scientifik.kmath.operations.ShortRing + + +typealias ShortNDElement = BufferedNDRingElement + +class ShortNDRing(override val shape: IntArray) : + BufferedNDRing { + + override val strides: Strides = DefaultStrides(shape) + + override val elementContext: ShortRing get() = ShortRing + override val zero by lazy { produce { ShortRing.zero } } + override val one by lazy { produce { ShortRing.one } } + + override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = + ShortBuffer(ShortArray(size) { initializer(it) }) + + /** + * Inline transform an NDStructure to + */ + override fun map( + arg: NDBuffer, + transform: ShortRing.(Short) -> Short + ): ShortNDElement { + check(arg) + val array = buildBuffer(arg.strides.linearSize) { offset -> ShortRing.transform(arg.buffer[offset]) } + return BufferedNDRingElement(this, array) + } + + override fun produce(initializer: ShortRing.(IntArray) -> Short): ShortNDElement { + val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } + return BufferedNDRingElement(this, array) + } + + override fun mapIndexed( + arg: NDBuffer, + transform: ShortRing.(index: IntArray, Short) -> Short + ): ShortNDElement { + check(arg) + return BufferedNDRingElement( + this, + buildBuffer(arg.strides.linearSize) { offset -> + elementContext.transform( + arg.strides.index(offset), + arg.buffer[offset] + ) + }) + } + + override fun combine( + a: NDBuffer, + b: NDBuffer, + transform: ShortRing.(Short, Short) -> Short + ): ShortNDElement { + check(a, b) + return BufferedNDRingElement( + this, + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) + } + + override fun NDBuffer.toElement(): RingElement, *, out BufferedNDRing> = + BufferedNDRingElement(this@ShortNDRing, buffer) +} + + +/** + * Fast element production using function inlining + */ +inline fun BufferedNDRing.produceInline(crossinline initializer: ShortRing.(Int) -> Short): ShortNDElement { + val array = ShortArray(strides.linearSize) { offset -> ShortRing.initializer(offset) } + return BufferedNDRingElement(this, ShortBuffer(array)) +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun Function1.invoke(ndElement: ShortNDElement) = + ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + + +/* plus and minus */ + +/** + * Summation operation for [StridedNDFieldElement] and single element + */ +operator fun ShortNDElement.plus(arg: Short) = + context.produceInline { i -> (buffer[i] + arg).toShort() } + +/** + * Subtraction operation between [StridedNDFieldElement] and single element + */ +operator fun ShortNDElement.minus(arg: Short) = + context.produceInline { i -> (buffer[i] - arg).toShort() } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt deleted file mode 100644 index 653f56013..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/StridedContext.kt +++ /dev/null @@ -1,97 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Field -import scientifik.kmath.operations.FieldElement - -abstract class StridedNDField>(final override val shape: IntArray) : NDField> { - val strides = DefaultStrides(shape) - - abstract fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer - - /** - * Convert any [NDStructure] to buffered structure using strides from this context. - * If the structure is already [NDBuffer], conversion is free. If not, it could be expensive because iteration over indexes - * - * If the argument is [NDBuffer] with different strides structure, the new element will be produced. - */ - fun NDStructure.toBuffer(): NDBuffer { - return if (this is NDBuffer && this.strides == this@StridedNDField.strides) { - this - } else { - produce { index -> get(index) } - } - } - - fun NDBuffer.toElement(): StridedNDFieldElement = - StridedNDFieldElement(this@StridedNDField, buffer) -} - -class StridedNDFieldElement>( - override val context: StridedNDField, - override val buffer: Buffer -) : - NDBuffer, - FieldElement, StridedNDFieldElement, StridedNDField>, - NDElement { - - override val elementField: F - get() = context.elementContext - - override fun unwrap(): NDBuffer = - this - - override fun NDBuffer.wrap(): StridedNDFieldElement = - StridedNDFieldElement(context, this.buffer) - - override val strides - get() = context.strides - - override val shape: IntArray - get() = context.shape - - override fun get(index: IntArray): T = - buffer[strides.offset(index)] - - override fun elements(): Sequence> = - strides.indices().map { it to get(it) } - - override fun map(action: F.(T) -> T) = - context.run { map(this@StridedNDFieldElement, action) }.wrap() - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T) = - context.run { mapIndexed(this@StridedNDFieldElement, transform) }.wrap() -} - -/** - * Element by element application of any operation on elements to the whole array. Just like in numpy - */ -operator fun > Function1.invoke(ndElement: StridedNDFieldElement) = - ndElement.context.run { ndElement.map { invoke(it) } } - -/* plus and minus */ - -/** - * Summation operation for [StridedNDFieldElement] and single element - */ -operator fun > StridedNDFieldElement.plus(arg: T) = - context.run { map { it + arg } } - -/** - * Subtraction operation between [StridedNDFieldElement] and single element - */ -operator fun > StridedNDFieldElement.minus(arg: T) = - context.run { map { it - arg } } - -/* prod and div */ - -/** - * Product operation for [StridedNDFieldElement] and single element - */ -operator fun > StridedNDFieldElement.times(arg: T) = - context.run { map { it * arg } } - -/** - * Division operation between [StridedNDFieldElement] and single element - */ -operator fun > StridedNDFieldElement.div(arg: T) = - context.run { map { it / arg } } \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index 516653817..635a90dbc 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -29,7 +29,7 @@ fun MutableBuffer.Companion.complex(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size) fun NDField.Companion.complex(shape: IntArray) = - BufferNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } + BoxingNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = NDField.complex(shape).produce(initializer) diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt index 7840586ef..924ddc653 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -8,8 +8,7 @@ class LazyNDField>( override val shape: IntArray, override val elementContext: F, val scope: CoroutineScope = GlobalScope -) : - NDField> { +) : NDField> { override val zero by lazy { produce { zero } } @@ -65,7 +64,8 @@ class LazyNDField>( class LazyNDStructure>( override val context: LazyNDField, val function: suspend (IntArray) -> T -) : FieldElement, LazyNDStructure, LazyNDField>, NDElement { +) : FieldElement, LazyNDStructure, LazyNDField>, + NDElement> { override fun unwrap(): NDStructure = this @@ -73,10 +73,6 @@ class LazyNDStructure>( override fun NDStructure.wrap(): LazyNDStructure = LazyNDStructure(context) { await(it) } override val shape: IntArray get() = context.shape - override val elementField: F get() = context.elementContext - - override fun mapIndexed(transform: F.(index: IntArray, T) -> T): NDElement = - context.run { mapIndexed(this@LazyNDStructure, transform) } private val cache = HashMap>() From f356e8956b899cfd700ad67813923b0ccc905537 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jan 2019 20:23:24 +0300 Subject: [PATCH 29/70] Cleanup after nd optimization phase II --- .../kmath/structures/NDFieldBenchmark.kt | 96 +++++++++---------- .../scientifik/kmath/structures/NDAlgebra.kt | 24 ++--- .../scientifik/kmath/structures/NDElement.kt | 4 +- 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index f4ffccd14..a0cbd66ca 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -16,54 +16,54 @@ fun main(args: Array) { //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) -// -// val autoTime = measureTimeMillis { -// autoField.run { -// var res = one -// repeat(n) { -// res += 1.0 -// } -// } -// } -// -// println("Buffered addition completed in $autoTime millis") -// -// val elementTime = measureTimeMillis { -// var res = genericField.one -// repeat(n) { -// res += 1.0 -// } -// } -// -// println("Element addition completed in $elementTime millis") -// -// val specializedTime = measureTimeMillis { -// specializedField.run { -// var res:NDBuffer = one -// repeat(n) { -// res += 1.0 -// } -// } -// } -// -// println("Specialized addition completed in $specializedTime millis") -// -// -// val lazyTime = measureTimeMillis { -// lazyField.run { -// val res = one.map { -// var c = 0.0 -// repeat(n) { -// c += 1.0 -// } -// c -// } -// -// res.elements().forEach { it.second } -// } -// } -// -// println("Lazy addition completed in $lazyTime millis") + + val autoTime = measureTimeMillis { + autoField.run { + var res = one + repeat(n) { + res += 1.0 + } + } + } + + println("Buffered addition completed in $autoTime millis") + + val elementTime = measureTimeMillis { + var res = genericField.one + repeat(n) { + res += 1.0 + } + } + + println("Element addition completed in $elementTime millis") + + val specializedTime = measureTimeMillis { + specializedField.run { + var res:NDBuffer = one + repeat(n) { + res += 1.0 + } + } + } + + println("Specialized addition completed in $specializedTime millis") + + + val lazyTime = measureTimeMillis { + lazyField.run { + val res = one.map { + var c = 0.0 + repeat(n) { + c += 1.0 + } + c + } + + res.elements().forEach { it.second } + } + } + + println("Lazy addition completed in $lazyTime millis") val genericTime = measureTimeMillis { //genericField.run(action) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index d8da8c937..63818afcd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -65,14 +65,12 @@ interface NDSpace, N : NDStructure> : Space, NDAlgebra add(aValue, bValue) } + override fun add(a: N, b: N): N = combine(a, b) { aValue, bValue -> add(aValue, bValue) } /** * Multiply all elements by constant */ - override fun multiply(a: N, k: Double): N = - map(a) { multiply(it, k) } + override fun multiply(a: N, k: Double): N = map(a) { multiply(it, k) } operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) } operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) } @@ -89,8 +87,7 @@ interface NDRing, N : NDStructure> : Ring, NDSpace /** * Element-by-element multiplication */ - override fun multiply(a: N, b: N): N = - combine(a, b) { aValue, bValue -> aValue * bValue } + override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) } operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) } @@ -109,11 +106,10 @@ interface NDField, N : NDStructure> : Field, NDRing aValue / bValue } + override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) } - operator fun N.div(arg: T) = map(this) { value -> arg / value } - operator fun T.div(arg: N) = arg / this + operator fun N.div(arg: T) = map(this) { value -> divide(arg, value) } + operator fun T.div(arg: N) = map(arg) { divide(it, this@div) } companion object { /** @@ -124,8 +120,12 @@ interface NDField, N : NDStructure> : Field, NDRing> buffered(shape: IntArray, field: F) = - BoxingNDField(shape, field, Buffer.Companion::boxing) + fun > buffered( + shape: IntArray, + field: F, + bufferFactory: BufferFactory = Buffer.Companion::boxing + ) = + BoxingNDField(shape, field, bufferFactory) /** * Create a most suitable implementation for nd-field using reified class. diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 6a09f1cb1..0fdb53f07 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -5,7 +5,9 @@ import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space - +/** + * The root for all [NDStructure] based algebra elements. Does not implement algebra element root because of problems with recursive self-types + */ interface NDElement> : NDStructure { val context: NDAlgebra From 4c1547ba5c4bcfefbea80a10092c00cdf5682559 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jan 2019 20:42:45 +0300 Subject: [PATCH 30/70] Added strides equality and fixed tests --- .../kotlin/scientifik/kmath/operations/Complex.kt | 4 +--- .../scientifik/kmath/operations/NumberAlgebra.kt | 4 +--- .../scientifik/kmath/structures/NDStructure.kt | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index a97afdb6b..107bb2f24 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -56,9 +56,7 @@ data class Complex(val re: Double, val im: Double) : FieldElement { override val context get() = RealField - companion object { - - } + companion object } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 3c422797c..d9601a5d4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -102,6 +102,20 @@ class DefaultStrides private constructor(override val shape: IntArray) : Strides override val linearSize: Int get() = strides[shape.size] + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is DefaultStrides) return false + + if (!shape.contentEquals(other.shape)) return false + + return true + } + + override fun hashCode(): Int { + return shape.contentHashCode() + } + companion object { private val defaultStridesCache = HashMap() From 037735c210e437a4ecf02fbbe24fb37132c6ef8a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 13 Jan 2019 10:42:53 +0300 Subject: [PATCH 31/70] Replaced Double in algebra by numbers, DiffExpressions --- kmath-commons/build.gradle.kts | 15 +++ .../kmath/expressions/DiffExpression.kt | 106 ++++++++++++++++++ .../kmath/expressions/AutoDiffTest.kt | 31 +++++ .../kmath/expressions/Expression.kt | 9 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 25 ++--- .../kotlin/scientifik/kmath/linear/Vector.kt | 2 +- .../scientifik/kmath/operations/Algebra.kt | 4 +- .../scientifik/kmath/operations/Complex.kt | 2 +- .../kmath/operations/NumberAlgebra.kt | 32 +++--- .../kmath/operations/OptionalOperations.kt | 2 +- .../scientifik/kmath/structures/NDAlgebra.kt | 2 +- .../kmath/structures/RealNDField.kt | 2 +- settings.gradle.kts | 1 + 13 files changed, 192 insertions(+), 41 deletions(-) create mode 100644 kmath-commons/build.gradle.kts create mode 100644 kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt create mode 100644 kmath-commons/src/test/kotlin/scientifik/kmath/expressions/AutoDiffTest.kt diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts new file mode 100644 index 000000000..7b859a2b0 --- /dev/null +++ b/kmath-commons/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("jvm") +} + +dependencies { + api(project(":kmath-core")) + api("org.apache.commons:commons-math3:3.6.1") + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") +} + +//dependencies { +//// compile(project(":kmath-core")) +//// //compile project(":kmath-coroutines") +////} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt new file mode 100644 index 000000000..4d5ea1b94 --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt @@ -0,0 +1,106 @@ +package scientifik.kmath.expressions + +import org.apache.commons.math3.analysis.differentiation.DerivativeStructure +import scientifik.kmath.operations.ExtendedField +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * A field wrapping commons-math derivative structures + */ +class DerivativeStructureField(val order: Int, val parameters: Map) : + ExtendedField { + + override val zero: DerivativeStructure by lazy { DerivativeStructure(order, parameters.size) } + + override val one: DerivativeStructure by lazy { DerivativeStructure(order, parameters.size, 1.0) } + + private val variables: Map = parameters.mapValues { (key, value) -> + DerivativeStructure(parameters.size, order, parameters.keys.indexOf(key), value) + } + + val variable = object : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): DerivativeStructure { + return variables[property.name] ?: error("A variable with name ${property.name} does not exist") + } + } + + fun variable(name: String): DerivativeStructure = + variables[name] ?: error("A variable with name ${name} does not exist") + + + fun Number.const() = DerivativeStructure(order, parameters.size, toDouble()) + + fun DerivativeStructure.deriv(parName: String, order: Int = 1): Double { + return deriv(mapOf(parName to order)) + } + + fun DerivativeStructure.deriv(orders: Map): Double { + return getPartialDerivative(*parameters.keys.map { orders[it] ?: 0 }.toIntArray()) + } + + fun DerivativeStructure.deriv(vararg orders: Pair): Double = deriv(mapOf(*orders)) + + override fun add(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.add(b) + + override fun multiply(a: DerivativeStructure, k: Number): DerivativeStructure = when (k) { + is Double -> a.multiply(k) + is Int -> a.multiply(k) + else -> a.multiply(k.toDouble()) + } + + override fun multiply(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.multiply(b) + + override fun divide(a: DerivativeStructure, b: DerivativeStructure): DerivativeStructure = a.divide(b) + + override fun sin(arg: DerivativeStructure): DerivativeStructure = arg.sin() + + override fun cos(arg: DerivativeStructure): DerivativeStructure = arg.cos() + + override fun power(arg: DerivativeStructure, pow: Number): DerivativeStructure = when (pow) { + is Double -> arg.pow(pow) + is Int -> arg.pow(pow) + else -> arg.pow(pow.toDouble()) + } + + fun power(arg: DerivativeStructure, pow: DerivativeStructure): DerivativeStructure = arg.pow(pow) + + override fun exp(arg: DerivativeStructure): DerivativeStructure = arg.exp() + + override fun ln(arg: DerivativeStructure): DerivativeStructure = arg.log() + + operator fun DerivativeStructure.plus(n: Number): DerivativeStructure = add(n.toDouble()) + operator fun DerivativeStructure.minus(n: Number): DerivativeStructure = subtract(n.toDouble()) + operator fun Number.plus(s: DerivativeStructure) = s + this + operator fun Number.minus(s: DerivativeStructure) = s - this +} + +/** + * A constructs that creates a derivative structure with required order on-demand + */ +class DiffExpression(val function: DerivativeStructureField.() -> DerivativeStructure) : Expression { + override fun invoke(arguments: Map): Double = DerivativeStructureField(0, arguments) + .run(function).value + + /** + * Get the derivative expression with given orders + * TODO make result [DiffExpression] + */ + fun derivative(orders: Map): Expression { + return object : Expression { + override fun invoke(arguments: Map): Double = + DerivativeStructureField(orders.values.max() ?: 0, arguments) + .run { + function().deriv(orders) + } + } + } + + //TODO add gradient and maybe other vector operators +} + +fun DiffExpression.derivative(vararg orders: Pair) = derivative(mapOf(*orders)) +fun DiffExpression.derivative(name: String) = derivative(name to 1) + + + diff --git a/kmath-commons/src/test/kotlin/scientifik/kmath/expressions/AutoDiffTest.kt b/kmath-commons/src/test/kotlin/scientifik/kmath/expressions/AutoDiffTest.kt new file mode 100644 index 000000000..2695bb918 --- /dev/null +++ b/kmath-commons/src/test/kotlin/scientifik/kmath/expressions/AutoDiffTest.kt @@ -0,0 +1,31 @@ +package scientifik.kmath.expressions + +import org.junit.Test +import kotlin.test.assertEquals + +inline fun diff(order: Int, vararg parameters: Pair, block: DerivativeStructureField.() -> R) = + DerivativeStructureField(order, mapOf(*parameters)).run(block) + +class AutoDiffTest { + @Test + fun derivativeStructureFieldTest() { + val res = diff(3, "x" to 1.0, "y" to 1.0) { + val x by variable + val y = variable("y") + val z = x * (-sin(x * y) + y) + z.deriv("x") + } + } + + @Test + fun autoDifTest() { + val f = DiffExpression { + val x by variable + val y by variable + x.pow(2) + 2 * x * y + y.pow(2) + 1 + } + + assertEquals(10.0, f("x" to 1.0, "y" to 2.0)) + assertEquals(6.0, f.derivative("x")("x" to 1.0, "y" to 2.0)) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt index f7ae09d02..2f54ae1b2 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt @@ -1,6 +1,7 @@ package scientifik.kmath.expressions import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space @@ -18,7 +19,7 @@ interface ExpressionContext { internal class VariableExpression(val name: String, val default: T? = null) : Expression { override fun invoke(arguments: Map): T = - arguments[name] ?: default ?: error("Parameter not found: $name") + arguments[name] ?: default ?: error("Parameter not found: $name") } internal class ConstantExpression(val value: T) : Expression { @@ -30,13 +31,13 @@ internal class SumExpression(val context: Space, val first: Expression, override fun invoke(arguments: Map): T = context.add(first.invoke(arguments), second.invoke(arguments)) } -internal class ProductExpression(val context: Field, val first: Expression, val second: Expression) : +internal class ProductExpression(val context: Ring, val first: Expression, val second: Expression) : Expression { override fun invoke(arguments: Map): T = context.multiply(first.invoke(arguments), second.invoke(arguments)) } -internal class ConstProductExpession(val context: Field, val expr: Expression, val const: Double) : +internal class ConstProductExpession(val context: Space, val expr: Expression, val const: Number) : Expression { override fun invoke(arguments: Map): T = context.multiply(expr.invoke(arguments), const) } @@ -58,7 +59,7 @@ class ExpressionField(val field: Field) : Field>, Expression override fun add(a: Expression, b: Expression): Expression = SumExpression(field, a, b) - override fun multiply(a: Expression, k: Double): Expression = ConstProductExpession(field, a, k) + override fun multiply(a: Expression, k: Number): Expression = ConstProductExpession(field, a, k) override fun multiply(a: Expression, b: Expression): Expression = ProductExpression(field, a, b) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 35ad86379..0edf748e6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -6,7 +6,6 @@ import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement import scientifik.kmath.structures.* import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory -import scientifik.kmath.structures.Buffer.Companion.auto import scientifik.kmath.structures.Buffer.Companion.boxing @@ -19,8 +18,6 @@ interface MatrixSpace> : Space> { val rowNum: Int val colNum: Int - val shape get() = intArrayOf(rowNum, colNum) - /** * Produce a matrix with this context and given dimensions */ @@ -38,7 +35,7 @@ interface MatrixSpace> : Space> { override fun add(a: Matrix, b: Matrix): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } - override fun multiply(a: Matrix, k: Double): Matrix = + override fun multiply(a: Matrix, k: Number): Matrix = produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } companion object { @@ -61,8 +58,8 @@ interface MatrixSpace> : Space> { /** * Automatic buffered matrix, unboxed if it is possible */ - inline fun > smart(rows: Int, columns: Int, ring: R): MatrixSpace = - buffered(rows, columns, ring, ::auto) + inline fun > auto(rows: Int, columns: Int, ring: R): MatrixSpace = + buffered(rows, columns, ring, Buffer.Companion::auto) } } @@ -80,21 +77,19 @@ interface Matrix> : NDStructure, SpaceElement> - get() = (0 until numRows).map { i -> + val rows: Point> + get() = ListBuffer((0 until numRows).map { i -> context.point(numCols) { j -> get(i, j) } - } + }) - val columns: List> - get() = (0 until numCols).map { j -> + val columns: Point> + get() = ListBuffer((0 until numCols).map { j -> context.point(numRows) { i -> get(i, j) } - } + }) val features: Set @@ -134,7 +129,7 @@ data class StructureMatrixSpace>( private val bufferFactory: BufferFactory ) : MatrixSpace { - override val shape: IntArray = intArrayOf(rowNum, colNum) + val shape: IntArray = intArrayOf(rowNum, colNum) private val strides = DefaultStrides(shape) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt index a673c0197..a7373f647 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -30,7 +30,7 @@ interface VectorSpace> : Space> { override fun add(a: Point, b: Point): Point = produce { with(space) { a[it] + b[it] } } - override fun multiply(a: Point, k: Double): Point = produce { with(space) { a[it] * k } } + override fun multiply(a: Point, k: Number): Point = produce { with(space) { a[it] * k } } //TODO add basis diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index d77df4b7f..1534f4c82 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -7,7 +7,7 @@ package scientifik.kmath.operations * One must note that in some cases context is a singleton class, but in some cases it * works as a context for operations inside it. * - * TODO do we need commutative context? + * TODO do we need non-commutative context? */ interface Space { /** @@ -23,7 +23,7 @@ interface Space { /** * Multiplication operation for context element and real number */ - fun multiply(a: T, k: Double): T + fun multiply(a: T, k: Number): T //Operation to be performed in this context operator fun T.unaryMinus(): T = multiply(this, -1.0) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 107bb2f24..20e8f47ec 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -12,7 +12,7 @@ object ComplexField : Field { override fun add(a: Complex, b: Complex): Complex = Complex(a.re + b.re, a.im + b.im) - override fun multiply(a: Complex, k: Double): Complex = Complex(a.re * k, a.im * k) + override fun multiply(a: Complex, k: Number): Complex = Complex(a.re * k.toDouble(), a.im * k.toDouble()) override fun multiply(a: Complex, b: Complex): Complex = Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index 953f2e326..a57688ad8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -34,21 +34,23 @@ inline class Real(val value: Double) : FieldElement { object RealField : ExtendedField, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b - override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b + override fun multiply(a: Double, b: Double): Double = a * b + override fun multiply(a: Double, k: Number): Double = a * k.toDouble() + override val one: Double = 1.0 override fun divide(a: Double, b: Double): Double = a / b override fun sin(arg: Double): Double = kotlin.math.sin(arg) override fun cos(arg: Double): Double = kotlin.math.cos(arg) - override fun power(arg: Double, pow: Double): Double = arg.pow(pow) + override fun power(arg: Double, pow: Number): Double = arg.pow(pow.toDouble()) override fun exp(arg: Double): Double = kotlin.math.exp(arg) override fun ln(arg: Double): Double = kotlin.math.ln(arg) override fun norm(arg: Double): Double = kotlin.math.abs(arg) - override fun Double.unaryMinus(): Double = -this + override fun Double.unaryMinus(): Double = -this override fun Double.minus(b: Double): Double = this - b } @@ -56,51 +58,51 @@ object RealField : ExtendedField, Norm { /** * A field for [Int] without boxing. Does not produce corresponding field element */ -object IntRing : Ring, Norm { +object IntRing : Ring, Norm { 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 fun multiply(a: Int, k: Number): Int = (k * a) override val one: Int = 1 - override fun norm(arg: Int): Int = arg + override fun norm(arg: Int): Int = arg } /** * A field for [Short] without boxing. Does not produce appropriate field element */ -object ShortRing : Ring, Norm{ +object ShortRing : Ring, Norm { override val zero: Short = 0 override fun add(a: Short, b: Short): Short = (a + b).toShort() override fun multiply(a: Short, b: Short): Short = (a * b).toShort() - override fun multiply(a: Short, k: Double): Short = (a * k).toShort() + override fun multiply(a: Short, k: Number): Short = (a * k) override val one: Short = 1 - override fun norm(arg: Short): Short = arg + override fun norm(arg: Short): Short = arg } /** * A field for [Byte] values */ -object ByteRing : Ring, Norm { +object ByteRing : Ring, Norm { override val zero: Byte = 0 override fun add(a: Byte, b: Byte): Byte = (a + b).toByte() override fun multiply(a: Byte, b: Byte): Byte = (a * b).toByte() - override fun multiply(a: Byte, k: Double): Byte = (a * k).toByte() + override fun multiply(a: Byte, k: Number): Byte = (a * k) override val one: Byte = 1 - override fun norm(arg: Byte): Byte = arg + override fun norm(arg: Byte): Byte = arg } /** * A field for [Long] values */ -object LongRing : Ring, Norm { +object LongRing : Ring, Norm { override val zero: Long = 0 override fun add(a: Long, b: Long): Long = (a + b) override fun multiply(a: Long, b: Long): Long = (a * b) - override fun multiply(a: Long, k: Double): Long = (a * k).toLong() + override fun multiply(a: Long, k: Number): Long = (a * k) override val one: Long = 1 - override fun norm(arg: Long): Long = arg + override fun norm(arg: Long): Long = arg } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt index 7a7866966..66ca205a1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -30,7 +30,7 @@ fun >> ctg(arg: T): T = arg.conte * A context extension to include power operations like square roots, etc */ interface PowerOperations { - fun power(arg: T, pow: Double): T + fun power(arg: T, pow: Number): T fun sqrt(arg: T) = power(arg, 0.5) } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 63818afcd..097d52723 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -70,7 +70,7 @@ interface NDSpace, N : NDStructure> : Space, NDAlgebra add(arg, value) } operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index 62c2338c6..bc5832e1c 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -65,7 +65,7 @@ class RealNDField(override val shape: IntArray) : override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = BufferedNDFieldElement(this@RealNDField, buffer) - override fun power(arg: NDBuffer, pow: Double) = map(arg) { power(it, pow) } + override fun power(arg: NDBuffer, pow: Number) = map(arg) { power(it, pow) } override fun exp(arg: NDBuffer) = map(arg) { exp(it) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 3a8aef730..fa0bb49a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,5 +12,6 @@ include( ":kmath-core", ":kmath-io", ":kmath-coroutines", + ":kmath-commons", ":benchmarks" ) From 7e83b080ade5111d64ccf91e8a3b07bae26aa341 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 16 Jan 2019 14:52:27 +0300 Subject: [PATCH 32/70] Matrix revision --- .../kmath/linear/LUDecomposition.kt | 258 +++++++++--------- .../scientifik/kmath/linear/LinearAlgrebra.kt | 57 ++-- .../kotlin/scientifik/kmath/linear/Matrix.kt | 217 ++++++++------- .../kmath/linear/StructureMatrix.kt | 50 ++++ .../scientifik/kmath/linear/VirtualMatrix.kt | 10 + .../scientifik/kmath/operations/Algebra.kt | 2 +- .../scientifik/kmath/structures/Buffers.kt | 21 ++ .../scientifik/kmath/linear/MatrixTest.kt | 2 +- .../kmath/linear/RealLUSolverTest.kt | 2 +- 9 files changed, 345 insertions(+), 274 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt 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 2ceb5bcd3..7b645c74b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -2,31 +2,39 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.* import scientifik.kmath.structures.MutableBuffer.Companion.boxing -import scientifik.kmath.structures.MutableNDStructure -import scientifik.kmath.structures.NDStructure -import scientifik.kmath.structures.get -import scientifik.kmath.structures.mutableNdStructure -import kotlin.math.absoluteValue /** - * Implementation based on Apache common-maths LU-decomposition + * Matrix LUP decomposition */ -abstract class LUDecomposition, F : Field>(val matrix: Matrix) { +interface LUPDecompositionFeature : DeterminantFeature { + /** + * A reference to L-matrix + */ + val l: Matrix + /** + * A reference to u-matrix + */ + val u: Matrix + /** + * Pivoting points for each row + */ + val pivot: IntArray + /** + * Permutation matrix based on [pivot] + */ + val p: Matrix +} - private val field get() = matrix.context.ring - /** Entries of LU decomposition. */ - internal val lu: NDStructure - /** Pivot permutation associated with LU decomposition. */ - internal val pivot: IntArray - /** Parity of the permutation associated with the LU decomposition. */ - private var even: Boolean = false - init { - val pair = calculateLU() - lu = pair.first - pivot = pair.second - } +private class LUPDecomposition, R : Ring>( + val context: R, + val lu: NDStructure, + override val pivot: IntArray, + private val even: Boolean +) : LUPDecompositionFeature { /** * Returns the matrix L of the decomposition. @@ -34,13 +42,11 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * L is a lower-triangular matrix * @return the L matrix (or null if decomposed matrix is singular) */ - val l: Matrix by lazy { - matrix.context.produce(matrix.numRows, matrix.numCols) { i, j -> - when { - j < i -> lu[i, j] - j == i -> matrix.context.ring.one - else -> matrix.context.ring.zero - } + override val l: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + when { + j < i -> lu[i, j] + j == i -> context.one + else -> context.zero } } @@ -51,12 +57,11 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * U is an upper-triangular matrix * @return the U matrix (or null if decomposed matrix is singular) */ - val u: Matrix by lazy { - matrix.context.produce(matrix.numRows, matrix.numCols) { i, j -> - if (j >= i) lu[i, j] else field.zero - } + override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + if (j >= i) lu[i, j] else context.zero } + /** * Returns the P rows permutation matrix. * @@ -67,59 +72,64 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr * @return the P rows permutation matrix (or null if decomposed matrix is singular) * @see .getPivot */ - val p: Matrix by lazy { - matrix.context.produce(matrix.numRows, matrix.numCols) { i, j -> - //TODO ineffective. Need sparse matrix for that - if (j == pivot[i]) field.one else field.zero - } + override val p: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + if (j == pivot[i]) context.one else context.zero } + /** * Return the determinant of the matrix * @return determinant of the matrix */ - val determinant: T - get() { - with(matrix.context.ring) { - var determinant = if (even) one else -one - for (i in 0 until matrix.numRows) { - determinant *= lu[i, i] - } - return determinant - } + override val determinant: T by lazy { + with(context) { + (0 until lu.shape[0]).fold(if (even) one else -one) { value, i -> value * lu[i, i] } } + } + +} + + +/** + * Implementation based on Apache common-maths LU-decomposition + */ +class LUPDecompositionBuilder, F : Field>(val context: F, val bufferFactory: MutableBufferFactory = ::boxing, val singularityCheck: (T) -> Boolean) { /** * In-place transformation for [MutableNDStructure], using given transformation for each element */ - operator fun MutableNDStructure.set(i: Int, j: Int, value: T) { + private 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 > context.zero) value else with(context) { -value } - private fun abs(value: T) = if (value > matrix.context.ring.zero) value else with(matrix.context.ring) { -value } + fun decompose(matrix: Matrix): LUPDecompositionFeature { + // Use existing decomposition if it is provided by matrix + matrix.features.find { it is LUPDecompositionFeature<*> }?.let { + @Suppress("UNCHECKED_CAST") + return it as LUPDecompositionFeature + } - private fun calculateLU(): Pair, IntArray> { - if (matrix.numRows != matrix.numCols) { + if (matrix.rowNum != matrix.colNum) { error("LU decomposition supports only square matrices") } - val m = matrix.numCols - val pivot = IntArray(matrix.numRows) - //TODO fix performance + val m = matrix.colNum + val pivot = IntArray(matrix.rowNum) + //TODO replace by custom optimized 2d structure val lu: MutableNDStructure = mutableNdStructure( - intArrayOf(matrix.numRows, matrix.numCols), - ::boxing + intArrayOf(matrix.rowNum, matrix.colNum), + bufferFactory ) { index: IntArray -> matrix[index[0], index[1]] } - with(matrix.context.ring) { + with(context) { // Initialize permutation array and parity for (row in 0 until m) { pivot[row] = row } - even = true + var even = true // Loop over columns for (col in 0 until m) { @@ -139,22 +149,18 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr for (i in 0 until col) { sum -= lu[row, i] * lu[i, col] } - //luRow[col] = sum lu[row, col] = sum abs(sum) } ?: col // Singularity check - if (isSingular(lu[max, col])) { + if (singularityCheck(lu[max, col])) { error("Singular matrix") } // Pivot if necessary if (max != col) { - //var tmp = zero - //val luMax = lu[max] - //val luCol = lu[col] for (i in 0 until m) { lu[max, i] = lu[col, i] lu[col, i] = lu[max, i] @@ -169,86 +175,70 @@ abstract class LUDecomposition, F : Field>(val matrix: Matr val luDiag = lu[col, col] for (row in col + 1 until m) { lu[row, col] = lu[row, col] / luDiag -// lu[row, col] /= luDiag } } + return LUPDecomposition(context, lu, pivot, even) } - return Pair(lu, pivot) - } - - /** - * Returns the pivot permutation vector. - * @return the pivot permutation vector - * @see .getP - */ - fun getPivot(): IntArray = pivot.copyOf() - -} - -class RealLUDecomposition(matrix: RealMatrix, private val singularityThreshold: Double = DEFAULT_TOO_SMALL) : - LUDecomposition(matrix) { - override fun isSingular(value: Double): Boolean { - return value.absoluteValue < singularityThreshold } companion object { - /** Default bound to determine effective singularity in LU decomposition. */ - private const val DEFAULT_TOO_SMALL = 1e-11 + val real: LUPDecompositionBuilder = LUPDecompositionBuilder(RealField) { it < 1e-11 } } + } -/** Specialized solver. */ -object RealLUSolver : LinearSolver { - - fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = - RealLUDecomposition(mat, threshold) - - override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix { - val decomposition = decompose(a, a.context.ring.zero) - - if (b.numRows != a.numCols) { - error("Matrix dimension mismatch expected ${a.numRows}, but got ${b.numCols}") - } - - // Apply permutations to b - val bp = Array(a.numRows) { DoubleArray(b.numCols) } - for (row in 0 until a.numRows) { - val bpRow = bp[row] - val pRow = decomposition.pivot[row] - for (col in 0 until b.numCols) { - bpRow[col] = b[pRow, col] - } - } - - // Solve LY = b - for (col in 0 until a.numRows) { - val bpCol = bp[col] - for (i in col + 1 until a.numRows) { - val bpI = bp[i] - val luICol = decomposition.lu[i, col] - for (j in 0 until b.numCols) { - bpI[j] -= bpCol[j] * luICol - } - } - } - - // Solve UX = Y - for (col in a.numRows - 1 downTo 0) { - val bpCol = bp[col] - val luDiag = decomposition.lu[col, col] - for (j in 0 until b.numCols) { - bpCol[j] /= luDiag - } - for (i in 0 until col) { - val bpI = bp[i] - val luICol = decomposition.lu[i, col] - for (j in 0 until b.numCols) { - bpI[j] -= bpCol[j] * luICol - } - } - } - - return a.context.produce(a.numRows, a.numCols) { i, j -> bp[i][j] } - } -} +//class LUSolver, F : Field>(val singularityCheck: (T) -> Boolean) : LinearSolver { +// +// +// override fun solve(a: Matrix, b: Matrix): Matrix { +// val decomposition = LUPDecompositionBuilder(ring, singularityCheck).decompose(a) +// +// if (b.rowNum != a.colNum) { +// error("Matrix dimension mismatch expected ${a.rowNum}, but got ${b.colNum}") +// } +// +// +//// val bp = Array(a.rowNum) { Array(b.colNum){ring.zero} } +//// for (row in 0 until a.rowNum) { +//// val bpRow = bp[row] +//// val pRow = decomposition.pivot[row] +//// for (col in 0 until b.colNum) { +//// bpRow[col] = b[pRow, col] +//// } +//// } +// +// // Apply permutations to b +// val bp = produce(a.rowNum, a.colNum) { i, j -> b[decomposition.pivot[i], j] } +// +// // Solve LY = b +// for (col in 0 until a.rowNum) { +// val bpCol = bp[col] +// for (i in col + 1 until a.rowNum) { +// val bpI = bp[i] +// val luICol = decomposition.lu[i, col] +// for (j in 0 until b.colNum) { +// bpI[j] -= bpCol[j] * luICol +// } +// } +// } +// +// // Solve UX = Y +// for (col in a.rowNum - 1 downTo 0) { +// val bpCol = bp[col] +// val luDiag = decomposition.lu[col, col] +// for (j in 0 until b.colNum) { +// bpCol[j] /= luDiag +// } +// for (i in 0 until col) { +// val bpI = bp[i] +// val luICol = decomposition.lu[i, col] +// for (j in 0 until b.colNum) { +// bpI[j] -= bpCol[j] * luICol +// } +// } +// } +// +// return produce(a.rowNum, a.colNum) { i, j -> bp[i][j] } +// } +//} 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 a3d2feccc..56ee448ab 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -4,18 +4,26 @@ import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring -import scientifik.kmath.structures.Buffer.Companion.boxing import scientifik.kmath.structures.asSequence - /** * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors */ -interface LinearSolver> { - fun solve(a: Matrix, b: Matrix): Matrix - fun solve(a: Matrix, b: Vector): Vector = solve(a, b.toMatrix()).toVector() - fun inverse(a: Matrix): Matrix = solve(a, a.context.one) +interface LinearSolver> : MatrixContext { + /** + * Convert matrix to vector if it is possible + */ + fun Matrix.toVector(): Point = + if (this.colNum == 1) { + point(rowNum){ get(it, 0) } + } else error("Can't convert matrix with more than one column to vector") + + fun Point.toMatrix(): Matrix = produce(size, 1) { i, _ -> get(i) } + + fun solve(a: Matrix, b: Matrix): Matrix + fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).toVector() + fun inverse(a: Matrix): Matrix = solve(a, one(a.rowNum, a.colNum)) } /** @@ -26,39 +34,10 @@ fun Array.toVector(field: Field) = Vector.generic(size, field) { fun DoubleArray.toVector() = Vector.real(this.size) { this[it] } fun List.toVector() = Vector.real(this.size) { this[it] } -/** - * Convert matrix to vector if it is possible - */ -fun > Matrix.toVector(): Vector = - if (this.numCols == 1) { -// if (this is ArrayMatrix) { -// //Reuse existing underlying array -// ArrayVector(ArrayVectorSpace(rows, context.field, context.ndFactory), array) -// } else { -// //Generic vector -// vector(rows, context.field) { get(it, 0) } -// } - Vector.generic(numRows, context.ring) { get(it, 0) } - } else error("Can't convert matrix with more than one column to vector") - -fun > Vector.toMatrix(): Matrix { -// val context = StructureMatrixContext(size, 1, context.space) -// -// return if (this is ArrayVector) { -// //Reuse existing underlying array -// StructureMatrix(context,this.buffer) -// } else { -// //Generic vector -// matrix(size, 1, context.field) { i, j -> get(i) } -// } - //return Matrix.of(size, 1, context.space) { i, _ -> get(i) } - return StructureMatrixSpace(size, 1, context.space, ::boxing).produce { i, _ -> get(i) } -} - -object VectorL2Norm : Norm, Double> { - override fun norm(arg: Vector): Double = - kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) +object VectorL2Norm : Norm, Double> { + override fun norm(arg: Point): Double = + kotlin.math.sqrt(arg.asSequence().sumByDouble { it.toDouble() }) } typealias RealVector = Vector -typealias RealMatrix = Matrix +typealias RealMatrix = Matrix diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 0edf748e6..e7b791a86 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -2,64 +2,91 @@ package scientifik.kmath.linear import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring -import scientifik.kmath.operations.Space -import scientifik.kmath.operations.SpaceElement import scientifik.kmath.structures.* import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory import scientifik.kmath.structures.Buffer.Companion.boxing -interface MatrixSpace> : Space> { +interface MatrixContext> { /** * The ring context for matrix elements */ val ring: R - val rowNum: Int - val colNum: Int - /** * Produce a matrix with this context and given dimensions */ - fun produce(rows: Int = rowNum, columns: Int = colNum, initializer: (i: Int, j: Int) -> T): Matrix + fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix /** * Produce a point compatible with matrix space */ fun point(size: Int, initializer: (Int) -> T): Point - override val zero: Matrix get() = produce { _, _ -> ring.zero } + fun scale(a: Matrix, k: Number): Matrix { + //TODO create a special wrapper class for scaled matrices + return produce(a.rowNum, a.colNum) { i, j -> ring.run { a[i, j] * k } } + } - val one get() = produce { i, j -> if (i == j) ring.one else ring.zero } + infix fun Matrix.dot(other: Matrix): Matrix { + //TODO add typed error + if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") + return produce(rowNum, other.colNum) { i, j -> + val row = rows[i] + val column = other.columns[j] + with(ring) { + row.asSequence().zip(column.asSequence(), ::multiply).sum() + } + } + } - override fun add(a: Matrix, b: Matrix): Matrix = - produce(rowNum, colNum) { i, j -> ring.run { a[i, j] + b[i, j] } } + infix fun Matrix.dot(vector: Point): Point { + //TODO add typed error + if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})") + return point(rowNum) { i -> + val row = rows[i] + with(ring) { + row.asSequence().zip(vector.asSequence(), ::multiply).sum() + } + } + } + + operator fun Matrix.unaryMinus() = + produce(rowNum, colNum) { i, j -> ring.run { -get(i, j) } } + + operator fun Matrix.plus(b: Matrix): Matrix { + if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] + [${b.rowNum},${b.colNum}]") + return produce(rowNum, colNum) { i, j -> ring.run { get(i, j) + b[i, j] } } + } + + operator fun Matrix.minus(b: Matrix): Matrix { + if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]") + return produce(rowNum, colNum) { i, j -> ring.run { get(i, j) + b[i, j] } } + } + + operator fun Matrix.times(number: Number): Matrix = + produce(rowNum, colNum) { i, j -> ring.run { get(i, j) * number } } + + operator fun Number.times(m: Matrix): Matrix = m * this - override fun multiply(a: Matrix, k: Number): Matrix = - produce(rowNum, colNum) { i, j -> ring.run { a[i, j] * k } } companion object { /** * Non-boxing double matrix */ - fun real(rows: Int, columns: Int): MatrixSpace = - StructureMatrixSpace(rows, columns, RealField, DoubleBufferFactory) + val real: MatrixContext = StructureMatrixContext(RealField, DoubleBufferFactory) /** * A structured matrix with custom buffer */ - fun > buffered( - rows: Int, - columns: Int, - ring: R, - bufferFactory: BufferFactory = ::boxing - ): MatrixSpace = StructureMatrixSpace(rows, columns, ring, bufferFactory) + fun > buffered(ring: R, bufferFactory: BufferFactory = ::boxing): MatrixContext = + StructureMatrixContext(ring, bufferFactory) /** * Automatic buffered matrix, unboxed if it is possible */ - inline fun > auto(rows: Int, columns: Int, ring: R): MatrixSpace = - buffered(rows, columns, ring, Buffer.Companion::auto) + inline fun > auto(ring: R): MatrixContext = + buffered(ring, Buffer.Companion::auto) } } @@ -69,108 +96,102 @@ interface MatrixSpace> : Space> { */ interface MatrixFeature +object DiagonalFeature : MatrixFeature + +object ZeroFeature : MatrixFeature + +object UnitFeature : MatrixFeature + +interface InverseMatrixFeature : MatrixFeature { + val inverse: Matrix +} + +interface DeterminantFeature : MatrixFeature { + val determinant: T +} + /** * Specialized 2-d structure */ -interface Matrix> : NDStructure, SpaceElement, Matrix, MatrixSpace> { +interface Matrix : NDStructure { + val rowNum: Int + val colNum: Int + + val features: Set + operator fun get(i: Int, j: Int): T override fun get(index: IntArray): T = get(index[0], index[1]) - val numRows get() = context.rowNum - val numCols get() = context.colNum + override val shape: IntArray get() = intArrayOf(rowNum, colNum) - //TODO replace by lazy buffers val rows: Point> - get() = ListBuffer((0 until numRows).map { i -> - context.point(numCols) { j -> get(i, j) } - }) + get() = VirtualBuffer(rowNum) { i -> + VirtualBuffer(colNum) { j -> get(i, j) } + } val columns: Point> - get() = ListBuffer((0 until numCols).map { j -> - context.point(numRows) { i -> get(i, j) } - }) + get() = VirtualBuffer(colNum) { j -> + VirtualBuffer(rowNum) { i -> get(i, j) } + } - val features: Set + override fun elements(): Sequence> = sequence { + for (i in (0 until rowNum)) { + for (j in (0 until colNum)) { + yield(intArrayOf(i, j) to get(i, j)) + } + } + } companion object { fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = - MatrixSpace.real(rows, columns).produce(rows, columns, initializer) + MatrixContext.real.produce(rows, columns, initializer) } } -infix fun > Matrix.dot(other: Matrix): Matrix { - //TODO add typed error - if (this.numCols != other.numRows) error("Matrix dot operation dimension mismatch: ($numRows, $numCols) x (${other.numRows}, ${other.numCols})") - return context.produce(numRows, other.numCols) { i, j -> - val row = rows[i] - val column = other.columns[j] - with(context.ring) { - row.asSequence().zip(column.asSequence(), ::multiply).sum() - } +/** + * Diagonal matrix of ones. The matrix is virtual no actual matrix is created + */ +fun > MatrixContext.one(rows: Int, columns: Int): Matrix { + return object : Matrix { + override val rowNum: Int get() = rows + override val colNum: Int get() = columns + override val features: Set get() = setOf(DiagonalFeature, UnitFeature) + override fun get(i: Int, j: Int): T = if (i == j) ring.one else ring.zero } } -infix fun > Matrix.dot(vector: Point): Point { - //TODO add typed error - if (this.numCols != vector.size) error("Matrix dot vector operation dimension mismatch: ($numRows, $numCols) x (${vector.size})") - return context.point(numRows) { i -> - val row = rows[i] - with(context.ring) { - row.asSequence().zip(vector.asSequence(), ::multiply).sum() - } +/** + * A virtual matrix of zeroes + */ +fun > MatrixContext.zero(rows: Int, columns: Int): Matrix { + return object : Matrix { + override val rowNum: Int get() = rows + override val colNum: Int get() = columns + override val features: Set get() = setOf(ZeroFeature) + override fun get(i: Int, j: Int): T = ring.zero } } -data class StructureMatrixSpace>( - override val rowNum: Int, - override val colNum: Int, - override val ring: R, - private val bufferFactory: BufferFactory -) : MatrixSpace { - val shape: IntArray = intArrayOf(rowNum, colNum) +inline class TransposedMatrix(val original: Matrix) : Matrix { + override val rowNum: Int get() = original.colNum + override val colNum: Int get() = original.rowNum + override val features: Set get() = emptySet() //TODO retain some features - private val strides = DefaultStrides(shape) + override fun get(i: Int, j: Int): T = original[j, i] - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { - return if (rows == rowNum && columns == colNum) { - val structure = ndStructure(strides, bufferFactory) { initializer(it[0], it[1]) } - StructureMatrix(this, structure) - } else { - val context = StructureMatrixSpace(rows, columns, ring, bufferFactory) - val structure = ndStructure(context.strides, bufferFactory) { initializer(it[0], it[1]) } - StructureMatrix(context, structure) - } - } - - override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) + override fun elements(): Sequence> = + original.elements().map { (key, value) -> intArrayOf(key[1], key[0]) to value } } -data class StructureMatrix>( - override val context: StructureMatrixSpace, - val structure: NDStructure, - override val features: Set = emptySet() -) : Matrix { - init { - if (structure.shape.size != 2 || structure.shape[0] != context.rowNum || structure.shape[1] != context.colNum) { - error("Dimension mismatch for structure, (${context.rowNum}, ${context.colNum}) expected, but ${structure.shape} found") - } +/** + * Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A` + */ +fun > Matrix.transpose(): Matrix { + return if (this is TransposedMatrix) { + original + } else { + TransposedMatrix(this) } - - override fun unwrap(): Matrix = this - - override fun Matrix.wrap(): Matrix = this - - override val shape: IntArray get() = structure.shape - - override fun get(index: IntArray): T = structure[index] - - override fun get(i: Int, j: Int): T = structure[i, j] - - override fun elements(): Sequence> = structure.elements() -} - -//TODO produce transposed matrix via reference without creating new space and structure -fun > Matrix.transpose(): Matrix = - context.produce(numCols, numRows) { i, j -> get(j, i) } \ No newline at end of file +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt new file mode 100644 index 000000000..a8a1d692c --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt @@ -0,0 +1,50 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.BufferFactory +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.get +import scientifik.kmath.structures.ndStructure + +/** + * Basic implementation of Matrix space based on [NDStructure] + */ +class StructureMatrixContext>( + override val ring: R, + private val bufferFactory: BufferFactory +) : MatrixContext { + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { + val structure = + ndStructure(intArrayOf(rows, columns), bufferFactory) { index -> initializer(index[0], index[1]) } + return StructureMatrix(structure) + } + + override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) +} + +data class StructureMatrix( + val structure: NDStructure, + override val features: Set = emptySet() +) : Matrix { + + init { + if (structure.shape.size != 2) { + error("Dimension mismatch for matrix structure") + } + } + + override val rowNum: Int + get() = structure.shape[0] + override val colNum: Int + get() = structure.shape[1] + + + override val shape: IntArray get() = structure.shape + + override fun get(index: IntArray): T = structure[index] + + override fun get(i: Int, j: Int): T = structure[i, j] + + override fun elements(): Sequence> = structure.elements() +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt new file mode 100644 index 000000000..59c89b197 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -0,0 +1,10 @@ +package scientifik.kmath.linear + +class VirtualMatrix( + override val rowNum: Int, + override val colNum: Int, + override val features: Set = emptySet(), + val generator: (i: Int, j: Int) -> T +) : Matrix { + override fun get(i: Int, j: Int): T = generator(i, j) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 1534f4c82..8aa3a0e05 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -33,8 +33,8 @@ interface Space { operator fun T.times(k: Number) = multiply(this, k.toDouble()) operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun Number.times(b: T) = b * this - fun Iterable.sum(): T = fold(zero) { left, right -> add(left,right) } + fun Iterable.sum(): T = fold(zero) { left, right -> add(left,right) } fun Sequence.sum(): T = fold(zero) { left, right -> add(left, right) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index f7c04bdbe..a8a9d7ed3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -95,6 +95,8 @@ inline class ListBuffer(private val list: List) : Buffer { override fun iterator(): Iterator = list.iterator() } +fun List.asBuffer() = ListBuffer(this) + inline class MutableListBuffer(private val list: MutableList) : MutableBuffer { override val size: Int @@ -191,6 +193,25 @@ inline class ReadOnlyBuffer(private val buffer: MutableBuffer) : Buffer override fun iterator(): Iterator = buffer.iterator() } +/** + * A buffer with content calculated on-demand. The calculated contect is not stored, so it is recalculated on each call. + * Useful when one needs single element from the buffer. + */ +class VirtualBuffer(override val size: Int, private val generator: (Int) -> T) : Buffer { + override fun get(index: Int): T = generator(index) + + override fun iterator(): Iterator = (0 until size).asSequence().map(generator).iterator() + + override fun contentEquals(other: Buffer<*>): Boolean { + return if (other is VirtualBuffer) { + this.size == other.size && this.generator == other.generator + } else { + super.contentEquals(other) + } + } + +} + /** * Convert this buffer to read-only buffer */ diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index d02cb2c6f..fbbe06b4f 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -22,7 +22,7 @@ class MatrixTest { @Test fun testTranspose() { - val matrix = MatrixSpace.real(3, 3).one + val matrix = MatrixContext.real(3, 3).one val transposed = matrix.transpose() assertEquals(matrix.context, transposed.context) assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 569b9e450..f167f2f8f 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class RealLUSolverTest { @Test fun testInvertOne() { - val matrix = MatrixSpace.real(2, 2).one + val matrix = MatrixContext.real(2, 2).one val inverted = RealLUSolver.inverse(matrix) assertEquals(matrix, inverted) } From 87e8566157f824d2b84144a289ea556181f63199 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 16 Jan 2019 15:18:26 +0300 Subject: [PATCH 33/70] Added internal mapping functionality to buffers. --- .../kmath/linear/LUDecomposition.kt | 104 +++++++++--------- .../scientifik/kmath/structures/Buffers.kt | 88 ++++++++++++++- 2 files changed, 139 insertions(+), 53 deletions(-) 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 7b645c74b..62e9ba903 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -188,57 +188,57 @@ class LUPDecompositionBuilder, F : Field>(val context: F, v } -//class LUSolver, F : Field>(val singularityCheck: (T) -> Boolean) : LinearSolver { -// -// -// override fun solve(a: Matrix, b: Matrix): Matrix { -// val decomposition = LUPDecompositionBuilder(ring, singularityCheck).decompose(a) -// -// if (b.rowNum != a.colNum) { -// error("Matrix dimension mismatch expected ${a.rowNum}, but got ${b.colNum}") -// } -// -// -//// val bp = Array(a.rowNum) { Array(b.colNum){ring.zero} } -//// for (row in 0 until a.rowNum) { -//// val bpRow = bp[row] -//// val pRow = decomposition.pivot[row] -//// for (col in 0 until b.colNum) { -//// bpRow[col] = b[pRow, col] -//// } -//// } -// -// // Apply permutations to b -// val bp = produce(a.rowNum, a.colNum) { i, j -> b[decomposition.pivot[i], j] } -// -// // Solve LY = b -// for (col in 0 until a.rowNum) { -// val bpCol = bp[col] -// for (i in col + 1 until a.rowNum) { -// val bpI = bp[i] -// val luICol = decomposition.lu[i, col] -// for (j in 0 until b.colNum) { -// bpI[j] -= bpCol[j] * luICol -// } +class LUSolver, F : Field>(val singularityCheck: (T) -> Boolean) : LinearSolver { + + + override fun solve(a: Matrix, b: Matrix): Matrix { + val decomposition = LUPDecompositionBuilder(ring, singularityCheck).decompose(a) + + if (b.rowNum != a.colNum) { + error("Matrix dimension mismatch expected ${a.rowNum}, but got ${b.colNum}") + } + + +// val bp = Array(a.rowNum) { Array(b.colNum){ring.zero} } +// for (row in 0 until a.rowNum) { +// val bpRow = bp[row] +// val pRow = decomposition.pivot[row] +// for (col in 0 until b.colNum) { +// bpRow[col] = b[pRow, col] // } // } -// -// // Solve UX = Y -// for (col in a.rowNum - 1 downTo 0) { -// val bpCol = bp[col] -// val luDiag = decomposition.lu[col, col] -// for (j in 0 until b.colNum) { -// bpCol[j] /= luDiag -// } -// for (i in 0 until col) { -// val bpI = bp[i] -// val luICol = decomposition.lu[i, col] -// for (j in 0 until b.colNum) { -// bpI[j] -= bpCol[j] * luICol -// } -// } -// } -// -// return produce(a.rowNum, a.colNum) { i, j -> bp[i][j] } -// } -//} + + // Apply permutations to b + val bp = produce(a.rowNum, a.colNum) { i, j -> b[decomposition.pivot[i], j] } + + // Solve LY = b + for (col in 0 until a.rowNum) { + val bpCol = bp[col] + for (i in col + 1 until a.rowNum) { + val bpI = bp[i] + val luICol = decomposition.lu[i, col] + for (j in 0 until b.colNum) { + bpI[j] -= bpCol[j] * luICol + } + } + } + + // Solve UX = Y + for (col in a.rowNum - 1 downTo 0) { + val bpCol = bp[col] + val luDiag = decomposition.lu[col, col] + for (j in 0 until b.colNum) { + bpCol[j] /= luDiag + } + for (i in 0 until col) { + val bpI = bp[i] + val luICol = decomposition.lu[i, col] + for (j in 0 until b.colNum) { + bpI[j] -= bpCol[j] * luICol + } + } + } + + return produce(a.rowNum, a.colNum) { i, j -> bp[i][j] } + } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index a8a9d7ed3..14fcfc2ff 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -10,15 +10,32 @@ typealias MutableBufferFactory = (Int, (Int) -> T) -> MutableBuffer */ interface Buffer { + /** + * The size of the buffer + */ val size: Int + /** + * Get element at given index + */ operator fun get(index: Int): T + /** + * Iterate over all elements + */ operator fun iterator(): Iterator + /** + * Check content eqiality with another buffer + */ fun contentEquals(other: Buffer<*>): Boolean = asSequence().mapIndexed { index, value -> value == other[index] }.all { it } + /** + * Map the contents of the buffer to new buffer of the same type + */ + fun transform(transformation: (index: Int, value: T) -> T): Buffer + companion object { /** @@ -61,6 +78,8 @@ interface MutableBuffer : Buffer { */ fun copy(): MutableBuffer + fun transformInPlace(transformation: (index: Int, value: T) -> T) + companion object { /** * Create a boxing mutable buffer of given type @@ -93,6 +112,9 @@ inline class ListBuffer(private val list: List) : Buffer { override fun get(index: Int): T = list[index] override fun iterator(): Iterator = list.iterator() + + override fun transform(transformation: (index: Int, value: T) -> T): ListBuffer = + list.mapIndexed(transformation).asBuffer() } fun List.asBuffer() = ListBuffer(this) @@ -110,11 +132,20 @@ inline class MutableListBuffer(private val list: MutableList) : MutableBuf override fun iterator(): Iterator = list.iterator() + override fun transform(transformation: (index: Int, value: T) -> T): ListBuffer = + list.mapIndexed(transformation).asBuffer() + override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) + + override fun transformInPlace(transformation: (index: Int, value: T) -> T) = list.forEachIndexed { index, value -> + list[index] = transformation(index, value) + } } +fun MutableList.asBuffer() = MutableListBuffer(this) + class ArrayBuffer(private val array: Array) : MutableBuffer { - //Can't inline because array invariant + //Can't inline because array is invariant override val size: Int get() = array.size @@ -127,8 +158,18 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) + + override fun transform(transformation: (index: Int, value: T) -> T): ArrayBuffer = + Array(size) { index -> transformation(index, get(index)) }.asBuffer() + + + override fun transformInPlace(transformation: (index: Int, value: T) -> T) = array.forEachIndexed { index, value -> + array[index] = transformation(index, value) + } } +fun Array.asBuffer() = ArrayBuffer(this) + inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { override val size: Int get() = array.size @@ -141,8 +182,18 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer = array.iterator() override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) + + override fun transform(transformation: (index: Int, value: Double) -> Double): DoubleBuffer = + DoubleArray(size) { index -> transformation(index, get(index)) }.asBuffer() + + override fun transformInPlace(transformation: (index: Int, value: Double) -> Double) = + array.forEachIndexed { index, value -> + array[index] = transformation(index, value) + } } +fun DoubleArray.asBuffer() = DoubleBuffer(this) + inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { override val size: Int get() = array.size @@ -155,8 +206,18 @@ inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = ShortBuffer(array.copyOf()) + + override fun transform(transformation: (index: Int, value: Short) -> Short): ShortBuffer = + ShortArray(size) { index -> transformation(index, get(index)) }.asBuffer() + + override fun transformInPlace(transformation: (index: Int, value: Short) -> Short) = + array.forEachIndexed { index, value -> + array[index] = transformation(index, value) + } } +fun ShortArray.asBuffer() = ShortBuffer(this) + inline class IntBuffer(private val array: IntArray) : MutableBuffer { override val size: Int get() = array.size @@ -169,8 +230,18 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer { override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) + + override fun transform(transformation: (index: Int, value: Int) -> Int): IntBuffer = + IntArray(size) { index -> transformation(index, get(index)) }.asBuffer() + + override fun transformInPlace(transformation: (index: Int, value: Int) -> Int) = + array.forEachIndexed { index, value -> + array[index] = transformation(index, value) + } } +fun IntArray.asBuffer() = IntBuffer(this) + inline class LongBuffer(private val array: LongArray) : MutableBuffer { override val size: Int get() = array.size @@ -183,14 +254,26 @@ inline class LongBuffer(private val array: LongArray) : MutableBuffer { override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = LongBuffer(array.copyOf()) + + override fun transform(transformation: (index: Int, value: Long) -> Long): LongBuffer = + LongArray(size) { index -> transformation(index, get(index)) }.asBuffer() + + override fun transformInPlace(transformation: (index: Int, value: Long) -> Long) = + array.forEachIndexed { index, value -> + array[index] = transformation(index, value) + } } +fun LongArray.asBuffer() = LongBuffer(this) + inline class ReadOnlyBuffer(private val buffer: MutableBuffer) : Buffer { override val size: Int get() = buffer.size override fun get(index: Int): T = buffer.get(index) override fun iterator(): Iterator = buffer.iterator() + + override fun transform(transformation: (index: Int, value: T) -> T): Buffer = buffer.transform(transformation) } /** @@ -210,6 +293,9 @@ class VirtualBuffer(override val size: Int, private val generator: (Int) -> T } } + // TODO this composition could become very compex very fast, replace it by boxed generator? + override fun transform(transformation: (index: Int, value: T) -> T): Buffer = + VirtualBuffer(size) { index -> transformation(index, generator(index)) } } /** From 58e939e0cf727fd3cc8a61223004db401c85107d Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 16 Jan 2019 15:51:28 +0300 Subject: [PATCH 34/70] Revert "Added internal mapping functionality to buffers" --- .../kmath/structures/BoxingNDField.kt | 8 +++ .../scientifik/kmath/structures/Buffers.kt | 60 ------------------- .../kmath/structures/NDStructure.kt | 2 +- 3 files changed, 9 insertions(+), 61 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt index e06119766..c1e1bcc38 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BoxingNDField.kt @@ -32,6 +32,10 @@ class BoxingNDField>( return BufferedNDFieldElement( this, buildBuffer(arg.strides.linearSize) { offset -> elementContext.transform(arg.buffer[offset]) }) + +// val buffer = arg.buffer.transform { _, value -> elementContext.transform(value) } +// return BufferedNDFieldElement(this, buffer) + } override fun mapIndexed( @@ -47,6 +51,10 @@ class BoxingNDField>( arg.buffer[offset] ) }) + +// val buffer = +// arg.buffer.transform { offset, value -> elementContext.transform(arg.strides.index(offset), value) } +// return BufferedNDFieldElement(this, buffer) } override fun combine( diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 14fcfc2ff..1cd2dc502 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -31,11 +31,6 @@ interface Buffer { fun contentEquals(other: Buffer<*>): Boolean = asSequence().mapIndexed { index, value -> value == other[index] }.all { it } - /** - * Map the contents of the buffer to new buffer of the same type - */ - fun transform(transformation: (index: Int, value: T) -> T): Buffer - companion object { /** @@ -78,8 +73,6 @@ interface MutableBuffer : Buffer { */ fun copy(): MutableBuffer - fun transformInPlace(transformation: (index: Int, value: T) -> T) - companion object { /** * Create a boxing mutable buffer of given type @@ -112,9 +105,6 @@ inline class ListBuffer(private val list: List) : Buffer { override fun get(index: Int): T = list[index] override fun iterator(): Iterator = list.iterator() - - override fun transform(transformation: (index: Int, value: T) -> T): ListBuffer = - list.mapIndexed(transformation).asBuffer() } fun List.asBuffer() = ListBuffer(this) @@ -131,15 +121,7 @@ inline class MutableListBuffer(private val list: MutableList) : MutableBuf } override fun iterator(): Iterator = list.iterator() - - override fun transform(transformation: (index: Int, value: T) -> T): ListBuffer = - list.mapIndexed(transformation).asBuffer() - override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) - - override fun transformInPlace(transformation: (index: Int, value: T) -> T) = list.forEachIndexed { index, value -> - list[index] = transformation(index, value) - } } fun MutableList.asBuffer() = MutableListBuffer(this) @@ -158,14 +140,6 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) - - override fun transform(transformation: (index: Int, value: T) -> T): ArrayBuffer = - Array(size) { index -> transformation(index, get(index)) }.asBuffer() - - - override fun transformInPlace(transformation: (index: Int, value: T) -> T) = array.forEachIndexed { index, value -> - array[index] = transformation(index, value) - } } fun Array.asBuffer() = ArrayBuffer(this) @@ -183,13 +157,6 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer = DoubleBuffer(array.copyOf()) - override fun transform(transformation: (index: Int, value: Double) -> Double): DoubleBuffer = - DoubleArray(size) { index -> transformation(index, get(index)) }.asBuffer() - - override fun transformInPlace(transformation: (index: Int, value: Double) -> Double) = - array.forEachIndexed { index, value -> - array[index] = transformation(index, value) - } } fun DoubleArray.asBuffer() = DoubleBuffer(this) @@ -207,13 +174,6 @@ inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { override fun copy(): MutableBuffer = ShortBuffer(array.copyOf()) - override fun transform(transformation: (index: Int, value: Short) -> Short): ShortBuffer = - ShortArray(size) { index -> transformation(index, get(index)) }.asBuffer() - - override fun transformInPlace(transformation: (index: Int, value: Short) -> Short) = - array.forEachIndexed { index, value -> - array[index] = transformation(index, value) - } } fun ShortArray.asBuffer() = ShortBuffer(this) @@ -231,13 +191,6 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer { override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) - override fun transform(transformation: (index: Int, value: Int) -> Int): IntBuffer = - IntArray(size) { index -> transformation(index, get(index)) }.asBuffer() - - override fun transformInPlace(transformation: (index: Int, value: Int) -> Int) = - array.forEachIndexed { index, value -> - array[index] = transformation(index, value) - } } fun IntArray.asBuffer() = IntBuffer(this) @@ -255,13 +208,6 @@ inline class LongBuffer(private val array: LongArray) : MutableBuffer { override fun copy(): MutableBuffer = LongBuffer(array.copyOf()) - override fun transform(transformation: (index: Int, value: Long) -> Long): LongBuffer = - LongArray(size) { index -> transformation(index, get(index)) }.asBuffer() - - override fun transformInPlace(transformation: (index: Int, value: Long) -> Long) = - array.forEachIndexed { index, value -> - array[index] = transformation(index, value) - } } fun LongArray.asBuffer() = LongBuffer(this) @@ -272,8 +218,6 @@ inline class ReadOnlyBuffer(private val buffer: MutableBuffer) : Buffer override fun get(index: Int): T = buffer.get(index) override fun iterator(): Iterator = buffer.iterator() - - override fun transform(transformation: (index: Int, value: T) -> T): Buffer = buffer.transform(transformation) } /** @@ -292,10 +236,6 @@ class VirtualBuffer(override val size: Int, private val generator: (Int) -> T super.contentEquals(other) } } - - // TODO this composition could become very compex very fast, replace it by boxed generator? - override fun transform(transformation: (index: Int, value: T) -> T): Buffer = - VirtualBuffer(size) { index -> transformation(index, generator(index)) } } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index d9601a5d4..6c545d1af 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -200,7 +200,7 @@ fun ndStructure(strides: Strides, bufferFactory: BufferFactory = Buffer.C * Inline create NDStructure with non-boxing buffer implementation if it is possible */ inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, Buffer.Companion.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) + BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T) = ndStructure(DefaultStrides(shape), bufferFactory, initializer) From cedb8a816e39896f76948cd384c06a55acef5832 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 16 Jan 2019 19:13:09 +0300 Subject: [PATCH 35/70] LUDecomposition finished. Not tested --- build.gradle.kts | 2 +- .../kmath/linear/LUDecomposition.kt | 102 +++++++++--------- .../scientifik/kmath/linear/LinearAlgrebra.kt | 26 +++-- .../kotlin/scientifik/kmath/linear/Matrix.kt | 20 ++-- .../kmath/linear/Mutable2DStructure.kt | 40 +++++++ .../kmath/linear/StructureMatrix.kt | 2 +- .../scientifik/kmath/structures/Buffers.kt | 2 - .../scientifik/kmath/linear/MatrixTest.kt | 5 +- .../kmath/linear/RealLUSolverTest.kt | 4 +- 9 files changed, 121 insertions(+), 82 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt diff --git a/build.gradle.kts b/build.gradle.kts index 37e61237e..21656f173 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ buildscript { - extra["kotlinVersion"] = "1.3.20-eap-52" + extra["kotlinVersion"] = "1.3.20-eap-100" extra["ioVersion"] = "0.1.2" extra["coroutinesVersion"] = "1.1.0" 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 62e9ba903..651613489 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -57,7 +57,7 @@ private class LUPDecomposition, R : Ring>( * U is an upper-triangular matrix * @return the U matrix (or null if decomposed matrix is singular) */ - override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> if (j >= i) lu[i, j] else context.zero } @@ -93,7 +93,11 @@ private class LUPDecomposition, R : Ring>( /** * Implementation based on Apache common-maths LU-decomposition */ -class LUPDecompositionBuilder, F : Field>(val context: F, val bufferFactory: MutableBufferFactory = ::boxing, val singularityCheck: (T) -> Boolean) { +class LUPDecompositionBuilder, F : Field>( + val context: F, + val bufferFactory: MutableBufferFactory = ::boxing, + val singularityCheck: (T) -> Boolean +) { /** * In-place transformation for [MutableNDStructure], using given transformation for each element @@ -105,23 +109,16 @@ class LUPDecompositionBuilder, F : Field>(val context: F, v private fun abs(value: T) = if (value > context.zero) value else with(context) { -value } fun decompose(matrix: Matrix): LUPDecompositionFeature { - // Use existing decomposition if it is provided by matrix - matrix.features.find { it is LUPDecompositionFeature<*> }?.let { - @Suppress("UNCHECKED_CAST") - return it as LUPDecompositionFeature - } - if (matrix.rowNum != matrix.colNum) { error("LU decomposition supports only square matrices") } val m = matrix.colNum val pivot = IntArray(matrix.rowNum) - //TODO replace by custom optimized 2d structure - val lu: MutableNDStructure = mutableNdStructure( - intArrayOf(matrix.rowNum, matrix.colNum), - bufferFactory - ) { index: IntArray -> matrix[index[0], index[1]] } + + val lu = Mutable2DStructure.create(matrix.rowNum, matrix.colNum, bufferFactory) { i, j -> + matrix[i, j] + } with(context) { @@ -188,57 +185,56 @@ class LUPDecompositionBuilder, F : Field>(val context: F, v } -class LUSolver, F : Field>(val singularityCheck: (T) -> Boolean) : LinearSolver { +class LUSolver, F : Field>( + override val context: MatrixContext, + val bufferFactory: MutableBufferFactory = ::boxing, + val singularityCheck: (T) -> Boolean +) : LinearSolver { override fun solve(a: Matrix, b: Matrix): Matrix { - val decomposition = LUPDecompositionBuilder(ring, singularityCheck).decompose(a) - if (b.rowNum != a.colNum) { error("Matrix dimension mismatch expected ${a.rowNum}, but got ${b.colNum}") } + // Use existing decomposition if it is provided by matrix + @Suppress("UNCHECKED_CAST") + val decomposition = a.features.find { it is LUPDecompositionFeature<*> }?.let { + it as LUPDecompositionFeature + } ?: LUPDecompositionBuilder(context.elementContext, bufferFactory, singularityCheck).decompose(a) -// val bp = Array(a.rowNum) { Array(b.colNum){ring.zero} } -// for (row in 0 until a.rowNum) { -// val bpRow = bp[row] -// val pRow = decomposition.pivot[row] -// for (col in 0 until b.colNum) { -// bpRow[col] = b[pRow, col] -// } -// } - - // Apply permutations to b - val bp = produce(a.rowNum, a.colNum) { i, j -> b[decomposition.pivot[i], j] } - - // Solve LY = b - for (col in 0 until a.rowNum) { - val bpCol = bp[col] - for (i in col + 1 until a.rowNum) { - val bpI = bp[i] - val luICol = decomposition.lu[i, col] - for (j in 0 until b.colNum) { - bpI[j] -= bpCol[j] * luICol + with(decomposition) { + with(context.elementContext) { + // Apply permutations to b + val bp = Mutable2DStructure.create(a.rowNum, a.colNum, bufferFactory) { i, j -> + b[pivot[i], j] } + + // Solve LY = b + for (col in 0 until a.rowNum) { + for (i in col + 1 until a.rowNum) { + for (j in 0 until b.colNum) { + bp[i, j] -= bp[col, j] * l[i, col] + } + } + } + + + // Solve UX = Y + for (col in a.rowNum - 1 downTo 0) { + for (i in 0 until col) { + for (j in 0 until b.colNum) { + bp[i, j] -= bp[col, j] / u[col, col] * u[i, col] + } + } + } + + return context.produce(a.rowNum, a.colNum) { i, j -> bp[i, j] } } } + } - // Solve UX = Y - for (col in a.rowNum - 1 downTo 0) { - val bpCol = bp[col] - val luDiag = decomposition.lu[col, col] - for (j in 0 until b.colNum) { - bpCol[j] /= luDiag - } - for (i in 0 until col) { - val bpI = bp[i] - val luICol = decomposition.lu[i, col] - for (j in 0 until b.colNum) { - bpI[j] -= bpCol[j] * luICol - } - } - } - - return produce(a.rowNum, a.colNum) { i, j -> bp[i][j] } + companion object { + val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } } } 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 56ee448ab..fb542c16b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -4,26 +4,20 @@ import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.VirtualBuffer import scientifik.kmath.structures.asSequence /** * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors */ -interface LinearSolver> : MatrixContext { - /** - * Convert matrix to vector if it is possible - */ - fun Matrix.toVector(): Point = - if (this.colNum == 1) { - point(rowNum){ get(it, 0) } - } else error("Can't convert matrix with more than one column to vector") +interface LinearSolver> { + val context: MatrixContext - fun Point.toMatrix(): Matrix = produce(size, 1) { i, _ -> get(i) } fun solve(a: Matrix, b: Matrix): Matrix fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).toVector() - fun inverse(a: Matrix): Matrix = solve(a, one(a.rowNum, a.colNum)) + fun inverse(a: Matrix): Matrix = solve(a, context.one(a.rowNum, a.colNum)) } /** @@ -41,3 +35,15 @@ object VectorL2Norm : Norm, Double> { typealias RealVector = Vector typealias RealMatrix = Matrix + + + +/** + * Convert matrix to vector if it is possible + */ +fun Matrix.toVector(): Point = + if (this.colNum == 1) { + VirtualBuffer(rowNum){ get(it, 0) } + } else error("Can't convert matrix with more than one column to vector") + +fun Point.toMatrix(): Matrix = VirtualMatrix(size, 1) { i, _ -> get(i) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index e7b791a86..8cd9a6b8f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -11,7 +11,7 @@ interface MatrixContext> { /** * The ring context for matrix elements */ - val ring: R + val elementContext: R /** * Produce a matrix with this context and given dimensions @@ -25,7 +25,7 @@ interface MatrixContext> { fun scale(a: Matrix, k: Number): Matrix { //TODO create a special wrapper class for scaled matrices - return produce(a.rowNum, a.colNum) { i, j -> ring.run { a[i, j] * k } } + return produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a[i, j] * k } } } infix fun Matrix.dot(other: Matrix): Matrix { @@ -34,7 +34,7 @@ interface MatrixContext> { return produce(rowNum, other.colNum) { i, j -> val row = rows[i] val column = other.columns[j] - with(ring) { + with(elementContext) { row.asSequence().zip(column.asSequence(), ::multiply).sum() } } @@ -45,27 +45,27 @@ interface MatrixContext> { if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})") return point(rowNum) { i -> val row = rows[i] - with(ring) { + with(elementContext) { row.asSequence().zip(vector.asSequence(), ::multiply).sum() } } } operator fun Matrix.unaryMinus() = - produce(rowNum, colNum) { i, j -> ring.run { -get(i, j) } } + produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } } operator fun Matrix.plus(b: Matrix): Matrix { if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] + [${b.rowNum},${b.colNum}]") - return produce(rowNum, colNum) { i, j -> ring.run { get(i, j) + b[i, j] } } + return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } } operator fun Matrix.minus(b: Matrix): Matrix { if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]") - return produce(rowNum, colNum) { i, j -> ring.run { get(i, j) + b[i, j] } } + return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } } operator fun Matrix.times(number: Number): Matrix = - produce(rowNum, colNum) { i, j -> ring.run { get(i, j) * number } } + produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } } operator fun Number.times(m: Matrix): Matrix = m * this @@ -157,7 +157,7 @@ fun > MatrixContext.one(rows: Int, columns: Int): Mat override val rowNum: Int get() = rows override val colNum: Int get() = columns override val features: Set get() = setOf(DiagonalFeature, UnitFeature) - override fun get(i: Int, j: Int): T = if (i == j) ring.one else ring.zero + override fun get(i: Int, j: Int): T = if (i == j) elementContext.one else elementContext.zero } } @@ -169,7 +169,7 @@ fun > MatrixContext.zero(rows: Int, columns: Int): Ma override val rowNum: Int get() = rows override val colNum: Int get() = columns override val features: Set get() = setOf(ZeroFeature) - override fun get(i: Int, j: Int): T = ring.zero + override fun get(i: Int, j: Int): T = elementContext.zero } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt new file mode 100644 index 000000000..0d35c57e6 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Mutable2DStructure.kt @@ -0,0 +1,40 @@ +package scientifik.kmath.linear + +import scientifik.kmath.structures.MutableBuffer +import scientifik.kmath.structures.MutableBufferFactory +import scientifik.kmath.structures.MutableNDStructure + +class Mutable2DStructure(val rowNum: Int, val colNum: Int, val buffer: MutableBuffer) : MutableNDStructure { + override val shape: IntArray + get() = intArrayOf(rowNum, colNum) + + operator fun get(i: Int, j: Int): T = buffer[i * colNum + j] + + override fun get(index: IntArray): T = get(index[0], index[1]) + + override fun elements(): Sequence> = sequence { + for (i in 0 until rowNum) { + for (j in 0 until colNum) { + yield(intArrayOf(i, j) to get(i, j)) + } + } + } + + operator fun set(i: Int, j: Int, value: T) { + buffer[i * colNum + j] = value + } + + override fun set(index: IntArray, value: T) = set(index[0], index[1], value) + + companion object { + fun create( + rowNum: Int, + colNum: Int, + bufferFactory: MutableBufferFactory, + init: (i: Int, j: Int) -> T + ): Mutable2DStructure { + val buffer = bufferFactory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } + return Mutable2DStructure(rowNum, colNum, buffer) + } + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt index a8a1d692c..1109297be 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt @@ -10,7 +10,7 @@ import scientifik.kmath.structures.ndStructure * Basic implementation of Matrix space based on [NDStructure] */ class StructureMatrixContext>( - override val ring: R, + override val elementContext: R, private val bufferFactory: BufferFactory ) : MatrixContext { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 1cd2dc502..8f77c1d81 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -124,8 +124,6 @@ inline class MutableListBuffer(private val list: MutableList) : MutableBuf override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) } -fun MutableList.asBuffer() = MutableListBuffer(this) - class ArrayBuffer(private val array: Array) : MutableBuffer { //Can't inline because array is invariant override val size: Int diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index fbbe06b4f..28934ab7f 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -22,9 +22,8 @@ class MatrixTest { @Test fun testTranspose() { - val matrix = MatrixContext.real(3, 3).one + val matrix = MatrixContext.real.one(3, 3) val transposed = matrix.transpose() - assertEquals(matrix.context, transposed.context) assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure) assertEquals(matrix, transposed) } @@ -37,7 +36,7 @@ class MatrixTest { val matrix1 = vector1.toMatrix() val matrix2 = vector2.toMatrix().transpose() - val product = matrix1 dot matrix2 + val product = MatrixContext.real.run { matrix1 dot matrix2 } assertEquals(5.0, product[1, 0]) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index f167f2f8f..daf749e8a 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -6,8 +6,8 @@ import kotlin.test.assertEquals class RealLUSolverTest { @Test fun testInvertOne() { - val matrix = MatrixContext.real(2, 2).one - val inverted = RealLUSolver.inverse(matrix) + val matrix = MatrixContext.real.one(2, 2) + val inverted = LUSolver.real.inverse(matrix) assertEquals(matrix, inverted) } From 271d7886df6c84a920eac5b88c811ede231c84f4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 16 Jan 2019 19:41:36 +0300 Subject: [PATCH 36/70] Added equality to matrices --- .../kotlin/scientifik/kmath/linear/Matrix.kt | 19 +++++------------- .../kmath/linear/StructureMatrix.kt | 19 +++++++++++++++++- .../scientifik/kmath/linear/VirtualMatrix.kt | 20 +++++++++++++++++++ .../kmath/linear/RealLUSolverTest.kt | 4 ++-- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 8cd9a6b8f..d9a14864b 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -152,25 +152,16 @@ interface Matrix : NDStructure { /** * Diagonal matrix of ones. The matrix is virtual no actual matrix is created */ -fun > MatrixContext.one(rows: Int, columns: Int): Matrix { - return object : Matrix { - override val rowNum: Int get() = rows - override val colNum: Int get() = columns - override val features: Set get() = setOf(DiagonalFeature, UnitFeature) - override fun get(i: Int, j: Int): T = if (i == j) elementContext.one else elementContext.zero - } +fun > MatrixContext.one(rows: Int, columns: Int): Matrix = VirtualMatrix(rows,columns){i, j-> + if (i == j) elementContext.one else elementContext.zero } + /** * A virtual matrix of zeroes */ -fun > MatrixContext.zero(rows: Int, columns: Int): Matrix { - return object : Matrix { - override val rowNum: Int get() = rows - override val colNum: Int get() = columns - override val features: Set get() = setOf(ZeroFeature) - override fun get(i: Int, j: Int): T = elementContext.zero - } +fun > MatrixContext.zero(rows: Int, columns: Int): Matrix = VirtualMatrix(rows,columns){i, j-> + elementContext.zero } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt index 1109297be..aa061fae2 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt @@ -23,7 +23,7 @@ class StructureMatrixContext>( override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) } -data class StructureMatrix( +class StructureMatrix( val structure: NDStructure, override val features: Set = emptySet() ) : Matrix { @@ -47,4 +47,21 @@ data class StructureMatrix( override fun get(i: Int, j: Int): T = structure[i, j] override fun elements(): Sequence> = structure.elements() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return when (other) { + is StructureMatrix<*> -> return this.structure == other.structure + is Matrix<*> -> elements().all { (index, value) -> value == other[index] } + else -> false + } + } + + override fun hashCode(): Int { + var result = structure.hashCode() + result = 31 * result + features.hashCode() + return result + } + + } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt index 59c89b197..cb7b0a08e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -7,4 +7,24 @@ class VirtualMatrix( val generator: (i: Int, j: Int) -> T ) : Matrix { override fun get(i: Int, j: Int): T = generator(i, j) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Matrix<*>) return false + + if (rowNum != other.rowNum) return false + if (colNum != other.colNum) return false + + return elements().all { (index, value) -> value == other[index] } + } + + override fun hashCode(): Int { + var result = rowNum + result = 31 * result + colNum + result = 31 * result + features.hashCode() + result = 31 * result + generator.hashCode() + return result + } + + } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index daf749e8a..54a308761 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -14,7 +14,7 @@ class RealLUSolverTest { // @Test // fun testInvert() { // val matrix = realMatrix(2,2){} -// val inverted = RealLUSolver.inverse(matrix) -// assertTrue { Matrix.equals(matrix,inverted) } +// val inverted = LUSolver.real.inverse(matrix) +// assertEquals(matrix, inverted) // } } \ No newline at end of file From b4e8b9050f1cd1f864c50017057fee2f13f7c650 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 17 Jan 2019 12:42:56 +0300 Subject: [PATCH 37/70] Fixed LUP solver implementation --- ...LUDecomposition.kt => LUPDecomposition.kt} | 114 +++++++----------- .../kotlin/scientifik/kmath/linear/Matrix.kt | 58 +++++---- .../kmath/linear/StructureMatrix.kt | 21 ++-- .../scientifik/kmath/linear/VirtualMatrix.kt | 14 +++ .../kmath/structures/NDStructure.kt | 26 +++- .../kmath/linear/RealLUSolverTest.kt | 33 ++++- 6 files changed, 154 insertions(+), 112 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/{LUDecomposition.kt => LUPDecomposition.kt} (65%) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt similarity index 65% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 651613489..dbc45618d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -1,52 +1,28 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field -import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* import scientifik.kmath.structures.MutableBuffer.Companion.boxing -/** - * Matrix LUP decomposition - */ -interface LUPDecompositionFeature : DeterminantFeature { - /** - * A reference to L-matrix - */ - val l: Matrix - /** - * A reference to u-matrix - */ - val u: Matrix - /** - * Pivoting points for each row - */ - val pivot: IntArray - /** - * Permutation matrix based on [pivot] - */ - val p: Matrix -} - -private class LUPDecomposition, R : Ring>( - val context: R, - val lu: NDStructure, - override val pivot: IntArray, +class LUPDecomposition>( + private val elementContext: Ring, + private val lu: NDStructure, + val pivot: IntArray, private val even: Boolean -) : LUPDecompositionFeature { +) : DeterminantFeature { /** * Returns the matrix L of the decomposition. * - * L is a lower-triangular matrix - * @return the L matrix (or null if decomposed matrix is singular) + * L is a lower-triangular matrix with [Ring.one] in diagonal */ - override val l: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + val l: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> when { j < i -> lu[i, j] - j == i -> context.one - else -> context.zero + j == i -> elementContext.one + else -> elementContext.zero } } @@ -54,26 +30,21 @@ private class LUPDecomposition, R : Ring>( /** * Returns the matrix U of the decomposition. * - * U is an upper-triangular matrix - * @return the U matrix (or null if decomposed matrix is singular) + * U is an upper-triangular matrix including the diagonal */ - override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> - if (j >= i) lu[i, j] else context.zero + val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + if (j >= i) lu[i, j] else elementContext.zero } /** * Returns the P rows permutation matrix. * - * P is a sparse matrix with exactly one element set to 1.0 in - * each row and each column, all other elements being set to 0.0. - * - * The positions of the 1 elements are given by the [ pivot permutation vector][.getPivot]. - * @return the P rows permutation matrix (or null if decomposed matrix is singular) - * @see .getPivot + * P is a sparse matrix with exactly one element set to [Ring.one] in + * each row and each column, all other elements being set to [Ring.zero]. */ - override val p: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> - if (j == pivot[i]) context.one else context.zero + val p: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + if (j == pivot[i]) elementContext.one else elementContext.zero } @@ -82,7 +53,7 @@ private class LUPDecomposition, R : Ring>( * @return determinant of the matrix */ override val determinant: T by lazy { - with(context) { + with(elementContext) { (0 until lu.shape[0]).fold(if (even) one else -one) { value, i -> value * lu[i, i] } } } @@ -90,14 +61,12 @@ private class LUPDecomposition, R : Ring>( } -/** - * Implementation based on Apache common-maths LU-decomposition - */ -class LUPDecompositionBuilder, F : Field>( - val context: F, +class LUSolver, F : Field>( + override val context: MatrixContext, val bufferFactory: MutableBufferFactory = ::boxing, val singularityCheck: (T) -> Boolean -) { +) : LinearSolver { + /** * In-place transformation for [MutableNDStructure], using given transformation for each element @@ -106,9 +75,10 @@ class LUPDecompositionBuilder, F : Field>( this[intArrayOf(i, j)] = value } - private fun abs(value: T) = if (value > context.zero) value else with(context) { -value } + private fun abs(value: T) = + if (value > context.elementContext.zero) value else with(context.elementContext) { -value } - fun decompose(matrix: Matrix): LUPDecompositionFeature { + fun buildDecomposition(matrix: Matrix): LUPDecomposition { if (matrix.rowNum != matrix.colNum) { error("LU decomposition supports only square matrices") } @@ -121,7 +91,7 @@ class LUPDecompositionBuilder, F : Field>( } - with(context) { + with(context.elementContext) { // Initialize permutation array and parity for (row in 0 until m) { pivot[row] = row @@ -174,23 +144,22 @@ class LUPDecompositionBuilder, F : Field>( lu[row, col] = lu[row, col] / luDiag } } - return LUPDecomposition(context, lu, pivot, even) + return LUPDecomposition(context.elementContext, lu, pivot, even) } } - companion object { - val real: LUPDecompositionBuilder = LUPDecompositionBuilder(RealField) { it < 1e-11 } + /** + * Produce a matrix with added decomposition feature + */ + fun decompose(matrix: Matrix): Matrix { + if (matrix.hasFeature>()) { + return matrix + } else { + val decomposition = buildDecomposition(matrix) + return VirtualMatrix.wrap(matrix, decomposition) + } } -} - - -class LUSolver, F : Field>( - override val context: MatrixContext, - val bufferFactory: MutableBufferFactory = ::boxing, - val singularityCheck: (T) -> Boolean -) : LinearSolver { - override fun solve(a: Matrix, b: Matrix): Matrix { if (b.rowNum != a.colNum) { @@ -198,10 +167,7 @@ class LUSolver, F : Field>( } // Use existing decomposition if it is provided by matrix - @Suppress("UNCHECKED_CAST") - val decomposition = a.features.find { it is LUPDecompositionFeature<*> }?.let { - it as LUPDecompositionFeature - } ?: LUPDecompositionBuilder(context.elementContext, bufferFactory, singularityCheck).decompose(a) + val decomposition = a.getFeature() ?: buildDecomposition(a) with(decomposition) { with(context.elementContext) { @@ -219,12 +185,14 @@ class LUSolver, F : Field>( } } - // Solve UX = Y for (col in a.rowNum - 1 downTo 0) { + for(j in 0 until b.colNum){ + bp[col,j]/= u[col,col] + } for (i in 0 until col) { for (j in 0 until b.colNum) { - bp[i, j] -= bp[col, j] / u[col, col] * u[i, col] + bp[i, j] -= bp[col, j] * u[i, col] } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index d9a14864b..6cd5e985f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -5,6 +5,7 @@ import scientifik.kmath.operations.Ring import scientifik.kmath.structures.* import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory import scientifik.kmath.structures.Buffer.Companion.boxing +import kotlin.math.sqrt interface MatrixContext> { @@ -146,43 +147,56 @@ interface Matrix : NDStructure { companion object { fun real(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = MatrixContext.real.produce(rows, columns, initializer) + + /** + * Build a square matrix from given elements. + */ + fun build(vararg elements: T): Matrix { + val buffer = elements.asBuffer() + val size: Int = sqrt(elements.size.toDouble()).toInt() + if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") + val structure = Mutable2DStructure(size, size, buffer) + return StructureMatrix(structure) + } } } +/** + * Check if matrix has the given feature class + */ +inline fun Matrix<*>.hasFeature(): Boolean = features.find { T::class.isInstance(it) } != null + +/** + * Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria + */ +inline fun Matrix<*>.getFeature(): T? = features.filterIsInstance().firstOrNull() + /** * Diagonal matrix of ones. The matrix is virtual no actual matrix is created */ -fun > MatrixContext.one(rows: Int, columns: Int): Matrix = VirtualMatrix(rows,columns){i, j-> - if (i == j) elementContext.one else elementContext.zero -} +fun > MatrixContext.one(rows: Int, columns: Int): Matrix = + VirtualMatrix(rows, columns) { i, j -> + if (i == j) elementContext.one else elementContext.zero + } /** * A virtual matrix of zeroes */ -fun > MatrixContext.zero(rows: Int, columns: Int): Matrix = VirtualMatrix(rows,columns){i, j-> - elementContext.zero -} +fun > MatrixContext.zero(rows: Int, columns: Int): Matrix = + VirtualMatrix(rows, columns) { i, j -> + elementContext.zero + } - -inline class TransposedMatrix(val original: Matrix) : Matrix { - override val rowNum: Int get() = original.colNum - override val colNum: Int get() = original.rowNum - override val features: Set get() = emptySet() //TODO retain some features - - override fun get(i: Int, j: Int): T = original[j, i] - - override fun elements(): Sequence> = - original.elements().map { (key, value) -> intArrayOf(key[1], key[0]) to value } -} +class TransposedFeature(val original: Matrix) : MatrixFeature /** * Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A` */ fun > Matrix.transpose(): Matrix { - return if (this is TransposedMatrix) { - original - } else { - TransposedMatrix(this) - } + return this.getFeature>()?.original ?: VirtualMatrix( + this.colNum, + this.rowNum, + setOf(TransposedFeature(this)) + ) { i, j -> get(j, i) } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt index aa061fae2..21876a9fe 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt @@ -1,10 +1,7 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Ring -import scientifik.kmath.structures.BufferFactory -import scientifik.kmath.structures.NDStructure -import scientifik.kmath.structures.get -import scientifik.kmath.structures.ndStructure +import scientifik.kmath.structures.* /** * Basic implementation of Matrix space based on [NDStructure] @@ -24,7 +21,7 @@ class StructureMatrixContext>( } class StructureMatrix( - val structure: NDStructure, + val structure: NDStructure, override val features: Set = emptySet() ) : Matrix { @@ -51,8 +48,7 @@ class StructureMatrix( override fun equals(other: Any?): Boolean { if (this === other) return true return when (other) { - is StructureMatrix<*> -> return this.structure == other.structure - is Matrix<*> -> elements().all { (index, value) -> value == other[index] } + is NDStructure<*> -> return NDStructure.equals(this, other) else -> false } } @@ -63,5 +59,16 @@ class StructureMatrix( return result } + override fun toString(): String { + return if (rowNum <= 5 && colNum <= 5) { + "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" + + rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") { + it.asSequence().joinToString(separator = "\t") { it.toString() } + } + } else { + "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)" + } + } + } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt index cb7b0a08e..98655ad48 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -27,4 +27,18 @@ class VirtualMatrix( } + companion object { + /** + * Wrap a matrix adding additional features to it + */ + fun wrap(matrix: Matrix, vararg features: MatrixFeature): Matrix { + return if (matrix is VirtualMatrix) { + VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features, matrix.generator) + } else { + VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features) { i, j -> + matrix[i, j] + } + } + } + } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 6c545d1af..4c437bf88 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -11,6 +11,16 @@ interface NDStructure { operator fun get(index: IntArray): T fun elements(): Sequence> + + companion object { + fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean { + return when { + st1 === st2 -> true + st1 is BufferNDStructure<*> && st2 is BufferNDStructure<*> && st1.strides == st2.strides -> st1.buffer.contentEquals(st2.buffer) + else -> st1.elements().all { (index, value) -> value == st2[index] } + } + } + } } operator fun NDStructure.get(vararg index: Int): T = get(index) @@ -140,7 +150,7 @@ interface NDBuffer : NDStructure { /** * Boxing generic [NDStructure] */ -data class BufferNDStructure( +class BufferNDStructure( override val strides: Strides, override val buffer: Buffer ) : NDBuffer { @@ -160,7 +170,7 @@ data class BufferNDStructure( override fun equals(other: Any?): Boolean { return when { this === other -> true - other is BufferNDStructure<*> -> this.strides == other.strides && this.buffer.contentEquals(other.buffer) + other is BufferNDStructure<*> && this.strides == other.strides -> this.buffer.contentEquals(other.buffer) other is NDStructure<*> -> elements().all { (index, value) -> value == other[index] } else -> false } @@ -193,7 +203,11 @@ inline fun NDStructure.mapToBuffer( * * Strides should be reused if possible */ -fun ndStructure(strides: Strides, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T) = +fun ndStructure( + strides: Strides, + bufferFactory: BufferFactory = Buffer.Companion::boxing, + initializer: (IntArray) -> T +) = BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) /** @@ -202,7 +216,11 @@ fun ndStructure(strides: Strides, bufferFactory: BufferFactory = Buffer.C inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) -fun ndStructure(shape: IntArray, bufferFactory: BufferFactory = Buffer.Companion::boxing, initializer: (IntArray) -> T) = +fun ndStructure( + shape: IntArray, + bufferFactory: BufferFactory = Buffer.Companion::boxing, + initializer: (IntArray) -> T +) = ndStructure(DefaultStrides(shape), bufferFactory, initializer) inline fun inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 54a308761..ea104355c 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -11,10 +11,31 @@ class RealLUSolverTest { assertEquals(matrix, inverted) } -// @Test -// fun testInvert() { -// val matrix = realMatrix(2,2){} -// val inverted = LUSolver.real.inverse(matrix) -// assertEquals(matrix, inverted) -// } + @Test + fun testInvert() { + val matrix = Matrix.build( + 3.0, 1.0, + 1.0, 3.0 + ) + + val decomposed = LUSolver.real.decompose(matrix) + val decomposition = decomposed.getFeature>()!! + + //Check determinant + assertEquals(8.0, decomposition.determinant) + + //Check decomposition + with(MatrixContext.real) { + assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u) + } + + val inverted = LUSolver.real.inverse(decomposed) + + val expected = Matrix.build( + 0.375, -0.125, + -0.125, 0.375 + ) + + assertEquals(expected, inverted) + } } \ No newline at end of file From a2a7ddcddaa400ccb7b6533fe4021a9583fbbf22 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 17 Jan 2019 19:26:17 +0300 Subject: [PATCH 38/70] Commons-math implementation for linear algebra --- benchmarks/build.gradle | 1 + .../kmath/linear/LinearAlgebraBenchmark.kt | 48 +++++++++++ .../scientifik/kmath/linear/CMMatrix.kt | 83 +++++++++++++++++++ .../kmath/linear/LUPDecomposition.kt | 28 +++---- .../scientifik/kmath/linear/LinearAlgrebra.kt | 5 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 4 +- 6 files changed, 149 insertions(+), 20 deletions(-) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt create mode 100644 kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index a9450a974..f732e2db6 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -7,6 +7,7 @@ plugins { dependencies { compile project(":kmath-core") compile project(":kmath-coroutines") + compile project(":kmath-commons") //jmh project(':kmath-core') } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt new file mode 100644 index 000000000..1a12d25b1 --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.linear + +import org.apache.commons.math3.linear.Array2DRowRealMatrix +import kotlin.random.Random +import kotlin.system.measureTimeMillis + +fun main() { + val random = Random(12224) + val dim = 100 + //creating invertible matrix + val u = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val l = Matrix.real(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 } + val matrix = l dot u + + val n = 500 // iterations + + val solver = LUSolver.real + + repeat(50) { + val res = solver.inverse(matrix) + } + + val inverseTime = measureTimeMillis { + repeat(n) { + val res = solver.inverse(matrix) + } + } + + println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis") + + //commons + + val cmSolver = CMSolver + + val commonsMatrix = Array2DRowRealMatrix(dim, dim) + matrix.elements().forEach { (index, value) -> commonsMatrix.setEntry(index[0], index[1], value) } + + val commonsTime = measureTimeMillis { + val cm = matrix.toCM() + repeat(n) { + //overhead on coversion could be mitigated + val res = cmSolver.inverse(cm) + } + } + + + println("[commons-math] Inversion of $n matrices $dim x $dim finished in $commonsTime millis") +} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt new file mode 100644 index 000000000..a3f206c54 --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt @@ -0,0 +1,83 @@ +package scientifik.kmath.linear + +import org.apache.commons.math3.linear.* +import org.apache.commons.math3.linear.RealMatrix +import org.apache.commons.math3.linear.RealVector +import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.DoubleBuffer + +inline class CMMatrix(val origin: RealMatrix) : Matrix { + override val rowNum: Int get() = origin.rowDimension + override val colNum: Int get() = origin.columnDimension + + override val features: Set get() = emptySet() + + override fun get(i: Int, j: Int): Double = origin.getEntry(i, j) +} + +fun Matrix.toCM(): CMMatrix = if (this is CMMatrix) { + this +} else { + //TODO add feature analysis + val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } } + CMMatrix(Array2DRowRealMatrix(array)) +} + +fun RealMatrix.toMatrix() = CMMatrix(this) + +inline class CMVector(val origin: RealVector) : Point { + override val size: Int get() = origin.dimension + + override fun get(index: Int): Double = origin.getEntry(index) + + override fun iterator(): Iterator = origin.toArray().iterator() +} + +fun Point.toCM(): CMVector = if (this is CMVector) { + this +} else { + val array = DoubleArray(size) { this[it] } + CMVector(ArrayRealVector(array)) +} + +fun RealVector.toPoint() = DoubleBuffer(this.toArray()) + +object CMMatrixContext : MatrixContext { + override val elementContext: RealField get() = RealField + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): Matrix { + val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } } + return CMMatrix(Array2DRowRealMatrix(array)) + } + + override fun point(size: Int, initializer: (Int) -> Double): Point { + val array = DoubleArray(size, initializer) + return CMVector(ArrayRealVector(array)) + } + + override fun Matrix.dot(other: Matrix): Matrix = this.toCM().dot(other.toCM()) +} + +operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) +operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.subtract(other.origin)) + +infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(this.origin.multiply(other.origin)) + +object CMSolver : LinearSolver { + + override fun solve(a: Matrix, b: Matrix): Matrix { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.solve(b.toCM().origin).toMatrix() + } + + override fun solve(a: Matrix, b: Point): Point { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.solve(b.toCM().origin).toPoint() + } + + override fun inverse(a: Matrix): Matrix { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.inverse.toMatrix() + } + +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index dbc45618d..79973bd37 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -2,13 +2,16 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.Ring -import scientifik.kmath.structures.* +import scientifik.kmath.structures.MutableBuffer import scientifik.kmath.structures.MutableBuffer.Companion.boxing +import scientifik.kmath.structures.MutableBufferFactory +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.get class LUPDecomposition>( private val elementContext: Ring, - private val lu: NDStructure, + internal val lu: NDStructure, val pivot: IntArray, private val even: Boolean ) : DeterminantFeature { @@ -62,19 +65,12 @@ class LUPDecomposition>( class LUSolver, F : Field>( - override val context: MatrixContext, + val context: MatrixContext, val bufferFactory: MutableBufferFactory = ::boxing, val singularityCheck: (T) -> Boolean ) : LinearSolver { - /** - * In-place transformation for [MutableNDStructure], using given transformation for each element - */ - private operator fun MutableNDStructure.set(i: Int, j: Int, value: T) { - this[intArrayOf(i, j)] = value - } - private fun abs(value: T) = if (value > context.elementContext.zero) value else with(context.elementContext) { -value } @@ -180,19 +176,19 @@ class LUSolver, F : Field>( for (col in 0 until a.rowNum) { for (i in col + 1 until a.rowNum) { for (j in 0 until b.colNum) { - bp[i, j] -= bp[col, j] * l[i, col] + bp[i, j] -= bp[col, j] * lu[i, col] } } } // Solve UX = Y for (col in a.rowNum - 1 downTo 0) { - for(j in 0 until b.colNum){ - bp[col,j]/= u[col,col] + for (j in 0 until b.colNum) { + bp[col, j] /= lu[col, col] } for (i in 0 until col) { for (j in 0 until b.colNum) { - bp[i, j] -= bp[col, j] * u[i, col] + bp[i, j] -= bp[col, j] * lu[i, col] } } } @@ -202,7 +198,9 @@ class LUSolver, F : Field>( } } + override fun inverse(a: Matrix): Matrix = solve(a, context.one(a.rowNum, a.colNum)) + companion object { val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } } -} +} \ No newline at end of file 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 fb542c16b..4c56b1e63 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -12,12 +12,9 @@ import scientifik.kmath.structures.asSequence * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors */ interface LinearSolver> { - val context: MatrixContext - - fun solve(a: Matrix, b: Matrix): Matrix fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).toVector() - fun inverse(a: Matrix): Matrix = solve(a, context.one(a.rowNum, a.colNum)) + fun inverse(a: Matrix): Matrix } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 6cd5e985f..7c98502f3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -199,4 +199,6 @@ fun > Matrix.transpose(): Matrix { this.rowNum, setOf(TransposedFeature(this)) ) { i, j -> get(j, i) } -} \ No newline at end of file +} + +infix fun Matrix.dot(other: Matrix): Matrix = with(MatrixContext.real) { dot(other) } \ No newline at end of file From a9e06c261abffaa1da3fc7411837b05663396b96 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 21 Jan 2019 10:22:37 +0300 Subject: [PATCH 39/70] Optimized performance for Double BufferMatrix product --- benchmarks/build.gradle | 26 ++++- .../kmath/linear/LinearAlgebraBenchmark.kt | 9 +- .../kmath/linear/MultiplicationBenchmark.kt | 30 ++++++ build.gradle.kts | 2 +- kmath-commons/build.gradle.kts | 7 +- .../scientifik/kmath/linear/CMMatrix.kt | 2 +- kmath-core/build.gradle | 11 +-- .../scientifik/kmath/linear/BufferMatrix.kt | 97 +++++++++++++++++++ .../kotlin/scientifik/kmath/linear/Matrix.kt | 9 +- .../kmath/linear/StructureMatrix.kt | 74 -------------- .../scientifik/kmath/structures/Buffers.kt | 24 +++-- .../scientifik/kmath/linear/MatrixTest.kt | 2 +- kmath-koma/build.gradle.kts | 53 ++++++++++ .../scientifik.kmath.linear/KomaMatrix.kt | 27 ++++++ settings.gradle.kts | 3 + 15 files changed, 263 insertions(+), 113 deletions(-) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt create mode 100644 kmath-koma/build.gradle.kts create mode 100644 kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index f732e2db6..ef9264d32 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -1,13 +1,14 @@ plugins { id "java" - id "kotlin" id "me.champeau.gradle.jmh" version "0.4.7" + id 'org.jetbrains.kotlin.jvm' } dependencies { - compile project(":kmath-core") - compile project(":kmath-coroutines") - compile project(":kmath-commons") + api project(":kmath-core") + api project(":kmath-coroutines") + api project(":kmath-commons") + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" //jmh project(':kmath-core') } @@ -15,4 +16,19 @@ jmh{ warmupIterations = 1 } -jmhClasses.dependsOn(compileKotlin) \ No newline at end of file +jmhClasses.dependsOn(compileKotlin) +repositories { + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } + mavenCentral() +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index 1a12d25b1..0cb7f15df 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -1,6 +1,5 @@ package scientifik.kmath.linear -import org.apache.commons.math3.linear.Array2DRowRealMatrix import kotlin.random.Random import kotlin.system.measureTimeMillis @@ -28,17 +27,13 @@ fun main() { println("[kmath] Inversion of $n matrices $dim x $dim finished in $inverseTime millis") - //commons + //commons-math val cmSolver = CMSolver - val commonsMatrix = Array2DRowRealMatrix(dim, dim) - matrix.elements().forEach { (index, value) -> commonsMatrix.setEntry(index[0], index[1], value) } - val commonsTime = measureTimeMillis { - val cm = matrix.toCM() + val cm = matrix.toCM() //avoid overhead on conversion repeat(n) { - //overhead on coversion could be mitigated val res = cmSolver.inverse(cm) } } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt new file mode 100644 index 000000000..da56a0dd0 --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt @@ -0,0 +1,30 @@ +package scientifik.kmath.linear + +import kotlin.random.Random +import kotlin.system.measureTimeMillis + +fun main() { + val random = Random(12224) + val dim = 1000 + //creating invertible matrix + val matrix1 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val matrix2 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + +// //warmup +// matrix1 dot matrix2 + + val cmMatrix1 = matrix1.toCM() + val cmMatrix2 = matrix2.toCM() + + val cmTime = measureTimeMillis { + cmMatrix1 dot cmMatrix2 + } + + println("CM implementation time: $cmTime") + + val genericTime = measureTimeMillis { + val res = matrix1 dot matrix2 + } + + println("Generic implementation time: $genericTime") +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 21656f173..e5c4fc39b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.3-dev-2" + version = "0.0.3-dev-3" repositories { maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 7b859a2b0..509809a15 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -2,14 +2,11 @@ plugins { kotlin("jvm") } +description = "Commons math binding for kmath" + dependencies { api(project(":kmath-core")) api("org.apache.commons:commons-math3:3.6.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } - -//dependencies { -//// compile(project(":kmath-core")) -//// //compile project(":kmath-coroutines") -////} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt index a3f206c54..15c7489ab 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt @@ -55,7 +55,7 @@ object CMMatrixContext : MatrixContext { return CMVector(ArrayRealVector(array)) } - override fun Matrix.dot(other: Matrix): Matrix = this.toCM().dot(other.toCM()) + override fun Matrix.dot(other: Matrix): Matrix = CMMatrix(this.toCM().origin.multiply(other.toCM().origin)) } operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index d51ccdb65..a8d0d2b22 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -3,14 +3,11 @@ plugins { } 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') + jvm { + compilations["main"].kotlinOptions.jvmTarget = "1.8" } + js() + sourceSets { commonMain { dependencies { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt new file mode 100644 index 000000000..1b23b553d --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -0,0 +1,97 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.* + +/** + * Basic implementation of Matrix space based on [NDStructure] + */ +class BufferMatrixContext>( + override val elementContext: R, + private val bufferFactory: BufferFactory +) : MatrixContext { + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix { + val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) } + return BufferMatrix(rows, columns, buffer) + } + + override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) +} + +class BufferMatrix( + override val rowNum: Int, + override val colNum: Int, + val buffer: Buffer, + override val features: Set = emptySet() +) : Matrix { + + init { + if (buffer.size != rowNum * colNum) { + error("Dimension mismatch for matrix structure") + } + } + + + override val shape: IntArray get() = intArrayOf(rowNum, colNum) + + override fun get(index: IntArray): T = get(index[0], index[1]) + + override fun get(i: Int, j: Int): T = buffer[i * colNum + j] + + override fun elements(): Sequence> = sequence { + for (i in 0 until rowNum) { + for (j in 0 until colNum) { + yield(intArrayOf(i, j) to get(i, j)) + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return when (other) { + is NDStructure<*> -> return NDStructure.equals(this, other) + else -> false + } + } + + override fun hashCode(): Int { + var result = buffer.hashCode() + result = 31 * result + features.hashCode() + return result + } + + override fun toString(): String { + return if (rowNum <= 5 && colNum <= 5) { + "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" + + rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") { + it.asSequence().joinToString(separator = "\t") { it.toString() } + } + } else { + "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)" + } + } +} + +/** + * Optimized dot product for real matrices + */ +infix fun BufferMatrix.dot(other: BufferMatrix): BufferMatrix { + if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") + + val array = DoubleArray(this.rowNum * other.colNum) + + val a = this.buffer.array + val b = other.buffer.array + + for (i in (0 until rowNum)) { + for (j in (0 until other.colNum)) { + for (k in (0 until colNum)) { + array[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j] + } + } + } + + val buffer = DoubleBuffer(array) + return BufferMatrix(rowNum, other.colNum, buffer) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 7c98502f3..0c606e293 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -75,13 +75,13 @@ interface MatrixContext> { /** * Non-boxing double matrix */ - val real: MatrixContext = StructureMatrixContext(RealField, DoubleBufferFactory) + val real = BufferMatrixContext(RealField, DoubleBufferFactory) /** * A structured matrix with custom buffer */ fun > buffered(ring: R, bufferFactory: BufferFactory = ::boxing): MatrixContext = - StructureMatrixContext(ring, bufferFactory) + BufferMatrixContext(ring, bufferFactory) /** * Automatic buffered matrix, unboxed if it is possible @@ -152,11 +152,10 @@ interface Matrix : NDStructure { * Build a square matrix from given elements. */ fun build(vararg elements: T): Matrix { - val buffer = elements.asBuffer() val size: Int = sqrt(elements.size.toDouble()).toInt() if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") - val structure = Mutable2DStructure(size, size, buffer) - return StructureMatrix(structure) + val buffer = elements.asBuffer() + return BufferMatrix(size, size, buffer) } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt deleted file mode 100644 index 21876a9fe..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/StructureMatrix.kt +++ /dev/null @@ -1,74 +0,0 @@ -package scientifik.kmath.linear - -import scientifik.kmath.operations.Ring -import scientifik.kmath.structures.* - -/** - * Basic implementation of Matrix space based on [NDStructure] - */ -class StructureMatrixContext>( - override val elementContext: R, - private val bufferFactory: BufferFactory -) : MatrixContext { - - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { - val structure = - ndStructure(intArrayOf(rows, columns), bufferFactory) { index -> initializer(index[0], index[1]) } - return StructureMatrix(structure) - } - - override fun point(size: Int, initializer: (Int) -> T): Point = bufferFactory(size, initializer) -} - -class StructureMatrix( - val structure: NDStructure, - override val features: Set = emptySet() -) : Matrix { - - init { - if (structure.shape.size != 2) { - error("Dimension mismatch for matrix structure") - } - } - - override val rowNum: Int - get() = structure.shape[0] - override val colNum: Int - get() = structure.shape[1] - - - override val shape: IntArray get() = structure.shape - - override fun get(index: IntArray): T = structure[index] - - override fun get(i: Int, j: Int): T = structure[i, j] - - override fun elements(): Sequence> = structure.elements() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - return when (other) { - is NDStructure<*> -> return NDStructure.equals(this, other) - else -> false - } - } - - override fun hashCode(): Int { - var result = structure.hashCode() - result = 31 * result + features.hashCode() - return result - } - - override fun toString(): String { - return if (rowNum <= 5 && colNum <= 5) { - "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" + - rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") { - it.asSequence().joinToString(separator = "\t") { it.toString() } - } - } else { - "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)" - } - } - - -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 8f77c1d81..1d6eecf52 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -97,7 +97,7 @@ interface MutableBuffer : Buffer { } -inline class ListBuffer(private val list: List) : Buffer { +inline class ListBuffer(val list: List) : Buffer { override val size: Int get() = list.size @@ -109,7 +109,7 @@ inline class ListBuffer(private val list: List) : Buffer { fun List.asBuffer() = ListBuffer(this) -inline class MutableListBuffer(private val list: MutableList) : MutableBuffer { +inline class MutableListBuffer(val list: MutableList) : MutableBuffer { override val size: Int get() = list.size @@ -142,7 +142,7 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { fun Array.asBuffer() = ArrayBuffer(this) -inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { +inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer { override val size: Int get() = array.size override fun get(index: Int): Double = array[index] @@ -157,9 +157,19 @@ inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer.array: DoubleArray + get() = if (this is DoubleBuffer) { + array + } else { + DoubleArray(size) { get(it) } + } + fun DoubleArray.asBuffer() = DoubleBuffer(this) -inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { +inline class ShortBuffer(val array: ShortArray) : MutableBuffer { override val size: Int get() = array.size override fun get(index: Int): Short = array[index] @@ -176,7 +186,7 @@ inline class ShortBuffer(private val array: ShortArray) : MutableBuffer { fun ShortArray.asBuffer() = ShortBuffer(this) -inline class IntBuffer(private val array: IntArray) : MutableBuffer { +inline class IntBuffer(val array: IntArray) : MutableBuffer { override val size: Int get() = array.size override fun get(index: Int): Int = array[index] @@ -193,7 +203,7 @@ inline class IntBuffer(private val array: IntArray) : MutableBuffer { fun IntArray.asBuffer() = IntBuffer(this) -inline class LongBuffer(private val array: LongArray) : MutableBuffer { +inline class LongBuffer(val array: LongArray) : MutableBuffer { override val size: Int get() = array.size override fun get(index: Int): Long = array[index] @@ -210,7 +220,7 @@ inline class LongBuffer(private val array: LongArray) : MutableBuffer { fun LongArray.asBuffer() = LongBuffer(this) -inline class ReadOnlyBuffer(private val buffer: MutableBuffer) : Buffer { +inline class ReadOnlyBuffer(val buffer: MutableBuffer) : Buffer { override val size: Int get() = buffer.size override fun get(index: Int): T = buffer.get(index) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 28934ab7f..101aae3b4 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -24,7 +24,7 @@ class MatrixTest { fun testTranspose() { val matrix = MatrixContext.real.one(3, 3) val transposed = matrix.transpose() - assertEquals((matrix as StructureMatrix).structure, (transposed as StructureMatrix).structure) + assertEquals((matrix as BufferMatrix).buffer, (transposed as BufferMatrix).buffer) assertEquals(matrix, transposed) } diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts new file mode 100644 index 000000000..623c7f3ba --- /dev/null +++ b/kmath-koma/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + id("kotlin-multiplatform") +} + +repositories { + maven("http://dl.bintray.com/kyonifer/maven") +} + +kotlin { + jvm { + compilations["main"].kotlinOptions.jvmTarget = "1.8" + } + js() + + sourceSets { + + val commonMain by getting { + dependencies { + api(project(":kmath-core")) + implementation("com.kyonifer:koma-core-api-common:0.12") + implementation("org.jetbrains.kotlin:kotlin-stdlib-common") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + implementation(kotlin("stdlib-jdk8")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + implementation("com.kyonifer:koma-core-ejml:0.12") + } + } + val jsMain by getting { + dependencies { + implementation(kotlin("stdlib-js")) + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } + } +} \ No newline at end of file diff --git a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt new file mode 100644 index 000000000..18839ec88 --- /dev/null +++ b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt @@ -0,0 +1,27 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.Ring + +class KomaMatrixContext> : MatrixContext { + override val elementContext: R + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun point(size: Int, initializer: (Int) -> T): Point { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + +} + +inline class KomaMatrix(val matrix: koma.matrix.Matrix) : Matrix { + override val rowNum: Int get() = matrix.numRows() + override val colNum: Int get() = matrix.numCols() + override val features: Set get() = emptySet() + + @Suppress("OVERRIDE_BY_INLINE") + override inline fun get(i: Int, j: Int): T = matrix.getGeneric(i, j) + +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index fa0bb49a0..a4464d01f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,6 +2,8 @@ pluginManagement { repositories { mavenCentral() maven("https://plugins.gradle.org/m2/") + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } + maven { setUrl("https://plugins.gradle.org/m2/") } } } @@ -13,5 +15,6 @@ include( ":kmath-io", ":kmath-coroutines", ":kmath-commons", + ":kmath-koma", ":benchmarks" ) From baae18b22304d6484924aba165eff39b2658ae63 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 21 Jan 2019 15:05:44 +0300 Subject: [PATCH 40/70] Koma integration + multiple fixes to Matrix API --- benchmarks/build.gradle | 7 +- .../kmath/linear/LinearAlgebraBenchmark.kt | 2 +- .../scientifik/kmath/linear/CMMatrix.kt | 65 +++++++------ .../scientifik/kmath/linear/BufferMatrix.kt | 2 +- .../kmath/linear/LUPDecomposition.kt | 4 +- .../scientifik/kmath/linear/LinearAlgrebra.kt | 3 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 92 +++++++++++-------- .../scientifik/kmath/linear/MatrixTest.kt | 4 +- .../kmath/linear/RealLUSolverTest.kt | 4 +- .../scientifik.kmath.linear/KomaMatrix.kt | 71 +++++++++++--- 10 files changed, 159 insertions(+), 95 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index ef9264d32..98a4a64c5 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -5,9 +5,10 @@ plugins { } dependencies { - api project(":kmath-core") - api project(":kmath-coroutines") - api project(":kmath-commons") + implementation project(":kmath-core") + implementation project(":kmath-coroutines") + implementation project(":kmath-commons") + implementation project(":kmath-koma") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" //jmh project(':kmath-core') } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index 0cb7f15df..16ffc4639 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -29,7 +29,7 @@ fun main() { //commons-math - val cmSolver = CMSolver + val cmSolver = CMMatrixContext val commonsTime = measureTimeMillis { val cm = matrix.toCM() //avoid overhead on conversion diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt index 15c7489ab..aed4f4bb1 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt @@ -3,8 +3,6 @@ package scientifik.kmath.linear import org.apache.commons.math3.linear.* import org.apache.commons.math3.linear.RealMatrix import org.apache.commons.math3.linear.RealVector -import scientifik.kmath.operations.RealField -import scientifik.kmath.structures.DoubleBuffer inline class CMMatrix(val origin: RealMatrix) : Matrix { override val rowNum: Int get() = origin.rowDimension @@ -40,44 +38,51 @@ fun Point.toCM(): CMVector = if (this is CMVector) { CMVector(ArrayRealVector(array)) } -fun RealVector.toPoint() = DoubleBuffer(this.toArray()) +fun RealVector.toPoint() = CMVector(this) -object CMMatrixContext : MatrixContext { - override val elementContext: RealField get() = RealField +object CMMatrixContext : MatrixContext, LinearSolver { - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): Matrix { + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix { val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } } return CMMatrix(Array2DRowRealMatrix(array)) } - override fun point(size: Int, initializer: (Int) -> Double): Point { - val array = DoubleArray(size, initializer) - return CMVector(ArrayRealVector(array)) + override fun solve(a: Matrix, b: Matrix): CMMatrix { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.solve(b.toCM().origin).toMatrix() } - override fun Matrix.dot(other: Matrix): Matrix = CMMatrix(this.toCM().origin.multiply(other.toCM().origin)) + override fun solve(a: Matrix, b: Point): CMVector { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.solve(b.toCM().origin).toPoint() + } + + override fun inverse(a: Matrix): CMMatrix { + val decomposition = LUDecomposition(a.toCM().origin) + return decomposition.solver.inverse.toMatrix() + } + + override fun Matrix.dot(other: Matrix) = + CMMatrix(this.toCM().origin.multiply(other.toCM().origin)) + + override fun Matrix.dot(vector: Point): CMVector = + CMVector(this.toCM().origin.preMultiply(vector.toCM().origin)) + + + override fun Matrix.unaryMinus(): CMMatrix = + produce(rowNum, colNum) { i, j -> -get(i, j) } + + override fun Matrix.plus(b: Matrix) = + CMMatrix(this.toCM().origin.multiply(b.toCM().origin)) + + override fun Matrix.minus(b: Matrix) = + CMMatrix(this.toCM().origin.subtract(b.toCM().origin)) + + override fun Matrix.times(value: Double) = + CMMatrix(this.toCM().origin.scalarMultiply(value.toDouble())) } operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.subtract(other.origin)) -infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(this.origin.multiply(other.origin)) - -object CMSolver : LinearSolver { - - override fun solve(a: Matrix, b: Matrix): Matrix { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.solve(b.toCM().origin).toMatrix() - } - - override fun solve(a: Matrix, b: Point): Point { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.solve(b.toCM().origin).toPoint() - } - - override fun inverse(a: Matrix): Matrix { - val decomposition = LUDecomposition(a.toCM().origin) - return decomposition.solver.inverse.toMatrix() - } - -} \ No newline at end of file +infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(this.origin.multiply(other.origin)) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index 1b23b553d..5b65821db 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -9,7 +9,7 @@ import scientifik.kmath.structures.* class BufferMatrixContext>( override val elementContext: R, private val bufferFactory: BufferFactory -) : MatrixContext { +) : GenericMatrixContext { override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix { val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index 79973bd37..d2de7609d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -65,7 +65,7 @@ class LUPDecomposition>( class LUSolver, F : Field>( - val context: MatrixContext, + val context: GenericMatrixContext, val bufferFactory: MutableBufferFactory = ::boxing, val singularityCheck: (T) -> Boolean ) : LinearSolver { @@ -201,6 +201,6 @@ class LUSolver, F : Field>( override fun inverse(a: Matrix): Matrix = solve(a, context.one(a.rowNum, a.colNum)) companion object { - val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } + val real = LUSolver(GenericMatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } } } \ No newline at end of file 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 4c56b1e63..51f61e030 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -3,7 +3,6 @@ package scientifik.kmath.linear import scientifik.kmath.operations.Field import scientifik.kmath.operations.Norm import scientifik.kmath.operations.RealField -import scientifik.kmath.operations.Ring import scientifik.kmath.structures.VirtualBuffer import scientifik.kmath.structures.asSequence @@ -11,7 +10,7 @@ import scientifik.kmath.structures.asSequence /** * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors */ -interface LinearSolver> { +interface LinearSolver { fun solve(a: Matrix, b: Matrix): Matrix fun solve(a: Matrix, b: Point): Point = solve(a, b.toMatrix()).toVector() fun inverse(a: Matrix): Matrix diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 0c606e293..5b4f3f12d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -8,28 +8,61 @@ import scientifik.kmath.structures.Buffer.Companion.boxing import kotlin.math.sqrt -interface MatrixContext> { +interface MatrixContext { + /** + * Produce a matrix with this context and given dimensions + */ + fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix + + infix fun Matrix.dot(other: Matrix): Matrix + + infix fun Matrix.dot(vector: Point): Point + + operator fun Matrix.unaryMinus(): Matrix + + operator fun Matrix.plus(b: Matrix): Matrix + + operator fun Matrix.minus(b: Matrix): Matrix + + operator fun Matrix.times(value: T): Matrix + + operator fun T.times(m: Matrix): Matrix = m * this + + companion object { + /** + * Non-boxing double matrix + */ + val real = BufferMatrixContext(RealField, DoubleBufferFactory) + + /** + * A structured matrix with custom buffer + */ + fun > buffered( + ring: R, + bufferFactory: BufferFactory = ::boxing + ): GenericMatrixContext = + BufferMatrixContext(ring, bufferFactory) + + /** + * Automatic buffered matrix, unboxed if it is possible + */ + inline fun > auto(ring: R): GenericMatrixContext = + buffered(ring, Buffer.Companion::auto) + } +} + +interface GenericMatrixContext> : MatrixContext { /** * The ring context for matrix elements */ val elementContext: R - /** - * Produce a matrix with this context and given dimensions - */ - fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix - /** * Produce a point compatible with matrix space */ fun point(size: Int, initializer: (Int) -> T): Point - fun scale(a: Matrix, k: Number): Matrix { - //TODO create a special wrapper class for scaled matrices - return produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a[i, j] * k } } - } - - infix fun Matrix.dot(other: Matrix): Matrix { + override infix fun Matrix.dot(other: Matrix): Matrix { //TODO add typed error if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})") return produce(rowNum, other.colNum) { i, j -> @@ -41,7 +74,7 @@ interface MatrixContext> { } } - infix fun Matrix.dot(vector: Point): Point { + override infix fun Matrix.dot(vector: Point): Point { //TODO add typed error if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})") return point(rowNum) { i -> @@ -52,15 +85,15 @@ interface MatrixContext> { } } - operator fun Matrix.unaryMinus() = + override operator fun Matrix.unaryMinus() = produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } } - operator fun Matrix.plus(b: Matrix): Matrix { + override operator fun Matrix.plus(b: Matrix): Matrix { if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] + [${b.rowNum},${b.colNum}]") return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } } - operator fun Matrix.minus(b: Matrix): Matrix { + override operator fun Matrix.minus(b: Matrix): Matrix { if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]") return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } } } @@ -68,27 +101,10 @@ interface MatrixContext> { operator fun Matrix.times(number: Number): Matrix = produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } } - operator fun Number.times(m: Matrix): Matrix = m * this + operator fun Number.times(matrix: Matrix): Matrix = matrix * this - - companion object { - /** - * Non-boxing double matrix - */ - val real = BufferMatrixContext(RealField, DoubleBufferFactory) - - /** - * A structured matrix with custom buffer - */ - fun > buffered(ring: R, bufferFactory: BufferFactory = ::boxing): MatrixContext = - BufferMatrixContext(ring, bufferFactory) - - /** - * Automatic buffered matrix, unboxed if it is possible - */ - inline fun > auto(ring: R): MatrixContext = - buffered(ring, Buffer.Companion::auto) - } + override fun Matrix.times(value: T): Matrix = + produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } } } /** @@ -173,7 +189,7 @@ inline fun Matrix<*>.getFeature(): T? = features.filterIsInsta /** * Diagonal matrix of ones. The matrix is virtual no actual matrix is created */ -fun > MatrixContext.one(rows: Int, columns: Int): Matrix = +fun > GenericMatrixContext.one(rows: Int, columns: Int): Matrix = VirtualMatrix(rows, columns) { i, j -> if (i == j) elementContext.one else elementContext.zero } @@ -182,7 +198,7 @@ fun > MatrixContext.one(rows: Int, columns: Int): Mat /** * A virtual matrix of zeroes */ -fun > MatrixContext.zero(rows: Int, columns: Int): Matrix = +fun > GenericMatrixContext.zero(rows: Int, columns: Int): Matrix = VirtualMatrix(rows, columns) { i, j -> elementContext.zero } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 101aae3b4..eaf00c59b 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -22,7 +22,7 @@ class MatrixTest { @Test fun testTranspose() { - val matrix = MatrixContext.real.one(3, 3) + val matrix = GenericMatrixContext.real.one(3, 3) val transposed = matrix.transpose() assertEquals((matrix as BufferMatrix).buffer, (transposed as BufferMatrix).buffer) assertEquals(matrix, transposed) @@ -36,7 +36,7 @@ class MatrixTest { val matrix1 = vector1.toMatrix() val matrix2 = vector2.toMatrix().transpose() - val product = MatrixContext.real.run { matrix1 dot matrix2 } + val product = GenericMatrixContext.real.run { matrix1 dot matrix2 } assertEquals(5.0, product[1, 0]) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index ea104355c..32b07eb86 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class RealLUSolverTest { @Test fun testInvertOne() { - val matrix = MatrixContext.real.one(2, 2) + val matrix = GenericMatrixContext.real.one(2, 2) val inverted = LUSolver.real.inverse(matrix) assertEquals(matrix, inverted) } @@ -25,7 +25,7 @@ class RealLUSolverTest { assertEquals(8.0, decomposition.determinant) //Check decomposition - with(MatrixContext.real) { + with(GenericMatrixContext.real) { assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u) } diff --git a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt index 18839ec88..3b7894d18 100644 --- a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt +++ b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt @@ -1,27 +1,70 @@ package scientifik.kmath.linear -import scientifik.kmath.operations.Ring +import koma.extensions.fill +import koma.matrix.MatrixFactory -class KomaMatrixContext> : MatrixContext { - override val elementContext: R - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. +class KomaMatrixContext(val factory: MatrixFactory>) : MatrixContext, + LinearSolver { - override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T) = + KomaMatrix(factory.zeros(rows, columns).fill(initializer)) + + fun Matrix.toKoma(): KomaMatrix = if (this is KomaMatrix) { + this + } else { + produce(rowNum, colNum) { i, j -> get(i, j) } } - override fun point(size: Int, initializer: (Int) -> T): Point { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + fun Point.toKoma(): KomaVector = if (this is KomaVector) { + this + } else { + KomaVector(factory.zeros(size, 1).fill { i, _ -> get(i) }) } + + override fun Matrix.dot(other: Matrix) = + KomaMatrix(this.toKoma().origin * other.toKoma().origin) + + override fun Matrix.dot(vector: Point) = + KomaVector(this.toKoma().origin * vector.toKoma().origin) + + override fun Matrix.unaryMinus() = + KomaMatrix(this.toKoma().origin.unaryMinus()) + + override fun Matrix.plus(b: Matrix) = + KomaMatrix(this.toKoma().origin + b.toKoma().origin) + + override fun Matrix.minus(b: Matrix) = + KomaMatrix(this.toKoma().origin - b.toKoma().origin) + + override fun Matrix.times(value: T) = + KomaMatrix(this.toKoma().origin * value) + + + override fun solve(a: Matrix, b: Matrix) = + KomaMatrix(a.toKoma().origin.solve(b.toKoma().origin)) + + override fun inverse(a: Matrix) = + KomaMatrix(a.toKoma().origin.inv()) } -inline class KomaMatrix(val matrix: koma.matrix.Matrix) : Matrix { - override val rowNum: Int get() = matrix.numRows() - override val colNum: Int get() = matrix.numCols() +inline class KomaMatrix(val origin: koma.matrix.Matrix) : Matrix { + override val rowNum: Int get() = origin.numRows() + override val colNum: Int get() = origin.numCols() override val features: Set get() = emptySet() - @Suppress("OVERRIDE_BY_INLINE") - override inline fun get(i: Int, j: Int): T = matrix.getGeneric(i, j) + override fun get(i: Int, j: Int): T = origin.getGeneric(i, j) +} + +class KomaVector internal constructor(val origin: koma.matrix.Matrix) : Point { + init { + if (origin.numCols() != 1) error("Only single column matrices are allowed") + } + + override val size: Int get() = origin.numRows() + + override fun get(index: Int): T = origin.getGeneric(index) + + override fun iterator(): Iterator = origin.toIterable().iterator() +} -} \ No newline at end of file From 61545885344776ae2564cbb9bdd7ab16da2dd00d Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 21 Jan 2019 16:20:14 +0300 Subject: [PATCH 41/70] Koma integration + multiple fixes to Matrix API --- benchmarks/build.gradle | 11 +++++++---- .../kmath/linear/LinearAlgebraBenchmark.kt | 16 ++++++++++++++++ kmath-core/build.gradle | 1 + .../scientifik/kmath/linear/LUPDecomposition.kt | 4 ++-- .../kotlin/scientifik/kmath/linear/MatrixTest.kt | 5 ++--- .../scientifik/kmath/linear/RealLUSolverTest.kt | 4 ++-- kmath-koma/build.gradle.kts | 8 ++++---- 7 files changed, 34 insertions(+), 15 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 98a4a64c5..78d902459 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -4,11 +4,18 @@ plugins { id 'org.jetbrains.kotlin.jvm' } +repositories { + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } + maven{ url "http://dl.bintray.com/kyonifer/maven"} + mavenCentral() +} + dependencies { implementation project(":kmath-core") implementation project(":kmath-coroutines") implementation project(":kmath-commons") implementation project(":kmath-koma") + compile group: "com.kyonifer", name:"koma-core-ejml", version: "0.12" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" //jmh project(':kmath-core') } @@ -18,10 +25,6 @@ jmh{ } jmhClasses.dependsOn(compileKotlin) -repositories { - maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } - mavenCentral() -} compileKotlin { kotlinOptions { diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index 16ffc4639..d1bc92d8c 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -1,5 +1,6 @@ package scientifik.kmath.linear +import koma.matrix.ejml.EJMLMatrixFactory import kotlin.random.Random import kotlin.system.measureTimeMillis @@ -40,4 +41,19 @@ fun main() { println("[commons-math] Inversion of $n matrices $dim x $dim finished in $commonsTime millis") + + //koma-ejml + + val komaContext = KomaMatrixContext(EJMLMatrixFactory()) + + val komaTime = measureTimeMillis { + komaContext.run { + val km = matrix.toKoma() //avoid overhead on conversion + repeat(n) { + val res = cmSolver.inverse(km) + } + } + } + + println("[koma-ejml] Inversion of $n matrices $dim x $dim finished in $komaTime millis") } \ No newline at end of file diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index a8d0d2b22..b1a6cccb5 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -5,6 +5,7 @@ plugins { kotlin { jvm { compilations["main"].kotlinOptions.jvmTarget = "1.8" + compilations["test"].kotlinOptions.jvmTarget = "1.8" } js() diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index d2de7609d..b2161baf2 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -68,7 +68,7 @@ class LUSolver, F : Field>( val context: GenericMatrixContext, val bufferFactory: MutableBufferFactory = ::boxing, val singularityCheck: (T) -> Boolean -) : LinearSolver { +) : LinearSolver { private fun abs(value: T) = @@ -201,6 +201,6 @@ class LUSolver, F : Field>( override fun inverse(a: Matrix): Matrix = solve(a, context.one(a.rowNum, a.colNum)) companion object { - val real = LUSolver(GenericMatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } + val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 } } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index eaf00c59b..2a00b84cd 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -22,9 +22,8 @@ class MatrixTest { @Test fun testTranspose() { - val matrix = GenericMatrixContext.real.one(3, 3) + val matrix = MatrixContext.real.one(3, 3) val transposed = matrix.transpose() - assertEquals((matrix as BufferMatrix).buffer, (transposed as BufferMatrix).buffer) assertEquals(matrix, transposed) } @@ -36,7 +35,7 @@ class MatrixTest { val matrix1 = vector1.toMatrix() val matrix2 = vector2.toMatrix().transpose() - val product = GenericMatrixContext.real.run { matrix1 dot matrix2 } + val product = MatrixContext.real.run { matrix1 dot matrix2 } assertEquals(5.0, product[1, 0]) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index 32b07eb86..ea104355c 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class RealLUSolverTest { @Test fun testInvertOne() { - val matrix = GenericMatrixContext.real.one(2, 2) + val matrix = MatrixContext.real.one(2, 2) val inverted = LUSolver.real.inverse(matrix) assertEquals(matrix, inverted) } @@ -25,7 +25,7 @@ class RealLUSolverTest { assertEquals(8.0, decomposition.determinant) //Check decomposition - with(GenericMatrixContext.real) { + with(MatrixContext.real) { assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u) } diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index 623c7f3ba..20ad02fbe 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -9,6 +9,7 @@ repositories { kotlin { jvm { compilations["main"].kotlinOptions.jvmTarget = "1.8" + compilations["test"].kotlinOptions.jvmTarget = "1.8" } js() @@ -17,8 +18,7 @@ kotlin { val commonMain by getting { dependencies { api(project(":kmath-core")) - implementation("com.kyonifer:koma-core-api-common:0.12") - implementation("org.jetbrains.kotlin:kotlin-stdlib-common") + api("com.kyonifer:koma-core-api-common:0.12") } } val commonTest by getting { @@ -29,7 +29,7 @@ kotlin { } val jvmMain by getting { dependencies { - implementation(kotlin("stdlib-jdk8")) + api("com.kyonifer:koma-core-api-jvm:0.12") } } val jvmTest by getting { @@ -41,7 +41,7 @@ kotlin { } val jsMain by getting { dependencies { - implementation(kotlin("stdlib-js")) + api("com.kyonifer:koma-core-api-js:0.12") } } val jsTest by getting { From 2b4419823b774a1685fee08182f79b812980768e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 21 Jan 2019 17:01:11 +0300 Subject: [PATCH 42/70] Fixed performance tests. --- .../kmath/linear/LinearAlgebraBenchmark.kt | 12 +++++---- .../kmath/linear/MultiplicationBenchmark.kt | 25 +++++++++++++++---- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt index d1bc92d8c..d57631eba 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/LinearAlgebraBenchmark.kt @@ -30,12 +30,14 @@ fun main() { //commons-math - val cmSolver = CMMatrixContext + val cmContext = CMMatrixContext val commonsTime = measureTimeMillis { - val cm = matrix.toCM() //avoid overhead on conversion - repeat(n) { - val res = cmSolver.inverse(cm) + cmContext.run { + val cm = matrix.toCM() //avoid overhead on conversion + repeat(n) { + val res = inverse(cm) + } } } @@ -50,7 +52,7 @@ fun main() { komaContext.run { val km = matrix.toKoma() //avoid overhead on conversion repeat(n) { - val res = cmSolver.inverse(km) + val res = inverse(km) } } } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt index da56a0dd0..0b3b7c275 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/linear/MultiplicationBenchmark.kt @@ -1,5 +1,6 @@ package scientifik.kmath.linear +import koma.matrix.ejml.EJMLMatrixFactory import kotlin.random.Random import kotlin.system.measureTimeMillis @@ -13,14 +14,28 @@ fun main() { // //warmup // matrix1 dot matrix2 - val cmMatrix1 = matrix1.toCM() - val cmMatrix2 = matrix2.toCM() + CMMatrixContext.run { + val cmMatrix1 = matrix1.toCM() + val cmMatrix2 = matrix2.toCM() - val cmTime = measureTimeMillis { - cmMatrix1 dot cmMatrix2 + val cmTime = measureTimeMillis { + cmMatrix1 dot cmMatrix2 + } + + println("CM implementation time: $cmTime") } - println("CM implementation time: $cmTime") + + KomaMatrixContext(EJMLMatrixFactory()).run { + val komaMatrix1 = matrix1.toKoma() + val komaMatrix2 = matrix2.toKoma() + + val komaTime = measureTimeMillis { + komaMatrix1 dot komaMatrix2 + } + + println("Koma-ejml implementation time: $komaTime") + } val genericTime = measureTimeMillis { val res = matrix1 dot matrix2 From 77bf8de4f1a5f8f686b3f2c0c71805eb641c67c3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 23 Jan 2019 13:30:26 +0300 Subject: [PATCH 43/70] Optimized mapping functions for NDElements --- .../scientifik/kmath/structures/NDAlgebra.kt | 9 +++++--- .../scientifik/kmath/structures/NDElement.kt | 23 ++++++++++++------- .../kmath/structures/RealNDField.kt | 20 +++++++++++++--- settings.gradle.kts | 3 +-- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 097d52723..7ea768c63 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -14,7 +14,7 @@ class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : Run /** * The base interface for all nd-algebra implementations * @param T the type of nd-structure element - * @param C the type of the context + * @param C the type of the element context * @param N the type of the structure */ interface NDAlgebra> { @@ -112,10 +112,13 @@ interface NDField, N : NDStructure> : Field, NDRing() + /** - * Create a nd-field for [Double] values + * Create a nd-field for [Double] values or pull it from cache if it was created previously */ - fun real(shape: IntArray) = RealNDField(shape) + fun real(shape: IntArray) = realNDFieldCache.getOrPut(shape){RealNDField(shape)} /** * Create a nd-field with boxing generic buffer diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index 0fdb53f07..c97f959f3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -7,6 +7,9 @@ import scientifik.kmath.operations.Space /** * The root for all [NDStructure] based algebra elements. Does not implement algebra element root because of problems with recursive self-types + * @param T the type of the element of the structure + * @param C the type of the context for the element + * @param N the type of the underlying [NDStructure] */ interface NDElement> : NDStructure { @@ -16,9 +19,6 @@ interface NDElement> : NDStructure { fun N.wrap(): NDElement - fun mapIndexed(transform: C.(index: IntArray, T) -> T) = context.mapIndexed(unwrap(), transform).wrap() - fun map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap() - companion object { /** * Create a optimized NDArray of doubles @@ -61,10 +61,17 @@ interface NDElement> : NDStructure { } } + +fun > NDElement.mapIndexed(transform: C.(index: IntArray, T) -> T) = + context.mapIndexed(unwrap(), transform).wrap() + +fun > NDElement.map(transform: C.(T) -> T) = context.map(unwrap(), transform).wrap() + + /** * Element by element application of any operation on elements to the whole [NDElement] */ -operator fun Function1.invoke(ndElement: NDElement) = +operator fun > Function1.invoke(ndElement: NDElement) = ndElement.map { value -> this@invoke(value) } /* plus and minus */ @@ -72,13 +79,13 @@ operator fun Function1.invoke(ndElement: NDElement) = /** * Summation operation for [NDElement] and single element */ -operator fun > NDElement.plus(arg: T) = +operator fun , N : NDStructure> NDElement.plus(arg: T) = map { value -> arg + value } /** * Subtraction operation between [NDElement] and single element */ -operator fun > NDElement.minus(arg: T) = +operator fun , N : NDStructure> NDElement.minus(arg: T) = map { value -> arg - value } /* prod and div */ @@ -86,13 +93,13 @@ operator fun > NDElement.minus(arg: T) = /** * Product operation for [NDElement] and single element */ -operator fun > NDElement.times(arg: T) = +operator fun , N : NDStructure> NDElement.times(arg: T) = map { value -> arg * value } /** * Division operation between [NDElement] and single element */ -operator fun > NDElement.div(arg: T) = +operator fun , N : NDStructure> NDElement.div(arg: T) = map { value -> arg / value } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index bc5832e1c..d652bb8a8 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -86,11 +86,25 @@ inline fun BufferedNDField.produceInline(crossinline initiali return BufferedNDFieldElement(this, DoubleBuffer(array)) } +/** + * Map one [RealNDElement] using function with indexes + */ +inline fun RealNDElement.mapIndexed(crossinline transform: RealField.(index: IntArray, Double) -> Double) = + context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) } + +/** + * Map one [RealNDElement] using function without indexes + */ +inline fun RealNDElement.map(crossinline transform: RealField.(Double) -> Double): RealNDElement { + val array = DoubleArray(strides.linearSize) { offset -> RealField.transform(buffer[offset]) } + return BufferedNDFieldElement(context, DoubleBuffer(array)) +} + /** * Element by element application of any operation on elements to the whole array. Just like in numpy */ operator fun Function1.invoke(ndElement: RealNDElement) = - ndElement.context.produceInline { i -> invoke(ndElement.buffer[i]) } + ndElement.map { this@invoke(it) } /* plus and minus */ @@ -99,10 +113,10 @@ operator fun Function1.invoke(ndElement: RealNDElement) = * Summation operation for [BufferedNDElement] and single element */ operator fun RealNDElement.plus(arg: Double) = - context.produceInline { i -> buffer[i] + arg } + map { it + arg } /** * Subtraction operation between [BufferedNDElement] and single element */ operator fun RealNDElement.minus(arg: Double) = - context.produceInline { i -> buffer[i] - arg } + map { it - arg } diff --git a/settings.gradle.kts b/settings.gradle.kts index a4464d01f..ca738647e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,8 +2,7 @@ pluginManagement { repositories { mavenCentral() maven("https://plugins.gradle.org/m2/") - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } - maven { setUrl("https://plugins.gradle.org/m2/") } + maven ("https://dl.bintray.com/kotlin/kotlin-eap") } } From ce29e0e26c99cf25351fc1c1652a6567454ceabe Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 26 Jan 2019 19:38:18 +0300 Subject: [PATCH 44/70] Migrate to stable 1.3.20 --- build.gradle.kts | 14 ++--- kmath-core/build.gradle | 50 ----------------- kmath-core/build.gradle.kts | 55 +++++++++++++++++++ .../kotlin/scientifik/kmath/linear/Matrix.kt | 2 +- .../kmath/linear/RealLUSolverTest.kt | 4 +- kmath-koma/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 7 files changed, 65 insertions(+), 64 deletions(-) delete mode 100644 kmath-core/build.gradle create mode 100644 kmath-core/build.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts index e5c4fc39b..6f0478fc2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,10 @@ buildscript { - extra["kotlinVersion"] = "1.3.20-eap-100" - extra["ioVersion"] = "0.1.2" - extra["coroutinesVersion"] = "1.1.0" - - val kotlinVersion: String by extra - val ioVersion: String by extra - val coroutinesVersion: String by extra + val kotlinVersion: String by rootProject.extra("1.3.20") + val ioVersion: String by rootProject.extra("0.1.2") + val coroutinesVersion: String by rootProject.extra("1.1.1") repositories { - maven("https://dl.bintray.com/kotlin/kotlin-eap") + //maven("https://dl.bintray.com/kotlin/kotlin-eap") jcenter() } @@ -28,7 +24,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.3-dev-3" + version = "0.0.3-dev-4" repositories { maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle deleted file mode 100644 index b1a6cccb5..000000000 --- a/kmath-core/build.gradle +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -kotlin { - jvm { - compilations["main"].kotlinOptions.jvmTarget = "1.8" - compilations["test"].kotlinOptions.jvmTarget = "1.8" - } - js() - - sourceSets { - commonMain { - dependencies { - api 'org.jetbrains.kotlin:kotlin-stdlib-common' - } - } - commonTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test-common' - implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common' - } - } - jvmMain { - dependencies { - api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - } - } - jvmTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test' - implementation 'org.jetbrains.kotlin:kotlin-test-junit' - } - } - jsMain { - dependencies { - api 'org.jetbrains.kotlin:kotlin-stdlib-js' - } - } - jsTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test-js' - } - } -// mingwMain { -// } -// mingwTest { -// } - } -} diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts new file mode 100644 index 000000000..2e753f624 --- /dev/null +++ b/kmath-core/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + kotlin("multiplatform") +} + + +kotlin { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += "-progressive" + } + } + } + js() + + sourceSets { + val commonMain by getting { + dependencies { + api(kotlin("stdlib")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + api(kotlin("stdlib-jdk8")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + val jsMain by getting { + dependencies { + api(kotlin("stdlib-js")) + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } +// mingwMain { +// } +// mingwTest { +// } + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 5b4f3f12d..abe44e81a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -167,7 +167,7 @@ interface Matrix : NDStructure { /** * Build a square matrix from given elements. */ - fun build(vararg elements: T): Matrix { + fun square(vararg elements: T): Matrix { val size: Int = sqrt(elements.size.toDouble()).toInt() if (size * size != elements.size) error("The number of elements ${elements.size} is not a full square") val buffer = elements.asBuffer() diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt index ea104355c..bfa720369 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/RealLUSolverTest.kt @@ -13,7 +13,7 @@ class RealLUSolverTest { @Test fun testInvert() { - val matrix = Matrix.build( + val matrix = Matrix.square( 3.0, 1.0, 1.0, 3.0 ) @@ -31,7 +31,7 @@ class RealLUSolverTest { val inverted = LUSolver.real.inverse(decomposed) - val expected = Matrix.build( + val expected = Matrix.square( 0.375, -0.125, -0.125, 0.375 ) diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index 20ad02fbe..0e527e263 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("kotlin-multiplatform") + kotlin("multiplatform") } repositories { diff --git a/settings.gradle.kts b/settings.gradle.kts index ca738647e..cee985432 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { repositories { mavenCentral() maven("https://plugins.gradle.org/m2/") - maven ("https://dl.bintray.com/kotlin/kotlin-eap") + //maven ("https://dl.bintray.com/kotlin/kotlin-eap") } } From 569ff6357b42fdbf90616127c59a40a7564608a5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 26 Jan 2019 22:04:28 +0300 Subject: [PATCH 45/70] Added matrix builder for small matrices --- .../kotlin/scientifik/kmath/linear/Matrix.kt | 30 +++++++------------ .../scientifik/kmath/linear/MatrixFeatures.kt | 30 +++++++++++++++++++ .../scientifik/kmath/linear/MatrixTest.kt | 10 +++++++ kmath-koma/build.gradle.kts | 8 +++-- 4 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index abe44e81a..68a81f077 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -107,26 +107,6 @@ interface GenericMatrixContext> : MatrixContext { produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } } } -/** - * A marker interface representing some matrix feature like diagonal, sparce, zero, etc. Features used to optimize matrix - * operations performance in some cases. - */ -interface MatrixFeature - -object DiagonalFeature : MatrixFeature - -object ZeroFeature : MatrixFeature - -object UnitFeature : MatrixFeature - -interface InverseMatrixFeature : MatrixFeature { - val inverse: Matrix -} - -interface DeterminantFeature : MatrixFeature { - val determinant: T -} - /** * Specialized 2-d structure */ @@ -173,6 +153,16 @@ interface Matrix : NDStructure { val buffer = elements.asBuffer() return BufferMatrix(size, size, buffer) } + + fun build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) + } +} + +class MatrixBuilder(val rows: Int, val columns: Int) { + operator fun invoke(vararg elements: T): Matrix { + if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns") + val buffer = elements.asBuffer() + return BufferMatrix(rows, columns, buffer) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt new file mode 100644 index 000000000..d9f4e58ca --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt @@ -0,0 +1,30 @@ +package scientifik.kmath.linear + +/** + * A marker interface representing some matrix feature like diagonal, sparce, zero, etc. Features used to optimize matrix + * operations performance in some cases. + */ +interface MatrixFeature + +/** + * The matrix with this feature is considered to have only diagonal non-null elements + */ +object DiagonalFeature : MatrixFeature + +/** + * Matix with this feature has all zero elements + */ +object ZeroFeature : MatrixFeature + +/** + * Matrix with this feature have unit elements on diagonal and zero elements in all other places + */ +object UnitFeature : MatrixFeature + +interface InverseMatrixFeature : MatrixFeature { + val inverse: Matrix +} + +interface DeterminantFeature : MatrixFeature { + val determinant: T +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index 2a00b84cd..61aa506c4 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -41,4 +41,14 @@ class MatrixTest { assertEquals(5.0, product[1, 0]) assertEquals(6.0, product[2, 2]) } + + @Test + fun testBuilder() { + val matrix = Matrix.build(2, 3)( + 1.0, 0.0, 0.0, + 0.0, 1.0, 2.0 + ) + + assertEquals(2.0, matrix[1, 2]) + } } \ No newline at end of file diff --git a/kmath-koma/build.gradle.kts b/kmath-koma/build.gradle.kts index 0e527e263..b95aaf3c8 100644 --- a/kmath-koma/build.gradle.kts +++ b/kmath-koma/build.gradle.kts @@ -8,8 +8,12 @@ repositories { kotlin { jvm { - compilations["main"].kotlinOptions.jvmTarget = "1.8" - compilations["test"].kotlinOptions.jvmTarget = "1.8" + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += "-progressive" + } + } } js() From a2ef50ab477125c4f2c477562fb0fda642cc54e8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 27 Jan 2019 09:45:32 +0300 Subject: [PATCH 46/70] Refactored Matrix features --- .../scientifik/kmath/linear/CMMatrix.kt | 11 ++++-- .../scientifik/kmath/linear/BufferMatrix.kt | 3 ++ .../kmath/linear/LUPDecomposition.kt | 9 +++-- .../kotlin/scientifik/kmath/linear/Matrix.kt | 8 +++++ .../scientifik/kmath/linear/MatrixFeatures.kt | 36 +++++++++++++++++-- .../scientifik/kmath/linear/VirtualMatrix.kt | 3 ++ .../kmath/structures/ShortNDRing.kt | 1 + .../scientifik.kmath.linear/KomaMatrix.kt | 19 ++++++++-- 8 files changed, 78 insertions(+), 12 deletions(-) diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt index aed4f4bb1..808b2768b 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/linear/CMMatrix.kt @@ -4,11 +4,16 @@ import org.apache.commons.math3.linear.* import org.apache.commons.math3.linear.RealMatrix import org.apache.commons.math3.linear.RealVector -inline class CMMatrix(val origin: RealMatrix) : Matrix { +class CMMatrix(val origin: RealMatrix, features: Set? = null) : Matrix { override val rowNum: Int get() = origin.rowDimension override val colNum: Int get() = origin.columnDimension - override val features: Set get() = emptySet() + override val features: Set = features ?: sequence { + if(origin is DiagonalMatrix) yield(DiagonalFeature) + }.toSet() + + override fun suggestFeature(vararg features: MatrixFeature) = + CMMatrix(origin, this.features + features) override fun get(i: Int, j: Int): Double = origin.getEntry(i, j) } @@ -23,7 +28,7 @@ fun Matrix.toCM(): CMMatrix = if (this is CMMatrix) { fun RealMatrix.toMatrix() = CMMatrix(this) -inline class CMVector(val origin: RealVector) : Point { +class CMVector(val origin: RealVector) : Point { override val size: Int get() = origin.dimension override fun get(index: Int): Double = origin.getEntry(index) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt index 5b65821db..3752f6db5 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/BufferMatrix.kt @@ -35,6 +35,9 @@ class BufferMatrix( override val shape: IntArray get() = intArrayOf(rowNum, colNum) + override fun suggestFeature(vararg features: MatrixFeature) = + BufferMatrix(rowNum, colNum, buffer, this.features + features) + override fun get(index: IntArray): T = get(index[0], index[1]) override fun get(i: Int, j: Int): T = buffer[i * colNum + j] diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt index b2161baf2..85ddb8786 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUPDecomposition.kt @@ -8,20 +8,19 @@ import scientifik.kmath.structures.MutableBufferFactory import scientifik.kmath.structures.NDStructure import scientifik.kmath.structures.get - class LUPDecomposition>( private val elementContext: Ring, internal val lu: NDStructure, val pivot: IntArray, private val even: Boolean -) : DeterminantFeature { +) : LUPDecompositionFeature, DeterminantFeature { /** * Returns the matrix L of the decomposition. * * L is a lower-triangular matrix with [Ring.one] in diagonal */ - val l: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + override val l: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(LFeature)) { i, j -> when { j < i -> lu[i, j] j == i -> elementContext.one @@ -35,7 +34,7 @@ class LUPDecomposition>( * * U is an upper-triangular matrix including the diagonal */ - val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(UFeature)) { i, j -> if (j >= i) lu[i, j] else elementContext.zero } @@ -46,7 +45,7 @@ class LUPDecomposition>( * P is a sparse matrix with exactly one element set to [Ring.one] in * each row and each column, all other elements being set to [Ring.zero]. */ - val p: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> + override val p: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> if (j == pivot[i]) elementContext.one else elementContext.zero } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 68a81f077..533e77d61 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -116,6 +116,14 @@ interface Matrix : NDStructure { val features: Set + /** + * Suggest new feature for this matrix. The result is the new matrix that may or may not reuse existing data structure. + * + * The implementation does not guarantee to check that matrix actually have the feature, so one should be careful to + * add only those features that are valid. + */ + fun suggestFeature(vararg features: MatrixFeature): Matrix + operator fun get(i: Int, j: Int): T override fun get(index: IntArray): T = get(index[0], index[1]) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt index d9f4e58ca..6b45a14b1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixFeatures.kt @@ -12,7 +12,7 @@ interface MatrixFeature object DiagonalFeature : MatrixFeature /** - * Matix with this feature has all zero elements + * Matrix with this feature has all zero elements */ object ZeroFeature : MatrixFeature @@ -21,10 +21,42 @@ object ZeroFeature : MatrixFeature */ object UnitFeature : MatrixFeature +/** + * Inverted matrix feature + */ interface InverseMatrixFeature : MatrixFeature { val inverse: Matrix } +/** + * A determinant container + */ interface DeterminantFeature : MatrixFeature { val determinant: T -} \ No newline at end of file +} + +@Suppress("FunctionName") +fun DeterminantFeature(determinant: T) = object: DeterminantFeature{ + override val determinant: T = determinant +} + +/** + * Lower triangular matrix + */ +object LFeature: MatrixFeature + +/** + * Upper triangular feature + */ +object UFeature: MatrixFeature + +/** + * TODO add documentation + */ +interface LUPDecompositionFeature : MatrixFeature { + val l: Matrix + val u: Matrix + val p: Matrix +} + +//TODO add sparse matrix feature \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt index 98655ad48..1bab52902 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/VirtualMatrix.kt @@ -8,6 +8,9 @@ class VirtualMatrix( ) : Matrix { override fun get(i: Int, j: Int): T = generator(i, j) + override fun suggestFeature(vararg features: MatrixFeature) = + VirtualMatrix(rowNum, colNum, this.features + features, generator) + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Matrix<*>) return false diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt index 5c887f343..09e93483d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -15,6 +15,7 @@ class ShortNDRing(override val shape: IntArray) : override val zero by lazy { produce { ShortRing.zero } } override val one by lazy { produce { ShortRing.one } } + @Suppress("OVERRIDE_BY_INLINE") override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = ShortBuffer(ShortArray(size) { initializer(it) }) diff --git a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt index 3b7894d18..343d5b8b9 100644 --- a/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt +++ b/kmath-koma/src/commonMain/kotlin/scientifik.kmath.linear/KomaMatrix.kt @@ -48,10 +48,25 @@ class KomaMatrixContext(val factory: MatrixFactory(val origin: koma.matrix.Matrix) : Matrix { +class KomaMatrix(val origin: koma.matrix.Matrix, features: Set? = null) : + Matrix { override val rowNum: Int get() = origin.numRows() override val colNum: Int get() = origin.numCols() - override val features: Set get() = emptySet() + + override val features: Set = features ?: setOf( + object : DeterminantFeature { + override val determinant: T get() = origin.det() + }, + object : LUPDecompositionFeature { + private val lup by lazy { origin.LU() } + override val l: Matrix get() = KomaMatrix(lup.second) + override val u: Matrix get() = KomaMatrix(lup.third) + override val p: Matrix get() = KomaMatrix(lup.first) + } + ) + + override fun suggestFeature(vararg features: MatrixFeature): Matrix = + KomaMatrix(this.origin, this.features + features) override fun get(i: Int, j: Int): T = origin.getGeneric(i, j) } From 388d016fdfd2efe7d1156c1738b179826b9f123f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 31 Jan 2019 18:19:02 +0300 Subject: [PATCH 47/70] Specialized 1d and 2d NDStructures --- build.gradle.kts | 22 ++++- kmath-core/build.gradle.kts | 7 +- .../kotlin/scientifik/kmath/linear/Matrix.kt | 4 +- .../kmath/structures/NDStructure.kt | 4 +- .../kmath/structures/SpecializedStructures.kt | 88 +++++++++++++++++++ 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6f0478fc2..06c37d2a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + buildscript { val kotlinVersion: String by rootProject.extra("1.3.20") val ioVersion: String by rootProject.extra("0.1.2") @@ -27,11 +29,27 @@ allprojects { version = "0.0.3-dev-4" repositories { - maven("https://dl.bintray.com/kotlin/kotlin-eap") + //maven("https://dl.bintray.com/kotlin/kotlin-eap") jcenter() } + + extensions.findByType()?.apply { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + targets.all { + sourceSets.all { + languageSettings.progressiveMode = true + languageSettings.enableLanguageFeature("+InlineClasses") + } + } + } } if (file("artifactory.gradle").exists()) { apply(from = "artifactory.gradle") -} \ No newline at end of file +} diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 2e753f624..43059db0e 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { kotlin("multiplatform") } @@ -8,13 +10,16 @@ kotlin { compilations.all { kotlinOptions { jvmTarget = "1.8" - freeCompilerArgs += "-progressive" } } } js() sourceSets { + all { + languageSettings.progressiveMode = true + } + val commonMain by getting { dependencies { api(kotlin("stdlib")) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 533e77d61..7ec3a95f4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -110,7 +110,7 @@ interface GenericMatrixContext> : MatrixContext { /** * Specialized 2-d structure */ -interface Matrix : NDStructure { +interface Matrix : Structure2D { val rowNum: Int val colNum: Int @@ -124,8 +124,6 @@ interface Matrix : NDStructure { */ fun suggestFeature(vararg features: MatrixFeature): Matrix - operator fun get(i: Int, j: Int): T - override fun get(index: IntArray): T = get(index[0], index[1]) override val shape: IntArray get() = intArrayOf(rowNum, colNum) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 4c437bf88..5f16f0444 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -16,7 +16,9 @@ interface NDStructure { fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean { return when { st1 === st2 -> true - st1 is BufferNDStructure<*> && st2 is BufferNDStructure<*> && st1.strides == st2.strides -> st1.buffer.contentEquals(st2.buffer) + st1 is BufferNDStructure<*> && st2 is BufferNDStructure<*> && st1.strides == st2.strides -> st1.buffer.contentEquals( + st2.buffer + ) else -> st1.elements().all { (index, value) -> value == st2[index] } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt new file mode 100644 index 000000000..b0373288b --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt @@ -0,0 +1,88 @@ +package scientifik.kmath.structures + +/** + * A structure that is guaranteed to be one-dimensional + */ +interface Structure1D : NDStructure, Buffer { + override val dimension: Int get() = 1 + + override fun get(index: IntArray): T { + if (index.size != 1) error("Index dimension mismatch. Expected 1 but found ${index.size}") + return get(index[0]) + } + + override fun iterator(): Iterator = (0 until size).asSequence().map { get(it) }.iterator() +} + +/** + * A 1D wrapper for nd-structure + */ +private inline class Structure1DWrapper(val structure: NDStructure) : Structure1D { + + override val shape: IntArray get() = structure.shape + override val size: Int get() = structure.shape[0] + + override fun get(index: Int): T = structure[index] + + override fun elements(): Sequence> = structure.elements() +} + +/** + * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch + */ +fun NDStructure.as1D(): Structure1D = if (shape.size == 1) { + Structure1DWrapper(this) +} else { + error("Can't create 1d-structure from ${shape.size}d-structure") +} + +/** + * A structure wrapper for buffer + */ +private inline class Buffer1DWrapper(val buffer: Buffer) : Structure1D { + override val shape: IntArray get() = intArrayOf(buffer.size) + + override val size: Int get() = buffer.size + + override fun elements(): Sequence> = + asSequence().mapIndexed { index, value -> intArrayOf(index) to value } + + override fun get(index: Int): T = buffer.get(index) +} + +/** + * Represent this buffer as 1D structure + */ +fun Buffer.asND(): Structure1D = Buffer1DWrapper(this) + +/** + * A structure that is guaranteed to be two-dimensional + */ +interface Structure2D : NDStructure { + operator fun get(i: Int, j: Int): T + + override fun get(index: IntArray): T { + if (index.size != 2) error("Index dimension mismatch. Expected 2 but found ${index.size}") + return get(index[0], index[1]) + } +} + +/** + * A 2D wrapper for nd-structure + */ +private inline class Structure2DWrapper(val structure: NDStructure) : Structure2D { + override fun get(i: Int, j: Int): T = structure[i, j] + + override val shape: IntArray get() = structure.shape + + override fun elements(): Sequence> = structure.elements() +} + +/** + * Represent a [NDStructure] as [Structure1D]. Throw error in case of dimension mismatch + */ +fun NDStructure.as2D(): Structure2D = if (shape.size == 2) { + Structure2DWrapper(this) +} else { + error("Can't create 2d-structure from ${shape.size}d-structure") +} \ No newline at end of file From a3e8ffa14739547504e7a9edfab1c8e72798f7bc Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 31 Jan 2019 18:21:06 +0300 Subject: [PATCH 48/70] minor matrix fix --- .../src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 7ec3a95f4..646cbb5c1 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -175,7 +175,7 @@ class MatrixBuilder(val rows: Int, val columns: Int) { /** * Check if matrix has the given feature class */ -inline fun Matrix<*>.hasFeature(): Boolean = features.find { T::class.isInstance(it) } != null +inline fun Matrix<*>.hasFeature(): Boolean = features.find { it is T } != null /** * Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria @@ -195,9 +195,7 @@ fun > GenericMatrixContext.one(rows: Int, columns: In * A virtual matrix of zeroes */ fun > GenericMatrixContext.zero(rows: Int, columns: Int): Matrix = - VirtualMatrix(rows, columns) { i, j -> - elementContext.zero - } + VirtualMatrix(rows, columns) { _, _ -> elementContext.zero } class TransposedFeature(val original: Matrix) : MatrixFeature From 1e99e89c4cea4d6e6948edb03e3bc208964fa665 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 2 Feb 2019 18:16:25 +0300 Subject: [PATCH 49/70] Sequential operations --- build.gradle.kts | 3 +- kmath-core/build.gradle.kts | 14 +- kmath-sequential/build.gradle.kts | 51 ++++++ .../kmath/sequential/Accumulators.kt | 35 ++++ .../scientifik/kmath/sequential/Chain.kt | 149 ++++++++++++++++++ .../kmath/sequential}/Cumulative.kt | 2 +- .../scientifik/kmath/sequential/Reducers.kt | 21 +++ .../scientifik/kmath/chains/ChainExt.kt | 41 +++++ settings.gradle.kts | 22 ++- 9 files changed, 321 insertions(+), 17 deletions(-) create mode 100644 kmath-sequential/build.gradle.kts create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt rename {kmath-core/src/commonMain/kotlin/scientifik/kmath/misc => kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential}/Cumulative.kt (97%) create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt create mode 100644 kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt diff --git a/build.gradle.kts b/build.gradle.kts index 06c37d2a3..d1667d7b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.3-dev-4" + version = "0.0.3-dev-5" repositories { //maven("https://dl.bintray.com/kotlin/kotlin-eap") @@ -44,7 +44,6 @@ allprojects { targets.all { sourceSets.all { languageSettings.progressiveMode = true - languageSettings.enableLanguageFeature("+InlineClasses") } } } diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 43059db0e..65086d665 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -1,25 +1,13 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { kotlin("multiplatform") } kotlin { - jvm { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - } - } - } + jvm () js() sourceSets { - all { - languageSettings.progressiveMode = true - } - val commonMain by getting { dependencies { api(kotlin("stdlib")) diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts new file mode 100644 index 000000000..57467d7b8 --- /dev/null +++ b/kmath-sequential/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + kotlin("multiplatform") + id("kotlinx-atomicfu") +} + + +kotlin { + jvm () + js() + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":kmath-core")) + api(project(":kmath-coroutines")) + compileOnly("org.jetbrains.kotlinx:atomicfu-common:0.12.1") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + compileOnly("org.jetbrains.kotlinx:atomicfu:0.12.1") + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } +// val jsMain by getting { +// dependencies { +// api(kotlin("stdlib-js")) +// } +// } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } +// mingwMain { +// } +// mingwTest { +// } + } +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt new file mode 100644 index 000000000..283db0809 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt @@ -0,0 +1,35 @@ +package scientifik.kmath.sequential + +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.getAndUpdate +import scientifik.kmath.operations.Space + +/** + * An object with a state that accumulates incoming elements + */ +interface Accumulator { + //PENDING use suspend operations? + fun push(value: T) +} + +fun Accumulator.pushAll(values: Iterable) { + values.forEach { push(it) } +} + +/** + * Generic thread-safe summator + */ +class GenericSum(val context: Space) : Accumulator { + //TODO add guard against overflow + val counter = atomic(0) + val sum = atomic(context.zero) + + val value get() = with(context) { sum.value / counter.value } + + override fun push(value: T) { + with(context) { + counter.incrementAndGet() + sum.getAndUpdate { it + value } + } + } +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt new file mode 100644 index 000000000..abecf014d --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package scientifik.kmath.sequential + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.isActive + + +/** + * A not-necessary-Markov chain of some type + * @param R - the chain element type + */ +interface Chain { + /** + * Last value of the chain. Returns null if [next] was not called + */ + val value: R? + + /** + * Generate next value, changing state if needed + */ + suspend fun next(): R + + /** + * Create a copy of current chain state. Consuming resulting chain does not affect initial chain + */ + fun fork(): Chain + +} + +/** + * Chain as a coroutine receive channel + */ +@ExperimentalCoroutinesApi +fun Chain.asChannel(scope: CoroutineScope): ReceiveChannel = scope.produce { while (isActive) send(next()) } + +fun Iterator.asChain(): Chain = SimpleChain { next() } +fun Sequence.asChain(): Chain = iterator().asChain() + + +/** + * Map the chain result using suspended transformation. Initial chain result can no longer be safely consumed + * since mapped chain consumes tokens. Accepts regular transformation function + */ +fun Chain.map(func: (T) -> R): Chain { + val parent = this; + return object : Chain { + override val value: R? get() = parent.value?.let(func) + + override suspend fun next(): R { + return func(parent.next()) + } + + override fun fork(): Chain { + return parent.fork().map(func) + } + } +} + +/** + * A simple chain of independent tokens + */ +class SimpleChain(private val gen: suspend () -> R) : Chain { + private val atomicValue = atomic(null) + override val value: R? get() = atomicValue.value + + override suspend fun next(): R = gen().also { atomicValue.lazySet(it) } + + override fun fork(): Chain = this +} + +//TODO force forks on mapping operations? + +/** + * A stateless Markov chain + */ +class MarkovChain(private val seed: () -> R, private val gen: suspend (R) -> R) : + Chain { + + constructor(seed: R, gen: suspend (R) -> R) : this({ seed }, gen) + + private val atomicValue by lazy { atomic(seed()) } + override val value: R get() = atomicValue.value + + override suspend fun next(): R { + atomicValue.lazySet(gen(value)) + return value + } + + override fun fork(): Chain { + return MarkovChain(value, gen) + } +} + +/** + * A chain with possibly mutable state. The state must not be changed outside the chain. Two chins should never share the state + * @param S - the state of the chain + */ +class StatefulChain( + private val state: S, + private val seed: S.() -> R, + private val gen: suspend S.(R) -> R +) : Chain { + + constructor(state: S, seed: R, gen: suspend S.(R) -> R) : this(state, { seed }, gen) + + private val atomicValue by lazy { atomic(seed(state)) } + override val value: R get() = atomicValue.value + + override suspend fun next(): R { + atomicValue.lazySet(gen(state, value)) + return value + } + + override fun fork(): Chain { + throw RuntimeException("Fork not supported for stateful chain") + } +} + +/** + * A chain that repeats the same value + */ +class ConstantChain(override val value: T) : Chain { + override suspend fun next(): T { + return value + } + + override fun fork(): Chain { + return this + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt similarity index 97% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt rename to kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt index 77a1d54bc..f02c3ad64 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/misc/Cumulative.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt @@ -1,4 +1,4 @@ -package scientifik.kmath.misc +package scientifik.kmath.sequential import kotlin.jvm.JvmName diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt new file mode 100644 index 000000000..65dea5ac0 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt @@ -0,0 +1,21 @@ +package scientifik.kmath.sequential + +import scientifik.kmath.operations.Space + + +typealias Reducer = (C, Iterable) -> R + +inline fun Iterable.reduce(context: C, crossinline reducer: Reducer) = + reducer(context, this@reduce) + +inline fun Sequence.reduce(context: C, crossinline reducer: Reducer) = + asIterable().reduce(context, reducer) + +inline fun Array.reduce(context: C, crossinline reducer: Reducer) = + asIterable().reduce(context, reducer) + +object Reducers { + fun mean(): Reducer, T> = { context, data -> + data.fold(GenericSum(context)) { sum, value -> sum.apply { push(value) } }.value + } +} \ No newline at end of file diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt new file mode 100644 index 000000000..af61a5059 --- /dev/null +++ b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt @@ -0,0 +1,41 @@ +package scientifik.kmath.chains + +import kotlinx.coroutines.runBlocking +import scientifik.kmath.sequential.Chain +import kotlin.sequences.Sequence + +/** + * Represent a chain as regular iterator (uses blocking calls) + */ +operator fun Chain.iterator() = object : Iterator { + override fun hasNext(): Boolean = true + + override fun next(): R = runBlocking { next() } +} + +/** + * Represent a chain as a sequence + */ +fun Chain.asSequence(): Sequence = object : Sequence { + override fun iterator(): Iterator = this@asSequence.iterator() +} + + +/** + * Map the chain result using suspended transformation. Initial chain result can no longer be safely consumed + * since mapped chain consumes tokens. Accepts suspending transformation function. + */ +fun Chain.map(func: suspend (T) -> R): Chain { + val parent = this; + return object : Chain { + override val value: R? get() = runBlocking { parent.value?.let { func(it) } } + + override suspend fun next(): R { + return func(parent.next()) + } + + override fun fork(): Chain { + return parent.fork().map(func) + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index cee985432..5638223fc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,9 +1,28 @@ +buildscript { + dependencies { + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.12.1") + } + + repositories { + jcenter() + } +} + pluginManagement { repositories { mavenCentral() - maven("https://plugins.gradle.org/m2/") + gradlePluginPortal() //maven ("https://dl.bintray.com/kotlin/kotlin-eap") } + resolutionStrategy { + eachPlugin { + when (requested.id.id) { + "kotlinx-atomicfu" -> { + useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.12.1") + } + } + } + } } //enableFeaturePreview("GRADLE_METADATA") @@ -15,5 +34,6 @@ include( ":kmath-coroutines", ":kmath-commons", ":kmath-koma", + ":kmath-sequential", ":benchmarks" ) From 002ddbee48f5612ce0e2af0e62a6aaab2d1d1654 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 2 Feb 2019 19:56:45 +0300 Subject: [PATCH 50/70] Sequential operations --- .../kmath/sequential/Accumulators.kt | 37 ++++++++++++++++--- .../scientifik/kmath/sequential/Reducers.kt | 2 +- .../kmath/sequential/AccumulatorsExt.kt | 28 ++++++++++++++ .../kmath/{chains => sequential}/ChainExt.kt | 3 +- 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt rename kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/{chains => sequential}/ChainExt.kt (93%) diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt index 283db0809..94ac0ac2e 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt @@ -1,25 +1,50 @@ package scientifik.kmath.sequential import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.atomicArrayOfNulls import kotlinx.atomicfu.getAndUpdate +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel import scientifik.kmath.operations.Space /** * An object with a state that accumulates incoming elements */ interface Accumulator { - //PENDING use suspend operations? + /** + * Push a value to accumulator. Blocks if [Accumulator] can't access any more elements at that time + */ fun push(value: T) -} -fun Accumulator.pushAll(values: Iterable) { - values.forEach { push(it) } + /** + * Does the same as [push], but suspends instead of blocking if accumulator is full + */ + suspend fun send(value: T) = push(value) } /** - * Generic thread-safe summator + * Push all elements to accumulator */ -class GenericSum(val context: Space) : Accumulator { +fun Accumulator.pushAll(values: Iterable) { + for (value in values) { + push(value) + } +} + +/** + * Offer all elements from channel to accumulator + */ +suspend fun Accumulator.offerAll(channel: ReceiveChannel) { + for (value in channel) { + send(value) + } +} + +/** + * Generic thread-safe average + */ +class GenericMean(val context: Space) : Accumulator { //TODO add guard against overflow val counter = atomic(0) val sum = atomic(context.zero) diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt index 65dea5ac0..cf0601135 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt @@ -16,6 +16,6 @@ inline fun Array.reduce(context: C, crossinline reducer: Reducer mean(): Reducer, T> = { context, data -> - data.fold(GenericSum(context)) { sum, value -> sum.apply { push(value) } }.value + data.fold(GenericMean(context)) { sum, value -> sum.apply { push(value) } }.value } } \ No newline at end of file diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt new file mode 100644 index 000000000..cc9a07668 --- /dev/null +++ b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt @@ -0,0 +1,28 @@ +package scientifik.kmath.sequential + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import scientifik.kmath.operations.Space +import scientifik.kmath.structures.runBlocking +import java.util.* + +/** + * A moving average with fixed window + */ +class MovingAverage(val window: Int, val context: Space) : Accumulator { + private val outputChannel = Channel() + private val queue = ArrayDeque(window) + + override suspend fun send(value: T) { + queue.add(value) + if (queue.size == window) { + val sum = queue.fold(context.zero) { a, b -> context.run { a + b } } + outputChannel.send(context.run { sum / window }) + queue.pop() + } + } + + override fun push(value: T) = runBlocking { send(value) } + + val output: ReceiveChannel = outputChannel +} \ No newline at end of file diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt similarity index 93% rename from kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt rename to kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt index af61a5059..74cb6bc6d 100644 --- a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/chains/ChainExt.kt +++ b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/ChainExt.kt @@ -1,7 +1,6 @@ -package scientifik.kmath.chains +package scientifik.kmath.sequential import kotlinx.coroutines.runBlocking -import scientifik.kmath.sequential.Chain import kotlin.sequences.Sequence /** From 3413d21385ffb58910c2463b2aad64d7bdb26e70 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 3 Feb 2019 13:07:35 +0300 Subject: [PATCH 51/70] Fixed atomicfu build issues (no js) --- build.gradle.kts | 1 + kmath-coroutines/build.gradle | 14 +++++----- .../kmath/structures/runBlocking.kt | 11 ++++++++ .../{_CoroutinesExtra.kt => runBlocking.kt} | 0 kmath-sequential/build.gradle.kts | 28 ++++++++++--------- .../kmath/sequential/Accumulators.kt | 5 +--- .../scientifik/kmath/sequential/Chain.kt | 14 ++++++---- .../kmath/sequential}/CumulativeKtTest.kt | 0 .../kmath/sequential/AccumulatorsExt.kt | 15 ++++++---- settings.gradle.kts | 12 +------- 10 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt rename kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/{_CoroutinesExtra.kt => runBlocking.kt} (100%) rename {kmath-core/src/commonTest/kotlin/scientifik/kmath/misc => kmath-sequential/src/commonTest/scientifik/kmath/sequential}/CumulativeKtTest.kt (100%) diff --git a/build.gradle.kts b/build.gradle.kts index d1667d7b9..4d15bfe0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ buildscript { val kotlinVersion: String by rootProject.extra("1.3.20") val ioVersion: String by rootProject.extra("0.1.2") val coroutinesVersion: String by rootProject.extra("1.1.1") + val atomicfuVersion: String by rootProject.extra("0.12.1") repositories { //maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/kmath-coroutines/build.gradle b/kmath-coroutines/build.gradle index 7149e8bb9..2c58a4112 100644 --- a/kmath-coroutines/build.gradle +++ b/kmath-coroutines/build.gradle @@ -3,13 +3,8 @@ plugins { } 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') - } + jvm() + js() sourceSets { commonMain { dependencies { @@ -34,6 +29,11 @@ kotlin { implementation 'org.jetbrains.kotlin:kotlin-test-junit' } } + jsMain{ + dependencies{ + api "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion" + } + } // mingwMain { // } // mingwTest { diff --git a/kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt b/kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt new file mode 100644 index 000000000..7331d1801 --- /dev/null +++ b/kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt @@ -0,0 +1,11 @@ +package scientifik.kmath.structures + +import kotlinx.coroutines.CoroutineScope +import kotlin.coroutines.CoroutineContext + +actual fun runBlocking( + context: CoroutineContext, + function: suspend CoroutineScope.() -> R +): R { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +} \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/runBlocking.kt similarity index 100% rename from kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt rename to kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/runBlocking.kt diff --git a/kmath-sequential/build.gradle.kts b/kmath-sequential/build.gradle.kts index 57467d7b8..391504f23 100644 --- a/kmath-sequential/build.gradle.kts +++ b/kmath-sequential/build.gradle.kts @@ -1,19 +1,20 @@ plugins { kotlin("multiplatform") - id("kotlinx-atomicfu") + id("kotlinx-atomicfu") version "0.12.1" } +val atomicfuVersion: String by rootProject.extra kotlin { jvm () - js() + //js() sourceSets { val commonMain by getting { dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) - compileOnly("org.jetbrains.kotlinx:atomicfu-common:0.12.1") + compileOnly("org.jetbrains.kotlinx:atomicfu-common:$atomicfuVersion") } } val commonTest by getting { @@ -24,7 +25,7 @@ kotlin { } val jvmMain by getting { dependencies { - compileOnly("org.jetbrains.kotlinx:atomicfu:0.12.1") + compileOnly("org.jetbrains.kotlinx:atomicfu:$atomicfuVersion") } } val jvmTest by getting { @@ -35,17 +36,18 @@ kotlin { } // val jsMain by getting { // dependencies { -// api(kotlin("stdlib-js")) +// compileOnly("org.jetbrains.kotlinx:atomicfu-js:$atomicfuVersion") // } // } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) - } - } -// mingwMain { -// } -// mingwTest { +// val jsTest by getting { +// dependencies { +// implementation(kotlin("test-js")) +// } // } + } +} + +atomicfu { + variant = "VH" } \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt index 94ac0ac2e..ef7950519 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt @@ -1,10 +1,7 @@ package scientifik.kmath.sequential import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.atomicArrayOfNulls import kotlinx.atomicfu.getAndUpdate -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import scientifik.kmath.operations.Space @@ -46,7 +43,7 @@ suspend fun Accumulator.offerAll(channel: ReceiveChannel) { */ class GenericMean(val context: Space) : Accumulator { //TODO add guard against overflow - val counter = atomic(0) + private val counter = atomic(0) val sum = atomic(context.zero) val value get() = with(context) { sum.value / counter.value } diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt index abecf014d..7633b2223 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Chain.kt @@ -97,11 +97,12 @@ class MarkovChain(private val seed: () -> R, private val gen: suspe constructor(seed: R, gen: suspend (R) -> R) : this({ seed }, gen) - private val atomicValue by lazy { atomic(seed()) } - override val value: R get() = atomicValue.value + private val atomicValue = atomic(null) + override val value: R get() = atomicValue.value ?: seed() override suspend fun next(): R { - atomicValue.lazySet(gen(value)) + val newValue = gen(value) + atomicValue.lazySet(newValue) return value } @@ -122,11 +123,12 @@ class StatefulChain( constructor(state: S, seed: R, gen: suspend S.(R) -> R) : this(state, { seed }, gen) - private val atomicValue by lazy { atomic(seed(state)) } - override val value: R get() = atomicValue.value + private val atomicValue = atomic(null) + override val value: R get() = atomicValue.value ?: seed(state) override suspend fun next(): R { - atomicValue.lazySet(gen(state, value)) + val newValue = gen(state, value) + atomicValue.lazySet(newValue) return value } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt b/kmath-sequential/src/commonTest/scientifik/kmath/sequential/CumulativeKtTest.kt similarity index 100% rename from kmath-core/src/commonTest/kotlin/scientifik/kmath/misc/CumulativeKtTest.kt rename to kmath-sequential/src/commonTest/scientifik/kmath/sequential/CumulativeKtTest.kt diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt index cc9a07668..37190f8f3 100644 --- a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt +++ b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt @@ -2,6 +2,8 @@ package scientifik.kmath.sequential import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import scientifik.kmath.operations.Space import scientifik.kmath.structures.runBlocking import java.util.* @@ -12,13 +14,16 @@ import java.util.* class MovingAverage(val window: Int, val context: Space) : Accumulator { private val outputChannel = Channel() private val queue = ArrayDeque(window) + private val mutex = Mutex() override suspend fun send(value: T) { - queue.add(value) - if (queue.size == window) { - val sum = queue.fold(context.zero) { a, b -> context.run { a + b } } - outputChannel.send(context.run { sum / window }) - queue.pop() + mutex.withLock { + queue.add(value) + if (queue.size == window) { + val sum = queue.fold(context.zero) { a, b -> context.run { a + b } } + outputChannel.send(context.run { sum / window }) + queue.pop() + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5638223fc..7045009ca 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,13 +1,3 @@ -buildscript { - dependencies { - classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.12.1") - } - - repositories { - jcenter() - } -} - pluginManagement { repositories { mavenCentral() @@ -18,7 +8,7 @@ pluginManagement { eachPlugin { when (requested.id.id) { "kotlinx-atomicfu" -> { - useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.12.1") + useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") } } } From 1fe786c90f7141bcadc657ccf0168e1fad582bed Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 5 Feb 2019 22:31:41 +0300 Subject: [PATCH 52/70] Basic streaming blocks --- .../kmath/sequential/Accumulators.kt | 57 ------ .../scientifik/kmath/sequential/Reducers.kt | 21 --- .../kmath/sequential/StreamingBlocks.kt | 170 ++++++++++++++++++ 3 files changed, 170 insertions(+), 78 deletions(-) delete mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt delete mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt deleted file mode 100644 index ef7950519..000000000 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Accumulators.kt +++ /dev/null @@ -1,57 +0,0 @@ -package scientifik.kmath.sequential - -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.getAndUpdate -import kotlinx.coroutines.channels.ReceiveChannel -import scientifik.kmath.operations.Space - -/** - * An object with a state that accumulates incoming elements - */ -interface Accumulator { - /** - * Push a value to accumulator. Blocks if [Accumulator] can't access any more elements at that time - */ - fun push(value: T) - - /** - * Does the same as [push], but suspends instead of blocking if accumulator is full - */ - suspend fun send(value: T) = push(value) -} - -/** - * Push all elements to accumulator - */ -fun Accumulator.pushAll(values: Iterable) { - for (value in values) { - push(value) - } -} - -/** - * Offer all elements from channel to accumulator - */ -suspend fun Accumulator.offerAll(channel: ReceiveChannel) { - for (value in channel) { - send(value) - } -} - -/** - * Generic thread-safe average - */ -class GenericMean(val context: Space) : Accumulator { - //TODO add guard against overflow - private val counter = atomic(0) - val sum = atomic(context.zero) - - val value get() = with(context) { sum.value / counter.value } - - override fun push(value: T) { - with(context) { - counter.incrementAndGet() - sum.getAndUpdate { it + value } - } - } -} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt deleted file mode 100644 index cf0601135..000000000 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Reducers.kt +++ /dev/null @@ -1,21 +0,0 @@ -package scientifik.kmath.sequential - -import scientifik.kmath.operations.Space - - -typealias Reducer = (C, Iterable) -> R - -inline fun Iterable.reduce(context: C, crossinline reducer: Reducer) = - reducer(context, this@reduce) - -inline fun Sequence.reduce(context: C, crossinline reducer: Reducer) = - asIterable().reduce(context, reducer) - -inline fun Array.reduce(context: C, crossinline reducer: Reducer) = - asIterable().reduce(context, reducer) - -object Reducers { - fun mean(): Reducer, T> = { context, data -> - data.fold(GenericMean(context)) { sum, value -> sum.apply { push(value) } }.value - } -} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt new file mode 100644 index 000000000..baacead46 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt @@ -0,0 +1,170 @@ +package scientifik.kmath.sequential + +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** + * Initial chain block. Could produce an element sequence and be connected to single [Consumer] + * + * The general rule is that channel is created on first call. Also each element is responsible for its connection so + * while the connections are symmetric, the scope, used for making the connection is responsible for cancelation. + * + * Also connections are not reversible. Once connected block stays faithful until it finishes processing. + * Manually putting elements to connected block could lead to undetermined behavior and must be avoided. + */ +interface Producer { + val output: ReceiveChannel + fun connect(consumer: Consumer) + + val consumer: Consumer? + + val outputIsConnected: Boolean get() = consumer != null +} + +/** + * Terminal chain block. Could consume an element sequence and be connected to signle [Producer] + */ +interface Consumer { + val input: SendChannel + fun connect(producer: Producer) + + val producer: Producer? + + val inputIsConnected: Boolean get() = producer != null +} + +interface Processor : Consumer, Producer + +abstract class AbstractProducer(protected val scope: CoroutineScope) : Producer { + override var consumer: Consumer? = null + protected set + + override fun connect(consumer: Consumer) { + //Ignore if already connected to specific consumer + if (consumer != this.consumer) { + if (outputIsConnected) error("The output slot of producer is occupied") + if (consumer.inputIsConnected) error("The input slot of consumer is occupied") + this.consumer = consumer + if (consumer.producer != null) { + //No need to save the job, it will be canceled on scope cancel + scope.launch { + output.toChannel(consumer.input) + } + // connect back, consumer is already set so no circular reference + consumer.connect(this) + } else error("Unreachable statement") + } + } +} + +abstract class AbstractConsumer(protected val scope: CoroutineScope) : Consumer { + override var producer: Producer? = null + protected set + + override fun connect(producer: Producer) { + //Ignore if already connected to specific consumer + if (producer != this.producer) { + if (inputIsConnected) error("The input slot of consumer is occupied") + if (producer.outputIsConnected) error("The input slot of producer is occupied") + this.producer = producer + //No need to save the job, it will be canceled on scope cancel + if (producer.consumer != null) { + scope.launch { + producer.output.toChannel(input) + } + // connect back + producer.connect(this) + } else error("Unreachable statement") + } + } +} + +abstract class AbstracProcessor(scope: CoroutineScope) : Processor, AbstractProducer(scope) { + + override var producer: Producer? = null + protected set + + override fun connect(producer: Producer) { + //Ignore if already connected to specific consumer + if (producer != this.producer) { + if (inputIsConnected) error("The input slot of consumer is occupied") + if (producer.outputIsConnected) error("The input slot of producer is occupied") + this.producer = producer + //No need to save the job, it will be canceled on scope cancel + if (producer.consumer != null) { + scope.launch { + producer.output.toChannel(input) + } + // connect back + producer.connect(this) + } else error("Unreachable statement") + } + } +} + +/** + * A simple [produce]-based producer + */ +class GenericProducer( + scope: CoroutineScope, + capacity: Int = Channel.UNLIMITED, + block: suspend ProducerScope.() -> Unit +) : AbstractProducer(scope) { + //The generation begins on first request to output + override val output: ReceiveChannel by lazy { scope.produce(capacity = capacity, block = block) } +} + +/** + * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure + * to incorporate them into state on-arrival. + * The current aggregated state could be accessed by [value]. The input channel is inactive unless requested + * @param T - the type of the input element + * @param S - the type of the aggregator + */ +class Reducer( + scope: CoroutineScope, + initialState: S, + fold: suspend (S, T) -> S +) : AbstractConsumer(scope) { + + private val state = atomic(initialState) + + val value: S = state.value + + override val input: SendChannel by lazy { + //create a channel and start process of reading all elements into aggregator + Channel(capacity = Channel.RENDEZVOUS).also { + scope.launch { + it.consumeEach { value -> state.update { fold(it, value) } } + } + } + } +} + +/** + * Collector that accumulates all values in a list. List could be accessed from non-suspending environment via [list] value. + */ +class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { + + private val _list = ArrayList() + private val mutex = Mutex() + val list: List get() = _list + + override val input: SendChannel by lazy { + //create a channel and start process of reading all elements into aggregator + Channel(capacity = Channel.RENDEZVOUS).also { + scope.launch { + it.consumeEach { value -> + mutex.withLock { + _list.add(value) + } + } + } + } + } +} \ No newline at end of file From f8f7aa2e44a0772c85807ca1563aae9be9eb4cf3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 6 Feb 2019 18:13:30 +0300 Subject: [PATCH 53/70] Specialized streaming blocks for doubles --- .../sequential/SpecializedStreamingBlocks.kt | 55 +++++++ .../kmath/sequential/StreamingBlocks.kt | 136 ++++++++++++++++-- 2 files changed, 176 insertions(+), 15 deletions(-) create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt new file mode 100644 index 000000000..43752af70 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt @@ -0,0 +1,55 @@ +package scientifik.kmath.sequential + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.toChannel + +interface DoubleProducer : Producer { + val arrayOutput: ReceiveChannel +} + +interface DoubleConsumer : Consumer { + val arrayInput: SendChannel +} + + +abstract class AbstractDoubleProducer(scope: CoroutineScope) : AbstractProducer(scope), DoubleProducer { + override suspend fun connectOutput(consumer: Consumer) { + if (consumer is DoubleConsumer) { + arrayOutput.toChannel(consumer.arrayInput) + } else { + super.connectOutput(consumer) + } + } +} + +abstract class AbstractDoubleConsumer(scope: CoroutineScope) : AbstractConsumer(scope), DoubleConsumer { + override suspend fun connectInput(producer: Producer) { + if (producer is DoubleProducer) { + producer.arrayOutput.toChannel(arrayInput) + } else { + super.connectInput(producer) + } + } +} + +abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcessor(scope), + DoubleProducer, DoubleConsumer { + + override suspend fun connectOutput(consumer: Consumer) { + if (consumer is DoubleConsumer) { + arrayOutput.toChannel(consumer.arrayInput) + } else { + super.connectOutput(consumer) + } + } + + override suspend fun connectInput(producer: Producer) { + if (producer is DoubleProducer) { + producer.arrayOutput.toChannel(arrayInput) + } else { + super.connectInput(producer) + } + } +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt index baacead46..852a0d682 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt @@ -3,10 +3,12 @@ package scientifik.kmath.sequential import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.* import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlin.coroutines.CoroutineContext /** * Initial chain block. Could produce an element sequence and be connected to single [Consumer] @@ -17,7 +19,7 @@ import kotlinx.coroutines.sync.withLock * Also connections are not reversible. Once connected block stays faithful until it finishes processing. * Manually putting elements to connected block could lead to undetermined behavior and must be avoided. */ -interface Producer { +interface Producer : CoroutineScope { val output: ReceiveChannel fun connect(consumer: Consumer) @@ -29,7 +31,7 @@ interface Producer { /** * Terminal chain block. Could consume an element sequence and be connected to signle [Producer] */ -interface Consumer { +interface Consumer : CoroutineScope { val input: SendChannel fun connect(producer: Producer) @@ -40,7 +42,9 @@ interface Consumer { interface Processor : Consumer, Producer -abstract class AbstractProducer(protected val scope: CoroutineScope) : Producer { +abstract class AbstractProducer(scope: CoroutineScope) : Producer { + override val coroutineContext: CoroutineContext = scope.coroutineContext + override var consumer: Consumer? = null protected set @@ -52,17 +56,23 @@ abstract class AbstractProducer(protected val scope: CoroutineScope) : Produc this.consumer = consumer if (consumer.producer != null) { //No need to save the job, it will be canceled on scope cancel - scope.launch { - output.toChannel(consumer.input) + launch { + connectOutput(consumer) } // connect back, consumer is already set so no circular reference consumer.connect(this) } else error("Unreachable statement") } } + + protected open suspend fun connectOutput(consumer: Consumer) { + output.toChannel(consumer.input) + } } -abstract class AbstractConsumer(protected val scope: CoroutineScope) : Consumer { +abstract class AbstractConsumer(scope: CoroutineScope) : Consumer { + override val coroutineContext: CoroutineContext = scope.coroutineContext + override var producer: Producer? = null protected set @@ -74,17 +84,21 @@ abstract class AbstractConsumer(protected val scope: CoroutineScope) : Consum this.producer = producer //No need to save the job, it will be canceled on scope cancel if (producer.consumer != null) { - scope.launch { - producer.output.toChannel(input) + launch { + connectInput(producer) } // connect back producer.connect(this) } else error("Unreachable statement") } } + + protected open suspend fun connectInput(producer: Producer) { + producer.output.toChannel(input) + } } -abstract class AbstracProcessor(scope: CoroutineScope) : Processor, AbstractProducer(scope) { +abstract class AbstractProcessor(scope: CoroutineScope) : Processor, AbstractProducer(scope) { override var producer: Producer? = null protected set @@ -97,14 +111,18 @@ abstract class AbstracProcessor(scope: CoroutineScope) : Processor, this.producer = producer //No need to save the job, it will be canceled on scope cancel if (producer.consumer != null) { - scope.launch { - producer.output.toChannel(input) + launch { + connectInput(producer) } // connect back producer.connect(this) } else error("Unreachable statement") } } + + protected open suspend fun connectInput(producer: Producer) { + producer.output.toChannel(input) + } } /** @@ -116,9 +134,67 @@ class GenericProducer( block: suspend ProducerScope.() -> Unit ) : AbstractProducer(scope) { //The generation begins on first request to output - override val output: ReceiveChannel by lazy { scope.produce(capacity = capacity, block = block) } + override val output: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } } +/** + * A simple pipeline [Processor] block + */ +class PipeProcessor( + scope: CoroutineScope, + capacity: Int = Channel.RENDEZVOUS, + process: suspend (T) -> R +) : AbstractProcessor(scope) { + + private val _input = Channel(capacity) + override val input: SendChannel get() = _input + override val output: ReceiveChannel = _input.map(coroutineContext, process) +} + +/** + * A [Processor] that splits the input in fixed chunk size and transforms each chunk + */ +class ChunkProcessor( + scope: CoroutineScope, + chunkSize: Int, + process: suspend (List) -> R +) : AbstractProcessor(scope) { + + private val _input = Channel(chunkSize) + + override val input: SendChannel get() = _input + + private val chunked = produce>(coroutineContext) { + val list = ArrayList(chunkSize) + repeat(chunkSize) { + list.add(_input.receive()) + } + send(list) + } + + override val output: ReceiveChannel = chunked.map(coroutineContext, process) +} + +/** + * A moving window [Processor] + */ +class WindowProcessor( + scope: CoroutineScope, + window: Int, + process: suspend (List) -> R +) : AbstractProcessor(scope) { + + + override val output: ReceiveChannel + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + override val input: SendChannel + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + + +} + +//TODO add circular buffer processor + /** * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure * to incorporate them into state on-arrival. @@ -139,7 +215,7 @@ class Reducer( override val input: SendChannel by lazy { //create a channel and start process of reading all elements into aggregator Channel(capacity = Channel.RENDEZVOUS).also { - scope.launch { + launch { it.consumeEach { value -> state.update { fold(it, value) } } } } @@ -158,7 +234,7 @@ class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { override val input: SendChannel by lazy { //create a channel and start process of reading all elements into aggregator Channel(capacity = Channel.RENDEZVOUS).also { - scope.launch { + launch { it.consumeEach { value -> mutex.withLock { _list.add(value) @@ -167,4 +243,34 @@ class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { } } } -} \ No newline at end of file +} + +/** + * Convert a sequence to [Producer] + */ +fun Sequence.produce(scope: CoroutineScope = GlobalScope) = + GenericProducer(scope) { forEach { send(it) } } + +/** + * Convert a [ReceiveChannel] to [Producer] + */ +fun ReceiveChannel.produce(scope: CoroutineScope = GlobalScope) = + GenericProducer(scope) { for (e in this@produce) send(e) } + +/** + * Create a reducer and connect this producer to reducer + */ +fun Producer.reduce(initialState: S, fold: suspend (S, T) -> S) = + Reducer(this, initialState, fold).also { connect(it) } + +/** + * Create a [Collector] and attach it to this [Producer] + */ +fun Producer.collect() = + Collector(this).also { connect(it) } + +fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = + PipeProcessor(this, capacity, process) + +fun Producer.chunk(chunkSize: Int, process: suspend (List) -> R) = + ChunkProcessor(this, chunkSize, process) \ No newline at end of file From c0bdacecb32a03b1e7e86d48448dfc7f02870e9b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 6 Feb 2019 18:18:10 +0300 Subject: [PATCH 54/70] Moved sums out of Space --- .../kotlin/scientifik/kmath/operations/Algebra.kt | 3 --- .../scientifik/kmath/operations/AlgebraExtensions.kt | 7 +++++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 8aa3a0e05..42c7e9377 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -33,9 +33,6 @@ interface Space { operator fun T.times(k: Number) = multiply(this, k.toDouble()) operator fun T.div(k: Number) = multiply(this, 1.0 / k.toDouble()) operator fun Number.times(b: T) = b * this - - fun Iterable.sum(): T = fold(zero) { left, right -> add(left,right) } - fun Sequence.sum(): T = fold(zero) { left, right -> add(left, right) } } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt new file mode 100644 index 000000000..6dec8bd79 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt @@ -0,0 +1,7 @@ +package scientifik.kmath.operations + +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asSequence + +fun Space.sum(data : Iterable): T = data.fold(zero) { left, right -> add(left,right) } +fun Space.sum(data : Sequence): T = data.fold(zero) { left, right -> add(left, right) } \ No newline at end of file From 28695148e92c6e41fd92ed187fe66c42f7b41ba8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 6 Feb 2019 22:24:03 +0300 Subject: [PATCH 55/70] Hidden channels in streaming blocks. --- .../sequential/SpecializedStreamingBlocks.kt | 38 +++++-- .../kmath/sequential/StreamingBlocks.kt | 102 +++++++++++------- 2 files changed, 96 insertions(+), 44 deletions(-) diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt index 43752af70..a4f26113f 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt @@ -1,16 +1,17 @@ package scientifik.kmath.sequential +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.channels.toChannel +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.launch interface DoubleProducer : Producer { - val arrayOutput: ReceiveChannel + suspend fun receiveArray(): DoubleArray } interface DoubleConsumer : Consumer { - val arrayInput: SendChannel + suspend fun sendArray(): DoubleArray } @@ -19,7 +20,7 @@ abstract class AbstractDoubleProducer(scope: CoroutineScope) : AbstractProducer< if (consumer is DoubleConsumer) { arrayOutput.toChannel(consumer.arrayInput) } else { - super.connectOutput(consumer) + connectOutput(super, consumer) } } } @@ -41,7 +42,7 @@ abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcesso if (consumer is DoubleConsumer) { arrayOutput.toChannel(consumer.arrayInput) } else { - super.connectOutput(consumer) + connectOutput(super, consumer) } } @@ -52,4 +53,27 @@ abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcesso super.connectInput(producer) } } +} + +class DoubleReducer( + scope: CoroutineScope, + initialState: S, + fold: suspend (S, DoubleArray) -> S +) : AbstractDoubleConsumer(scope) { + private val state = atomic(initialState) + + val value: S = state.value + + override val arrayInput: SendChannel by lazy { + //create a channel and start process of reading all elements into aggregator + Channel(capacity = Channel.RENDEZVOUS).also { + launch { + it.consumeEach { value -> state.update { fold(it, value) } } + } + } + } + + override val input: SendChannel = object :Abstr + + } \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt index 852a0d682..2cc412a9a 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt @@ -5,6 +5,7 @@ import kotlinx.atomicfu.update import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.* +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -20,24 +21,30 @@ import kotlin.coroutines.CoroutineContext * Manually putting elements to connected block could lead to undetermined behavior and must be avoided. */ interface Producer : CoroutineScope { - val output: ReceiveChannel fun connect(consumer: Consumer) + suspend fun receive(): T + val consumer: Consumer? val outputIsConnected: Boolean get() = consumer != null + + //fun close() } /** * Terminal chain block. Could consume an element sequence and be connected to signle [Producer] */ interface Consumer : CoroutineScope { - val input: SendChannel fun connect(producer: Producer) + suspend fun send(value: T) + val producer: Producer? val inputIsConnected: Boolean get() = producer != null + + //fun close() } interface Processor : Consumer, Producer @@ -56,17 +63,19 @@ abstract class AbstractProducer(scope: CoroutineScope) : Producer { this.consumer = consumer if (consumer.producer != null) { //No need to save the job, it will be canceled on scope cancel - launch { - connectOutput(consumer) - } + connectOutput(consumer) // connect back, consumer is already set so no circular reference consumer.connect(this) } else error("Unreachable statement") } } - protected open suspend fun connectOutput(consumer: Consumer) { - output.toChannel(consumer.input) + protected open fun connectOutput(consumer: Consumer) { + launch { + while (this.isActive) { + consumer.send(receive()) + } + } } } @@ -84,17 +93,19 @@ abstract class AbstractConsumer(scope: CoroutineScope) : Consumer { this.producer = producer //No need to save the job, it will be canceled on scope cancel if (producer.consumer != null) { - launch { - connectInput(producer) - } + connectInput(producer) // connect back producer.connect(this) } else error("Unreachable statement") } } - protected open suspend fun connectInput(producer: Producer) { - producer.output.toChannel(input) + protected open fun connectInput(producer: Producer) { + launch { + while (isActive) { + send(producer.receive()) + } + } } } @@ -111,17 +122,19 @@ abstract class AbstractProcessor(scope: CoroutineScope) : Processor, this.producer = producer //No need to save the job, it will be canceled on scope cancel if (producer.consumer != null) { - launch { - connectInput(producer) - } + connectInput(producer) // connect back producer.connect(this) } else error("Unreachable statement") } } - protected open suspend fun connectInput(producer: Producer) { - producer.output.toChannel(input) + protected open fun connectInput(producer: Producer) { + launch { + while (isActive) { + send(producer.receive()) + } + } } } @@ -133,8 +146,10 @@ class GenericProducer( capacity: Int = Channel.UNLIMITED, block: suspend ProducerScope.() -> Unit ) : AbstractProducer(scope) { - //The generation begins on first request to output - override val output: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } + + private val channel: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } + + override suspend fun receive(): T = channel.receive() } /** @@ -146,9 +161,14 @@ class PipeProcessor( process: suspend (T) -> R ) : AbstractProcessor(scope) { - private val _input = Channel(capacity) - override val input: SendChannel get() = _input - override val output: ReceiveChannel = _input.map(coroutineContext, process) + private val input = Channel(capacity) + private val output: ReceiveChannel = input.map(coroutineContext, process) + + override suspend fun receive(): R = output.receive() + + override suspend fun send(value: T) { + input.send(value) + } } /** @@ -160,41 +180,45 @@ class ChunkProcessor( process: suspend (List) -> R ) : AbstractProcessor(scope) { - private val _input = Channel(chunkSize) - - override val input: SendChannel get() = _input + private val input = Channel(chunkSize) private val chunked = produce>(coroutineContext) { val list = ArrayList(chunkSize) repeat(chunkSize) { - list.add(_input.receive()) + list.add(input.receive()) } send(list) } - override val output: ReceiveChannel = chunked.map(coroutineContext, process) + private val output: ReceiveChannel = chunked.map(coroutineContext, process) + + override suspend fun receive(): R = output.receive() + + override suspend fun send(value: T) { + input.send(value) + } } /** - * A moving window [Processor] + * A moving window [Processor] with circular buffer */ -class WindowProcessor( +class WindowedProcessor( scope: CoroutineScope, window: Int, process: suspend (List) -> R ) : AbstractProcessor(scope) { + override suspend fun receive(): R { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } - override val output: ReceiveChannel - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. - override val input: SendChannel - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. + override suspend fun send(value: T) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } } -//TODO add circular buffer processor - /** * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure * to incorporate them into state on-arrival. @@ -212,7 +236,7 @@ class Reducer( val value: S = state.value - override val input: SendChannel by lazy { + private val input: SendChannel by lazy { //create a channel and start process of reading all elements into aggregator Channel(capacity = Channel.RENDEZVOUS).also { launch { @@ -220,6 +244,8 @@ class Reducer( } } } + + override suspend fun send(value: T) = input.send(value) } /** @@ -231,7 +257,7 @@ class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { private val mutex = Mutex() val list: List get() = _list - override val input: SendChannel by lazy { + private val input: SendChannel by lazy { //create a channel and start process of reading all elements into aggregator Channel(capacity = Channel.RENDEZVOUS).also { launch { @@ -243,6 +269,8 @@ class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { } } } + + override suspend fun send(value: T) = input.send(value) } /** From 58a22e4338db026a4466a1066f600d2bbf06b8d1 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 9 Feb 2019 10:44:28 +0300 Subject: [PATCH 56/70] Ring buffer. At last. --- .../kotlin/scientifik/kmath/linear/Matrix.kt | 5 +- .../scientifik/kmath/structures/Buffers.kt | 2 +- .../scientifik/kmath/sequential/Cumulative.kt | 13 ++ .../kmath/sequential/DoubleStreaming.kt | 136 ++++++++++++++++++ .../scientifik/kmath/sequential/RingBuffer.kt | 89 ++++++++++++ .../sequential/SpecializedStreamingBlocks.kt | 79 ---------- .../{StreamingBlocks.kt => Streaming.kt} | 70 +++++---- .../kmath/sequential/CumulativeKtTest.kt | 1 + .../kmath/sequential/RingBufferTest.kt | 19 +++ .../kmath/sequential/AccumulatorsExt.kt | 33 ----- settings.gradle.kts | 2 +- 11 files changed, 296 insertions(+), 153 deletions(-) create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt delete mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt rename kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/{StreamingBlocks.kt => Streaming.kt} (83%) rename kmath-sequential/src/commonTest/{ => kotlin}/scientifik/kmath/sequential/CumulativeKtTest.kt (86%) create mode 100644 kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt delete mode 100644 kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt index 646cbb5c1..92acf2bbb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -2,6 +2,7 @@ package scientifik.kmath.linear import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.sum import scientifik.kmath.structures.* import scientifik.kmath.structures.Buffer.Companion.DoubleBufferFactory import scientifik.kmath.structures.Buffer.Companion.boxing @@ -69,7 +70,7 @@ interface GenericMatrixContext> : MatrixContext { val row = rows[i] val column = other.columns[j] with(elementContext) { - row.asSequence().zip(column.asSequence(), ::multiply).sum() + sum(row.asSequence().zip(column.asSequence(), ::multiply)) } } } @@ -80,7 +81,7 @@ interface GenericMatrixContext> : MatrixContext { return point(rowNum) { i -> val row = rows[i] with(elementContext) { - row.asSequence().zip(vector.asSequence(), ::multiply).sum() + sum(row.asSequence().zip(vector.asSequence(), ::multiply)) } } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 1d6eecf52..8256b596d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -77,7 +77,7 @@ interface MutableBuffer : Buffer { /** * Create a boxing mutable buffer of given type */ - inline fun boxing(size: Int, initializer: (Int) -> T): MutableBuffer = + inline fun boxing(size: Int, initializer: (Int) -> T): MutableBuffer = MutableListBuffer(MutableList(size, initializer)) /** diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt index f02c3ad64..b0e1e9ac5 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Cumulative.kt @@ -1,5 +1,6 @@ package scientifik.kmath.sequential +import scientifik.kmath.operations.Space import kotlin.jvm.JvmName @@ -32,6 +33,10 @@ fun List.cumulative(initial: R, operation: (T, R) -> R): List = //Cumulative sum +fun Iterable.cumulativeSum(space: Space) = with(space) { + cumulative(zero) { element: T, sum: T -> sum + element } +} + @JvmName("cumulativeSumOfDouble") fun Iterable.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @@ -41,6 +46,10 @@ fun Iterable.cumulativeSum() = this.cumulative(0) { element, sum -> sum + e @JvmName("cumulativeSumOfLong") fun Iterable.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } +fun Sequence.cumulativeSum(space: Space) = with(space) { + cumulative(zero) { element: T, sum: T -> sum + element } +} + @JvmName("cumulativeSumOfDouble") fun Sequence.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } @@ -50,6 +59,10 @@ fun Sequence.cumulativeSum() = this.cumulative(0) { element, sum -> sum + e @JvmName("cumulativeSumOfLong") fun Sequence.cumulativeSum() = this.cumulative(0L) { element, sum -> sum + element } +fun List.cumulativeSum(space: Space) = with(space) { + cumulative(zero) { element: T, sum: T -> sum + element } +} + @JvmName("cumulativeSumOfDouble") fun List.cumulativeSum() = this.cumulative(0.0) { element, sum -> sum + element } diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt new file mode 100644 index 000000000..d18c76634 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt @@ -0,0 +1,136 @@ +package scientifik.kmath.sequential + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex + +interface DoubleProducer : Producer { + suspend fun receiveArray(): DoubleArray +} + +interface DoubleConsumer : Consumer { + suspend fun sendArray(array: DoubleArray) +} + +abstract class AbstractDoubleProducer(scope: CoroutineScope) : AbstractProducer(scope), DoubleProducer { + + override fun connectOutput(consumer: Consumer) { + if (consumer is DoubleConsumer) { + launch { + while (this.isActive) { + consumer.sendArray(receiveArray()) + } + } + } else { + super.connectOutput(consumer) + } + } +} + +abstract class AbstractDoubleConsumer(scope: CoroutineScope) : AbstractConsumer(scope), DoubleConsumer { + override fun connectInput(producer: Producer) { + if (producer is DoubleProducer) { + launch { + while (isActive) { + sendArray(producer.receiveArray()) + } + } + } else { + super.connectInput(producer) + } + } +} + +abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcessor(scope), + DoubleProducer, DoubleConsumer { + + override fun connectOutput(consumer: Consumer) { + if (consumer is DoubleConsumer) { + launch { + while (this.isActive) { + consumer.sendArray(receiveArray()) + } + } + } else { + super.connectOutput(consumer) + } + } + + override fun connectInput(producer: Producer) { + if (producer is DoubleProducer) { + launch { + while (isActive) { + sendArray(producer.receiveArray()) + } + } + } else { + super.connectInput(producer) + } + } +} + +/** + * The basic [Double] producer supporting both arrays and element-by-element simultaneously + */ +class BasicDoubleProducer( + scope: CoroutineScope, + capacity: Int = Channel.UNLIMITED, + block: suspend ProducerScope.() -> Unit +) : AbstractDoubleProducer(scope) { + + + private val currentArray = atomic?>(null) + private val channel: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } + private val cachingChannel by lazy { + channel.map { + it.also { doubles -> currentArray.lazySet(doubles.asChannel()) } + } + } + + private fun DoubleArray.asChannel() = produce { + for (value in this@asChannel) { + send(value) + } + } + + override suspend fun receiveArray(): DoubleArray = cachingChannel.receive() + + override suspend fun receive(): Double = (currentArray.value ?: cachingChannel.receive().asChannel()).receive() +} + + +class DoubleReducer( + scope: CoroutineScope, + initialState: S, + val fold: suspend (S, DoubleArray) -> S +) : AbstractDoubleConsumer(scope) { + + var state: S = initialState + private set + + private val mutex = Mutex() + + override suspend fun sendArray(array: DoubleArray) { + state = fold(state, array) + } + + override suspend fun send(value: Double) = sendArray(doubleArrayOf(value)) +} + +/** + * Convert an array to single element producer, splitting it in chunks if necessary + */ +fun DoubleArray.produce(scope: CoroutineScope = GlobalScope, chunkSize: Int = Int.MAX_VALUE) = if (size < chunkSize) { + BasicDoubleProducer(scope) { send(this@produce) } +} else { + BasicDoubleProducer(scope) { + //TODO optimize this! + asSequence().chunked(chunkSize).forEach { + send(it.toDoubleArray()) + } + } +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt new file mode 100644 index 000000000..108b7f828 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/RingBuffer.kt @@ -0,0 +1,89 @@ +package scientifik.kmath.sequential + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.MutableBuffer +import scientifik.kmath.structures.VirtualBuffer + +/** + * Thread-safe ring buffer + */ +internal class RingBuffer( + private val buffer: MutableBuffer, + private var startIndex: Int = 0, + size: Int = 0 +) : Buffer { + + private val mutex = Mutex() + + override var size: Int = size + private set + + override fun get(index: Int): T { + require(index >= 0) { "Index must be positive" } + require(index < size) { "Index $index is out of circular buffer size $size" } + return buffer[startIndex.forward(index)] + } + + fun isFull() = size == buffer.size + + /** + * Iterator could provide wrong results if buffer is changed in initialization (iteration is safe) + */ + override fun iterator(): Iterator = object : AbstractIterator() { + private var count = size + private var index = startIndex + val copy = buffer.copy() + + override fun computeNext() { + if (count == 0) { + done() + } else { + setNext(copy[index]) + index = index.forward(1) + count-- + } + } + } + + /** + * A safe snapshot operation + */ + suspend fun snapshot(): Buffer { + mutex.withLock { + val copy = buffer.copy() + return VirtualBuffer(size) { i -> copy[startIndex.forward(i)] } + } + } + + suspend fun push(element: T) { + mutex.withLock { + buffer[startIndex.forward(size)] = element + if (isFull()) { + startIndex++ + } else { + size++ + } + } + } + + + @Suppress("NOTHING_TO_INLINE") + private inline fun Int.forward(n: Int): Int = (this + n) % (buffer.size) + + companion object { + inline fun build(size: Int, empty: T): RingBuffer { + val buffer = MutableBuffer.auto(size) { empty } + return RingBuffer(buffer) + } + + /** + * Slow yet universal buffer + */ + fun boxing(size: Int): RingBuffer { + val buffer: MutableBuffer = MutableBuffer.boxing(size) { null } + return RingBuffer(buffer) + } + } +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt deleted file mode 100644 index a4f26113f..000000000 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/SpecializedStreamingBlocks.kt +++ /dev/null @@ -1,79 +0,0 @@ -package scientifik.kmath.sequential - -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.update -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.* -import kotlinx.coroutines.launch - -interface DoubleProducer : Producer { - suspend fun receiveArray(): DoubleArray -} - -interface DoubleConsumer : Consumer { - suspend fun sendArray(): DoubleArray -} - - -abstract class AbstractDoubleProducer(scope: CoroutineScope) : AbstractProducer(scope), DoubleProducer { - override suspend fun connectOutput(consumer: Consumer) { - if (consumer is DoubleConsumer) { - arrayOutput.toChannel(consumer.arrayInput) - } else { - connectOutput(super, consumer) - } - } -} - -abstract class AbstractDoubleConsumer(scope: CoroutineScope) : AbstractConsumer(scope), DoubleConsumer { - override suspend fun connectInput(producer: Producer) { - if (producer is DoubleProducer) { - producer.arrayOutput.toChannel(arrayInput) - } else { - super.connectInput(producer) - } - } -} - -abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcessor(scope), - DoubleProducer, DoubleConsumer { - - override suspend fun connectOutput(consumer: Consumer) { - if (consumer is DoubleConsumer) { - arrayOutput.toChannel(consumer.arrayInput) - } else { - connectOutput(super, consumer) - } - } - - override suspend fun connectInput(producer: Producer) { - if (producer is DoubleProducer) { - producer.arrayOutput.toChannel(arrayInput) - } else { - super.connectInput(producer) - } - } -} - -class DoubleReducer( - scope: CoroutineScope, - initialState: S, - fold: suspend (S, DoubleArray) -> S -) : AbstractDoubleConsumer(scope) { - private val state = atomic(initialState) - - val value: S = state.value - - override val arrayInput: SendChannel by lazy { - //create a channel and start process of reading all elements into aggregator - Channel(capacity = Channel.RENDEZVOUS).also { - launch { - it.consumeEach { value -> state.update { fold(it, value) } } - } - } - } - - override val input: SendChannel = object :Abstr - - -} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt similarity index 83% rename from kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt rename to kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt index 2cc412a9a..cc4a68761 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/StreamingBlocks.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt @@ -1,7 +1,5 @@ package scientifik.kmath.sequential -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.update import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.* @@ -9,6 +7,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import scientifik.kmath.structures.Buffer import kotlin.coroutines.CoroutineContext /** @@ -172,7 +171,7 @@ class PipeProcessor( } /** - * A [Processor] that splits the input in fixed chunk size and transforms each chunk + * A [Processor] that splits the input in fixed chunked size and transforms each chunked */ class ChunkProcessor( scope: CoroutineScope, @@ -205,47 +204,44 @@ class ChunkProcessor( class WindowedProcessor( scope: CoroutineScope, window: Int, - process: suspend (List) -> R + val process: suspend (Buffer) -> R ) : AbstractProcessor(scope) { + private val ringBuffer = RingBuffer.boxing(window) + + private val channel = Channel(Channel.RENDEZVOUS) + override suspend fun receive(): R { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + return channel.receive() } override suspend fun send(value: T) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + ringBuffer.push(value) + channel.send(process(ringBuffer.snapshot())) } - - } /** * Thread-safe aggregator of values from input. The aggregator does not store all incoming values, it uses fold procedure * to incorporate them into state on-arrival. - * The current aggregated state could be accessed by [value]. The input channel is inactive unless requested + * The current aggregated state could be accessed by [state]. The input channel is inactive unless requested * @param T - the type of the input element * @param S - the type of the aggregator */ class Reducer( scope: CoroutineScope, initialState: S, - fold: suspend (S, T) -> S + val fold: suspend (S, T) -> S ) : AbstractConsumer(scope) { - private val state = atomic(initialState) + var state: S = initialState + private set - val value: S = state.value + private val mutex = Mutex() - private val input: SendChannel by lazy { - //create a channel and start process of reading all elements into aggregator - Channel(capacity = Channel.RENDEZVOUS).also { - launch { - it.consumeEach { value -> state.update { fold(it, value) } } - } - } + override suspend fun send(value: T) = mutex.withLock { + state = fold(state, value) } - - override suspend fun send(value: T) = input.send(value) } /** @@ -257,20 +253,11 @@ class Collector(scope: CoroutineScope) : AbstractConsumer(scope) { private val mutex = Mutex() val list: List get() = _list - private val input: SendChannel by lazy { - //create a channel and start process of reading all elements into aggregator - Channel(capacity = Channel.RENDEZVOUS).also { - launch { - it.consumeEach { value -> - mutex.withLock { - _list.add(value) - } - } - } + override suspend fun send(value: T) { + mutex.withLock { + _list.add(value) } } - - override suspend fun send(value: T) = input.send(value) } /** @@ -285,6 +272,10 @@ fun Sequence.produce(scope: CoroutineScope = GlobalScope) = fun ReceiveChannel.produce(scope: CoroutineScope = GlobalScope) = GenericProducer(scope) { for (e in this@produce) send(e) } + +fun > Producer.consumer(consumerFactory: () -> C): C = + consumerFactory().also { connect(it) } + /** * Create a reducer and connect this producer to reducer */ @@ -297,8 +288,13 @@ fun Producer.reduce(initialState: S, fold: suspend (S, T) -> S) = fun Producer.collect() = Collector(this).also { connect(it) } -fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = - PipeProcessor(this, capacity, process) +fun > Producer.process(processorBuilder: () -> P): P = + processorBuilder().also { connect(it) } -fun Producer.chunk(chunkSize: Int, process: suspend (List) -> R) = - ChunkProcessor(this, chunkSize, process) \ No newline at end of file +fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = + PipeProcessor(this, capacity, process).also { connect(it) } + +fun Producer.chunked(chunkSize: Int, process: suspend (List) -> R) = + ChunkProcessor(this, chunkSize, process).also { connect(it) } + +fun Producer.chunked(chunkSize: Int) = chunked(chunkSize) { it } diff --git a/kmath-sequential/src/commonTest/scientifik/kmath/sequential/CumulativeKtTest.kt b/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt similarity index 86% rename from kmath-sequential/src/commonTest/scientifik/kmath/sequential/CumulativeKtTest.kt rename to kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt index e7c99e7d0..cafa0526f 100644 --- a/kmath-sequential/src/commonTest/scientifik/kmath/sequential/CumulativeKtTest.kt +++ b/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/CumulativeKtTest.kt @@ -1,5 +1,6 @@ package scientifik.kmath.misc +import scientifik.kmath.sequential.cumulativeSum import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt b/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt new file mode 100644 index 000000000..e2bb18280 --- /dev/null +++ b/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt @@ -0,0 +1,19 @@ +package scientifik.kmath.sequential + +import scientifik.kmath.structures.asSequence +import scientifik.kmath.structures.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals + +class RingBufferTest { + @Test + fun testPush() { + val buffer = RingBuffer.build(20, Double.NaN) + runBlocking { + for (i in 1..30) { + buffer.push(i.toDouble()) + } + assertEquals(410.0, buffer.asSequence().sum()) + } + } +} \ No newline at end of file diff --git a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt b/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt deleted file mode 100644 index 37190f8f3..000000000 --- a/kmath-sequential/src/jvmMain/kotlin/scientifik/kmath/sequential/AccumulatorsExt.kt +++ /dev/null @@ -1,33 +0,0 @@ -package scientifik.kmath.sequential - -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import scientifik.kmath.operations.Space -import scientifik.kmath.structures.runBlocking -import java.util.* - -/** - * A moving average with fixed window - */ -class MovingAverage(val window: Int, val context: Space) : Accumulator { - private val outputChannel = Channel() - private val queue = ArrayDeque(window) - private val mutex = Mutex() - - override suspend fun send(value: T) { - mutex.withLock { - queue.add(value) - if (queue.size == window) { - val sum = queue.fold(context.zero) { a, b -> context.run { a + b } } - outputChannel.send(context.run { sum / window }) - queue.pop() - } - } - } - - override fun push(value: T) = runBlocking { send(value) } - - val output: ReceiveChannel = outputChannel -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 7045009ca..b306d1c8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,7 +20,7 @@ pluginManagement { rootProject.name = "kmath" include( ":kmath-core", - ":kmath-io", +// ":kmath-io", ":kmath-coroutines", ":kmath-commons", ":kmath-koma", From f6cc23ce0a92aad58158a40f370d62727fb29f86 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 10 Feb 2019 09:11:58 +0300 Subject: [PATCH 57/70] Optimized Field for DoubleBuffer operations. --- .../kmath/structures/RealBufferField.kt | 59 +++++++++++++++++++ .../kmath/structures/SpecializedStructures.kt | 6 ++ 2 files changed, 65 insertions(+) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt new file mode 100644 index 000000000..f1df3a0f4 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt @@ -0,0 +1,59 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field + +/** + * A simple field over linear buffers of [Double] + */ +class RealBufferField(val size: Int) : Field> { + override val zero: Buffer = Buffer.DoubleBufferFactory(size) { 0.0 } + + override val one: Buffer = Buffer.DoubleBufferFactory(size) { 1.0 } + + override fun add(a: Buffer, b: Buffer): DoubleBuffer { + require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } + require(b.size == size) { "The size of buffer is ${b.size} but context requires $size " } + return if (a is DoubleBuffer && b is DoubleBuffer) { + val aArray = a.array + val bArray = b.array + DoubleBuffer(DoubleArray(size) { aArray[it] + bArray[it] }) + } else { + DoubleBuffer(DoubleArray(size) { a[it] + b[it] }) + } + } + + override fun multiply(a: Buffer, k: Number): DoubleBuffer { + require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } + val kValue = k.toDouble() + return if (a is DoubleBuffer) { + val aArray = a.array + DoubleBuffer(DoubleArray(size) { aArray[it] * kValue }) + } else { + DoubleBuffer(DoubleArray(size) { a[it] * kValue }) + } + } + + override fun multiply(a: Buffer, b: Buffer): DoubleBuffer { + require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } + require(b.size == size) { "The size of buffer is ${b.size} but context requires $size " } + return if (a is DoubleBuffer && b is DoubleBuffer) { + val aArray = a.array + val bArray = b.array + DoubleBuffer(DoubleArray(size) { aArray[it] * bArray[it] }) + } else { + DoubleBuffer(DoubleArray(size) { a[it] * b[it] }) + } + } + + override fun divide(a: Buffer, b: Buffer): DoubleBuffer { + require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } + require(b.size == size) { "The size of buffer is ${b.size} but context requires $size " } + return if (a is DoubleBuffer && b is DoubleBuffer) { + val aArray = a.array + val bArray = b.array + DoubleBuffer(DoubleArray(size) { aArray[it] / bArray[it] }) + } else { + DoubleBuffer(DoubleArray(size) { a[it] / b[it] }) + } + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt index b0373288b..734aba5ac 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/SpecializedStructures.kt @@ -36,6 +36,12 @@ fun NDStructure.as1D(): Structure1D = if (shape.size == 1) { error("Can't create 1d-structure from ${shape.size}d-structure") } +fun NDBuffer.as1D(): Structure1D = if (shape.size == 1) { + Buffer1DWrapper(this.buffer) +} else { + error("Can't create 1d-structure from ${shape.size}d-structure") +} + /** * A structure wrapper for buffer */ From c3989159ab99002750d4eecea0fad0777859add8 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Feb 2019 11:58:58 +0300 Subject: [PATCH 58/70] Complex buffer optimization --- benchmarks/build.gradle | 6 +- .../kmath/structures/ComplexNDBenchmark.kt | 36 +++++ .../kmath/structures/NDFieldBenchmark.kt | 2 +- build.gradle.kts | 6 +- kmath-core/build.gradle.kts | 4 +- .../scientifik/kmath/operations/Complex.kt | 29 ++-- .../kmath/structures/BoxingNDField.kt | 2 +- .../kmath/structures/BufferedNDAlgebra.kt | 2 - .../kmath/structures/RealBufferField.kt | 55 +++++++- .../kmath/structures/RealNDField.kt | 3 +- .../kmath/structures/ShortNDRing.kt | 3 +- .../scientifik/kmath/structures/BufferSpec.kt | 39 ++---- .../kmath/structures/ComplexBufferSpec.kt | 23 ++-- .../kmath/structures/ComplexNDField.kt | 125 ++++++++++++++++++ .../kmath/structures/ObjectBuffer.kt | 3 + .../kmath/structures/RealBufferSpec.kt | 13 +- 16 files changed, 286 insertions(+), 65 deletions(-) create mode 100644 benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 78d902459..c57d84fe4 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "me.champeau.gradle.jmh" version "0.4.7" + id "me.champeau.gradle.jmh" version "0.4.8" id 'org.jetbrains.kotlin.jvm' } @@ -15,8 +15,8 @@ dependencies { implementation project(":kmath-coroutines") implementation project(":kmath-commons") implementation project(":kmath-koma") - compile group: "com.kyonifer", name:"koma-core-ejml", version: "0.12" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation group: "com.kyonifer", name:"koma-core-ejml", version: "0.12" + //compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" //jmh project(':kmath-core') } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt new file mode 100644 index 000000000..90bc5a9b4 --- /dev/null +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt @@ -0,0 +1,36 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.toComplex +import kotlin.system.measureTimeMillis + +fun main() { + val dim = 1000 + val n = 1000 + + val realField = NDField.real(intArrayOf(dim, dim)) + val complexField = NDField.complex(intArrayOf(dim, dim)) + + + val realTime = measureTimeMillis { + realField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0 + } + } + } + + println("Real addition completed in $realTime millis") + + val complexTime = measureTimeMillis { + complexField.run { + var res: NDBuffer = one + repeat(n) { + res += 1.0.toComplex() + } + } + } + + println("Complex addition completed in $complexTime millis") +} \ No newline at end of file diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index a0cbd66ca..1e139e247 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -39,7 +39,7 @@ fun main(args: Array) { val specializedTime = measureTimeMillis { specializedField.run { - var res:NDBuffer = one + var res: NDBuffer = one repeat(n) { res += 1.0 } diff --git a/build.gradle.kts b/build.gradle.kts index 4d15bfe0d..3c1c669ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension buildscript { - val kotlinVersion: String by rootProject.extra("1.3.20") - val ioVersion: String by rootProject.extra("0.1.2") + val kotlinVersion: String by rootProject.extra("1.3.21") + val ioVersion: String by rootProject.extra("0.1.4") val coroutinesVersion: String by rootProject.extra("1.1.1") val atomicfuVersion: String by rootProject.extra("0.12.1") @@ -27,7 +27,7 @@ allprojects { apply(plugin = "com.jfrog.artifactory") group = "scientifik" - version = "0.0.3-dev-5" + version = "0.0.3" repositories { //maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index 65086d665..f6d10875c 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -2,9 +2,11 @@ plugins { kotlin("multiplatform") } +val ioVersion: String by rootProject.extra + kotlin { - jvm () + jvm() js() sourceSets { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 20e8f47ec..a7d9ef7f4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -1,9 +1,11 @@ package scientifik.kmath.operations +import kotlin.math.* + /** * A field for complex numbers */ -object ComplexField : Field { +object ComplexField : ExtendedField { override val zero: Complex = Complex(0.0, 0.0) override val one: Complex = Complex(1.0, 0.0) @@ -22,6 +24,17 @@ object ComplexField : Field { return Complex((a.re * b.re + a.im * b.im) / norm, (a.re * b.im - a.im * b.re) / norm) } + override fun sin(arg: Complex): Complex = i / 2 * (exp(-i * arg) - exp(i * arg)) + + override fun cos(arg: Complex): Complex = (exp(-i * arg) + exp(i * arg)) / 2 + + override fun power(arg: Complex, pow: Number): Complex = + arg.abs.pow(pow.toDouble()) * (cos(pow.toDouble() * arg.theta) + i * sin(pow.toDouble() * arg.theta)) + + override fun exp(arg: Complex): Complex = exp(arg.re) * (cos(arg.im) + i * sin(arg.im)) + + override fun ln(arg: Complex): Complex = ln(arg.abs) + i * atan2(arg.im, arg.re) + operator fun Double.plus(c: Complex) = add(this.toComplex(), c) operator fun Double.minus(c: Complex) = add(this.toComplex(), -c) @@ -41,20 +54,18 @@ data class Complex(val re: Double, val im: Double) : FieldElement>( override val strides: Strides = DefaultStrides(shape) - override fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = + fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer = bufferFactory(size, initializer) override fun check(vararg elements: NDBuffer) { diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt index 03b5407f5..9742f3662 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferedNDAlgebra.kt @@ -5,8 +5,6 @@ import scientifik.kmath.operations.* interface BufferedNDAlgebra: NDAlgebra>{ val strides: Strides - fun buildBuffer(size: Int, initializer: (Int) -> T): Buffer - override fun check(vararg elements: NDBuffer) { if (!elements.all { it.strides == this.strides }) error("Strides mismatch") } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt index f1df3a0f4..42e74a27f 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt @@ -1,11 +1,12 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.Field +import scientifik.kmath.operations.ExtendedField +import kotlin.math.* /** * A simple field over linear buffers of [Double] */ -class RealBufferField(val size: Int) : Field> { +class RealBufferField(val size: Int) : ExtendedField> { override val zero: Buffer = Buffer.DoubleBufferFactory(size) { 0.0 } override val one: Buffer = Buffer.DoubleBufferFactory(size) { 1.0 } @@ -56,4 +57,54 @@ class RealBufferField(val size: Int) : Field> { DoubleBuffer(DoubleArray(size) { a[it] / b[it] }) } } + + override fun sin(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { sin(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { sin(arg[it]) }) + } + } + + override fun cos(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { cos(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { cos(arg[it]) }) + } + } + + override fun power(arg: Buffer, pow: Number): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { array[it].pow(pow.toDouble()) }) + } else { + DoubleBuffer(DoubleArray(size) { arg[it].pow(pow.toDouble()) }) + } + } + + override fun exp(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { exp(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { exp(arg[it]) }) + } + } + + override fun ln(arg: Buffer): Buffer { + require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + return if (arg is DoubleBuffer) { + val array = arg.array + DoubleBuffer(DoubleArray(size) { ln(array[it]) }) + } else { + DoubleBuffer(DoubleArray(size) { ln(arg[it]) }) + } + } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt index d652bb8a8..82a237817 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealNDField.kt @@ -15,8 +15,7 @@ class RealNDField(override val shape: IntArray) : override val zero by lazy { produce { zero } } override val one by lazy { produce { one } } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer = DoubleBuffer(DoubleArray(size) { initializer(it) }) /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt index 09e93483d..6b09c91de 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ShortNDRing.kt @@ -15,8 +15,7 @@ class ShortNDRing(override val shape: IntArray) : override val zero by lazy { produce { ShortRing.zero } } override val one by lazy { produce { ShortRing.one } } - @Suppress("OVERRIDE_BY_INLINE") - override inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Short): Buffer = ShortBuffer(ShortArray(size) { initializer(it) }) /** diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt index 9f95360fd..65a69cbc2 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt @@ -7,8 +7,15 @@ import java.nio.ByteBuffer * A specification for serialization and deserialization objects to buffer */ interface BufferSpec { - fun fromBuffer(buffer: ByteBuffer): T - fun toBuffer(value: T): ByteBuffer + /** + * Read an object from buffer in current position + */ + fun ByteBuffer.readObject(): T + + /** + * Write object to [ByteBuffer] in current buffer position + */ + fun ByteBuffer.writeObject(value: T) } /** @@ -17,39 +24,21 @@ interface BufferSpec { interface FixedSizeBufferSpec : BufferSpec { 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() + position(index * unitSize) + return 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) + position(index * unitSize) + writeObject(obj) } -} \ No newline at end of file +} diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index 635a90dbc..f8c93e754 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -4,16 +4,20 @@ import scientifik.kmath.operations.Complex import scientifik.kmath.operations.ComplexField import java.nio.ByteBuffer +/** + * A serialization specification for complex numbers + */ object ComplexBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 16 - override fun fromBuffer(buffer: ByteBuffer): Complex { - val re = buffer.getDouble(0) - val im = buffer.getDouble(8) + override fun ByteBuffer.readObject(): Complex { + val re = double + val im = double return Complex(re, im) } - override fun toBuffer(value: Complex): ByteBuffer = ByteBuffer.allocate(16).apply { + override fun ByteBuffer.writeObject(value: Complex) { putDouble(value.re) putDouble(value.im) } @@ -22,14 +26,13 @@ object ComplexBufferSpec : FixedSizeBufferSpec { /** * Create a read-only/mutable buffer which ignores boxing */ -fun Buffer.Companion.complex(size: Int): Buffer = - ObjectBuffer.create(ComplexBufferSpec, size) +fun Buffer.Companion.complex(size: Int, initializer: ((Int) -> Complex)? = null): Buffer = + ObjectBuffer.create(ComplexBufferSpec, size, initializer) -fun MutableBuffer.Companion.complex(size: Int) = - ObjectBuffer.create(ComplexBufferSpec, size) +fun MutableBuffer.Companion.complex(size: Int, initializer: ((Int) -> Complex)? = null) = + ObjectBuffer.create(ComplexBufferSpec, size, initializer) -fun NDField.Companion.complex(shape: IntArray) = - BoxingNDField(shape, ComplexField) { size, init -> ObjectBuffer.create(ComplexBufferSpec, size, init) } +fun NDField.Companion.complex(shape: IntArray) = ComplexNDField(shape) fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = NDField.complex(shape).produce(initializer) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt new file mode 100644 index 000000000..190d8a46b --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt @@ -0,0 +1,125 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField +import scientifik.kmath.operations.FieldElement + +typealias ComplexNDElement = BufferedNDFieldElement + +/** + * An optimized nd-field for complex numbers + */ +class ComplexNDField(override val shape: IntArray) : + BufferedNDField, + ExtendedNDField> { + + override val strides: Strides = DefaultStrides(shape) + + override val elementContext: ComplexField get() = ComplexField + override val zero by lazy { produce { zero } } + override val one by lazy { produce { one } } + + inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer = + Buffer.complex(size) { initializer(it) } + + /** + * Inline transform an NDStructure to another structure + */ + override fun map( + arg: NDBuffer, + transform: ComplexField.(Complex) -> Complex + ): ComplexNDElement { + check(arg) + val array = buildBuffer(arg.strides.linearSize) { offset -> ComplexField.transform(arg.buffer[offset]) } + return BufferedNDFieldElement(this, array) + } + + override fun produce(initializer: ComplexField.(IntArray) -> Complex): ComplexNDElement { + val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) } + return BufferedNDFieldElement(this, array) + } + + override fun mapIndexed( + arg: NDBuffer, + transform: ComplexField.(index: IntArray, Complex) -> Complex + ): ComplexNDElement { + check(arg) + return BufferedNDFieldElement( + this, + buildBuffer(arg.strides.linearSize) { offset -> + elementContext.transform( + arg.strides.index(offset), + arg.buffer[offset] + ) + }) + } + + override fun combine( + a: NDBuffer, + b: NDBuffer, + transform: ComplexField.(Complex, Complex) -> Complex + ): ComplexNDElement { + check(a, b) + return BufferedNDFieldElement( + this, + buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) }) + } + + override fun NDBuffer.toElement(): FieldElement, *, out BufferedNDField> = + BufferedNDFieldElement(this@ComplexNDField, buffer) + + override fun power(arg: NDBuffer, pow: Number) = map(arg) { power(it, pow) } + + override fun exp(arg: NDBuffer) = map(arg) { exp(it) } + + override fun ln(arg: NDBuffer) = map(arg) { ln(it) } + + override fun sin(arg: NDBuffer) = map(arg) { sin(it) } + + override fun cos(arg: NDBuffer) = map(arg) { cos(it) } + +} + + +/** + * Fast element production using function inlining + */ +inline fun BufferedNDField.produceInline(crossinline initializer: ComplexField.(Int) -> Complex): ComplexNDElement { + val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.initializer(offset) } + return BufferedNDFieldElement(this, buffer) +} + +/** + * Map one [ComplexNDElement] using function with indexes + */ +inline fun ComplexNDElement.mapIndexed(crossinline transform: ComplexField.(index: IntArray, Complex) -> Complex) = + context.produceInline { offset -> transform(strides.index(offset), buffer[offset]) } + +/** + * Map one [ComplexNDElement] using function without indexes + */ +inline fun ComplexNDElement.map(crossinline transform: ComplexField.(Complex) -> Complex): ComplexNDElement { + val buffer = Buffer.complex(strides.linearSize) { offset -> ComplexField.transform(buffer[offset]) } + return BufferedNDFieldElement(context, buffer) +} + +/** + * Element by element application of any operation on elements to the whole array. Just like in numpy + */ +operator fun Function1.invoke(ndElement: ComplexNDElement) = + ndElement.map { this@invoke(it) } + + +/* plus and minus */ + +/** + * Summation operation for [BufferedNDElement] and single element + */ +operator fun ComplexNDElement.plus(arg: Complex) = + map { it + arg } + +/** + * Subtraction operation between [BufferedNDElement] and single element + */ +operator fun ComplexNDElement.minus(arg: Complex) = + map { it - arg } \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt index 8d7bece0c..422ed64ad 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -2,6 +2,9 @@ package scientifik.kmath.structures import java.nio.ByteBuffer +/** + * A non-boxing buffer based on [ByteBuffer] storage + */ class ObjectBuffer(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec) : MutableBuffer { override val size: Int diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt index 761cfc2db..fdd9e92b7 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt @@ -6,17 +6,22 @@ import java.nio.ByteBuffer object RealBufferSpec : FixedSizeBufferSpec { override val unitSize: Int = 8 - override fun fromBuffer(buffer: ByteBuffer): Real = Real(buffer.double) + override fun ByteBuffer.readObject(): Real = Real(double) - override fun toBuffer(value: Real): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value.value) } + override fun ByteBuffer.writeObject(value: Real) { + putDouble(value.value) + } } object DoubleBufferSpec : FixedSizeBufferSpec { override val unitSize: Int = 8 - override fun fromBuffer(buffer: ByteBuffer): Double = buffer.double + override fun ByteBuffer.readObject() = double + + override fun ByteBuffer.writeObject(value: Double) { + putDouble(value) + } - override fun toBuffer(value: Double): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value) } } fun Double.Companion.createBuffer(size: Int) = ObjectBuffer.create(DoubleBufferSpec, size) From 91207c8c9aa398bec0cf500481e65e9a558021d3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Feb 2019 12:28:35 +0300 Subject: [PATCH 59/70] Complex buffer optimization --- .../scientifik/kmath/structures/ComplexBufferSpec.kt | 10 ++++++++-- .../scientifik/kmath/structures/ObjectBuffer.kt | 11 ++++++----- .../kmath/structures/ComplexBufferSpecTest.kt | 6 +----- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt index f8c93e754..34f21cf75 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -26,10 +26,16 @@ object ComplexBufferSpec : FixedSizeBufferSpec { /** * Create a read-only/mutable buffer which ignores boxing */ -fun Buffer.Companion.complex(size: Int, initializer: ((Int) -> Complex)? = null): Buffer = +fun Buffer.Companion.complex(size: Int): Buffer = + ObjectBuffer.create(ComplexBufferSpec, size) + +inline fun Buffer.Companion.complex(size: Int, crossinline initializer: (Int) -> Complex): Buffer = ObjectBuffer.create(ComplexBufferSpec, size, initializer) -fun MutableBuffer.Companion.complex(size: Int, initializer: ((Int) -> Complex)? = null) = +fun MutableBuffer.Companion.complex(size: Int) = + ObjectBuffer.create(ComplexBufferSpec, size) + +inline fun MutableBuffer.Companion.complex(size: Int, crossinline initializer: (Int) -> Complex) = ObjectBuffer.create(ComplexBufferSpec, size, initializer) fun NDField.Companion.complex(shape: IntArray) = ComplexNDField(shape) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt index 422ed64ad..e95c54b2c 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -26,12 +26,13 @@ class ObjectBuffer(private val buffer: ByteBuffer, private val spec: Fi } companion object { - fun create(spec: FixedSizeBufferSpec, size: Int, initializer: ((Int) -> T)? = null) = + fun create(spec: FixedSizeBufferSpec, size: Int) = + ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec) + + inline fun create(spec: FixedSizeBufferSpec, size: Int, crossinline initializer: (Int) -> T) = ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec).also { buffer -> - if (initializer != null) { - (0 until size).forEach { - buffer[it] = initializer(it) - } + (0 until size).forEach { + buffer[it] = initializer(it) } } } diff --git a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt index 1a3aea10f..a07f2167e 100644 --- a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt +++ b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt @@ -7,11 +7,7 @@ import kotlin.test.assertEquals class ComplexBufferSpecTest { @Test fun testComplexBuffer() { - val buffer = MutableBuffer.complex(20) - (0 until 20).forEach { - buffer[it] = Complex(it.toDouble(), -it.toDouble()) - } - + val buffer = MutableBuffer.complex(20){Complex(it.toDouble(), -it.toDouble())} assertEquals(Complex(5.0, -5.0), buffer[5]) } } \ No newline at end of file From d04ee956e5a75189f65f659168508cd34d45d448 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Feb 2019 14:06:37 +0300 Subject: [PATCH 60/70] Lazy structures revision --- .../kmath/structures/ComplexNDBenchmark.kt | 4 +- .../kmath/structures/NDFieldBenchmark.kt | 21 ++-- build.gradle.kts | 4 +- examples/build.gradle.kts | 15 +++ .../scientifik/kmath/structures/BufferSpec.kt | 2 +- .../kmath/structures/ComplexNDField.kt | 6 + .../kmath/structures/CoroutinesExtra.kt | 5 - .../kmath/structures/LazyNDField.kt | 113 ------------------ .../kmath/structures/runBlocking.kt | 11 -- .../kmath/structures/LazyNDStructure.kt | 47 ++++++++ .../kmath/structures/runBlocking.kt | 7 -- settings.gradle.kts | 3 +- 12 files changed, 85 insertions(+), 153 deletions(-) create mode 100644 examples/build.gradle.kts delete mode 100644 kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt delete mode 100644 kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt create mode 100644 kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt delete mode 100644 kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/runBlocking.kt diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt index 90bc5a9b4..02f19cd55 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt @@ -25,9 +25,9 @@ fun main() { val complexTime = measureTimeMillis { complexField.run { - var res: NDBuffer = one + var res: ComplexNDElement = one repeat(n) { - res += 1.0.toComplex() + res += 1.0 } } } diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 1e139e247..170320d22 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -1,5 +1,6 @@ package scientifik.kmath.structures +import kotlinx.coroutines.GlobalScope import scientifik.kmath.operations.RealField import kotlin.system.measureTimeMillis @@ -11,8 +12,6 @@ fun main(args: Array) { val autoField = NDField.auto(intArrayOf(dim, dim), RealField) // specialized nd-field for Double. It works as generic Double field as well val specializedField = NDField.real(intArrayOf(dim, dim)) - //A field implementing lazy computations. All elements are computed on-demand - val lazyField = NDField.lazy(intArrayOf(dim, dim), RealField) //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) @@ -26,7 +25,7 @@ fun main(args: Array) { } } - println("Buffered addition completed in $autoTime millis") + println("Automatic field addition completed in $autoTime millis") val elementTime = measureTimeMillis { var res = genericField.one @@ -50,17 +49,15 @@ fun main(args: Array) { val lazyTime = measureTimeMillis { - lazyField.run { - val res = one.map { - var c = 0.0 - repeat(n) { - c += 1.0 - } - c + val res = specializedField.one.mapAsync(GlobalScope) { + var c = 0.0 + repeat(n) { + c += 1.0 } - - res.elements().forEach { it.second } + c } + + res.elements().forEach { it.second } } println("Lazy addition completed in $lazyTime millis") diff --git a/build.gradle.kts b/build.gradle.kts index 3c1c669ba..b794956a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,9 @@ plugins { allprojects { apply(plugin = "maven-publish") - apply(plugin = "com.jfrog.artifactory") + if(project.name.startsWith("kmath")) { + apply(plugin = "com.jfrog.artifactory") + } group = "scientifik" version = "0.0.3" diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts new file mode 100644 index 000000000..86ed9ced6 --- /dev/null +++ b/examples/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("jvm") +} + +description = "Examples for different kmath features" + +dependencies { + implementation(project(":kmath-core")) + implementation(project(":kmath-coroutines")) + implementation(project(":kmath-commons")) + implementation(project(":kmath-koma")) + implementation(group = "com.kyonifer", name = "koma-core-ejml", version = "0.12") + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") +} diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt index 65a69cbc2..ce87a1298 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt @@ -4,7 +4,7 @@ import java.nio.ByteBuffer /** - * A specification for serialization and deserialization objects to buffer + * A specification for serialization and deserialization objects to buffer (at current buffer position) */ interface BufferSpec { /** diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt index 190d8a46b..a8b31af3e 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt @@ -122,4 +122,10 @@ operator fun ComplexNDElement.plus(arg: Complex) = * Subtraction operation between [BufferedNDElement] and single element */ operator fun ComplexNDElement.minus(arg: Complex) = + map { it - arg } + +operator fun ComplexNDElement.plus(arg: Double) = + map { it + arg } + +operator fun ComplexNDElement.minus(arg: Double) = map { it - arg } \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt index 6bb8682ff..f6f21af09 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt @@ -6,9 +6,4 @@ import kotlinx.coroutines.Dispatchers import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -expect fun runBlocking( - context: CoroutineContext = EmptyCoroutineContext, - function: suspend CoroutineScope.() -> R -): R - val Dispatchers.Math: CoroutineDispatcher get() = Dispatchers.Default \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt deleted file mode 100644 index 924ddc653..000000000 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt +++ /dev/null @@ -1,113 +0,0 @@ -package scientifik.kmath.structures - -import kotlinx.coroutines.* -import scientifik.kmath.operations.Field -import scientifik.kmath.operations.FieldElement - -class LazyNDField>( - override val shape: IntArray, - override val elementContext: F, - val scope: CoroutineScope = GlobalScope -) : NDField> { - - override val zero by lazy { produce { zero } } - - override val one by lazy { produce { one } } - - override fun produce(initializer: F.(IntArray) -> T) = - LazyNDStructure(this) { elementContext.initializer(it) } - - override fun mapIndexed( - arg: NDStructure, - transform: F.(index: IntArray, T) -> T - ): LazyNDStructure { - check(arg) - return if (arg is LazyNDStructure) { - LazyNDStructure(this) { index -> - //FIXME if value of arg is already calculated, it should be used - elementContext.transform(index, arg.function(index)) - } - } else { - LazyNDStructure(this) { elementContext.transform(it, arg.await(it)) } - } -// return LazyNDStructure(this) { elementField.transform(it, arg.await(it)) } - } - - override fun map(arg: NDStructure, transform: F.(T) -> T) = - mapIndexed(arg) { _, t -> transform(t) } - - override fun combine(a: NDStructure, b: NDStructure, transform: F.(T, T) -> T): LazyNDStructure { - check(a, b) - return if (a is LazyNDStructure && b is LazyNDStructure) { - LazyNDStructure(this@LazyNDField) { index -> - elementContext.transform( - a.function(index), - b.function(index) - ) - } - } else { - LazyNDStructure(this@LazyNDField) { elementContext.transform(a.await(it), b.await(it)) } - } -// return LazyNDStructure(this) { elementField.transform(a.await(it), b.await(it)) } - } - - fun NDStructure.lazy(): LazyNDStructure { - check(this) - return if (this is LazyNDStructure) { - LazyNDStructure(this@LazyNDField, function) - } else { - LazyNDStructure(this@LazyNDField) { get(it) } - } - } -} - -class LazyNDStructure>( - override val context: LazyNDField, - val function: suspend (IntArray) -> T -) : FieldElement, LazyNDStructure, LazyNDField>, - NDElement> { - - - override fun unwrap(): NDStructure = this - - override fun NDStructure.wrap(): LazyNDStructure = LazyNDStructure(context) { await(it) } - - override val shape: IntArray get() = context.shape - - private val cache = HashMap>() - - fun deferred(index: IntArray) = cache.getOrPut(index) { - context.scope.async(context = Dispatchers.Math) { - function(index) - } - } - - suspend fun await(index: IntArray): T = deferred(index).await() - - override fun get(index: IntArray): T = runBlocking { - deferred(index).await() - } - - override fun elements(): Sequence> { - val strides = DefaultStrides(shape) - val res = runBlocking { - strides.indices().toList().map { index -> index to await(index) } - } - return res.asSequence() - } -} - -fun NDStructure.deferred(index: IntArray) = - if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) - -suspend fun NDStructure.await(index: IntArray) = - if (this is LazyNDStructure) this.await(index) else get(index) - - -fun > NDField.Companion.lazy(shape: IntArray, field: F, scope: CoroutineScope = GlobalScope) = - LazyNDField(shape, field, scope) - -fun > NDStructure.lazy(field: F, scope: CoroutineScope = GlobalScope): LazyNDStructure { - val context: LazyNDField = LazyNDField(shape, field, scope) - return LazyNDStructure(context) { get(it) } -} \ No newline at end of file diff --git a/kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt b/kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt deleted file mode 100644 index 7331d1801..000000000 --- a/kmath-coroutines/src/jsMain/kotlin/scientifik/kmath/structures/runBlocking.kt +++ /dev/null @@ -1,11 +0,0 @@ -package scientifik.kmath.structures - -import kotlinx.coroutines.CoroutineScope -import kotlin.coroutines.CoroutineContext - -actual fun runBlocking( - context: CoroutineContext, - function: suspend CoroutineScope.() -> R -): R { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. -} \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt new file mode 100644 index 000000000..b4832827d --- /dev/null +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/LazyNDStructure.kt @@ -0,0 +1,47 @@ +package scientifik.kmath.structures + +import kotlinx.coroutines.* + +class LazyNDStructure( + val scope: CoroutineScope, + override val shape: IntArray, + val function: suspend (IntArray) -> T +) : NDStructure { + + private val cache = HashMap>() + + fun deferred(index: IntArray) = cache.getOrPut(index) { + scope.async(context = Dispatchers.Math) { + function(index) + } + } + + suspend fun await(index: IntArray): T = deferred(index).await() + + override fun get(index: IntArray): T = runBlocking { + deferred(index).await() + } + + override fun elements(): Sequence> { + val strides = DefaultStrides(shape) + val res = runBlocking { + strides.indices().toList().map { index -> index to await(index) } + } + return res.asSequence() + } +} + +fun NDStructure.deferred(index: IntArray) = + if (this is LazyNDStructure) this.deferred(index) else CompletableDeferred(get(index)) + +suspend fun NDStructure.await(index: IntArray) = + if (this is LazyNDStructure) this.await(index) else get(index) + +/** + * PENDING would benifit from KEEP-176 + */ +fun NDStructure.mapAsyncIndexed(scope: CoroutineScope, function: suspend (T, index: IntArray) -> R) = + LazyNDStructure(scope, shape) { index -> function(get(index), index) } + +fun NDStructure.mapAsync(scope: CoroutineScope, function: suspend (T) -> R) = + LazyNDStructure(scope, shape) { index -> function(get(index)) } \ No newline at end of file diff --git a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/runBlocking.kt b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/runBlocking.kt deleted file mode 100644 index 0bc3b0dea..000000000 --- a/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/runBlocking.kt +++ /dev/null @@ -1,7 +0,0 @@ -package scientifik.kmath.structures - -import kotlinx.coroutines.CoroutineScope -import kotlin.coroutines.CoroutineContext - -actual fun runBlocking(context: CoroutineContext, function: suspend CoroutineScope.() -> R): R = - kotlinx.coroutines.runBlocking(context, function) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b306d1c8d..d6fd42594 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,5 +25,6 @@ include( ":kmath-commons", ":kmath-koma", ":kmath-sequential", - ":benchmarks" + ":benchmarks", + ":examples" ) From f8ec82d8c0d39a1d5068d90a4f8e91d6ea89bd33 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Feb 2019 14:14:16 +0300 Subject: [PATCH 61/70] Updated build in coroutines --- build.gradle.kts | 5 ++-- kmath-coroutines/build.gradle | 42 ---------------------------- kmath-coroutines/build.gradle.kts | 46 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 45 deletions(-) delete mode 100644 kmath-coroutines/build.gradle create mode 100644 kmath-coroutines/build.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts index b794956a4..437f0030a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,8 +18,7 @@ buildscript { } plugins { - id("com.jfrog.artifactory") version "4.8.1" apply false -// id("org.jetbrains.kotlin.multiplatform") apply false + id("com.jfrog.artifactory") version "4.9.1" apply false } allprojects { @@ -29,7 +28,7 @@ allprojects { } group = "scientifik" - version = "0.0.3" + version = "0.0.3-dev" repositories { //maven("https://dl.bintray.com/kotlin/kotlin-eap") diff --git a/kmath-coroutines/build.gradle b/kmath-coroutines/build.gradle deleted file mode 100644 index 2c58a4112..000000000 --- a/kmath-coroutines/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -kotlin { - jvm() - js() - 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' - } - } - jsMain{ - dependencies{ - api "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion" - } - } -// mingwMain { -// } -// mingwTest { -// } - } -} diff --git a/kmath-coroutines/build.gradle.kts b/kmath-coroutines/build.gradle.kts new file mode 100644 index 000000000..c73d4e4dc --- /dev/null +++ b/kmath-coroutines/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + kotlin("multiplatform") +} + +val coroutinesVersion: String by rootProject.extra + +kotlin { + jvm() + js() + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":kmath-core")) + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + val jsMain by getting { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } + } +} From b5dd86455666b2288465511c3de6381a95d433ae Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Feb 2019 14:17:14 +0300 Subject: [PATCH 62/70] Fixed ring buffer test --- .../kotlin/scientifik.kmath.sequential}/RingBufferTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename kmath-sequential/src/{commonTest/kotlin/scientifik/kmath/sequential => jvmTest/kotlin/scientifik.kmath.sequential}/RingBufferTest.kt (90%) diff --git a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt b/kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt similarity index 90% rename from kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt rename to kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt index e2bb18280..c8f84e7d8 100644 --- a/kmath-sequential/src/commonTest/kotlin/scientifik/kmath/sequential/RingBufferTest.kt +++ b/kmath-sequential/src/jvmTest/kotlin/scientifik.kmath.sequential/RingBufferTest.kt @@ -1,7 +1,7 @@ package scientifik.kmath.sequential +import kotlinx.coroutines.runBlocking import scientifik.kmath.structures.asSequence -import scientifik.kmath.structures.runBlocking import kotlin.test.Test import kotlin.test.assertEquals From 123e0176b8f37a0c7b906a5b08d7851b297f797c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 13 Feb 2019 14:55:44 +0300 Subject: [PATCH 63/70] Documentation in progress. --- build.gradle.kts | 4 +-- doc/buffers.md | 1 + doc/expressions.md | 26 +++++++++++++++++++ doc/features.md | 0 doc/histograms.md | 1 + doc/linear.md | 0 doc/operations.md | 6 ++--- examples/build.gradle.kts | 15 ----------- examples/expressions.main.kts | 1 + .../kmath/expressions/DiffExpression.kt | 26 +++++++++++++++++-- .../kmath/expressions/Expression.kt | 13 +++++++++- .../kmath/structures/LazyNDFieldTest.kt | 16 ------------ settings.gradle.kts | 3 +-- 13 files changed, 71 insertions(+), 41 deletions(-) create mode 100644 doc/buffers.md create mode 100644 doc/expressions.md create mode 100644 doc/features.md create mode 100644 doc/histograms.md create mode 100644 doc/linear.md delete mode 100644 examples/build.gradle.kts create mode 100644 examples/expressions.main.kts delete mode 100644 kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 437f0030a..702373b1e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,8 +22,8 @@ plugins { } allprojects { - apply(plugin = "maven-publish") - if(project.name.startsWith("kmath")) { + if (project.name.startsWith("kmath")) { + apply(plugin = "maven-publish") apply(plugin = "com.jfrog.artifactory") } diff --git a/doc/buffers.md b/doc/buffers.md new file mode 100644 index 000000000..6208deaff --- /dev/null +++ b/doc/buffers.md @@ -0,0 +1 @@ +**TODO** \ No newline at end of file diff --git a/doc/expressions.md b/doc/expressions.md new file mode 100644 index 000000000..1e05e5340 --- /dev/null +++ b/doc/expressions.md @@ -0,0 +1,26 @@ +# Expressions + +**Experimental: this API is in early stage and could change any time** + +Expressions is an experimental feature which allows to construct lazily or immediately calculated parametric mathematical +expressions. + +The potential use-cases for it (so far) are following: + +* Lazy evaluation (in general simple lambda is better, but there are some border cases) + +* Automatic differentiation in single-dimension and in multiple dimensions + +* Generation of mathematical syntax trees with subsequent code generation for other languages + +* Maybe symbolic computations (needs additional research) + +The workhorse of this API is `Expression` interface which exposes single `operator fun invoke(arguments: Map): T` +method. `ExpressionContext` is used to generate expressions and introduce variables. + +Currently there are two implementations: + +* Generic `ExpressionField` in `kmath-core` which allows construction of custom lazy expressions + +* Auto-differentiation expression in `kmath-commons` module allows to use full power of `DerivativeStructure` +from commons-math. **TODO: add example** diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 000000000..e69de29bb diff --git a/doc/histograms.md b/doc/histograms.md new file mode 100644 index 000000000..6208deaff --- /dev/null +++ b/doc/histograms.md @@ -0,0 +1 @@ +**TODO** \ No newline at end of file diff --git a/doc/linear.md b/doc/linear.md new file mode 100644 index 000000000..e69de29bb diff --git a/doc/operations.md b/doc/operations.md index ff4a407ee..425d791e8 100644 --- a/doc/operations.md +++ b/doc/operations.md @@ -14,7 +14,7 @@ val c3 = c1 + c2 `ComplexField` also features special operations to mix complex and real numbers, for example: ```kotlin -val c1 = Complex(1.0,2.0) +val c1 = Complex(1.0, 2.0) val c2 = ComplexField.run{ c1 - 1.0} // Returns: [re:0.0, im: 2.0] val c3 = ComplexField.run{ c1 - i*2.0} ``` @@ -27,8 +27,8 @@ that. Watch [KT-10468](https://youtrack.jetbrains.com/issue/KT-10468) and [KEEP- Contexts allow one to build more complex structures. For example, it is possible to create a `Matrix` from complex elements like so: ```kotlin -val element = NDElements.create(field = ComplexField, shape = intArrayOf(2,2)){index: IntArray -> - Complex(index[0] - index[1], index[0] + index[1]) +val element = NDElement.complex(shape = intArrayOf(2,2)){ index: IntArray -> + Complex(index[0].toDouble() - index[1].toDouble(), index[0].toDouble() + index[1].toDouble()) } ``` diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts deleted file mode 100644 index 86ed9ced6..000000000 --- a/examples/build.gradle.kts +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - kotlin("jvm") -} - -description = "Examples for different kmath features" - -dependencies { - implementation(project(":kmath-core")) - implementation(project(":kmath-coroutines")) - implementation(project(":kmath-commons")) - implementation(project(":kmath-koma")) - implementation(group = "com.kyonifer", name = "koma-core-ejml", version = "0.12") - testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit") -} diff --git a/examples/expressions.main.kts b/examples/expressions.main.kts new file mode 100644 index 000000000..baf6a4ed0 --- /dev/null +++ b/examples/expressions.main.kts @@ -0,0 +1 @@ +@file:DependsOn("scientifik:kmath-core-jvm:0.0.3-dev") \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt index 4d5ea1b94..79cf92c94 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt @@ -2,6 +2,7 @@ package scientifik.kmath.expressions import org.apache.commons.math3.analysis.differentiation.DerivativeStructure import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.Field import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -25,8 +26,8 @@ class DerivativeStructureField(val order: Int, val parameters: Map DerivativeStru fun DiffExpression.derivative(vararg orders: Pair) = derivative(mapOf(*orders)) fun DiffExpression.derivative(name: String) = derivative(name to 1) +/** + * A context for [DiffExpression] (not to be confused with [DerivativeStructure]) + */ +object DiffExpressionContext : ExpressionContext, Field { + override fun variable(name: String, default: Double?) = DiffExpression { variable(name, default?.const()) } + + override fun const(value: Double): DiffExpression = DiffExpression { value.const() } + + override fun add(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) + b.function(this) } + + override val zero = DiffExpression { 0.0.const() } + + override fun multiply(a: DiffExpression, k: Number) = DiffExpression { a.function(this) * k } + + override val one = DiffExpression { 1.0.const() } + + override fun multiply(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) * b.function(this) } + + override fun divide(a: DiffExpression, b: DiffExpression) = DiffExpression { a.function(this) / b.function(this) } +} + diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt index 2f54ae1b2..ad345bc5a 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt @@ -4,16 +4,27 @@ import scientifik.kmath.operations.Field import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space - +/** + * An elementary function that could be invoked on a map of arguments + */ interface Expression { operator fun invoke(arguments: Map): T } operator fun Expression.invoke(vararg pairs: Pair): T = invoke(mapOf(*pairs)) +/** + * A context for expression construction + */ interface ExpressionContext { + /** + * Introduce a variable into expression context + */ fun variable(name: String, default: T? = null): Expression + /** + * A constant expression which does not depend on arguments + */ fun const(value: T): Expression } diff --git a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt deleted file mode 100644 index ea3cb9494..000000000 --- a/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package scientifik.kmath.structures - - -class LazyNDFieldTest { -// @Test -// fun testLazyStructure() { -// var counter = 0 -// val regularStructure = NDField.auto(intArrayOf(2, 2, 2), IntRing).produce { it[0] + it[1] - it[2] } -// val result = (regularStructure.lazy(IntRing) + 2).map { -// counter++ -// it * it -// } -// assertEquals(4, result[0, 0, 0]) -// assertEquals(1, counter) -// } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index d6fd42594..b306d1c8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,5 @@ include( ":kmath-commons", ":kmath-koma", ":kmath-sequential", - ":benchmarks", - ":examples" + ":benchmarks" ) From 9e2d6125f17ac70abddd37170f9e6fe77c201010 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 13 Feb 2019 18:21:51 +0300 Subject: [PATCH 64/70] Histograms evicted from core --- examples/expressions.main.kts | 1 - kmath-histograms/build.gradle.kts | 34 +++++++++++++++++++ .../scientifik/kmath/histogram/Counters.kt | 0 .../kmath/histogram/FastHistogram.kt | 5 ++- .../scientifik/kmath/histogram/Histogram.kt | 3 +- .../kmath/histogram/PhantomHistogram.kt | 3 +- .../histogram/MultivariateHistogramTest.kt | 5 ++- .../scientifik/kmath/histogram/Counters.kt | 0 .../scientifik/kmath/histogram/Counters.kt | 0 .../kmath/histogram/UnivariateHistogram.kt | 10 ++++-- settings.gradle.kts | 1 + 11 files changed, 55 insertions(+), 7 deletions(-) delete mode 100644 examples/expressions.main.kts create mode 100644 kmath-histograms/build.gradle.kts rename {kmath-core => kmath-histograms}/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt (100%) rename {kmath-core => kmath-histograms}/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt (96%) rename {kmath-core => kmath-histograms}/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt (93%) rename {kmath-core => kmath-histograms}/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt (97%) rename {kmath-core/src/commonTest/kotlin/scientifik => kmath-histograms/src/commonTest/kotlin/scietifik}/kmath/histogram/MultivariateHistogramTest.kt (87%) rename {kmath-core => kmath-histograms}/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt (100%) rename {kmath-core => kmath-histograms}/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt (100%) rename {kmath-core => kmath-histograms}/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt (89%) diff --git a/examples/expressions.main.kts b/examples/expressions.main.kts deleted file mode 100644 index baf6a4ed0..000000000 --- a/examples/expressions.main.kts +++ /dev/null @@ -1 +0,0 @@ -@file:DependsOn("scientifik:kmath-core-jvm:0.0.3-dev") \ No newline at end of file diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts new file mode 100644 index 000000000..81b3fb83f --- /dev/null +++ b/kmath-histograms/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + js() + + sourceSets { + + val commonMain by getting { + dependencies { + api(project(":kmath-core")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt similarity index 100% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt rename to kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt similarity index 96% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt rename to kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt index d9d4da66a..869f9a02e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -118,7 +118,10 @@ class FastHistogram( *``` */ fun fromRanges(vararg ranges: ClosedFloatingPointRange): FastHistogram { - return FastHistogram(ranges.map { it.start }.toVector(), ranges.map { it.endInclusive }.toVector()) + return FastHistogram( + ranges.map { it.start }.toVector(), + ranges.map { it.endInclusive }.toVector() + ) } /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt similarity index 93% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt rename to kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt index a71ab7207..c5fc67b5c 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -42,7 +42,8 @@ interface Histogram> : Iterable { } -interface MutableHistogram> : Histogram { +interface MutableHistogram> : + Histogram { /** * Increment appropriate bin diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt similarity index 97% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt rename to kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt index 860eb9346..6c6614851 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt @@ -27,7 +27,8 @@ interface HistogramSpace, H : Histogram> : Space { val binSpace: Space> } -class PhantomBin>(val template: BinTemplate, override val value: Number) : Bin { +class PhantomBin>(val template: BinTemplate, override val value: Number) : + Bin { override fun contains(vector: Point): Boolean = template.contains(vector) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt similarity index 87% rename from kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt rename to kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt index 94ec6666c..d59409e76 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt @@ -1,5 +1,8 @@ -package scientifik.kmath.histogram +package scietifik.kmath.histogram +import scientifik.kmath.histogram.FastHistogram +import scientifik.kmath.histogram.fill +import scientifik.kmath.histogram.put import scientifik.kmath.linear.Vector import kotlin.random.Random import kotlin.test.Test diff --git a/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt similarity index 100% rename from kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt rename to kmath-histograms/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt similarity index 100% rename from kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt rename to kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt similarity index 89% rename from kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt rename to kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt index 4d18b714d..f7c4e7fa4 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -76,8 +76,14 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U val sorted = borders.sortedArray() return UnivariateHistogram { value -> when { - value < sorted.first() -> UnivariateBin(Double.NEGATIVE_INFINITY, Double.MAX_VALUE) - value > sorted.last() -> UnivariateBin(Double.POSITIVE_INFINITY, Double.MAX_VALUE) + value < sorted.first() -> UnivariateBin( + Double.NEGATIVE_INFINITY, + Double.MAX_VALUE + ) + value > sorted.last() -> UnivariateBin( + Double.POSITIVE_INFINITY, + Double.MAX_VALUE + ) else -> { val index = (0 until sorted.size).first { value > sorted[it] } val left = sorted[index] diff --git a/settings.gradle.kts b/settings.gradle.kts index b306d1c8d..7d8731532 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ include( ":kmath-core", // ":kmath-io", ":kmath-coroutines", + ":kmath-histograms", ":kmath-commons", ":kmath-koma", ":kmath-sequential", From 566883c52176e49405cb3f7a778fe35f6bce2f7f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 14 Feb 2019 09:38:42 +0300 Subject: [PATCH 65/70] Algebra operations separated --- .../scientifik/kmath/operations/Algebra.kt | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 42c7e9377..ee9833623 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,20 +1,7 @@ package scientifik.kmath.operations -/** - * A general interface representing linear context of some kind. - * The context defines sum operation for its elements and multiplication by real value. - * One must note that in some cases context is a singleton class, but in some cases it - * works as a context for operations inside it. - * - * TODO do we need non-commutative context? - */ -interface Space { - /** - * Neutral element for sum operation - */ - val zero: T - +interface SpaceOperations { /** * Addition operation for two context elements */ @@ -35,21 +22,39 @@ interface Space { operator fun Number.times(b: T) = b * this } -/** - * The same as {@link Space} but with additional multiplication operation - */ -interface Ring : Space { - /** - * neutral operation for multiplication - */ - val one: T +/** + * A general interface representing linear context of some kind. + * The context defines sum operation for its elements and multiplication by real value. + * One must note that in some cases context is a singleton class, but in some cases it + * works as a context for operations inside it. + * + * TODO do we need non-commutative context? + */ +interface Space : SpaceOperations { + /** + * Neutral element for sum operation + */ + val zero: T +} + +interface RingOperations : SpaceOperations { /** * Multiplication for two field elements */ fun multiply(a: T, b: T): T operator fun T.times(b: T): T = multiply(this, b) +} + +/** + * The same as {@link Space} but with additional multiplication operation + */ +interface Ring : Space, RingOperations { + /** + * neutral operation for multiplication + */ + val one: T // operator fun T.plus(b: Number) = this.plus(b * one) // operator fun Number.plus(b: T) = b + this @@ -59,11 +64,17 @@ interface Ring : Space { } /** - * Four operations algebra + * All ring operations but without neutral elements */ -interface Field : Ring { +interface FieldOperations : RingOperations { fun divide(a: T, b: T): T operator fun T.div(b: T): T = divide(this, b) +} + +/** + * Four operations algebra + */ +interface Field : Ring, FieldOperations { operator fun Number.div(b: T) = this * divide(one, b) } From cdcba85ada07f7a0566a06bf3ac538c50744f2ff Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 14 Feb 2019 20:31:08 +0300 Subject: [PATCH 66/70] Some unifications for buffers and NDStructures --- .../structures/StructureWriteBenchmark.kt | 2 +- .../kmath/expressions/DiffExpression.kt | 1 + .../scientifik/kmath/operations/Complex.kt | 2 +- .../kmath/operations/NumberAlgebra.kt | 7 +- .../kmath/operations/OptionalOperations.kt | 2 +- .../scientifik/kmath/structures/Buffers.kt | 8 +- .../kmath/structures/ExtendedNDField.kt | 8 +- .../kmath/structures/NDStructure.kt | 80 ++++------- .../kmath/structures/RealBufferField.kt | 125 ++++++++++++------ .../scientifik/kmath/histogram/Counters.kt | 2 +- .../scientifik/kmath/histogram/Histogram.kt | 11 +- .../kmath/histogram/PhantomHistogram.kt | 64 --------- .../{FastHistogram.kt => RealHistogram.kt} | 89 +++++++------ .../histogram/MultivariateHistogramTest.kt | 6 +- .../kmath/histogram/UnivariateHistogram.kt | 2 +- 15 files changed, 190 insertions(+), 219 deletions(-) delete mode 100644 kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt rename kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/{FastHistogram.kt => RealHistogram.kt} (57%) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt index a2be2aa9c..e5147b941 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/StructureWriteBenchmark.kt @@ -8,7 +8,7 @@ fun main(args: Array) { val n = 6000 - val structure = ndStructure(intArrayOf(n, n), DoubleBufferFactory) { 1.0 } + val structure = NDStructure.build(intArrayOf(n, n), DoubleBufferFactory) { 1.0 } structure.mapToBuffer { it + 1 } // warm-up diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt index 79cf92c94..6c1759d54 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/expressions/DiffExpression.kt @@ -2,6 +2,7 @@ package scientifik.kmath.expressions import org.apache.commons.math3.analysis.differentiation.DerivativeStructure import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.ExtendedFieldOperations import scientifik.kmath.operations.Field import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index a7d9ef7f4..3003ce1bc 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -5,7 +5,7 @@ import kotlin.math.* /** * A field for complex numbers */ -object ComplexField : ExtendedField { +object ComplexField : ExtendedFieldOperations, Field { override val zero: Complex = Complex(0.0, 0.0) override val one: Complex = Complex(1.0, 0.0) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt index a57688ad8..b80212375 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/NumberAlgebra.kt @@ -5,12 +5,13 @@ import kotlin.math.pow /** * Advanced Number-like field that implements basic operations */ -interface ExtendedField : - Field, +interface ExtendedFieldOperations : + FieldOperations, TrigonometricOperations, PowerOperations, ExponentialOperations +interface ExtendedField : ExtendedFieldOperations, Field /** * Real field element wrapping double. @@ -31,7 +32,7 @@ inline class Real(val value: Double) : FieldElement { * A field for double without boxing. Does not produce appropriate field element */ @Suppress("EXTENSION_SHADOWED_BY_MEMBER") -object RealField : ExtendedField, Norm { +object RealField : Field, ExtendedFieldOperations, Norm { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b override fun multiply(a: Double, b: Double): Double = a * b diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt index 66ca205a1..ffbbf69dd 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -10,7 +10,7 @@ package scientifik.kmath.operations * It also allows to override behavior for optional operations * */ -interface TrigonometricOperations : Field { +interface TrigonometricOperations : FieldOperations { fun sin(arg: T): T fun cos(arg: T): T diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 8256b596d..225c00c82 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -109,6 +109,9 @@ inline class ListBuffer(val list: List) : Buffer { fun List.asBuffer() = ListBuffer(this) +@Suppress("FunctionName") +inline fun ListBuffer(size: Int, init: (Int) -> T) = List(size, init).asBuffer() + inline class MutableListBuffer(val list: MutableList) : MutableBuffer { override val size: Int @@ -154,9 +157,12 @@ inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer { override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) - } +@Suppress("FunctionName") +inline fun DoubleBuffer(size: Int, init: (Int) -> Double) = DoubleBuffer(DoubleArray(size) { init(it) }) + + /** * Transform buffer of doubles into array for high performance operations */ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 0a8f1de88..370dc646e 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -1,16 +1,14 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.ExponentialOperations -import scientifik.kmath.operations.ExtendedField -import scientifik.kmath.operations.PowerOperations -import scientifik.kmath.operations.TrigonometricOperations +import scientifik.kmath.operations.* -interface ExtendedNDField, N : NDStructure> : +interface ExtendedNDField> : NDField, TrigonometricOperations, PowerOperations, ExponentialOperations + where F : ExtendedFieldOperations, F : Field ///** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt index 5f16f0444..2cf9af6fb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -22,6 +22,33 @@ interface NDStructure { else -> st1.elements().all { (index, value) -> value == st2[index] } } } + + /** + * Create a NDStructure with explicit buffer factory + * + * Strides should be reused if possible + */ + fun build( + strides: Strides, + bufferFactory: BufferFactory = Buffer.Companion::boxing, + initializer: (IntArray) -> T + ) = + BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) + + /** + * Inline create NDStructure with non-boxing buffer implementation if it is possible + */ + inline fun auto(strides: Strides, crossinline initializer: (IntArray) -> T) = + BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) + + fun build( + shape: IntArray, + bufferFactory: BufferFactory = Buffer.Companion::boxing, + initializer: (IntArray) -> T + ) = build(DefaultStrides(shape), bufferFactory, initializer) + + inline fun auto(shape: IntArray, crossinline initializer: (IntArray) -> T) = + auto(DefaultStrides(shape), initializer) } } @@ -200,34 +227,6 @@ inline fun NDStructure.mapToBuffer( } } -/** - * Create a NDStructure with explicit buffer factory - * - * Strides should be reused if possible - */ -fun ndStructure( - strides: Strides, - bufferFactory: BufferFactory = Buffer.Companion::boxing, - initializer: (IntArray) -> T -) = - BufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) - -/** - * Inline create NDStructure with non-boxing buffer implementation if it is possible - */ -inline fun inlineNDStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, Buffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) - -fun ndStructure( - shape: IntArray, - bufferFactory: BufferFactory = Buffer.Companion::boxing, - initializer: (IntArray) -> T -) = - ndStructure(DefaultStrides(shape), bufferFactory, initializer) - -inline fun inlineNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = - inlineNDStructure(DefaultStrides(shape), initializer) - /** * Mutable ND buffer based on linear [autoBuffer] */ @@ -245,33 +244,10 @@ class MutableBufferNDStructure( override fun set(index: IntArray, value: T) = buffer.set(strides.offset(index), value) } -/** - * The same as [inlineNDStructure], but mutable - */ -fun mutableNdStructure( - strides: Strides, - bufferFactory: MutableBufferFactory = MutableBuffer.Companion::boxing, - initializer: (IntArray) -> T -) = - MutableBufferNDStructure(strides, bufferFactory(strides.linearSize) { i -> initializer(strides.index(i)) }) - -inline fun inlineMutableNdStructure(strides: Strides, crossinline initializer: (IntArray) -> T) = - MutableBufferNDStructure(strides, MutableBuffer.auto(strides.linearSize) { i -> initializer(strides.index(i)) }) - -fun mutableNdStructure( - shape: IntArray, - bufferFactory: MutableBufferFactory = MutableBuffer.Companion::boxing, - initializer: (IntArray) -> T -) = - mutableNdStructure(DefaultStrides(shape), bufferFactory, initializer) - -inline fun inlineMutableNdStructure(shape: IntArray, crossinline initializer: (IntArray) -> T) = - inlineMutableNdStructure(DefaultStrides(shape), initializer) - inline fun NDStructure.combine( struct: NDStructure, crossinline block: (T, T) -> T ): NDStructure { if (!this.shape.contentEquals(struct.shape)) error("Shape mismatch in structure combination") - return inlineNdStructure(shape) { block(this[it], struct[it]) } + return NDStructure.auto(shape) { block(this[it], struct[it]) } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt index 42e74a27f..b56f42717 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/RealBufferField.kt @@ -1,110 +1,153 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.ExtendedFieldOperations +import scientifik.kmath.operations.Field import kotlin.math.* + /** * A simple field over linear buffers of [Double] */ -class RealBufferField(val size: Int) : ExtendedField> { - override val zero: Buffer = Buffer.DoubleBufferFactory(size) { 0.0 } - - override val one: Buffer = Buffer.DoubleBufferFactory(size) { 1.0 } - +object RealBufferFieldOperations : ExtendedFieldOperations> { override fun add(a: Buffer, b: Buffer): DoubleBuffer { - require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } - require(b.size == size) { "The size of buffer is ${b.size} but context requires $size " } + require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } return if (a is DoubleBuffer && b is DoubleBuffer) { val aArray = a.array val bArray = b.array - DoubleBuffer(DoubleArray(size) { aArray[it] + bArray[it] }) + DoubleBuffer(DoubleArray(a.size) { aArray[it] + bArray[it] }) } else { - DoubleBuffer(DoubleArray(size) { a[it] + b[it] }) + DoubleBuffer(DoubleArray(a.size) { a[it] + b[it] }) } } override fun multiply(a: Buffer, k: Number): DoubleBuffer { - require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } val kValue = k.toDouble() return if (a is DoubleBuffer) { val aArray = a.array - DoubleBuffer(DoubleArray(size) { aArray[it] * kValue }) + DoubleBuffer(DoubleArray(a.size) { aArray[it] * kValue }) } else { - DoubleBuffer(DoubleArray(size) { a[it] * kValue }) + DoubleBuffer(DoubleArray(a.size) { a[it] * kValue }) } } override fun multiply(a: Buffer, b: Buffer): DoubleBuffer { - require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } - require(b.size == size) { "The size of buffer is ${b.size} but context requires $size " } + require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } return if (a is DoubleBuffer && b is DoubleBuffer) { val aArray = a.array val bArray = b.array - DoubleBuffer(DoubleArray(size) { aArray[it] * bArray[it] }) + DoubleBuffer(DoubleArray(a.size) { aArray[it] * bArray[it] }) } else { - DoubleBuffer(DoubleArray(size) { a[it] * b[it] }) + DoubleBuffer(DoubleArray(a.size) { a[it] * b[it] }) } } override fun divide(a: Buffer, b: Buffer): DoubleBuffer { - require(a.size == size) { "The size of buffer is ${a.size} but context requires $size " } - require(b.size == size) { "The size of buffer is ${b.size} but context requires $size " } + require(b.size == a.size) { "The size of the first buffer ${a.size} should be the same as for second one: ${b.size} " } return if (a is DoubleBuffer && b is DoubleBuffer) { val aArray = a.array val bArray = b.array - DoubleBuffer(DoubleArray(size) { aArray[it] / bArray[it] }) + DoubleBuffer(DoubleArray(a.size) { aArray[it] / bArray[it] }) } else { - DoubleBuffer(DoubleArray(size) { a[it] / b[it] }) + DoubleBuffer(DoubleArray(a.size) { a[it] / b[it] }) } } - override fun sin(arg: Buffer): Buffer { - require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + override fun sin(arg: Buffer): DoubleBuffer { return if (arg is DoubleBuffer) { val array = arg.array - DoubleBuffer(DoubleArray(size) { sin(array[it]) }) + DoubleBuffer(DoubleArray(arg.size) { sin(array[it]) }) } else { - DoubleBuffer(DoubleArray(size) { sin(arg[it]) }) + DoubleBuffer(DoubleArray(arg.size) { sin(arg[it]) }) } } - override fun cos(arg: Buffer): Buffer { - require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + override fun cos(arg: Buffer): DoubleBuffer { return if (arg is DoubleBuffer) { val array = arg.array - DoubleBuffer(DoubleArray(size) { cos(array[it]) }) + DoubleBuffer(DoubleArray(arg.size) { cos(array[it]) }) } else { - DoubleBuffer(DoubleArray(size) { cos(arg[it]) }) + DoubleBuffer(DoubleArray(arg.size) { cos(arg[it]) }) } } - override fun power(arg: Buffer, pow: Number): Buffer { - require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + override fun power(arg: Buffer, pow: Number): DoubleBuffer { return if (arg is DoubleBuffer) { val array = arg.array - DoubleBuffer(DoubleArray(size) { array[it].pow(pow.toDouble()) }) + DoubleBuffer(DoubleArray(arg.size) { array[it].pow(pow.toDouble()) }) } else { - DoubleBuffer(DoubleArray(size) { arg[it].pow(pow.toDouble()) }) + DoubleBuffer(DoubleArray(arg.size) { arg[it].pow(pow.toDouble()) }) } } - override fun exp(arg: Buffer): Buffer { - require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + override fun exp(arg: Buffer): DoubleBuffer { return if (arg is DoubleBuffer) { val array = arg.array - DoubleBuffer(DoubleArray(size) { exp(array[it]) }) + DoubleBuffer(DoubleArray(arg.size) { exp(array[it]) }) } else { - DoubleBuffer(DoubleArray(size) { exp(arg[it]) }) + DoubleBuffer(DoubleArray(arg.size) { exp(arg[it]) }) } } - override fun ln(arg: Buffer): Buffer { - require(arg.size == size) { "The size of buffer is ${arg.size} but context requires $size " } + override fun ln(arg: Buffer): DoubleBuffer { return if (arg is DoubleBuffer) { val array = arg.array - DoubleBuffer(DoubleArray(size) { ln(array[it]) }) + DoubleBuffer(DoubleArray(arg.size) { ln(array[it]) }) } else { - DoubleBuffer(DoubleArray(size) { ln(arg[it]) }) + DoubleBuffer(DoubleArray(arg.size) { ln(arg[it]) }) } } +} + +class RealBufferField(val size: Int) : Field>, ExtendedFieldOperations> { + + override val zero: Buffer by lazy { DoubleBuffer(size) { 0.0 } } + + override val one: Buffer by lazy { DoubleBuffer(size) { 1.0 } } + + override fun add(a: Buffer, b: Buffer): DoubleBuffer { + require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } + return RealBufferFieldOperations.add(a, b) + } + + override fun multiply(a: Buffer, k: Number): DoubleBuffer { + require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } + return RealBufferFieldOperations.multiply(a, k) + } + + override fun multiply(a: Buffer, b: Buffer): DoubleBuffer { + require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } + return RealBufferFieldOperations.multiply(a, b) + } + + + override fun divide(a: Buffer, b: Buffer): DoubleBuffer { + require(a.size == size) { "The buffer size ${a.size} does not match context size $size" } + return RealBufferFieldOperations.divide(a, b) + } + + override fun sin(arg: Buffer): DoubleBuffer { + require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } + return RealBufferFieldOperations.sin(arg) + } + + override fun cos(arg: Buffer): DoubleBuffer { + require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } + return RealBufferFieldOperations.cos(arg) + } + + override fun power(arg: Buffer, pow: Number): DoubleBuffer { + require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } + return RealBufferFieldOperations.power(arg, pow) + } + + override fun exp(arg: Buffer): DoubleBuffer { + require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } + return RealBufferFieldOperations.exp(arg) + } + + override fun ln(arg: Buffer): DoubleBuffer { + require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" } + return RealBufferFieldOperations.ln(arg) + } + } \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt index 9a470014a..9c7de3303 100644 --- a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -2,9 +2,9 @@ package scientifik.kmath.histogram /* * Common representation for atomic counters + * TODO replace with atomics */ - expect class LongCounter() { fun decrement() fun increment() diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt index c5fc67b5c..329af72a1 100644 --- a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -2,12 +2,8 @@ package scientifik.kmath.histogram import scientifik.kmath.linear.Point import scientifik.kmath.structures.ArrayBuffer -import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.DoubleBuffer - -typealias RealPoint = Buffer - /** * A simple geometric domain * TODO move to geometry module @@ -42,13 +38,14 @@ interface Histogram> : Iterable { } -interface MutableHistogram> : - Histogram { +interface MutableHistogram> : Histogram { /** * Increment appropriate bin */ - fun put(point: Point, weight: Double = 1.0) + fun putWithWeight(point: Point, weight: Double) + + fun put(point: Point) = putWithWeight(point, 1.0) } fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt deleted file mode 100644 index 6c6614851..000000000 --- a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt +++ /dev/null @@ -1,64 +0,0 @@ -package scientifik.kmath.histogram - -import scientifik.kmath.linear.Point -import scientifik.kmath.linear.Vector -import scientifik.kmath.operations.Space -import scientifik.kmath.structures.NDStructure -import scientifik.kmath.structures.asSequence - -data class BinTemplate>(val center: Vector, val sizes: Point) { - fun contains(vector: Point): 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, H : Histogram> : Space { - /** - * Rules for performing operations on bins - */ - val binSpace: Space> -} - -class PhantomBin>(val template: BinTemplate, override val value: Number) : - Bin { - - override fun contains(vector: Point): Boolean = template.contains(vector) - - override val dimension: Int - get() = template.center.size - - override val center: Point - 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 transform a template into structure index - */ -class PhantomHistogram>( - val bins: Map, IntArray>, - val data: NDStructure -) : Histogram> { - - override val dimension: Int - get() = data.dimension - - override fun iterator(): Iterator> = - bins.asSequence().map { entry -> PhantomBin(entry.key, data[entry.value]) }.iterator() - - override fun get(point: Point): PhantomBin? { - val template = bins.keys.find { it.contains(point) } - return template?.let { PhantomBin(it, data[bins[it]!!]) } - } - -} \ No newline at end of file diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt similarity index 57% rename from kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt rename to kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt index 869f9a02e..75296ba27 100644 --- a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt @@ -1,45 +1,66 @@ package scientifik.kmath.histogram +import scientifik.kmath.linear.Point import scientifik.kmath.linear.toVector +import scientifik.kmath.operations.SpaceOperations import scientifik.kmath.structures.* import kotlin.math.floor -private operator fun RealPoint.minus(other: RealPoint) = ListBuffer((0 until size).map { get(it) - other[it] }) -private inline fun Buffer.mapIndexed(crossinline mapper: (Int, Double) -> T): Sequence = - (0 until size).asSequence().map { mapper(it, get(it)) } +data class BinDef>(val space: SpaceOperations>, val center: Point, val sizes: Point) { + fun contains(vector: Point): Boolean { + if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}") + val upper = space.run { center + sizes / 2.0 } + val lower = space.run { center - sizes / 2.0 } + return vector.asSequence().mapIndexed { i, value -> + value in lower[i]..upper[i] + }.all { it } + } +} + + +class MultivariateBin>(val def: BinDef, override val value: Number) : Bin { + + override fun contains(vector: Point): Boolean = def.contains(vector) + + override val dimension: Int + get() = def.center.size + + override val center: Point + get() = def.center + +} /** * Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions. */ -class FastHistogram( - private val lower: RealPoint, - private val upper: RealPoint, +class RealHistogram( + private val lower: Buffer, + private val upper: Buffer, private val binNums: IntArray = IntArray(lower.size) { 20 } -) : MutableHistogram> { +) : MutableHistogram> { private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 }) - private val values: NDStructure = inlineNDStructure(strides) { LongCounter() } + private val values: NDStructure = NDStructure.auto(strides) { LongCounter() } //private val weight: NDStructure = ndStructure(strides){null} - //TODO optimize binSize performance if needed - private val binSize: RealPoint = - ListBuffer((upper - lower).mapIndexed { index, value -> value / binNums[index] }.toList()) + + override val dimension: Int get() = lower.size + + + private val binSize = DoubleBuffer(dimension) { (upper[it] - lower[it]) / binNums[it] } init { // argument checks if (lower.size != upper.size) error("Dimension mismatch in histogram lower and upper limits.") if (lower.size != binNums.size) error("Dimension mismatch in bin count.") - if ((upper - lower).asSequence().any { it <= 0 }) error("Range for one of axis is not strictly positive") + if ((0 until dimension).any { upper[it] - lower[it] < 0 }) error("Range for one of axis is not strictly positive") } - override val dimension: Int get() = lower.size - - /** * Get internal [NDStructure] bin index for given axis */ @@ -61,49 +82,41 @@ class FastHistogram( return getValue(getIndex(point)) } - private fun getTemplate(index: IntArray): BinTemplate { + private fun getDef(index: IntArray): BinDef { val center = index.mapIndexed { axis, i -> when (i) { 0 -> Double.NEGATIVE_INFINITY strides.shape[axis] - 1 -> Double.POSITIVE_INFINITY else -> lower[axis] + (i.toDouble() - 0.5) * binSize[axis] } - }.toVector() - return BinTemplate(center, binSize) + }.asBuffer() + return BinDef(RealBufferFieldOperations, center, binSize) } - fun getTemplate(point: Buffer): BinTemplate { - return getTemplate(getIndex(point)) + fun getDef(point: Buffer): BinDef { + return getDef(getIndex(point)) } - override fun get(point: Buffer): PhantomBin? { + override fun get(point: Buffer): MultivariateBin? { val index = getIndex(point) - return PhantomBin(getTemplate(index), getValue(index)) + return MultivariateBin(getDef(index), getValue(index)) } - override fun put(point: Buffer, weight: Double) { + override fun putWithWeight(point: Buffer, weight: Double) { if (weight != 1.0) TODO("Implement weighting") val index = getIndex(point) values[index].increment() } - override fun iterator(): Iterator> = values.elements().map { (index, value) -> - PhantomBin(getTemplate(index), value.sum()) + override fun iterator(): Iterator> = values.elements().map { (index, value) -> + MultivariateBin(getDef(index), value.sum()) }.iterator() /** * Convert this histogram into NDStructure containing bin values but not bin descriptions */ fun asNDStructure(): NDStructure { - return inlineNdStructure(this.values.shape) { values[it].sum() } - } - - /** - * Create a phantom lightweight immutable copy of this histogram - */ - fun asPhantomHistogram(): PhantomHistogram { - val binTemplates = values.elements().associate { (index, _) -> getTemplate(index) to index } - return PhantomHistogram(binTemplates, asNDStructure()) + return NDStructure.auto(this.values.shape) { values[it].sum() } } companion object { @@ -117,8 +130,8 @@ class FastHistogram( *) *``` */ - fun fromRanges(vararg ranges: ClosedFloatingPointRange): FastHistogram { - return FastHistogram( + fun fromRanges(vararg ranges: ClosedFloatingPointRange): RealHistogram { + return RealHistogram( ranges.map { it.start }.toVector(), ranges.map { it.endInclusive }.toVector() ) @@ -133,8 +146,8 @@ class FastHistogram( *) *``` */ - fun fromRanges(vararg ranges: Pair, Int>): FastHistogram { - return FastHistogram( + fun fromRanges(vararg ranges: Pair, Int>): RealHistogram { + return RealHistogram( ListBuffer(ranges.map { it.first.start }), ListBuffer(ranges.map { it.first.endInclusive }), ranges.map { it.second }.toIntArray() diff --git a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt index d59409e76..678b8eba7 100644 --- a/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-histograms/src/commonTest/kotlin/scietifik/kmath/histogram/MultivariateHistogramTest.kt @@ -1,6 +1,6 @@ package scietifik.kmath.histogram -import scientifik.kmath.histogram.FastHistogram +import scientifik.kmath.histogram.RealHistogram import scientifik.kmath.histogram.fill import scientifik.kmath.histogram.put import scientifik.kmath.linear.Vector @@ -13,7 +13,7 @@ import kotlin.test.assertTrue class MultivariateHistogramTest { @Test fun testSinglePutHistogram() { - val histogram = FastHistogram.fromRanges( + val histogram = RealHistogram.fromRanges( (-1.0..1.0), (-1.0..1.0) ) @@ -26,7 +26,7 @@ class MultivariateHistogramTest { @Test fun testSequentialPut() { - val histogram = FastHistogram.fromRanges( + val histogram = RealHistogram.fromRanges( (-1.0..1.0), (-1.0..1.0), (-1.0..1.0) diff --git a/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt index f7c4e7fa4..53c2da641 100644 --- a/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-histograms/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -59,7 +59,7 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U (get(value) ?: createBin(value)).inc() } - override fun put(point: Buffer, weight: Double) { + override fun putWithWeight(point: Buffer, weight: Double) { if (weight != 1.0) TODO("Implement weighting") put(point[0]) } From 815066cf6c943b8385c6fee46ac65b9f10938cac Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 15 Feb 2019 20:00:42 +0300 Subject: [PATCH 67/70] FFT buffer transformations for commons-math --- doc/linear.md | 1 + kmath-commons/build.gradle.kts | 1 + .../kmath/transform/Transformations.kt | 63 +++++++++++++++++++ .../scientifik/kmath/structures/Buffers.kt | 16 +++-- .../kmath/sequential/DoubleProcessors.kt | 7 +++ .../kmath/sequential/DoubleStreaming.kt | 50 ++++++++------- 6 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt diff --git a/doc/linear.md b/doc/linear.md index e69de29bb..6208deaff 100644 --- a/doc/linear.md +++ b/doc/linear.md @@ -0,0 +1 @@ +**TODO** \ No newline at end of file diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 509809a15..305761c01 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -6,6 +6,7 @@ description = "Commons math binding for kmath" dependencies { api(project(":kmath-core")) + api(project(":kmath-sequential")) api("org.apache.commons:commons-math3:3.6.1") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt new file mode 100644 index 000000000..a4db4b1bd --- /dev/null +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt @@ -0,0 +1,63 @@ +package scientifik.kmath.transform + +import org.apache.commons.math3.transform.* +import scientifik.kmath.operations.Complex +import scientifik.kmath.structures.* + + +/** + * + */ +object Transformations { + + private fun Buffer.toArray(): Array = + Array(size) { org.apache.commons.math3.complex.Complex(get(it).re, get(it).im) } + + private fun Buffer.asArray() = if (this is DoubleBuffer) { + array + } else { + DoubleArray(size) { i -> get(i) } + } + + /** + * Create a virtual buffer on top of array + */ + private fun Array.asBuffer() = VirtualBuffer(size) { + val value = get(it) + Complex(value.real, value.imaginary) + } + + fun fourier( + normalization: DftNormalization = DftNormalization.STANDARD, + direction: TransformType = TransformType.FORWARD + ): BufferTransform = { + FastFourierTransformer(normalization).transform(it.toArray(), direction).asBuffer() + } + + fun realFourier( + normalization: DftNormalization = DftNormalization.STANDARD, + direction: TransformType = TransformType.FORWARD + ): BufferTransform = { + FastFourierTransformer(normalization).transform(it.asArray(), direction).asBuffer() + } + + fun sine( + normalization: DstNormalization = DstNormalization.STANDARD_DST_I, + direction: TransformType = TransformType.FORWARD + ): BufferTransform = { + FastSineTransformer(normalization).transform(it.asArray(), direction).asBuffer() + } + + fun cosine( + normalization: DctNormalization = DctNormalization.STANDARD_DCT_I, + direction: TransformType = TransformType.FORWARD + ): BufferTransform = { + FastCosineTransformer(normalization).transform(it.asArray(), direction).asBuffer() + } + + fun hadamard( + direction: TransformType = TransformType.FORWARD + ): BufferTransform = { + FastHadamardTransformer().transform(it.asArray(), direction).asBuffer() + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 225c00c82..a89729bae 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -154,7 +154,7 @@ inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator() = array.iterator() override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) } @@ -162,7 +162,6 @@ inline class DoubleBuffer(val array: DoubleArray) : MutableBuffer { @Suppress("FunctionName") inline fun DoubleBuffer(size: Int, init: (Int) -> Double) = DoubleBuffer(DoubleArray(size) { init(it) }) - /** * Transform buffer of doubles into array for high performance operations */ @@ -184,7 +183,7 @@ inline class ShortBuffer(val array: ShortArray) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator() = array.iterator() override fun copy(): MutableBuffer = ShortBuffer(array.copyOf()) @@ -201,7 +200,7 @@ inline class IntBuffer(val array: IntArray) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator() = array.iterator() override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) @@ -218,7 +217,7 @@ inline class LongBuffer(val array: LongArray) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator() = array.iterator() override fun copy(): MutableBuffer = LongBuffer(array.copyOf()) @@ -231,7 +230,7 @@ inline class ReadOnlyBuffer(val buffer: MutableBuffer) : Buffer { override fun get(index: Int): T = buffer.get(index) - override fun iterator(): Iterator = buffer.iterator() + override fun iterator() = buffer.iterator() } /** @@ -260,3 +259,8 @@ fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { } else { this } + +/** + * Typealias for buffer transformations + */ +typealias BufferTransform = (Buffer) -> Buffer \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt new file mode 100644 index 000000000..bdeb2d319 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt @@ -0,0 +1,7 @@ +package scientifik.kmath.sequential + +import kotlinx.coroutines.CoroutineScope + +//class FFTProcessor(scope: CoroutineScope): AbstractDoubleProcessor(scope){ +// +//} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt index d18c76634..c2f048c34 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt @@ -7,13 +7,22 @@ import kotlinx.coroutines.channels.* import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asBuffer +import scientifik.kmath.structures.asSequence + +fun Buffer.asChannel(scope: CoroutineScope): ReceiveChannel = scope.produce { + for (i in (0 until size)) { + send(get(i)) + } +} interface DoubleProducer : Producer { - suspend fun receiveArray(): DoubleArray + suspend fun receiveArray(): Buffer } interface DoubleConsumer : Consumer { - suspend fun sendArray(array: DoubleArray) + suspend fun sendArray(array: Buffer) } abstract class AbstractDoubleProducer(scope: CoroutineScope) : AbstractProducer(scope), DoubleProducer { @@ -79,15 +88,15 @@ abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcesso class BasicDoubleProducer( scope: CoroutineScope, capacity: Int = Channel.UNLIMITED, - block: suspend ProducerScope.() -> Unit + block: suspend ProducerScope>.() -> Unit ) : AbstractDoubleProducer(scope) { private val currentArray = atomic?>(null) - private val channel: ReceiveChannel by lazy { produce(capacity = capacity, block = block) } + private val channel: ReceiveChannel> by lazy { produce(capacity = capacity, block = block) } private val cachingChannel by lazy { channel.map { - it.also { doubles -> currentArray.lazySet(doubles.asChannel()) } + it.also { doubles -> currentArray.lazySet(doubles.asChannel(this)) } } } @@ -97,40 +106,39 @@ class BasicDoubleProducer( } } - override suspend fun receiveArray(): DoubleArray = cachingChannel.receive() + override suspend fun receiveArray(): Buffer = cachingChannel.receive() - override suspend fun receive(): Double = (currentArray.value ?: cachingChannel.receive().asChannel()).receive() + override suspend fun receive(): Double = (currentArray.value ?: cachingChannel.receive().asChannel(this)).receive() } class DoubleReducer( scope: CoroutineScope, initialState: S, - val fold: suspend (S, DoubleArray) -> S + val fold: suspend (S, Buffer) -> S ) : AbstractDoubleConsumer(scope) { var state: S = initialState private set - private val mutex = Mutex() - - override suspend fun sendArray(array: DoubleArray) { + override suspend fun sendArray(array: Buffer) { state = fold(state, array) } - override suspend fun send(value: Double) = sendArray(doubleArrayOf(value)) + override suspend fun send(value: Double) = sendArray(doubleArrayOf(value).asBuffer()) } /** * Convert an array to single element producer, splitting it in chunks if necessary */ -fun DoubleArray.produce(scope: CoroutineScope = GlobalScope, chunkSize: Int = Int.MAX_VALUE) = if (size < chunkSize) { - BasicDoubleProducer(scope) { send(this@produce) } -} else { - BasicDoubleProducer(scope) { - //TODO optimize this! - asSequence().chunked(chunkSize).forEach { - send(it.toDoubleArray()) +fun Buffer.produce(scope: CoroutineScope = GlobalScope, chunkSize: Int = Int.MAX_VALUE) = + if (size < chunkSize) { + BasicDoubleProducer(scope) { send(this@produce) } + } else { + BasicDoubleProducer(scope) { + //TODO optimize this! + asSequence().chunked(chunkSize).forEach { + send(it.asBuffer()) + } } - } -} \ No newline at end of file + } \ No newline at end of file From 9c416e185b27379521d5ac2928012729fef32bae Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 15 Feb 2019 22:09:30 +0300 Subject: [PATCH 68/70] Buffer streaming replaced primitive streaming --- .../kmath/sequential/BufferStreaming.kt | 178 ++++++++++++++++++ .../kmath/sequential/DoubleProcessors.kt | 7 - .../kmath/sequential/DoubleStreaming.kt | 144 -------------- 3 files changed, 178 insertions(+), 151 deletions(-) create mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt delete mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt delete mode 100644 kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt new file mode 100644 index 000000000..9ab23fbb4 --- /dev/null +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt @@ -0,0 +1,178 @@ +package scientifik.kmath.sequential + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asBuffer +import scientifik.kmath.structures.asSequence + +fun Buffer.asChannel(scope: CoroutineScope): ReceiveChannel = scope.produce { + for (i in (0 until size)) { + send(get(i)) + } +} + + +interface BufferProducer : Producer { + suspend fun receiveBuffer(): Buffer +} + +interface BufferConsumer : Consumer { + suspend fun sendBuffer(buffer: Buffer) +} + +abstract class AbstractBufferProducer(scope: CoroutineScope) : AbstractProducer(scope), BufferProducer { + + override fun connectOutput(consumer: Consumer) { + if (consumer is BufferConsumer) { + launch { + while (this.isActive) { + consumer.sendBuffer(receiveBuffer()) + } + } + } else { + super.connectOutput(consumer) + } + } +} + +abstract class AbstractBufferConsumer(scope: CoroutineScope) : AbstractConsumer(scope), BufferConsumer { + override fun connectInput(producer: Producer) { + if (producer is BufferProducer) { + launch { + while (isActive) { + sendBuffer(producer.receiveBuffer()) + } + } + } else { + super.connectInput(producer) + } + } +} + +abstract class AbstractBufferProcessor(scope: CoroutineScope) : + AbstractProcessor(scope), + BufferProducer, + BufferConsumer { + + override fun connectOutput(consumer: Consumer) { + if (consumer is BufferConsumer) { + launch { + while (this.isActive) { + consumer.sendBuffer(receiveBuffer()) + } + } + } else { + super.connectOutput(consumer) + } + } + + override fun connectInput(producer: Producer) { + if (producer is BufferProducer) { + launch { + while (isActive) { + sendBuffer(producer.receiveBuffer()) + } + } + } else { + super.connectInput(producer) + } + } +} + +/** + * The basic generic buffer producer supporting both arrays and element-by-element simultaneously + */ +class BasicBufferProducer( + scope: CoroutineScope, + capacity: Int = Channel.UNLIMITED, + block: suspend ProducerScope>.() -> Unit +) : AbstractBufferProducer(scope) { + + + private val currentArray = atomic?>(null) + private val channel: ReceiveChannel> by lazy { produce(capacity = capacity, block = block) } + private val cachingChannel by lazy { + channel.map { + it.also { buffer -> currentArray.lazySet(buffer.asChannel(this)) } + } + } + + private fun DoubleArray.asChannel() = produce { + for (value in this@asChannel) { + send(value) + } + } + + override suspend fun receiveBuffer(): Buffer = cachingChannel.receive() + + override suspend fun receive(): T = (currentArray.value ?: cachingChannel.receive().asChannel(this)).receive() +} + + +class BufferReducer( + scope: CoroutineScope, + initialState: S, + val fold: suspend (S, Buffer) -> S +) : AbstractBufferConsumer(scope) { + + var state: S = initialState + private set + + override suspend fun sendBuffer(buffer: Buffer) { + state = fold(state, buffer) + } + + override suspend fun send(value: T) = sendBuffer(arrayOf(value).asBuffer()) +} + +/** + * Convert a [Buffer] to single element producer, splitting it in chunks if necessary + */ +fun Buffer.produce(scope: CoroutineScope = GlobalScope, chunkSize: Int = Int.MAX_VALUE) = + if (size < chunkSize) { + BasicBufferProducer(scope) { send(this@produce) } + } else { + BasicBufferProducer(scope) { + //TODO optimize this! + asSequence().chunked(chunkSize).forEach { + send(it.asBuffer()) + } + } + } + + +/** + * A buffer processor that works with buffers but could accumulate at lest [accumulate] elements from single input before processing. + * + * This class combines functions from [ChunkProcessor] and single buffer processor + */ +class AccumulatingBufferProcessor( + scope: CoroutineScope, + val accumulate: Int, + val process: suspend (Buffer) -> Buffer +) : + AbstractBufferProcessor(scope) { + + private val inputChannel = Channel>() + private val outputChannel = inputChannel.map { process(it) } + + override suspend fun receive(): R { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override suspend fun send(value: T) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override suspend fun receiveBuffer(): Buffer = outputChannel.receive() + + override suspend fun sendBuffer(buffer: Buffer) { + inputChannel.send(buffer) + } + +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt deleted file mode 100644 index bdeb2d319..000000000 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleProcessors.kt +++ /dev/null @@ -1,7 +0,0 @@ -package scientifik.kmath.sequential - -import kotlinx.coroutines.CoroutineScope - -//class FFTProcessor(scope: CoroutineScope): AbstractDoubleProcessor(scope){ -// -//} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt deleted file mode 100644 index c2f048c34..000000000 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/DoubleStreaming.kt +++ /dev/null @@ -1,144 +0,0 @@ -package scientifik.kmath.sequential - -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.* -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.asBuffer -import scientifik.kmath.structures.asSequence - -fun Buffer.asChannel(scope: CoroutineScope): ReceiveChannel = scope.produce { - for (i in (0 until size)) { - send(get(i)) - } -} - -interface DoubleProducer : Producer { - suspend fun receiveArray(): Buffer -} - -interface DoubleConsumer : Consumer { - suspend fun sendArray(array: Buffer) -} - -abstract class AbstractDoubleProducer(scope: CoroutineScope) : AbstractProducer(scope), DoubleProducer { - - override fun connectOutput(consumer: Consumer) { - if (consumer is DoubleConsumer) { - launch { - while (this.isActive) { - consumer.sendArray(receiveArray()) - } - } - } else { - super.connectOutput(consumer) - } - } -} - -abstract class AbstractDoubleConsumer(scope: CoroutineScope) : AbstractConsumer(scope), DoubleConsumer { - override fun connectInput(producer: Producer) { - if (producer is DoubleProducer) { - launch { - while (isActive) { - sendArray(producer.receiveArray()) - } - } - } else { - super.connectInput(producer) - } - } -} - -abstract class AbstractDoubleProcessor(scope: CoroutineScope) : AbstractProcessor(scope), - DoubleProducer, DoubleConsumer { - - override fun connectOutput(consumer: Consumer) { - if (consumer is DoubleConsumer) { - launch { - while (this.isActive) { - consumer.sendArray(receiveArray()) - } - } - } else { - super.connectOutput(consumer) - } - } - - override fun connectInput(producer: Producer) { - if (producer is DoubleProducer) { - launch { - while (isActive) { - sendArray(producer.receiveArray()) - } - } - } else { - super.connectInput(producer) - } - } -} - -/** - * The basic [Double] producer supporting both arrays and element-by-element simultaneously - */ -class BasicDoubleProducer( - scope: CoroutineScope, - capacity: Int = Channel.UNLIMITED, - block: suspend ProducerScope>.() -> Unit -) : AbstractDoubleProducer(scope) { - - - private val currentArray = atomic?>(null) - private val channel: ReceiveChannel> by lazy { produce(capacity = capacity, block = block) } - private val cachingChannel by lazy { - channel.map { - it.also { doubles -> currentArray.lazySet(doubles.asChannel(this)) } - } - } - - private fun DoubleArray.asChannel() = produce { - for (value in this@asChannel) { - send(value) - } - } - - override suspend fun receiveArray(): Buffer = cachingChannel.receive() - - override suspend fun receive(): Double = (currentArray.value ?: cachingChannel.receive().asChannel(this)).receive() -} - - -class DoubleReducer( - scope: CoroutineScope, - initialState: S, - val fold: suspend (S, Buffer) -> S -) : AbstractDoubleConsumer(scope) { - - var state: S = initialState - private set - - override suspend fun sendArray(array: Buffer) { - state = fold(state, array) - } - - override suspend fun send(value: Double) = sendArray(doubleArrayOf(value).asBuffer()) -} - -/** - * Convert an array to single element producer, splitting it in chunks if necessary - */ -fun Buffer.produce(scope: CoroutineScope = GlobalScope, chunkSize: Int = Int.MAX_VALUE) = - if (size < chunkSize) { - BasicDoubleProducer(scope) { send(this@produce) } - } else { - BasicDoubleProducer(scope) { - //TODO optimize this! - asSequence().chunked(chunkSize).forEach { - send(it.asBuffer()) - } - } - } \ No newline at end of file From 472dfd7b880f3402e4a9728d7f8de55d6b3334be Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 17 Feb 2019 15:38:34 +0300 Subject: [PATCH 69/70] Buffer streaming next iteration --- .../kmath/transform/Transformations.kt | 23 ++ .../kmath/sequential/BufferStreaming.kt | 214 +++++------------- .../scientifik/kmath/sequential/Streaming.kt | 35 +-- 3 files changed, 85 insertions(+), 187 deletions(-) diff --git a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt index a4db4b1bd..17907adbe 100644 --- a/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt +++ b/kmath-commons/src/main/kotlin/scientifik/kmath/transform/Transformations.kt @@ -2,6 +2,9 @@ package scientifik.kmath.transform import org.apache.commons.math3.transform.* import scientifik.kmath.operations.Complex +import scientifik.kmath.sequential.Processor +import scientifik.kmath.sequential.Producer +import scientifik.kmath.sequential.map import scientifik.kmath.structures.* @@ -60,4 +63,24 @@ object Transformations { ): BufferTransform = { FastHadamardTransformer().transform(it.asArray(), direction).asBuffer() } +} + +/** + * Process given [Producer] with commons-math fft transformation + */ +fun Producer>.FFT( + normalization: DftNormalization = DftNormalization.STANDARD, + direction: TransformType = TransformType.FORWARD +): Processor, Buffer> { + val transform = Transformations.fourier(normalization, direction) + return map { transform(it) } +} + +@JvmName("realFFT") +fun Producer>.FFT( + normalization: DftNormalization = DftNormalization.STANDARD, + direction: TransformType = TransformType.FORWARD +): Processor, Buffer> { + val transform = Transformations.realFourier(normalization, direction) + return map { transform(it) } } \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt index 9ab23fbb4..ad9e4f259 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt @@ -1,178 +1,78 @@ package scientifik.kmath.sequential -import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.produce import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import scientifik.kmath.structures.Buffer -import scientifik.kmath.structures.asBuffer -import scientifik.kmath.structures.asSequence - -fun Buffer.asChannel(scope: CoroutineScope): ReceiveChannel = scope.produce { - for (i in (0 until size)) { - send(get(i)) - } -} - - -interface BufferProducer : Producer { - suspend fun receiveBuffer(): Buffer -} - -interface BufferConsumer : Consumer { - suspend fun sendBuffer(buffer: Buffer) -} - -abstract class AbstractBufferProducer(scope: CoroutineScope) : AbstractProducer(scope), BufferProducer { - - override fun connectOutput(consumer: Consumer) { - if (consumer is BufferConsumer) { - launch { - while (this.isActive) { - consumer.sendBuffer(receiveBuffer()) - } - } - } else { - super.connectOutput(consumer) - } - } -} - -abstract class AbstractBufferConsumer(scope: CoroutineScope) : AbstractConsumer(scope), BufferConsumer { - override fun connectInput(producer: Producer) { - if (producer is BufferProducer) { - launch { - while (isActive) { - sendBuffer(producer.receiveBuffer()) - } - } - } else { - super.connectInput(producer) - } - } -} - -abstract class AbstractBufferProcessor(scope: CoroutineScope) : - AbstractProcessor(scope), - BufferProducer, - BufferConsumer { - - override fun connectOutput(consumer: Consumer) { - if (consumer is BufferConsumer) { - launch { - while (this.isActive) { - consumer.sendBuffer(receiveBuffer()) - } - } - } else { - super.connectOutput(consumer) - } - } - - override fun connectInput(producer: Producer) { - if (producer is BufferProducer) { - launch { - while (isActive) { - sendBuffer(producer.receiveBuffer()) - } - } - } else { - super.connectInput(producer) - } - } -} +import scientifik.kmath.structures.BufferFactory /** - * The basic generic buffer producer supporting both arrays and element-by-element simultaneously + * A processor that collects incoming elements into fixed size buffers */ -class BasicBufferProducer( +class JoinProcessor( scope: CoroutineScope, - capacity: Int = Channel.UNLIMITED, - block: suspend ProducerScope>.() -> Unit -) : AbstractBufferProducer(scope) { + bufferSize: Int, + bufferFactory: BufferFactory = Buffer.Companion::boxing +) : AbstractProcessor>(scope) { + private val input = Channel(bufferSize) - private val currentArray = atomic?>(null) - private val channel: ReceiveChannel> by lazy { produce(capacity = capacity, block = block) } - private val cachingChannel by lazy { - channel.map { - it.also { buffer -> currentArray.lazySet(buffer.asChannel(this)) } - } - } - - private fun DoubleArray.asChannel() = produce { - for (value in this@asChannel) { - send(value) - } - } - - override suspend fun receiveBuffer(): Buffer = cachingChannel.receive() - - override suspend fun receive(): T = (currentArray.value ?: cachingChannel.receive().asChannel(this)).receive() -} - - -class BufferReducer( - scope: CoroutineScope, - initialState: S, - val fold: suspend (S, Buffer) -> S -) : AbstractBufferConsumer(scope) { - - var state: S = initialState - private set - - override suspend fun sendBuffer(buffer: Buffer) { - state = fold(state, buffer) - } - - override suspend fun send(value: T) = sendBuffer(arrayOf(value).asBuffer()) -} - -/** - * Convert a [Buffer] to single element producer, splitting it in chunks if necessary - */ -fun Buffer.produce(scope: CoroutineScope = GlobalScope, chunkSize: Int = Int.MAX_VALUE) = - if (size < chunkSize) { - BasicBufferProducer(scope) { send(this@produce) } - } else { - BasicBufferProducer(scope) { - //TODO optimize this! - asSequence().chunked(chunkSize).forEach { - send(it.asBuffer()) + private val output = produce(coroutineContext) { + val list = ArrayList(bufferSize) + while (isActive) { + list.clear() + repeat(bufferSize) { + list.add(input.receive()) } + val buffer = bufferFactory(bufferSize) { list[it] } + send(buffer) } } - -/** - * A buffer processor that works with buffers but could accumulate at lest [accumulate] elements from single input before processing. - * - * This class combines functions from [ChunkProcessor] and single buffer processor - */ -class AccumulatingBufferProcessor( - scope: CoroutineScope, - val accumulate: Int, - val process: suspend (Buffer) -> Buffer -) : - AbstractBufferProcessor(scope) { - - private val inputChannel = Channel>() - private val outputChannel = inputChannel.map { process(it) } - - override suspend fun receive(): R { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } + override suspend fun receive(): Buffer = output.receive() override suspend fun send(value: T) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + input.send(value) + } +} + +/** + * A processor that splits incoming buffers into individual elements + */ +class SplitProcessor(scope: CoroutineScope) : AbstractProcessor, T>(scope) { + + private val input = Channel>() + + private val mutex = Mutex() + + private var currentBuffer: Buffer? = null + + private var pos = 0 + + + override suspend fun receive(): T { + mutex.withLock { + while (currentBuffer == null || pos == currentBuffer!!.size) { + currentBuffer = input.receive() + pos = 0 + } + return currentBuffer!![pos].also { pos++ } + } } - override suspend fun receiveBuffer(): Buffer = outputChannel.receive() - - override suspend fun sendBuffer(buffer: Buffer) { - inputChannel.send(buffer) + override suspend fun send(value: Buffer) { + input.send(value) } +} + +fun Producer.chunked(chunkSize: Int, bufferFactory: BufferFactory) = + JoinProcessor(this, chunkSize, bufferFactory).also { connect(it) } + +inline fun Producer.chunked(chunkSize: Int) = + JoinProcessor(this, chunkSize, Buffer.Companion::auto).also { connect(it) } + + -} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt index cc4a68761..c0332d639 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/Streaming.kt @@ -170,33 +170,6 @@ class PipeProcessor( } } -/** - * A [Processor] that splits the input in fixed chunked size and transforms each chunked - */ -class ChunkProcessor( - scope: CoroutineScope, - chunkSize: Int, - process: suspend (List) -> R -) : AbstractProcessor(scope) { - - private val input = Channel(chunkSize) - - private val chunked = produce>(coroutineContext) { - val list = ArrayList(chunkSize) - repeat(chunkSize) { - list.add(input.receive()) - } - send(list) - } - - private val output: ReceiveChannel = chunked.map(coroutineContext, process) - - override suspend fun receive(): R = output.receive() - - override suspend fun send(value: T) { - input.send(value) - } -} /** * A moving window [Processor] with circular buffer @@ -276,6 +249,9 @@ fun ReceiveChannel.produce(scope: CoroutineScope = GlobalScope) = fun > Producer.consumer(consumerFactory: () -> C): C = consumerFactory().also { connect(it) } +fun Producer.map(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = + PipeProcessor(this, capacity, process).also { connect(it) } + /** * Create a reducer and connect this producer to reducer */ @@ -294,7 +270,6 @@ fun > Producer.process(processorBuilder: () -> P): fun Producer.process(capacity: Int = Channel.RENDEZVOUS, process: suspend (T) -> R) = PipeProcessor(this, capacity, process).also { connect(it) } -fun Producer.chunked(chunkSize: Int, process: suspend (List) -> R) = - ChunkProcessor(this, chunkSize, process).also { connect(it) } -fun Producer.chunked(chunkSize: Int) = chunked(chunkSize) { it } +fun Producer.windowed(window: Int, process: suspend (Buffer) -> R) = + WindowedProcessor(this, window, process).also { connect(it) } \ No newline at end of file From 25e8e03494898428b34a229c766d142f8d943a0f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 20 Feb 2019 12:54:39 +0300 Subject: [PATCH 70/70] Memory on JVM and JS --- benchmarks/build.gradle | 1 + .../kmath/structures/NDFieldBenchmark.kt | 2 +- .../kmath/structures/ComplexNDBenchmark.kt | 6 +- .../kmath/structures/NDFieldBenchmark.kt | 4 +- build.gradle.kts | 2 +- kmath-core/build.gradle.kts | 1 + .../scientifik/kmath/operations/Complex.kt | 28 +++++- .../kmath/structures/ComplexNDField.kt | 5 +- .../scientifik/kmath/structures/NDAlgebra.kt | 7 +- .../scientifik/kmath/structures/NDElement.kt | 4 +- .../kmath/structures/ObjectBuffer.kt | 48 ++++++++++ .../kmath/structures/ComplexBufferSpecTest.kt | 5 +- .../kmath/structures/NumberNDFieldTest.kt | 2 +- .../scientifik/kmath/structures/BufferSpec.kt | 44 --------- .../kmath/structures/ComplexBufferSpec.kt | 46 --------- .../kmath/structures/ObjectBuffer.kt | 39 -------- .../kmath/structures/RealBufferSpec.kt | 28 ------ kmath-memory/build.gradle.kts | 50 ++++++++++ .../kotlin/scientifik/memory/Memory.kt | 71 ++++++++++++++ .../kotlin/scientifik/memory/MemorySpec.kt | 34 +++++++ .../scientifik/memory/DataViewMemory.kt | 91 ++++++++++++++++++ .../scientifik/memory/ByteBufferMemory.kt | 93 +++++++++++++++++++ .../kmath/sequential/BufferStreaming.kt | 4 + settings.gradle.kts | 3 +- 24 files changed, 441 insertions(+), 177 deletions(-) rename kmath-core/src/{jvmMain => commonMain}/kotlin/scientifik/kmath/structures/ComplexNDField.kt (97%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt rename kmath-core/src/{jvmTest => commonTest}/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt (61%) delete mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt delete mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt delete mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt delete mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt create mode 100644 kmath-memory/build.gradle.kts create mode 100644 kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt create mode 100644 kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt create mode 100644 kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt create mode 100644 kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index c57d84fe4..989590397 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation project(":kmath-commons") implementation project(":kmath-koma") implementation group: "com.kyonifer", name:"koma-core-ejml", version: "0.12" + implementation "org.jetbrains.kotlinx:kotlinx-io-jvm:0.1.5" //compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" //jmh project(':kmath-core') } diff --git a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index ad180b727..0cef4bdb4 100644 --- a/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -61,7 +61,7 @@ open class NDFieldBenchmark { val dim = 1000 val n = 100 - val bufferedField = NDField.auto(intArrayOf(dim, dim), RealField) + val bufferedField = NDField.auto(RealField, intArrayOf(dim, dim)) val specializedField = NDField.real(intArrayOf(dim, dim)) val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) val lazyNDField = NDField.lazy(intArrayOf(dim, dim), RealField) diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt index 02f19cd55..46b1b56dd 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/ComplexNDBenchmark.kt @@ -1,15 +1,13 @@ package scientifik.kmath.structures -import scientifik.kmath.operations.Complex -import scientifik.kmath.operations.toComplex import kotlin.system.measureTimeMillis fun main() { val dim = 1000 val n = 1000 - val realField = NDField.real(intArrayOf(dim, dim)) - val complexField = NDField.complex(intArrayOf(dim, dim)) + val realField = NDField.real(dim, dim) + val complexField = NDField.complex(dim, dim) val realTime = measureTimeMillis { diff --git a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt index 170320d22..53a23c2bc 100644 --- a/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt +++ b/benchmarks/src/main/kotlin/scientifik/kmath/structures/NDFieldBenchmark.kt @@ -9,9 +9,9 @@ fun main(args: Array) { val n = 1000 // automatically build context most suited for given type. - val autoField = NDField.auto(intArrayOf(dim, dim), RealField) + val autoField = NDField.auto(RealField, dim, dim) // specialized nd-field for Double. It works as generic Double field as well - val specializedField = NDField.real(intArrayOf(dim, dim)) + val specializedField = NDField.real(dim, dim) //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.buffered(intArrayOf(dim, dim), RealField) diff --git a/build.gradle.kts b/build.gradle.kts index 702373b1e..dadb99422 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension buildscript { val kotlinVersion: String by rootProject.extra("1.3.21") - val ioVersion: String by rootProject.extra("0.1.4") + val ioVersion: String by rootProject.extra("0.1.5") val coroutinesVersion: String by rootProject.extra("1.1.1") val atomicfuVersion: String by rootProject.extra("0.12.1") diff --git a/kmath-core/build.gradle.kts b/kmath-core/build.gradle.kts index f6d10875c..bd125b373 100644 --- a/kmath-core/build.gradle.kts +++ b/kmath-core/build.gradle.kts @@ -12,6 +12,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { + api(project(":kmath-memory")) api(kotlin("stdlib")) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index 3003ce1bc..4b4002cbb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -1,5 +1,11 @@ package scientifik.kmath.operations +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.MutableBuffer +import scientifik.kmath.structures.ObjectBuffer +import scientifik.memory.MemoryReader +import scientifik.memory.MemorySpec +import scientifik.memory.MemoryWriter import kotlin.math.* /** @@ -67,7 +73,25 @@ data class Complex(val re: Double, val im: Double) : FieldElement { + override val objectSize: Int = 16 + + override fun MemoryReader.read(offset: Int): Complex = + Complex(readDouble(offset), readDouble(offset + 8)) + + override fun MemoryWriter.write(offset: Int, value: Complex) { + writeDouble(offset, value.re) + writeDouble(offset + 8, value.im) + } + } } -fun Double.toComplex() = Complex(this, 0.0) \ No newline at end of file +fun Double.toComplex() = Complex(this, 0.0) + +fun Buffer.Companion.complex(size: Int, init: (Int) -> Complex): Buffer { + return ObjectBuffer.create(Complex, size, init) +} + +fun MutableBuffer.Companion.complex(size: Int, init: (Int) -> Complex): Buffer { + return ObjectBuffer.create(Complex, size, init) +} diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt similarity index 97% rename from kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt index a8b31af3e..6444f667d 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ComplexNDField.kt @@ -3,6 +3,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Complex import scientifik.kmath.operations.ComplexField import scientifik.kmath.operations.FieldElement +import scientifik.kmath.operations.complex typealias ComplexNDElement = BufferedNDFieldElement @@ -128,4 +129,6 @@ operator fun ComplexNDElement.plus(arg: Double) = map { it + arg } operator fun ComplexNDElement.minus(arg: Double) = - map { it - arg } \ No newline at end of file + map { it - arg } + +fun NDField.Companion.complex(vararg shape: Int) = ComplexNDField(shape) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt index 7ea768c63..887a480bb 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDAlgebra.kt @@ -3,6 +3,7 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space +import kotlin.jvm.JvmName /** @@ -118,7 +119,7 @@ interface NDField, N : NDStructure> : Field, NDRing, N : NDStructure> : Field, NDRing> auto(shape: IntArray, field: F): BufferedNDField = + inline fun > auto(field: F, vararg shape: Int): BufferedNDField = when { - T::class == Double::class -> real(shape) as BufferedNDField + T::class == Double::class -> real(*shape) as BufferedNDField else -> BoxingNDField(shape, field, Buffer.Companion::auto) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt index c97f959f3..59cf98df3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDElement.kt @@ -24,7 +24,7 @@ interface NDElement> : NDStructure { * Create a optimized NDArray of doubles */ fun real(shape: IntArray, initializer: RealField.(IntArray) -> Double = { 0.0 }) = - NDField.real(shape).produce(initializer) + NDField.real(*shape).produce(initializer) fun real1D(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }) = @@ -55,7 +55,7 @@ interface NDElement> : NDStructure { field: F, noinline initializer: F.(IntArray) -> T ): BufferedNDFieldElement { - val ndField = NDField.auto(shape, field) + val ndField = NDField.auto(field, *shape) return BufferedNDFieldElement(ndField, ndField.produce(initializer).buffer) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt new file mode 100644 index 000000000..badf8a483 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.structures + +import scientifik.memory.* + +/** + * A non-boxing buffer based on [ByteBuffer] storage + */ +open class ObjectBuffer(protected val memory: Memory, protected val spec: MemorySpec) : Buffer { + + override val size: Int get() = memory.size / spec.objectSize + + private val reader = memory.reader() + + override fun get(index: Int): T = reader.read(spec, spec.objectSize * index) + + override fun iterator(): Iterator = (0 until size).asSequence().map { get(it) }.iterator() + + + companion object { + fun create(spec: MemorySpec, size: Int) = + ObjectBuffer(Memory.allocate(size * spec.objectSize), spec) + + inline fun create( + spec: MemorySpec, + size: Int, + crossinline initializer: (Int) -> T + ): ObjectBuffer = + MutableObjectBuffer(Memory.allocate(size * spec.objectSize), spec).also { buffer -> + (0 until size).forEach { + buffer[it] = initializer(it) + } + } + } +} + +class MutableObjectBuffer(memory: Memory, spec: MemorySpec) : ObjectBuffer(memory, spec), + MutableBuffer { + + private val writer = memory.writer() + + override fun set(index: Int, value: T) = writer.write(spec, spec.objectSize * index, value) + + override fun copy(): MutableBuffer = MutableObjectBuffer(memory.copy(), spec) + + companion object { + + } +} \ No newline at end of file diff --git a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt similarity index 61% rename from kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt index a07f2167e..454683dac 100644 --- a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt @@ -1,13 +1,14 @@ package scientifik.kmath.structures -import org.junit.Test import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.complex +import kotlin.test.Test import kotlin.test.assertEquals class ComplexBufferSpecTest { @Test fun testComplexBuffer() { - val buffer = MutableBuffer.complex(20){Complex(it.toDouble(), -it.toDouble())} + val buffer = Buffer.complex(20) { Complex(it.toDouble(), -it.toDouble()) } assertEquals(Complex(5.0, -5.0), buffer[5]) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 3c4160329..60f1f9979 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -63,7 +63,7 @@ class NumberNDFieldTest { @Test fun testInternalContext() { - NDField.real(array1.shape).run { + NDField.real(*array1.shape).run { with(L2Norm) { 1 + norm(array1) + exp(array2) } diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt deleted file mode 100644 index ce87a1298..000000000 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt +++ /dev/null @@ -1,44 +0,0 @@ -package scientifik.kmath.structures - -import java.nio.ByteBuffer - - -/** - * A specification for serialization and deserialization objects to buffer (at current buffer position) - */ -interface BufferSpec { - /** - * Read an object from buffer in current position - */ - fun ByteBuffer.readObject(): T - - /** - * Write object to [ByteBuffer] in current buffer position - */ - fun ByteBuffer.writeObject(value: T) -} - -/** - * A [BufferSpec] with fixed unit size. Allows storage of any object without boxing. - */ -interface FixedSizeBufferSpec : BufferSpec { - val unitSize: Int - - - /** - * Read an object from buffer in given index (not buffer position - */ - fun ByteBuffer.readObject(index: Int): T { - position(index * unitSize) - return readObject() - } - - - /** - * Put an object in given index - */ - fun ByteBuffer.writeObject(index: Int, obj: T) { - position(index * unitSize) - writeObject(obj) - } -} diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt deleted file mode 100644 index 34f21cf75..000000000 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt +++ /dev/null @@ -1,46 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Complex -import scientifik.kmath.operations.ComplexField -import java.nio.ByteBuffer - -/** - * A serialization specification for complex numbers - */ -object ComplexBufferSpec : FixedSizeBufferSpec { - - override val unitSize: Int = 16 - - override fun ByteBuffer.readObject(): Complex { - val re = double - val im = double - return Complex(re, im) - } - - override fun ByteBuffer.writeObject(value: Complex) { - putDouble(value.re) - putDouble(value.im) - } -} - -/** - * Create a read-only/mutable buffer which ignores boxing - */ -fun Buffer.Companion.complex(size: Int): Buffer = - ObjectBuffer.create(ComplexBufferSpec, size) - -inline fun Buffer.Companion.complex(size: Int, crossinline initializer: (Int) -> Complex): Buffer = - ObjectBuffer.create(ComplexBufferSpec, size, initializer) - -fun MutableBuffer.Companion.complex(size: Int) = - ObjectBuffer.create(ComplexBufferSpec, size) - -inline fun MutableBuffer.Companion.complex(size: Int, crossinline initializer: (Int) -> Complex) = - ObjectBuffer.create(ComplexBufferSpec, size, initializer) - -fun NDField.Companion.complex(shape: IntArray) = ComplexNDField(shape) - -fun NDElement.Companion.complex(shape: IntArray, initializer: ComplexField.(IntArray) -> Complex) = - NDField.complex(shape).produce(initializer) - - diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt deleted file mode 100644 index e95c54b2c..000000000 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt +++ /dev/null @@ -1,39 +0,0 @@ -package scientifik.kmath.structures - -import java.nio.ByteBuffer - -/** - * A non-boxing buffer based on [ByteBuffer] storage - */ -class ObjectBuffer(private val buffer: ByteBuffer, private val spec: FixedSizeBufferSpec) : - MutableBuffer { - override val size: Int - get() = buffer.limit() / spec.unitSize - - override fun get(index: Int): T = with(spec) { buffer.readObject(index) } - - override fun iterator(): Iterator = (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 { - val dup = buffer.duplicate() - val copy = ByteBuffer.allocate(dup.capacity()) - dup.rewind() - copy.put(dup) - copy.flip() - return ObjectBuffer(copy, spec) - } - - companion object { - fun create(spec: FixedSizeBufferSpec, size: Int) = - ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec) - - inline fun create(spec: FixedSizeBufferSpec, size: Int, crossinline initializer: (Int) -> T) = - ObjectBuffer(ByteBuffer.allocate(size * spec.unitSize), spec).also { buffer -> - (0 until size).forEach { - buffer[it] = initializer(it) - } - } - } -} \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt deleted file mode 100644 index fdd9e92b7..000000000 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt +++ /dev/null @@ -1,28 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Real -import java.nio.ByteBuffer - -object RealBufferSpec : FixedSizeBufferSpec { - override val unitSize: Int = 8 - - override fun ByteBuffer.readObject(): Real = Real(double) - - override fun ByteBuffer.writeObject(value: Real) { - putDouble(value.value) - } -} - -object DoubleBufferSpec : FixedSizeBufferSpec { - override val unitSize: Int = 8 - - override fun ByteBuffer.readObject() = double - - override fun ByteBuffer.writeObject(value: Double) { - putDouble(value) - } - -} - -fun Double.Companion.createBuffer(size: Int) = ObjectBuffer.create(DoubleBufferSpec, size) -fun Real.Companion.createBuffer(size: Int) = ObjectBuffer.create(RealBufferSpec, size) \ No newline at end of file diff --git a/kmath-memory/build.gradle.kts b/kmath-memory/build.gradle.kts new file mode 100644 index 000000000..f6d10875c --- /dev/null +++ b/kmath-memory/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + kotlin("multiplatform") +} + +val ioVersion: String by rootProject.extra + + +kotlin { + jvm() + js() + + sourceSets { + val commonMain by getting { + dependencies { + api(kotlin("stdlib")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + val jvmMain by getting { + dependencies { + api(kotlin("stdlib-jdk8")) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation(kotlin("test-junit")) + } + } + val jsMain by getting { + dependencies { + api(kotlin("stdlib-js")) + } + } + val jsTest by getting { + dependencies { + implementation(kotlin("test-js")) + } + } +// mingwMain { +// } +// mingwTest { +// } + } +} \ No newline at end of file diff --git a/kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt b/kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt new file mode 100644 index 000000000..f9f938dcc --- /dev/null +++ b/kmath-memory/src/commonMain/kotlin/scientifik/memory/Memory.kt @@ -0,0 +1,71 @@ +package scientifik.memory + +interface Memory { + val size: Int + + /** + * Get a projection of this memory (it reflects the changes in the parent memory block) + */ + fun view(offset: Int, length: Int): Memory + + /** + * Create a copy of this memory, which does not know anything about this memory + */ + fun copy(): Memory + + /** + * Create and possibly register a new reader + */ + fun reader(): MemoryReader + + fun writer(): MemoryWriter + + companion object { + + } +} + +interface MemoryReader { + val memory: Memory + + fun readDouble(offset: Int): Double + fun readFloat(offset: Int): Float + fun readByte(offset: Int): Byte + fun readShort(offset: Int): Short + fun readInt(offset: Int): Int + fun readLong(offset: Int): Long + + fun release() +} + +/** + * Use the memory for read then release the reader + */ +inline fun Memory.read(block: MemoryReader.() -> Unit) { + reader().apply(block).apply { release() } +} + +interface MemoryWriter { + val memory: Memory + + fun writeDouble(offset: Int, value: Double) + fun writeFloat(offset: Int, value: Float) + fun writeByte(offset: Int, value: Byte) + fun writeShort(offset: Int, value: Short) + fun writeInt(offset: Int, value: Int) + fun writeLong(offset: Int, value: Long) + + fun release() +} + +/** + * Use the memory for write then release the writer + */ +inline fun Memory.write(block: MemoryWriter.() -> Unit) { + writer().apply(block).apply { release() } +} + +/** + * Allocate the most effective platform-specific memory + */ +expect fun Memory.Companion.allocate(length: Int): Memory diff --git a/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt b/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt new file mode 100644 index 000000000..a3166ecd7 --- /dev/null +++ b/kmath-memory/src/commonMain/kotlin/scientifik/memory/MemorySpec.kt @@ -0,0 +1,34 @@ +package scientifik.memory + +/** + * A specification to read or write custom objects with fixed size in bytes + */ +interface MemorySpec { + /** + * Size of [T] in bytes after serialization + */ + val objectSize: Int + + fun MemoryReader.read(offset: Int): T + fun MemoryWriter.write(offset: Int, value: T) +} + +fun MemoryReader.read(spec: MemorySpec, offset: Int): T = spec.run { read(offset) } +fun MemoryWriter.write(spec: MemorySpec, offset: Int, value: T) = spec.run { write(offset, value) } + +inline fun MemoryReader.readArray(spec: MemorySpec, offset: Int, size: Int) = + Array(size) { i -> + spec.run { + read(offset + i * objectSize) + } + } + +fun MemoryWriter.writeArray(spec: MemorySpec, offset: Int, array: Array) { + spec.run { + for (i in 0 until array.size) { + write(offset + i * objectSize, array[i]) + } + } +} + +//TODO It is possible to add elastic MemorySpec with unknown object size \ No newline at end of file diff --git a/kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt b/kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt new file mode 100644 index 000000000..843464ab9 --- /dev/null +++ b/kmath-memory/src/jsMain/kotlin/scientifik/memory/DataViewMemory.kt @@ -0,0 +1,91 @@ +package scientifik.memory + +import org.khronos.webgl.ArrayBuffer +import org.khronos.webgl.DataView + +/** + * Allocate the most effective platform-specific memory + */ +actual fun Memory.Companion.allocate(length: Int): Memory { + val buffer = ArrayBuffer(length) + return DataViewMemory(DataView(buffer, 0, length)) +} + +class DataViewMemory(val view: DataView) : Memory { + + override val size: Int get() = view.byteLength + + override fun view(offset: Int, length: Int): Memory { + require(offset >= 0) { "offset shouldn't be negative: $offset" } + require(length >= 0) { "length shouldn't be negative: $length" } + if (offset + length > size) { + throw IndexOutOfBoundsException("offset + length > size: $offset + $length > $size") + } + return DataViewMemory(DataView(view.buffer, view.byteOffset + offset, length)) + } + + + override fun copy(): Memory { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + private val reader = object : MemoryReader { + override val memory: Memory get() = this@DataViewMemory + + override fun readDouble(offset: Int): Double = view.getFloat64(offset, false) + + override fun readFloat(offset: Int): Float = view.getFloat32(offset, false) + + override fun readByte(offset: Int): Byte = view.getInt8(offset) + + override fun readShort(offset: Int): Short = view.getInt16(offset, false) + + override fun readInt(offset: Int): Int = view.getInt32(offset, false) + + override fun readLong(offset: Int): Long = (view.getInt32(offset, false).toLong() shl 32) or + view.getInt32(offset + 4, false).toLong() + + override fun release() { + // does nothing on JS because of GC + } + } + + override fun reader(): MemoryReader = reader + + private val writer = object : MemoryWriter { + override val memory: Memory get() = this@DataViewMemory + + override fun writeDouble(offset: Int, value: Double) { + view.setFloat64(offset, value, false) + } + + override fun writeFloat(offset: Int, value: Float) { + view.setFloat32(offset, value, false) + } + + override fun writeByte(offset: Int, value: Byte) { + view.setInt8(offset, value) + } + + override fun writeShort(offset: Int, value: Short) { + view.setUint16(offset, value, false) + } + + override fun writeInt(offset: Int, value: Int) { + view.setInt32(offset, value, false) + } + + override fun writeLong(offset: Int, value: Long) { + view.setInt32(offset, (value shr 32).toInt(), littleEndian = false) + view.setInt32(offset + 4, (value and 0xffffffffL).toInt(), littleEndian = false) + } + + override fun release() { + //does nothing on JS + } + + } + + override fun writer(): MemoryWriter = writer + +} \ No newline at end of file diff --git a/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt b/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt new file mode 100644 index 000000000..30a87228a --- /dev/null +++ b/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt @@ -0,0 +1,93 @@ +package scientifik.memory + +import java.nio.ByteBuffer + + +/** + * Allocate the most effective platform-specific memory + */ +actual fun Memory.Companion.allocate(length: Int): Memory { + val buffer = ByteBuffer.allocate(length) + return ByteBufferMemory(buffer) +} + +class ByteBufferMemory( + val buffer: ByteBuffer, + val startOffset: Int = 0, + override val size: Int = buffer.limit() +) : Memory { + + + @Suppress("NOTHING_TO_INLINE") + private inline fun position(o: Int): Int = startOffset + o + + override fun view(offset: Int, length: Int): Memory { + if (offset + length > size) error("Selecting a Memory view outside of memory range") + return ByteBufferMemory(buffer, position(offset), length) + } + + override fun copy(): Memory { + val copy = ByteBuffer.allocate(buffer.capacity()) + buffer.rewind() + copy.put(buffer) + copy.flip() + return ByteBufferMemory(copy) + + } + + private val reader = object : MemoryReader { + override val memory: Memory get() = this@ByteBufferMemory + + override fun readDouble(offset: Int) = buffer.getDouble(position(offset)) + + override fun readFloat(offset: Int) = buffer.getFloat(position(offset)) + + override fun readByte(offset: Int) = buffer.get(position(offset)) + + override fun readShort(offset: Int) = buffer.getShort(position(offset)) + + override fun readInt(offset: Int) = buffer.getInt(position(offset)) + + override fun readLong(offset: Int) = buffer.getLong(position(offset)) + + override fun release() { + //does nothing on JVM + } + } + + override fun reader(): MemoryReader = reader + + private val writer = object : MemoryWriter { + override val memory: Memory get() = this@ByteBufferMemory + + override fun writeDouble(offset: Int, value: Double) { + buffer.putDouble(position(offset), value) + } + + override fun writeFloat(offset: Int, value: Float) { + buffer.putFloat(position(offset), value) + } + + override fun writeByte(offset: Int, value: Byte) { + buffer.put(position(offset), value) + } + + override fun writeShort(offset: Int, value: Short) { + buffer.putShort(position(offset), value) + } + + override fun writeInt(offset: Int, value: Int) { + buffer.putInt(position(offset), value) + } + + override fun writeLong(offset: Int, value: Long) { + buffer.putLong(position(offset), value) + } + + override fun release() { + //does nothing on JVM + } + } + + override fun writer(): MemoryWriter = writer +} \ No newline at end of file diff --git a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt index ad9e4f259..b98f48186 100644 --- a/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt +++ b/kmath-sequential/src/commonMain/kotlin/scientifik/kmath/sequential/BufferStreaming.kt @@ -1,6 +1,7 @@ package scientifik.kmath.sequential import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.produce import kotlinx.coroutines.isActive @@ -12,6 +13,7 @@ import scientifik.kmath.structures.BufferFactory /** * A processor that collects incoming elements into fixed size buffers */ +@ExperimentalCoroutinesApi class JoinProcessor( scope: CoroutineScope, bufferSize: Int, @@ -68,9 +70,11 @@ class SplitProcessor(scope: CoroutineScope) : AbstractProcessor, T> } } +@ExperimentalCoroutinesApi fun Producer.chunked(chunkSize: Int, bufferFactory: BufferFactory) = JoinProcessor(this, chunkSize, bufferFactory).also { connect(it) } +@ExperimentalCoroutinesApi inline fun Producer.chunked(chunkSize: Int) = JoinProcessor(this, chunkSize, Buffer.Companion::auto).also { connect(it) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 7d8731532..8684de3ee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,10 +15,11 @@ pluginManagement { } } -//enableFeaturePreview("GRADLE_METADATA") +enableFeaturePreview("GRADLE_METADATA") rootProject.name = "kmath" include( + ":kmath-memory", ":kmath-core", // ":kmath-io", ":kmath-coroutines",