From f950313f418d5ae17bd81ed24a89819ffef0e1a2 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 21 Oct 2018 18:41:39 +0300 Subject: [PATCH 1/9] NDStructures redone --- .gitignore | 2 +- build.gradle | 2 +- kmath-core/build.gradle | 9 +- .../kmath/linear/LUDecomposition.kt | 65 +------ .../scientifik/kmath/linear/LinearAlgrebra.kt | 21 ++- .../kmath/structures/BufferNDField.kt | 112 ----------- .../scientifik/kmath/structures/Buffers.kt | 74 ++++++++ .../scientifik/kmath/structures/NDArrays.kt | 72 -------- .../structures/{NDArray.kt => NDField.kt} | 145 +++++++-------- .../kmath/structures/NDStructure.kt | 174 ++++++++++++++++++ .../kmath/linear/ArrayMatrixTest.kt | 10 +- ...leNDFieldTest.kt => GenericNDFieldTest.kt} | 4 +- .../kmath/structures/ArrayBenchmark.kt | 3 +- .../scientifik/kmath/structures/_NDArrays.kt | 8 - .../scientifik/kmath/structures/_NDArrays.kt | 26 --- 15 files changed, 351 insertions(+), 376 deletions(-) delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt delete mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt rename kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/{NDArray.kt => NDField.kt} (51%) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt rename kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/{SimpleNDFieldTest.kt => GenericNDFieldTest.kt} (70%) delete mode 100644 kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt delete mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt diff --git a/.gitignore b/.gitignore index 5b55fc854..d07c3c850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .gradle -/build/ +**/build/ /.idea/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) diff --git a/build.gradle b/build.gradle index 04700f377..14ecae465 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.0-rc-146' + ext.kotlin_version = '1.3.0-rc-190' repositories { jcenter() diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index 8e0e6ca2e..948dc87bf 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -1,6 +1,6 @@ plugins { id 'kotlin-multiplatform'// version '1.3.0-rc-116' - id "me.champeau.gradle.jmh" version "0.4.5" + id "me.champeau.gradle.jmh" version "0.4.7" } repositories { @@ -8,9 +8,14 @@ repositories { mavenCentral() } +dependencies{ + jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} + kotlin { targets { - fromPreset(presets.jvm, 'jvm') + fromPreset(presets.jvmWithJava, 'jvm') + //fromPreset(presets.jvm, 'jvm') fromPreset(presets.js, 'js') // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 // For Linux, preset should be changed to e.g. presets.linuxX64 diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt index e51d574f8..0b71405c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -1,18 +1,19 @@ package scientifik.kmath.linear -import scientifik.kmath.structures.MutableNDArray -import scientifik.kmath.structures.NDArray -import scientifik.kmath.structures.NDArrays +import scientifik.kmath.structures.MutableNDStructure +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.genericNdStructure +import scientifik.kmath.structures.get import kotlin.math.absoluteValue /** - * Implementation copier from Apache common-maths + * Implementation based on Apache common-maths LU-decomposition */ abstract class LUDecomposition>(val matrix: Matrix) { private val field get() = matrix.context.field /** Entries of LU decomposition. */ - internal val lu: NDArray + internal val lu: NDStructure /** Pivot permutation associated with LU decomposition. */ internal val pivot: IntArray /** Parity of the permutation associated with the LU decomposition. */ @@ -85,26 +86,18 @@ abstract class LUDecomposition>(val matrix: Matrix) { } } -// /** -// * Get a solver for finding the A X = B solution in exact linear -// * sense. -// * @return a solver -// */ -// val solver: DecompositionSolver -// get() = Solver(lu, pivot, singular) - /** * In-place transformation for [MutableNDArray], using given transformation for each element */ - operator fun MutableNDArray.set(i: Int, j: Int, value: T) { - this[listOf(i, j)] = value + operator fun MutableNDStructure.set(i: Int, j: Int, value: T) { + this[intArrayOf(i, j)] = value } abstract fun isSingular(value: T): Boolean private fun abs(value: T) = if (value > matrix.context.field.zero) value else with(matrix.context.field) { -value } - private fun calculateLU(): Pair, IntArray> { + private fun calculateLU(): Pair, IntArray> { if (matrix.rows != matrix.columns) { error("LU decomposition supports only square matrices") } @@ -112,7 +105,7 @@ abstract class LUDecomposition>(val matrix: Matrix) { val m = matrix.columns val pivot = IntArray(matrix.rows) //TODO fix performance - val lu: MutableNDArray = NDArrays.createMutable(matrix.context.field, listOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } + val lu: MutableNDStructure = genericNdStructure(intArrayOf(matrix.rows, matrix.columns)) { index -> matrix[index[0], index[1]] } with(matrix.context.field) { @@ -203,44 +196,6 @@ class RealLUDecomposition(matrix: Matrix, private val singularityThresho /** Specialized solver. */ object RealLUSolver : LinearSolver { -// -// /** {@inheritDoc} */ -// override fun solve(b: RealVector): RealVector { -// val m = pivot.size -// if (b.getDimension() != m) { -// throw DimensionMismatchException(b.getDimension(), m) -// } -// if (singular) { -// throw SingularMatrixException() -// } -// -// val bp = DoubleArray(m) -// -// // Apply permutations to b -// for (row in 0 until m) { -// bp[row] = b.getEntry(pivot[row]) -// } -// -// // Solve LY = b -// for (col in 0 until m) { -// val bpCol = bp[col] -// for (i in col + 1 until m) { -// bp[i] -= bpCol * lu[i][col] -// } -// } -// -// // Solve UX = Y -// for (col in m - 1 downTo 0) { -// bp[col] /= lu[col][col] -// val bpCol = bp[col] -// for (i in 0 until col) { -// bp[i] -= bpCol * lu[i][col] -// } -// } -// -// return ArrayRealVector(bp, false) -// } - fun decompose(mat: Matrix, threshold: Double = 1e-11): RealLUDecomposition = RealLUDecomposition(mat, threshold) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt index 68961a2db..097a65bb4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -4,10 +4,10 @@ import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.structures.GenericNDField import scientifik.kmath.structures.NDArray -import scientifik.kmath.structures.NDArrays.createFactory -import scientifik.kmath.structures.NDFieldFactory -import scientifik.kmath.structures.realNDFieldFactory +import scientifik.kmath.structures.NDField +import scientifik.kmath.structures.get /** * The space for linear elements. Supports scalar product alongside with standard linear operations. @@ -193,6 +193,11 @@ interface Vector : SpaceElement, VectorSpace> { } } +typealias NDFieldFactory = (IntArray) -> NDField + +internal fun genericNDFieldFactory(field: Field): NDFieldFactory = { index -> GenericNDField(index, field) } +internal val realNDFieldFactory: NDFieldFactory = { index -> GenericNDField(index, DoubleField) } + /** * NDArray-based implementation of vector space. By default uses slow [SimpleNDField], but could be overridden with custom [NDField] factory. @@ -201,11 +206,11 @@ class ArrayMatrixSpace( rows: Int, columns: Int, field: Field, - val ndFactory: NDFieldFactory = createFactory(field) + val ndFactory: NDFieldFactory = genericNDFieldFactory(field) ) : MatrixSpace(rows, columns, field) { val ndField by lazy { - ndFactory(listOf(rows, columns)) + ndFactory(intArrayOf(rows, columns)) } override fun produce(initializer: (Int, Int) -> T): Matrix = ArrayMatrix(this, initializer) @@ -218,10 +223,10 @@ class ArrayMatrixSpace( class ArrayVectorSpace( size: Int, field: Field, - val ndFactory: NDFieldFactory = createFactory(field) + val ndFactory: NDFieldFactory = genericNDFieldFactory(field) ) : VectorSpace(size, field) { val ndField by lazy { - ndFactory(listOf(size)) + ndFactory(intArrayOf(size)) } override fun produce(initializer: (Int) -> T): Vector = ArrayVector(this, initializer) @@ -306,6 +311,6 @@ fun Vector.toMatrix(): Matrix { // //Generic vector // matrix(size, 1, context.field) { i, j -> get(i) } // } - return Matrix.of(size, 1, context.field) { i, j -> get(i) } + return Matrix.of(size, 1, context.field) { i, _ -> get(i) } } diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt deleted file mode 100644 index aab0f1b97..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/BufferNDField.kt +++ /dev/null @@ -1,112 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Field - - -/** - * A generic buffer for both primitives and objects - */ -interface Buffer { - operator fun get(index: Int): T - operator fun set(index: Int, value: T) - - /** - * A shallow copy of the buffer - */ - fun copy(): Buffer -} - -/** - * Generic implementation of NDField based on continuous buffer - */ -abstract class BufferNDField(shape: List, field: Field) : NDField(shape, field) { - - /** - * Strides for memory access - */ - private val strides: List by lazy { - ArrayList(shape.size).apply { - var current = 1 - add(1) - shape.forEach { - current *= it - add(current) - } - } - } - - protected fun offset(index: List): Int { - return index.mapIndexed { i, value -> - if (value < 0 || value >= shape[i]) { - throw RuntimeException("Index out of shape bounds: ($i,$value)") - } - value * strides[i] - }.sum() - } - - //TODO introduce a fast way to calculate index of the next element? - protected fun index(offset: Int): List { - return sequence { - var current = offset - var strideIndex = strides.size - 2 - while (strideIndex >= 0) { - yield(current / strides[strideIndex]) - current %= strides[strideIndex] - strideIndex-- - } - }.toList().reversed() - } - - private val capacity: Int - get() = strides[shape.size] - - - protected abstract fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer - - override fun produce(initializer: (List) -> T): NDArray { - val buffer = createBuffer(capacity) { initializer(index(it)) } - return BufferNDArray(this, buffer) - } - - /** - * Produce mutable NDArray instance - */ - fun produceMutable(initializer: (List) -> T): MutableNDArray { - val buffer = createBuffer(capacity) { initializer(index(it)) } - return MutableBufferedNDArray(this, buffer) - } - - - private open class BufferNDArray(override val context: BufferNDField, val data: Buffer) : NDArray { - - override fun get(vararg index: Int): T { - return data[context.offset(index.asList())] - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is BufferNDArray<*>) return false - - if (context != other.context) return false - if (data != other.data) return false - - return true - } - - override fun hashCode(): Int { - var result = context.hashCode() - result = 31 * result + data.hashCode() - return result - } - - override val self: NDArray get() = this - } - - private class MutableBufferedNDArray(context: BufferNDField, data: Buffer): BufferNDArray(context,data), MutableNDArray{ - override operator fun set(index: List, value: T){ - data[context.offset(index)] = value - } - } -} - - diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt new file mode 100644 index 000000000..70407e482 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -0,0 +1,74 @@ +package scientifik.kmath.structures + + +/** + * A generic linear buffer for both primitives and objects + */ +interface Buffer { + + val size: Int + + operator fun get(index: Int): T + + /** + * A shallow copy of the buffer + */ + fun copy(): Buffer +} + +interface MutableBuffer : Buffer { + operator fun set(index: Int, value: T) + + /** + * A shallow copy of the buffer + */ + override fun copy(): MutableBuffer +} + +inline class ListBuffer(private val list: MutableList) : MutableBuffer { + + override val size: Int + get() = list.size + + override fun get(index: Int): T = list[index] + + override fun set(index: Int, value: T) { + list[index] = value + } + + override fun copy(): MutableBuffer = ListBuffer(ArrayList(list)) +} + +class ArrayBuffer(private val array: Array) : MutableBuffer { + override val size: Int + get() = array.size + + override fun get(index: Int): T = array[index] + + override fun set(index: Int, value: T) { + array[index] = value + } + + override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) +} + +class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { + override val size: Int + get() = array.size + + override fun get(index: Int): Double = array[index] + + override fun set(index: Int, value: Double) { + array[index] = value + } + + override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) +} + +inline fun buffer(size: Int, noinline initializer: (Int) -> T): Buffer { + return ArrayBuffer(Array(size, initializer)) +} + +inline fun mutableBuffer(size: Int, noinline initializer: (Int) -> T): MutableBuffer { + return ArrayBuffer(Array(size, initializer)) +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt deleted file mode 100644 index 739e91832..000000000 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArrays.kt +++ /dev/null @@ -1,72 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.Field - -typealias NDFieldFactory = (shape: List) -> NDField - -/** - * The factory class for fast platform-dependent implementation of NDField of doubles - */ -expect val realNDFieldFactory: NDFieldFactory - - -class SimpleNDField(field: Field, shape: List) : BufferNDField(shape, field) { - override fun createBuffer(capacity: Int, initializer: (Int) -> T): Buffer { - val array = ArrayList(capacity) - (0 until capacity).forEach { - array.add(initializer(it)) - } - - return BufferOfObjects(array) - } - - private class BufferOfObjects(val array: ArrayList) : Buffer { - override fun get(index: Int): T = array[index] - - override fun set(index: Int, value: T) { - array[index] = value - } - - override fun copy(): Buffer = BufferOfObjects(ArrayList(array)) - } -} - -object NDArrays { - /** - * Create a platform-optimized NDArray of doubles - */ - fun realNDArray(shape: List, initializer: (List) -> Double = { 0.0 }): NDArray { - return realNDFieldFactory(shape).produce(initializer) - } - - fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray { - return realNDArray(listOf(dim)) { initializer(it[0]) } - } - - fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray { - return realNDArray(listOf(dim1, dim2)) { initializer(it[0], it[1]) } - } - - fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDArray { - return realNDArray(listOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } - } - - /** - * Simple boxing NDField - */ - fun createFactory(field: Field): NDFieldFactory = { shape -> SimpleNDField(field, shape) } - - /** - * Simple boxing NDArray - */ - fun create(field: Field, shape: List, initializer: (List) -> T): NDArray { - return SimpleNDField(field, shape).produce { initializer(it) } - } - - /** - * Mutable boxing NDArray - */ - fun createMutable(field: Field, shape: List, initializer: (List) -> T): MutableNDArray { - return SimpleNDField(field, shape).produceMutable { initializer(it) } - } -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArray.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt similarity index 51% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArray.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 85f60704f..7e0032597 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -1,12 +1,13 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement /** * An exception is thrown when the expected ans actual shape of NDArray differs */ -class ShapeMismatchException(val expected: List, val actual: List) : RuntimeException() +class ShapeMismatchException(val expected: IntArray, val actual: IntArray) : RuntimeException() /** * Field for n-dimensional arrays. @@ -14,13 +15,15 @@ class ShapeMismatchException(val expected: List, val actual: List) : R * @param field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ -abstract class NDField(val shape: List, val field: Field) : Field> { +abstract class NDField(val shape: IntArray, val field: Field) : Field> { + + abstract fun produceStructure(initializer: (IntArray) -> T): NDStructure /** * Create new instance of NDArray using field shape and given initializer * The producer takes list of indices as argument and returns contained value */ - abstract fun produce(initializer: (List) -> T): NDArray + fun produce(initializer: (IntArray) -> T): NDArray = NDArray(this, produceStructure(initializer)) override val zero: NDArray by lazy { produce { this.field.zero } @@ -31,7 +34,7 @@ abstract class NDField(val shape: List, val field: Field) : Field) { arrays.forEach { - if (shape != it.shape) { + if (!shape.contentEquals(it.shape)) { throw ShapeMismatchException(shape, it.shape) } } @@ -71,76 +74,44 @@ abstract class NDField(val shape: List, val field: Field) : Field : FieldElement, NDField> { /** - * The list of dimensions of this NDArray + * Reverse sum operation */ - val shape: List - get() = context.shape + operator fun T.plus(arg: NDArray): NDArray = arg + this /** - * The number of dimentsions for this array + * Reverse minus operation */ - val dimension: Int - get() = shape.size - - /** - * Get the element with given indexes. If number of indexes is different from {@link dimension}, throws exception. - */ - operator fun get(vararg index: Int): T - - operator fun get(index: List): T { - return get(*index.toIntArray()) - } - - operator fun iterator(): Iterator, T>> { - return iterateIndexes(shape).map { Pair(it, this[it]) }.iterator() + operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field) { + this@minus - value + } } /** - * Generate new NDArray, using given transformation for each element + * Reverse product operation */ - fun transform(action: (List, T) -> T): NDArray = context.produce { action(it, this[it]) } + operator fun T.times(arg: NDArray): NDArray = arg * this - companion object { - /** - * Iterate over all indexes in the nd-shape - */ - fun iterateIndexes(shape: List): Sequence> { - return if (shape.size == 1) { - (0 until shape[0]).asSequence().map { listOf(it) } - } else { - val tailShape = ArrayList(shape).apply { removeAt(0) } - val tailSequence: List> = iterateIndexes(tailShape).toList() - (0 until shape[0]).asSequence().map { firstIndex -> - //adding first element to each of provided index lists - tailSequence.map { listOf(firstIndex) + it }.asSequence() - }.flatten() - } + /** + * Reverse division operation + */ + operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field) { + this@div / value } } } /** - * In-place mutable [NDArray] + * NDStructure coupled to the context. Emulates Python ndarray */ -interface MutableNDArray : NDArray { - operator fun set(index: List, value: T) -} +data class NDArray(override val context: NDField, private val structure: NDStructure) : FieldElement, NDField>, NDStructure by structure { + override val self: NDArray + get() = this -/** - * In-place transformation for [MutableNDArray], using given transformation for each element - */ -fun MutableNDArray.transformInPlace(action: (List, T) -> T) { - for ((index, oldValue) in this) { - this[index] = action(index, oldValue) - } + fun transform(action: (IntArray, T) -> T): NDArray = context.produce { action(it, get(*it)) } } /** @@ -159,11 +130,6 @@ operator fun NDArray.plus(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse sum operation - */ -operator fun T.plus(arg: NDArray): NDArray = arg + this - /** * Subtraction operation between [NDArray] and single element */ @@ -173,15 +139,6 @@ operator fun NDArray.minus(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse minus operation - */ -operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> - with(arg.context.field) { - this@minus - value - } -} - /* prod and div */ /** @@ -193,11 +150,6 @@ operator fun NDArray.times(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse product operation - */ -operator fun T.times(arg: NDArray): NDArray = arg * this - /** * Division operation between [NDArray] and single element */ @@ -207,12 +159,41 @@ operator fun NDArray.div(arg: T): NDArray = transform { _, value -> } } -/** - * Reverse division operation - */ -operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> - with(arg.context.field) { - this@div / value - } +class GenericNDField(shape: IntArray, field: Field) : NDField(shape, field) { + override fun produceStructure(initializer: (IntArray) -> T): NDStructure = genericNdStructure(shape, initializer) } +//typealias NDFieldFactory = (IntArray)->NDField + +object NDArrays { + /** + * Create a platform-optimized NDArray of doubles + */ + fun realNDArray(shape: IntArray, initializer: (IntArray) -> Double = { 0.0 }): NDArray { + return GenericNDField(shape, DoubleField).produce(initializer) + } + + fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray { + return realNDArray(intArrayOf(dim)) { initializer(it[0]) } + } + + fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray { + return realNDArray(intArrayOf(dim1, dim2)) { initializer(it[0], it[1]) } + } + + fun real3DArray(dim1: Int, dim2: Int, dim3: Int, initializer: (Int, Int, Int) -> Double = { _, _, _ -> 0.0 }): NDArray { + return realNDArray(intArrayOf(dim1, dim2, dim3)) { initializer(it[0], it[1], it[2]) } + } + +// /** +// * Simple boxing NDField +// */ +// fun fieldFactory(field: Field): NDFieldFactory = { shape -> GenericNDField(shape, field) } + + /** + * Simple boxing NDArray + */ + fun create(field: Field, shape: IntArray, initializer: (IntArray) -> T): NDArray { + return GenericNDField(shape, field).produce { initializer(it) } + } +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt new file mode 100644 index 000000000..ba3a91487 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -0,0 +1,174 @@ +package scientifik.kmath.structures + + +interface NDStructure : Iterable> { + + val shape: IntArray + + val dimension + get() = shape.size + + operator fun get(index: IntArray): T +} + +operator fun NDStructure.get(vararg index: Int): T = get(index) + +interface MutableNDStructure : NDStructure { + operator fun set(index: IntArray, value: T) +} + +fun MutableNDStructure.transformInPlace(action: (IntArray, T) -> T) { + for ((index, oldValue) in this) { + this[index] = action(index, oldValue) + } +} + +/** + * A way to convert ND index to linear one and back + */ +interface Strides { + /** + * Shape of NDstructure + */ + val shape: IntArray + + /** + * Array strides + */ + val strides: List + + /** + * Get linear index from multidimensional index + */ + fun offset(index: IntArray): Int + + /** + * Get multidimensional from linear + */ + fun index(offset: Int): IntArray + + val linearSize: Int + + /** + * Iterate over ND indices in a natural order + */ + fun indices(): Sequence { + //TODO introduce a fast way to calculate index of the next element? + return (0 until linearSize).asSequence().map { index(it) } + } +} + +class DefaultStrides(override val shape: IntArray) : Strides { + /** + * Strides for memory access + */ + override val strides by lazy { + sequence { + var current = 1 + yield(1) + shape.forEach { + current *= it + yield(current) + } + }.toList() + } + + override fun offset(index: IntArray): Int { + return index.mapIndexed { i, value -> + if (value < 0 || value >= shape[i]) { + throw RuntimeException("Index out of shape bounds: ($i,$value)") + } + value * strides[i] + }.sum() + } + + override fun index(offset: Int): IntArray { + return sequence { + var current = offset + var strideIndex = strides.size - 2 + while (strideIndex >= 0) { + yield(current / strides[strideIndex]) + current %= strides[strideIndex] + strideIndex-- + } + }.toList().reversed().toIntArray() + } + + override val linearSize: Int + get() = strides[shape.size] +} + +abstract class GenericNDStructure> : NDStructure { + protected abstract val buffer: B + protected abstract val strides: Strides + + override fun get(index: IntArray): T = buffer[strides.offset(index)] + + override val shape: IntArray + get() = strides.shape + + override fun iterator(): Iterator> = + strides.indices().map { it to this[it] }.iterator() +} + +/** + * Boxing generic [NDStructure] + */ +class BufferNDStructure( + override val strides: Strides, + override val buffer: Buffer +) : GenericNDStructure>() { + + init { + if (strides.linearSize != buffer.size) { + error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") + } + } +} + +inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = + BufferNDStructure(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))}) + +inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = + ndStructure(DefaultStrides(shape), initializer) + + +/** + * Mutable ND buffer based on linear [Buffer] + */ +class MutableBufferNDStructure( + override val strides: Strides, + override val buffer: MutableBuffer +) : GenericNDStructure>(), MutableNDStructure { + + init { + if (strides.linearSize != buffer.size) { + error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") + } + } + + override fun set(index: IntArray, value: T) = buffer.set(strides.offset(index), value) +} + +/** + * Create optimized mutable structure for given type + */ +inline fun mutableNdStructure(strides: Strides, noinline initializer: (IntArray) -> T) = + MutableBufferNDStructure(strides, mutableBuffer(strides.linearSize) { i -> initializer(strides.index(i)) }) + +inline fun mutableNdStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = + mutableNdStructure(DefaultStrides(shape), initializer) + +/** + * Create universal mutable structure + */ +fun genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure{ + val strides = DefaultStrides(shape) + val sequence = sequence{ + strides.indices().forEach{ + yield(initializer(it)) + } + } + val buffer = ListBuffer(sequence.toMutableList()) + return MutableBufferNDStructure(strides, buffer) +} diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt index ecc2ca28c..f767d682b 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/ArrayMatrixTest.kt @@ -7,15 +7,15 @@ class ArrayMatrixTest { @Test fun testSum() { - val vector1 = realVector(5) { it.toDouble() } - val vector2 = realVector(5) { 5 - it.toDouble() } + val vector1 = Vector.ofReal(5) { it.toDouble() } + val vector2 = Vector.ofReal(5) { 5 - it.toDouble() } val sum = vector1 + vector2 assertEquals(5.0, sum[2]) } @Test fun testVectorToMatrix() { - val vector = realVector(5) { it.toDouble() } + val vector = Vector.ofReal(5) { it.toDouble() } val matrix = vector.toMatrix() assertEquals(4.0, matrix[4, 0]) } @@ -23,8 +23,8 @@ class ArrayMatrixTest { @Test fun testDot() { - val vector1 = realVector(5) { it.toDouble() } - val vector2 = realVector(5) { 5 - it.toDouble() } + val vector1 = Vector.ofReal(5) { it.toDouble() } + val vector2 = Vector.ofReal(5) { 5 - it.toDouble() } val product = vector1.toMatrix() dot (vector2.toMatrix().transpose()) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/SimpleNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt similarity index 70% rename from kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/SimpleNDFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt index 94c1e15cc..338f5f052 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/SimpleNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/GenericNDFieldTest.kt @@ -6,10 +6,10 @@ import kotlin.test.Test import kotlin.test.assertEquals -class SimpleNDFieldTest{ +class GenericNDFieldTest{ @Test fun testStrides(){ - val ndArray = create(DoubleField, listOf(10,10)){(it[0]+it[1]).toDouble()} + val ndArray = create(DoubleField, intArrayOf(10,10)){(it[0]+it[1]).toDouble()} assertEquals(ndArray[5,5], 10.0) } diff --git a/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt b/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt index 3430b14d4..9658e8e75 100644 --- a/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt +++ b/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt @@ -1,12 +1,11 @@ package scietifik.kmath.structures -import org.openjdk.jmh.annotations.* import java.nio.IntBuffer @Fork(1) @Warmup(iterations = 2) -@Measurement(iterations = 50) +@Measurement(iterations = 5) @State(Scope.Benchmark) open class ArrayBenchmark { diff --git a/kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt b/kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt deleted file mode 100644 index 32d66a04b..000000000 --- a/kmath-core/src/jsMain/kotlin/scientifik/kmath/structures/_NDArrays.kt +++ /dev/null @@ -1,8 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.DoubleField - -/** - * Using boxing implementation for js - */ -actual val realNDFieldFactory: NDFieldFactory = NDArrays.createFactory(DoubleField) \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt deleted file mode 100644 index 19d6c4041..000000000 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/_NDArrays.kt +++ /dev/null @@ -1,26 +0,0 @@ -package scientifik.kmath.structures - -import scientifik.kmath.operations.DoubleField -import java.nio.DoubleBuffer - -private class RealNDField(shape: List) : BufferNDField(shape, DoubleField) { - override fun createBuffer(capacity: Int, initializer: (Int) -> Double): Buffer { - val array = DoubleArray(capacity, initializer) - val buffer = DoubleBuffer.wrap(array) - return BufferOfDoubles(buffer) - } - - private class BufferOfDoubles(val buffer: DoubleBuffer): Buffer{ - override fun get(index: Int): Double = buffer.get(index) - - override fun set(index: Int, value: Double) { - buffer.put(index, value) - } - - override fun copy(): Buffer { - return BufferOfDoubles(buffer) - } - } -} - -actual val realNDFieldFactory: NDFieldFactory = { shape -> RealNDField(shape) } \ No newline at end of file From f506e76b69f443eca88cf6801733bf76910ed0f1 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 26 Oct 2018 17:45:41 +0300 Subject: [PATCH 2/9] Moved JMH out of core --- kmath-core/build.gradle | 10 +--- kmath-jmh/build.gradle | 15 ++++++ .../kmath/structures/ArrayBenchmark.kt | 53 +++++++++++++++++++ settings.gradle | 1 + 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 kmath-jmh/build.gradle create mode 100644 kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index 948dc87bf..18be067e6 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -1,6 +1,5 @@ plugins { - id 'kotlin-multiplatform'// version '1.3.0-rc-116' - id "me.champeau.gradle.jmh" version "0.4.7" + id 'kotlin-multiplatform' } repositories { @@ -8,14 +7,9 @@ repositories { mavenCentral() } -dependencies{ - jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' -} - kotlin { targets { - fromPreset(presets.jvmWithJava, 'jvm') - //fromPreset(presets.jvm, 'jvm') + fromPreset(presets.jvm, 'jvm') fromPreset(presets.js, 'js') // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 // For Linux, preset should be changed to e.g. presets.linuxX64 diff --git a/kmath-jmh/build.gradle b/kmath-jmh/build.gradle new file mode 100644 index 000000000..a16bf3022 --- /dev/null +++ b/kmath-jmh/build.gradle @@ -0,0 +1,15 @@ +plugins { + id "java" + id "kotlin" + id "me.champeau.gradle.jmh" version "0.4.7" +} + +repositories { + maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' } + mavenCentral() +} + +dependencies { + implementation project(':kmath-core') + jmh 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' +} \ No newline at end of file diff --git a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt new file mode 100644 index 000000000..eb40195e0 --- /dev/null +++ b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt @@ -0,0 +1,53 @@ +package scientifik.kmath.structures + +import org.openjdk.jmh.annotations.* +import java.nio.IntBuffer + + +@Fork(1) +@Warmup(iterations = 2) +@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] + } + print(res) + } + + @Benchmark + fun benchmarkBufferRead() { + var res = 0 + for (i in 1..10000) { + res += arrayBuffer.get(10000 - i) + } + print(res) + } + + @Benchmark + fun nativeBufferRead() { + var res = 0 + for (i in 1..10000) { + res += nativeBuffer.get(10000 - i) + } + print(res) + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 03760221a..0e4b6fea7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,4 +10,5 @@ enableFeaturePreview('GRADLE_METADATA') rootProject.name = 'kmath' include ':kmath-core' +include ':kmath-jmh' From ae3434643786e062b87ee6b6869dcc3619ccd193 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 26 Oct 2018 20:14:07 +0300 Subject: [PATCH 3/9] Quick AIDA port --- .gitignore | 2 +- build.gradle | 28 ++-------- kmath-aida/build.gradle.kts | 15 ++++++ .../main/kotlin/scientifik/kmath/aida/AIDA.kt | 9 ++++ .../scientifik/kmath/aida/Histograms.kt | 23 ++++++++ .../kmath/structures/ArrayBenchmark.kt | 52 ------------------- settings.gradle | 3 +- 7 files changed, 53 insertions(+), 79 deletions(-) create mode 100644 kmath-aida/build.gradle.kts create mode 100644 kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt create mode 100644 kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt delete mode 100644 kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt diff --git a/.gitignore b/.gitignore index d07c3c850..e91684008 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ # Cache of project .gradletasknamecache -gradle.properties \ No newline at end of file +artifactory.gradle \ No newline at end of file diff --git a/build.gradle b/build.gradle index 14ecae465..e802034f0 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ buildscript { classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+" } } + allprojects { apply plugin: 'maven-publish' apply plugin: "com.jfrog.artifactory" @@ -21,29 +22,6 @@ allprojects { version = '0.0.1-SNAPSHOT' } - -artifactory { - contextUrl = "${artifactory_contextUrl}" //The base Artifactory URL if not overridden by the publisher/resolver - publish { - repository { - repoKey = 'gradle-dev-local' - username = "${artifactory_user}" - password = "${artifactory_password}" - } - - defaults { - publications('jvm', 'js', 'kotlinMultiplatform', 'metadata') - publishBuildInfo = false - publishArtifacts = true - publishPom = true - publishIvy = false - } - } - resolve { - repository { - repoKey = 'gradle-dev' - username = "${artifactory_user}" - password = "${artifactory_password}" - } - } +if(file('artifactory.gradle').exists()){ + apply from: 'artifactory.gradle' } \ No newline at end of file diff --git a/kmath-aida/build.gradle.kts b/kmath-aida/build.gradle.kts new file mode 100644 index 000000000..2e7deb8b0 --- /dev/null +++ b/kmath-aida/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() + maven ("http://dl.bintray.com/kotlin/kotlin-eap") + maven("http://java.freehep.org/maven2/") +} + +dependencies { + implementation(kotlin("stdlib")) + api(project(":kmath-core")) + api(group = "org.freehep", name = "freehep-jaida", version = "3.4.13") +} \ No newline at end of file diff --git a/kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt b/kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt new file mode 100644 index 000000000..fc7a044d3 --- /dev/null +++ b/kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt @@ -0,0 +1,9 @@ +package scientifik.kmath.aida + +import hep.aida.IAnalysisFactory +import hep.aida.IHistogramFactory +import hep.aida.ITreeFactory + +val analysisFactory: IAnalysisFactory by lazy { IAnalysisFactory.create() } +val treeFactory: ITreeFactory by lazy { analysisFactory.createTreeFactory() } +val histogramFactory: IHistogramFactory by lazy { analysisFactory.createHistogramFactory(treeFactory.create()) } \ No newline at end of file diff --git a/kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt b/kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt new file mode 100644 index 000000000..974fa64a5 --- /dev/null +++ b/kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt @@ -0,0 +1,23 @@ +package scientifik.kmath.aida + +import hep.aida.IHistogram1D +import hep.aida.IHistogram2D + +fun Iterable.histogram(range: ClosedFloatingPointRange, bins: Int = 100, path: String = ""): IHistogram1D{ + val h1d = Histograms.create1D(range,bins,path) + forEach{ + h1d.fill(it) + } + return h1d +} + + +object Histograms { + fun create1D(range: ClosedFloatingPointRange, bins: Int = 100, path: String = ""): IHistogram1D { + return histogramFactory.createHistogram1D(path, bins, range.start, range.endInclusive) + } + + fun create2D(xRange: ClosedFloatingPointRange, yRange: ClosedFloatingPointRange, xBins: Int = 100, yBins: Int = 100, path: String = ""): IHistogram2D { + return histogramFactory.createHistogram2D(path, xBins, xRange.start, xRange.endInclusive, yBins, yRange.start, yRange.endInclusive) + } +} \ No newline at end of file diff --git a/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt b/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt deleted file mode 100644 index 9658e8e75..000000000 --- a/kmath-core/src/jmh/kotlin/scietifik/kmath/structures/ArrayBenchmark.kt +++ /dev/null @@ -1,52 +0,0 @@ -package scietifik.kmath.structures - -import java.nio.IntBuffer - - -@Fork(1) -@Warmup(iterations = 2) -@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] - } - print(res) - } - - @Benchmark - fun benchmarkBufferRead() { - var res = 0 - for (i in 1..10000) { - res += arrayBuffer.get(10000 - i) - } - print(res) - } - - @Benchmark - fun nativeBufferRead() { - var res = 0 - for (i in 1..10000) { - res += nativeBuffer.get(10000 - i) - } - print(res) - } -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 0e4b6fea7..90567bfb2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,7 @@ pluginManagement { repositories { - maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' } mavenCentral() + maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' } maven { url = 'https://plugins.gradle.org/m2/' } } } @@ -10,5 +10,6 @@ enableFeaturePreview('GRADLE_METADATA') rootProject.name = 'kmath' include ':kmath-core' +include ':kmath-aida' include ':kmath-jmh' From c7575caeee5e13e054d3122009cee133f05a6ac4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 27 Oct 2018 10:08:07 +0300 Subject: [PATCH 4/9] AIDA is broken, rollback to standalone histograms --- kmath-aida/build.gradle.kts | 15 ------- .../main/kotlin/scientifik/kmath/aida/AIDA.kt | 9 ---- .../scientifik/kmath/aida/Histograms.kt | 23 ---------- .../scientifik/kmath/histogram/Histogram.kt | 42 +++++++++++++++++++ .../scientifik/kmath/linear/LinearAlgrebra.kt | 3 +- settings.gradle | 1 - 6 files changed, 44 insertions(+), 49 deletions(-) delete mode 100644 kmath-aida/build.gradle.kts delete mode 100644 kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt delete mode 100644 kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt diff --git a/kmath-aida/build.gradle.kts b/kmath-aida/build.gradle.kts deleted file mode 100644 index 2e7deb8b0..000000000 --- a/kmath-aida/build.gradle.kts +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - kotlin("jvm") -} - -repositories { - mavenCentral() - maven ("http://dl.bintray.com/kotlin/kotlin-eap") - maven("http://java.freehep.org/maven2/") -} - -dependencies { - implementation(kotlin("stdlib")) - api(project(":kmath-core")) - api(group = "org.freehep", name = "freehep-jaida", version = "3.4.13") -} \ No newline at end of file diff --git a/kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt b/kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt deleted file mode 100644 index fc7a044d3..000000000 --- a/kmath-aida/src/main/kotlin/scientifik/kmath/aida/AIDA.kt +++ /dev/null @@ -1,9 +0,0 @@ -package scientifik.kmath.aida - -import hep.aida.IAnalysisFactory -import hep.aida.IHistogramFactory -import hep.aida.ITreeFactory - -val analysisFactory: IAnalysisFactory by lazy { IAnalysisFactory.create() } -val treeFactory: ITreeFactory by lazy { analysisFactory.createTreeFactory() } -val histogramFactory: IHistogramFactory by lazy { analysisFactory.createHistogramFactory(treeFactory.create()) } \ No newline at end of file diff --git a/kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt b/kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt deleted file mode 100644 index 974fa64a5..000000000 --- a/kmath-aida/src/main/kotlin/scientifik/kmath/aida/Histograms.kt +++ /dev/null @@ -1,23 +0,0 @@ -package scientifik.kmath.aida - -import hep.aida.IHistogram1D -import hep.aida.IHistogram2D - -fun Iterable.histogram(range: ClosedFloatingPointRange, bins: Int = 100, path: String = ""): IHistogram1D{ - val h1d = Histograms.create1D(range,bins,path) - forEach{ - h1d.fill(it) - } - return h1d -} - - -object Histograms { - fun create1D(range: ClosedFloatingPointRange, bins: Int = 100, path: String = ""): IHistogram1D { - return histogramFactory.createHistogram1D(path, bins, range.start, range.endInclusive) - } - - fun create2D(xRange: ClosedFloatingPointRange, yRange: ClosedFloatingPointRange, xBins: Int = 100, yBins: Int = 100, path: String = ""): IHistogram2D { - return histogramFactory.createHistogram2D(path, xBins, xRange.start, xRange.endInclusive, yBins, yRange.start, yRange.endInclusive) - } -} \ 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 new file mode 100644 index 000000000..0a781e613 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -0,0 +1,42 @@ +package scientifik.kmath.histogram + +import scientifik.kmath.linear.RealVector +import scientifik.kmath.operations.Space + +/** + * The bin in the histogram. The histogram is by definition always done in the real space + */ +interface Bin { + /** + * The value of this bin + */ + val value: Number + val center: RealVector +} + +/** + * Creates a new bin with zero count corresponding to given point + */ +interface BinFactory { + fun createBin(point: RealVector): B +} + +interface Histogram : Iterable { + + /** + * Find existing bin, corresponding to given coordinates + */ + fun findBin(point: RealVector): B? + + /** + * Dimension of the histogram + */ + val dimension: Int +} + +interface HistogramSpace> : Space { + /** + * Rules for performing operations on bins + */ + val binSpace: Space +} \ 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 097a65bb4..333292997 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -181,7 +181,6 @@ interface Vector : SpaceElement, VectorSpace> { fun ofReal(size: Int, initializer: (Int) -> Double) = ArrayVector(ArrayVectorSpace(size, DoubleField, realNDFieldFactory), initializer) - fun equals(v1: Vector<*>, v2: Vector<*>): Boolean { if (v1 === v2) return true if (v1.context != v2.context) return false @@ -268,6 +267,8 @@ class ArrayVector internal constructor(override val context: ArrayVecto override val self: ArrayVector get() = this } +typealias RealVector = Vector + /** * A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors */ diff --git a/settings.gradle b/settings.gradle index 90567bfb2..e35188620 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,5 @@ enableFeaturePreview('GRADLE_METADATA') rootProject.name = 'kmath' include ':kmath-core' -include ':kmath-aida' include ':kmath-jmh' From c8329eef8a8d21c3fbd0dfac17bf4a16414613f6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 28 Oct 2018 16:47:54 +0300 Subject: [PATCH 5/9] Histograms for jvm are in working order. --- .../scientifik/kmath/histogram/Counters.kt | 20 +++ .../scientifik/kmath/histogram/Histogram.kt | 38 ++++-- .../scientifik/kmath/linear/LinearAlgrebra.kt | 12 +- .../scientifik/kmath/structures/NDField.kt | 5 +- .../kmath/structures/NDStructure.kt | 2 +- .../scientifik/kmath/histogram/Counters.kt | 16 +++ .../scientifik/kmath/histogram/Counters.kt | 7 ++ .../kmath/histogram/FastHistogram.kt | 117 ++++++++++++++++++ .../kmath/histogram/UnivariateHistogram.kt | 88 +++++++++++++ .../histogram/MultivariateHistogramTest.kt | 43 +++++++ 10 files changed, 334 insertions(+), 14 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt create mode 100644 kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt create mode 100644 kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt create mode 100644 kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt new file mode 100644 index 000000000..f6cc1f822 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -0,0 +1,20 @@ +package scientifik.kmath.histogram + +/* + * Common representation for atomic counters + */ + + +expect class LongCounter(){ + fun decrement() + fun increment() + fun reset() + fun sum(): Long + fun add(l:Long) +} + +expect class DoubleCounter(){ + fun reset() + fun sum(): Double + fun add(d: Double) +} \ 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 0a781e613..79b06ee10 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -1,12 +1,22 @@ package scientifik.kmath.histogram import scientifik.kmath.linear.RealVector +import scientifik.kmath.linear.toVector import scientifik.kmath.operations.Space +/** + * A simple geometric domain + * TODO move to geometry module + */ +interface Domain { + operator fun contains(vector: RealVector): Boolean + val dimension: Int +} + /** * The bin in the histogram. The histogram is by definition always done in the real space */ -interface Bin { +interface Bin : Domain { /** * The value of this bin */ @@ -14,26 +24,36 @@ interface Bin { val center: RealVector } -/** - * Creates a new bin with zero count corresponding to given point - */ -interface BinFactory { - fun createBin(point: RealVector): B -} - interface Histogram : Iterable { /** * Find existing bin, corresponding to given coordinates */ - fun findBin(point: RealVector): B? + operator fun get(point: RealVector): B? /** * Dimension of the histogram */ val dimension: Int + + /** + * Increment appropriate bin + */ + fun put(point: RealVector) } +fun Histogram<*>.put(vararg point: Double) = put(point.toVector()) + +fun Histogram<*>.fill(sequence: Iterable) = sequence.forEach { put(it) } + +/** + * Pass a sequence builder into histogram + */ +fun Histogram<*>.fill(buider: suspend SequenceScope.() -> Unit) = fill(sequence(buider).asIterable()) + +/** + * A space to perform arithmetic operations on histograms + */ interface HistogramSpace> : Space { /** * Rules for performing operations on bins 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 333292997..269a098e3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -162,9 +162,8 @@ abstract class VectorSpace(val size: Int, val field: Field) : Space< } -interface Vector : SpaceElement, VectorSpace> { - val size: Int - get() = context.size +interface Vector : SpaceElement, VectorSpace>, Iterable { + val size: Int get() = context.size operator fun get(i: Int): T @@ -181,6 +180,8 @@ interface Vector : SpaceElement, VectorSpace> { fun ofReal(size: Int, initializer: (Int) -> Double) = ArrayVector(ArrayVectorSpace(size, DoubleField, realNDFieldFactory), initializer) + fun ofReal(vararg point: Double) = point.toVector() + fun equals(v1: Vector<*>, v2: Vector<*>): Boolean { if (v1 === v2) return true if (v1.context != v2.context) return false @@ -265,6 +266,10 @@ class ArrayVector internal constructor(override val context: ArrayVecto } override val self: ArrayVector get() = this + + override fun iterator(): Iterator = (0 until size).map { array[it] }.iterator() + + override fun toString(): String = this.joinToString(prefix = "[",postfix = "]", separator = ", "){it.toString()} } typealias RealVector = Vector @@ -284,6 +289,7 @@ interface LinearSolver { fun Array.toVector(field: Field) = Vector.of(size, field) { this[it] } fun DoubleArray.toVector() = Vector.ofReal(this.size) { this[it] } +fun List.toVector() = Vector.ofReal(this.size) { this[it] } /** * Convert matrix to vector if it is possible 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 7e0032597..9461bee85 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -105,9 +105,12 @@ abstract class NDField(val shape: IntArray, val field: Field) : Field(override val context: NDField, private val structure: NDStructure) : FieldElement, NDField>, NDStructure by structure { + + //TODO ensure structure is immutable + override val self: NDArray 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 ba3a91487..a6880362d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -76,7 +76,7 @@ class DefaultStrides(override val shape: IntArray) : Strides { override fun offset(index: IntArray): Int { return index.mapIndexed { i, value -> if (value < 0 || value >= shape[i]) { - throw RuntimeException("Index out of shape bounds: ($i,$value)") + throw RuntimeException("Index $value out of shape bounds: (0,${shape[i]})") } value * strides[i] }.sum() diff --git a/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt new file mode 100644 index 000000000..3c2915587 --- /dev/null +++ b/kmath-core/src/jsMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -0,0 +1,16 @@ +package scientifik.kmath.histogram + +actual class LongCounter{ + private var sum: Long = 0 + actual fun decrement() {sum--} + actual fun increment() {sum++} + actual fun reset() {sum = 0} + actual fun sum(): Long = sum + actual fun add(l: Long) {sum+=l} +} +actual class DoubleCounter{ + private var sum: Double = 0.0 + actual fun reset() {sum = 0.0} + actual fun sum(): Double = sum + actual fun add(d: Double) {sum+=d} +} \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt new file mode 100644 index 000000000..bb3667f7d --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/Counters.kt @@ -0,0 +1,7 @@ +package scientifik.kmath.histogram + +import java.util.concurrent.atomic.DoubleAdder +import java.util.concurrent.atomic.LongAdder + +actual typealias LongCounter = LongAdder +actual typealias DoubleCounter = DoubleAdder \ No newline at end of file diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt new file mode 100644 index 000000000..f2f5c04bb --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -0,0 +1,117 @@ +package scientifik.kmath.histogram + +import scientifik.kmath.linear.RealVector +import scientifik.kmath.linear.toVector +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.ndStructure +import kotlin.math.floor + +class MultivariateBin(override val center: RealVector, val sizes: RealVector, val counter: LongCounter = LongCounter()) : Bin { + init { + if (center.size != sizes.size) error("Dimension mismatch in bin creation. Expected ${center.size}, but found ${sizes.size}") + } + + override fun contains(vector: RealVector): Boolean { + assert(vector.size == center.size) + return vector.asSequence().mapIndexed { i, value -> value in (center[i] - sizes[i] / 2)..(center[i] + sizes[i] / 2) }.all { it } + } + + override val value: Number get() = counter.sum() + internal operator fun inc() = this.also { counter.increment() } + + override val dimension: Int get() = center.size +} + +/** + * Uniform multivariate histogram with fixed borders. Based on NDStructure implementation with complexity of m for bin search, where m is the number of dimensions + */ +class FastHistogram( + private val lower: RealVector, + private val upper: RealVector, + private val binNums: IntArray = IntArray(lower.size) { 100 } +) : Histogram { + + 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).any { it <= 0 }) error("Range for one of axis is not strictly positive") + } + + + override val dimension: Int get() = lower.size + + //TODO optimize binSize performance if needed + private val binSize = (upper - lower).mapIndexed { index, value -> value / binNums[index] }.toVector() + + private val bins: NDStructure by lazy { + val actualSizes = IntArray(binNums.size) { binNums[it] + 2 } + ndStructure(actualSizes) { indexArray -> + val center = indexArray.mapIndexed { axis, index -> + when (index) { + 0 -> Double.NEGATIVE_INFINITY + actualSizes[axis] -> Double.POSITIVE_INFINITY + else -> lower[axis] + (index - 1) * binSize[axis] + } + }.toVector() + MultivariateBin(center, binSize) + } + } + + /** + * Get internal [NDStructure] bin index for given axis + */ + 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 + } + } + + + override fun get(point: RealVector): MultivariateBin? { + val index = IntArray(dimension) { getIndex(it, point[it]) } + return bins[index] + } + + override fun put(point: RealVector) { + this[point]?.inc() ?: error("Could not find appropriate bin (should not be possible)") + } + + override fun iterator(): Iterator = bins.asSequence().map { it.second }.iterator() + + companion object { + + /** + * Use it like + * ``` + *FastHistogram.fromRanges( + * (-1.0..1.0), + * (-1.0..1.0) + *) + *``` + */ + fun fromRanges(vararg ranges: ClosedFloatingPointRange): FastHistogram { + return FastHistogram(ranges.map { it.start }.toVector(), ranges.map { it.endInclusive }.toVector()) + } + + /** + * Use it like + * ``` + *FastHistogram.fromRanges( + * (-1.0..1.0) to 50, + * (-1.0..1.0) to 32 + *) + *``` + */ + fun fromRanges(vararg ranges: Pair,Int>): FastHistogram { + return FastHistogram( + ranges.map { it.first.start }.toVector(), + ranges.map { it.first.endInclusive }.toVector(), + ranges.map { it.second }.toIntArray() + ) + } + } + +} \ 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 new file mode 100644 index 000000000..993b1e850 --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -0,0 +1,88 @@ +package scientifik.kmath.histogram + +import scientifik.kmath.linear.RealVector +import scientifik.kmath.linear.toVector +import java.util.* +import kotlin.math.floor + +//TODO move to common + +class UnivariateBin(val position: Double, val size: Double, val counter: LongCounter = LongCounter()) : Bin { + //TODO add weighting + override val value: Number get() = counter.sum() + + override val center: RealVector get() = doubleArrayOf(position).toVector() + + operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2) + + override fun contains(vector: RealVector): Boolean = contains(vector[0]) + + internal operator fun inc() = this.also { counter.increment()} + + override val dimension: Int get() = 1 +} + +/** + * Univariate histogram with log(n) bin search speed + */ +class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : Histogram { + + private val bins: TreeMap = TreeMap() + + private operator fun get(value: Double): UnivariateBin? { + // check ceiling entry and return it if it is what needed + val ceil = bins.ceilingEntry(value)?.value + if (ceil != null && value in ceil) return ceil + //check floor entry + val floor = bins.floorEntry(value)?.value + if (floor != null && value in floor) return floor + //neither is valid, not found + return null + } + + private fun createBin(value: Double): UnivariateBin = factory(value).also { + synchronized(this) { bins.put(it.position, it) } + } + + override fun get(point: RealVector): UnivariateBin? = get(point[0]) + + override val dimension: Int get() = 1 + + override fun iterator(): Iterator = bins.values.iterator() + + /** + * Thread safe put operation + */ + fun put(value: Double) { + (get(value) ?: createBin(value)).inc() + } + + override fun put(point: RealVector) = put(point[0]) + + companion object { + fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram { + return UnivariateHistogram { value -> + val center = start + binSize * floor((value - start) / binSize + 0.5) + UnivariateBin(center, binSize) + } + } + + fun custom(borders: DoubleArray): UnivariateHistogram { + val sorted = borders.sortedArray() + return UnivariateHistogram { value -> + if (value < sorted.first()) { + UnivariateBin(Double.NEGATIVE_INFINITY, Double.MAX_VALUE) + } else if (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] + val right = sorted[index + 1] + UnivariateBin((left + right) / 2, (right - left)) + } + } + } + } +} + +fun UnivariateHistogram.fill(sequence: Iterable) = sequence.forEach { put(it) } diff --git a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt new file mode 100644 index 000000000..e80a7340e --- /dev/null +++ b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt @@ -0,0 +1,43 @@ +package scientifik.kmath.histogram + +import org.junit.Test +import scientifik.kmath.linear.Vector +import kotlin.random.Random +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class MultivariateHistogramTest { + @Test + fun testSinglePutHistogram() { + val histogram = FastHistogram.fromRanges( + (-1.0..1.0), + (-1.0..1.0) + ) + histogram.put(0.6, 0.6) + val bin = histogram.find { it.value.toInt() > 0 }!! + assertTrue { bin.contains(Vector.ofReal(0.6, 0.6)) } + assertFalse { bin.contains(Vector.ofReal(-0.6, 0.6)) } + } + + @Test + fun testSequentialPut(){ + val histogram = FastHistogram.fromRanges( + (-1.0..1.0), + (-1.0..1.0), + (-1.0..1.0) + ) + val random = Random(1234) + + fun nextDouble() = random.nextDouble(-1.0,1.0) + + val n = 10000 + + histogram.fill { + repeat(n){ + yield(Vector.ofReal(nextDouble(),nextDouble(),nextDouble())) + } + } + assertEquals(n, histogram.sumBy { it.value.toInt() }) + } +} \ No newline at end of file From 34374e1006e9abad47b7643676c6e3dcfd944fc3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 28 Oct 2018 16:56:06 +0300 Subject: [PATCH 6/9] Moved multivariate histogram to common --- .../kmath/histogram/FastHistogram.kt | 2 +- .../histogram/MultivariateHistogramTest.kt | 2 +- .../kmath/histogram/UnivariateHistogram.kt | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) rename kmath-core/src/{jvmMain => commonMain}/kotlin/scientifik/kmath/histogram/FastHistogram.kt (96%) rename kmath-core/src/{jvmTest => commonTest}/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt (97%) diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt similarity index 96% rename from kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt index f2f5c04bb..5fd4c7f03 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -12,7 +12,7 @@ class MultivariateBin(override val center: RealVector, val sizes: RealVector, va } override fun contains(vector: RealVector): Boolean { - assert(vector.size == center.size) + if(vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}") return vector.asSequence().mapIndexed { i, value -> value in (center[i] - sizes[i] / 2)..(center[i] + sizes[i] / 2) }.all { it } } diff --git a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt similarity index 97% rename from kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt index e80a7340e..2fbbbc88d 100644 --- a/kmath-core/src/jvmTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt @@ -1,8 +1,8 @@ package scientifik.kmath.histogram -import org.junit.Test import scientifik.kmath.linear.Vector import kotlin.random.Random +import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue 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 993b1e850..a3b066b02 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -70,15 +70,15 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U fun custom(borders: DoubleArray): UnivariateHistogram { val sorted = borders.sortedArray() return UnivariateHistogram { value -> - if (value < sorted.first()) { - UnivariateBin(Double.NEGATIVE_INFINITY, Double.MAX_VALUE) - } else if (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] - val right = sorted[index + 1] - UnivariateBin((left + right) / 2, (right - left)) + when { + 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] + val right = sorted[index + 1] + UnivariateBin((left + right) / 2, (right - left)) + } } } } From 6a0ef6a2356c26911189e90e8ad8d4213ecce757 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 28 Oct 2018 21:41:58 +0300 Subject: [PATCH 7/9] Added limited polimorphism to Histogram --- .../kmath/histogram/FastHistogram.kt | 10 +++---- .../scientifik/kmath/histogram/Histogram.kt | 26 +++++++++---------- .../kmath/histogram/UnivariateHistogram.kt | 4 +-- 3 files changed, 20 insertions(+), 20 deletions(-) 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 5fd4c7f03..b043b1e18 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -6,13 +6,13 @@ import scientifik.kmath.structures.NDStructure import scientifik.kmath.structures.ndStructure import kotlin.math.floor -class MultivariateBin(override val center: RealVector, val sizes: RealVector, val counter: LongCounter = LongCounter()) : Bin { +class MultivariateBin(override val center: RealVector, val sizes: RealVector, val counter: LongCounter = LongCounter()) : Bin { init { if (center.size != sizes.size) error("Dimension mismatch in bin creation. Expected ${center.size}, but found ${sizes.size}") } override fun contains(vector: RealVector): Boolean { - if(vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}") + if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}") return vector.asSequence().mapIndexed { i, value -> value in (center[i] - sizes[i] / 2)..(center[i] + sizes[i] / 2) }.all { it } } @@ -28,8 +28,8 @@ class MultivariateBin(override val center: RealVector, val sizes: RealVector, va class FastHistogram( private val lower: RealVector, private val upper: RealVector, - private val binNums: IntArray = IntArray(lower.size) { 100 } -) : Histogram { + private val binNums: IntArray = IntArray(lower.size) { 20 } +) : Histogram { init { // argument checks @@ -105,7 +105,7 @@ class FastHistogram( *) *``` */ - fun fromRanges(vararg ranges: Pair,Int>): FastHistogram { + fun fromRanges(vararg ranges: Pair, Int>): FastHistogram { return FastHistogram( ranges.map { it.first.start }.toVector(), ranges.map { it.first.endInclusive }.toVector(), 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 79b06ee10..9f6365888 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -1,6 +1,6 @@ package scientifik.kmath.histogram -import scientifik.kmath.linear.RealVector +import scientifik.kmath.linear.Vector import scientifik.kmath.linear.toVector import scientifik.kmath.operations.Space @@ -8,28 +8,28 @@ import scientifik.kmath.operations.Space * A simple geometric domain * TODO move to geometry module */ -interface Domain { - operator fun contains(vector: RealVector): Boolean +interface Domain { + operator fun contains(vector: Vector): Boolean val dimension: Int } /** * 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 */ val value: Number - val center: RealVector + val center: Vector } -interface Histogram : Iterable { +interface Histogram> : Iterable { /** * Find existing bin, corresponding to given coordinates */ - operator fun get(point: RealVector): B? + operator fun get(point: Vector): B? /** * Dimension of the histogram @@ -39,24 +39,24 @@ interface Histogram : Iterable { /** * Increment appropriate bin */ - fun put(point: RealVector) + fun put(point: Vector) } -fun Histogram<*>.put(vararg point: Double) = put(point.toVector()) +fun Histogram.put(vararg point: Double) = put(point.toVector()) -fun Histogram<*>.fill(sequence: Iterable) = sequence.forEach { put(it) } +fun Histogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } /** * Pass a sequence builder into histogram */ -fun Histogram<*>.fill(buider: suspend SequenceScope.() -> Unit) = fill(sequence(buider).asIterable()) +fun Histogram.fill(buider: suspend SequenceScope>.() -> Unit) = fill(sequence(buider).asIterable()) /** * A space to perform arithmetic operations on histograms */ -interface HistogramSpace> : Space { +interface HistogramSpace, H : Histogram> : Space { /** * Rules for performing operations on bins */ - val binSpace: Space + val binSpace: Space> } \ 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 a3b066b02..b614c0716 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -7,7 +7,7 @@ import kotlin.math.floor //TODO move to common -class UnivariateBin(val position: Double, val size: Double, val counter: LongCounter = LongCounter()) : Bin { +class UnivariateBin(val position: Double, val size: Double, val counter: LongCounter = LongCounter()) : Bin { //TODO add weighting override val value: Number get() = counter.sum() @@ -25,7 +25,7 @@ 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) : Histogram { +class UnivariateHistogram private constructor(private val factory: (Double) -> UnivariateBin) : Histogram { private val bins: TreeMap = TreeMap() From 881ee9f6880ccadb8c359fcc60ab33b8673b4494 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 28 Oct 2018 21:53:40 +0300 Subject: [PATCH 8/9] Added limited polimorphism to Histogram --- .../kmath/histogram/FastHistogram.kt | 7 ++++--- .../scientifik/kmath/histogram/Histogram.kt | 18 +++++++++--------- .../scientifik/kmath/linear/LinearAlgrebra.kt | 19 ++++++++----------- .../scientifik/kmath/structures/Buffers.kt | 8 +++++++- .../kmath/histogram/UnivariateHistogram.kt | 7 ++++--- 5 files changed, 32 insertions(+), 27 deletions(-) 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 b043b1e18..5f69f9d12 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -2,6 +2,7 @@ package scientifik.kmath.histogram import scientifik.kmath.linear.RealVector import scientifik.kmath.linear.toVector +import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.NDStructure import scientifik.kmath.structures.ndStructure import kotlin.math.floor @@ -11,7 +12,7 @@ class MultivariateBin(override val center: RealVector, val sizes: RealVector, va if (center.size != sizes.size) error("Dimension mismatch in bin creation. Expected ${center.size}, but found ${sizes.size}") } - override fun contains(vector: RealVector): Boolean { + override fun contains(vector: Buffer): Boolean { if (vector.size != center.size) error("Dimension mismatch for input vector. Expected ${center.size}, but found ${vector.size}") return vector.asSequence().mapIndexed { i, value -> value in (center[i] - sizes[i] / 2)..(center[i] + sizes[i] / 2) }.all { it } } @@ -70,12 +71,12 @@ class FastHistogram( } - override fun get(point: RealVector): MultivariateBin? { + override fun get(point: Buffer): MultivariateBin? { val index = IntArray(dimension) { getIndex(it, point[it]) } return bins[index] } - override fun put(point: RealVector) { + override fun put(point: Buffer) { this[point]?.inc() ?: error("Could not find appropriate bin (should not be possible)") } 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 9f6365888..1b5c231b4 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/Histogram.kt @@ -1,15 +1,15 @@ package scientifik.kmath.histogram -import scientifik.kmath.linear.Vector -import scientifik.kmath.linear.toVector import scientifik.kmath.operations.Space +import scientifik.kmath.structures.ArrayBuffer +import scientifik.kmath.structures.Buffer /** * A simple geometric domain * TODO move to geometry module */ interface Domain { - operator fun contains(vector: Vector): Boolean + operator fun contains(vector: Buffer): Boolean val dimension: Int } @@ -21,7 +21,7 @@ interface Bin : Domain { * The value of this bin */ val value: Number - val center: Vector + val center: Buffer } interface Histogram> : Iterable { @@ -29,7 +29,7 @@ interface Histogram> : Iterable { /** * Find existing bin, corresponding to given coordinates */ - operator fun get(point: Vector): B? + operator fun get(point: Buffer): B? /** * Dimension of the histogram @@ -39,17 +39,17 @@ interface Histogram> : Iterable { /** * Increment appropriate bin */ - fun put(point: Vector) + fun put(point: Buffer) } -fun Histogram.put(vararg point: Double) = put(point.toVector()) +fun Histogram.put(vararg point: T) = put(ArrayBuffer(point)) -fun Histogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } +fun Histogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } /** * Pass a sequence builder into histogram */ -fun Histogram.fill(buider: suspend SequenceScope>.() -> Unit) = fill(sequence(buider).asIterable()) +fun Histogram.fill(buider: suspend SequenceScope>.() -> Unit) = fill(sequence(buider).asIterable()) /** * A space to perform arithmetic operations on histograms 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 269a098e3..e585b884d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -4,10 +4,7 @@ import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement -import scientifik.kmath.structures.GenericNDField -import scientifik.kmath.structures.NDArray -import scientifik.kmath.structures.NDField -import scientifik.kmath.structures.get +import scientifik.kmath.structures.* /** * The space for linear elements. Supports scalar product alongside with standard linear operations. @@ -162,10 +159,8 @@ abstract class VectorSpace(val size: Int, val field: Field) : Space< } -interface Vector : SpaceElement, VectorSpace>, Iterable { - val size: Int get() = context.size - - operator fun get(i: Int): T +interface Vector : SpaceElement, VectorSpace>, Buffer, Iterable { + override val size: Int get() = context.size companion object { /** @@ -261,15 +256,17 @@ class ArrayVector internal constructor(override val context: ArrayVecto } } - override fun get(i: Int): T { - return array[i] + override fun get(index: Int): T { + return array[index] } override val self: ArrayVector get() = this override fun iterator(): Iterator = (0 until size).map { array[it] }.iterator() - override fun toString(): String = this.joinToString(prefix = "[",postfix = "]", separator = ", "){it.toString()} + override fun copy(): ArrayVector = ArrayVector(context, array) + + override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } } typealias RealVector = Vector 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 70407e482..04983ef85 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -4,7 +4,7 @@ package scientifik.kmath.structures /** * A generic linear buffer for both primitives and objects */ -interface Buffer { +interface Buffer : Iterable { val size: Int @@ -36,6 +36,8 @@ inline class ListBuffer(private val list: MutableList) : MutableBuffer list[index] = value } + override fun iterator(): Iterator = list.iterator() + override fun copy(): MutableBuffer = ListBuffer(ArrayList(list)) } @@ -49,6 +51,8 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { array[index] = value } + override fun iterator(): Iterator = array.iterator() + override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) } @@ -62,6 +66,8 @@ class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { array[index] = value } + override fun iterator(): Iterator = array.iterator() + override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) } 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 b614c0716..2382c379c 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -2,6 +2,7 @@ package scientifik.kmath.histogram import scientifik.kmath.linear.RealVector import scientifik.kmath.linear.toVector +import scientifik.kmath.structures.Buffer import java.util.* import kotlin.math.floor @@ -15,7 +16,7 @@ class UnivariateBin(val position: Double, val size: Double, val counter: LongCou operator fun contains(value: Double): Boolean = value in (position - size / 2)..(position + size / 2) - override fun contains(vector: RealVector): Boolean = contains(vector[0]) + override fun contains(vector: Buffer): Boolean = contains(vector[0]) internal operator fun inc() = this.also { counter.increment()} @@ -44,7 +45,7 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U synchronized(this) { bins.put(it.position, it) } } - override fun get(point: RealVector): UnivariateBin? = get(point[0]) + override fun get(point: Buffer): UnivariateBin? = get(point[0]) override val dimension: Int get() = 1 @@ -57,7 +58,7 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U (get(value) ?: createBin(value)).inc() } - override fun put(point: RealVector) = put(point[0]) + override fun put(point: Buffer) = put(point[0]) companion object { fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram { From 98622e64c469942c3199b30c573982e310fa54d3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 29 Oct 2018 18:58:35 +0300 Subject: [PATCH 9/9] 1.3 stable! --- build.gradle | 5 +---- settings.gradle | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index e802034f0..51d476ed6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,8 @@ buildscript { - ext.kotlin_version = '1.3.0-rc-190' + ext.kotlin_version = '1.3.0' repositories { jcenter() - maven { - url = "http://dl.bintray.com/kotlin/kotlin-eap" - } } dependencies { diff --git a/settings.gradle b/settings.gradle index e35188620..df4ccc99e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,6 @@ pluginManagement { repositories { mavenCentral() - maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' } maven { url = 'https://plugins.gradle.org/m2/' } } }