diff --git a/README.md b/README.md index 63526480e..8e07b546a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ and `scipy` it is modular and has a lightweight core. on arrays and numbers just like it works in python (with 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. Expressions could be used +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. ## Planned features @@ -58,11 +58,11 @@ repositories { Then use regular dependency like ```groovy -compile(group: 'scientifik', name: 'kmath-core-jvm', version: '0.0.1-SNAPSHOT') +compile(group: 'scientifik', name: 'kmath-core', version: '0.0.1-SNAPSHOT') ``` or in kotlin ```kotlin -compile(group = "scientifik", name = "kmath-core-jvm", version = "0.0.1-SNAPSHOT") +compile(group = "scientifik", name = "kmath-core", version = "0.0.1-SNAPSHOT") ``` Work builds could be obtained with [![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 51d476ed6..000000000 --- a/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.0' - - repositories { - jcenter() - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+" - } -} - -allprojects { - apply plugin: 'maven-publish' - apply plugin: "com.jfrog.artifactory" - - group = 'scientifik' - version = '0.0.1-SNAPSHOT' -} - -if(file('artifactory.gradle').exists()){ - apply from: 'artifactory.gradle' -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..5b95be71a --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,35 @@ +buildscript { + extra["kotlinVersion"] = "1.3.11" + extra["ioVersion"] = "0.1.2-dev-2" + extra["coroutinesVersion"] = "1.0.1" + + val kotlinVersion: String by extra + val ioVersion: String by extra + val coroutinesVersion: String by extra + + repositories { + jcenter() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+") + } +} + +plugins { + id("com.jfrog.artifactory") version "4.8.1" apply false +// id("org.jetbrains.kotlin.multiplatform") apply false +} + +allprojects { + apply(plugin = "maven-publish") + apply(plugin = "com.jfrog.artifactory") + + group = "scientifik" + version = "0.0.2-dev-1" +} + +if(file("artifactory.gradle").exists()){ + apply(from = "artifactory.gradle") +} \ No newline at end of file diff --git a/kmath-core/build.gradle b/kmath-core/build.gradle index 18be067e6..d51ccdb65 100644 --- a/kmath-core/build.gradle +++ b/kmath-core/build.gradle @@ -1,10 +1,5 @@ plugins { - id 'kotlin-multiplatform' -} - -repositories { - maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' } - mavenCentral() + id "org.jetbrains.kotlin.multiplatform" } kotlin { @@ -19,7 +14,7 @@ kotlin { sourceSets { commonMain { dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + api 'org.jetbrains.kotlin:kotlin-stdlib-common' } } commonTest { @@ -30,7 +25,7 @@ kotlin { } jvmMain { dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' } } jvmTest { @@ -41,7 +36,7 @@ kotlin { } jsMain { dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' + api 'org.jetbrains.kotlin:kotlin-stdlib-js' } } jsTest { 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 5f69f9d12..e48979430 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/FastHistogram.kt @@ -1,63 +1,42 @@ 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 scientifik.kmath.structures.* 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}") - } +private operator fun RealPoint.minus(other: RealPoint) = ListBuffer((0 until size).map { get(it) - other[it] }) - 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 } - } - - override val value: Number get() = counter.sum() - internal operator fun inc() = this.also { counter.increment() } - - override val dimension: Int get() = center.size -} +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 + * 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 lower: RealPoint, + private val upper: RealPoint, private val binNums: IntArray = IntArray(lower.size) { 20 } -) : Histogram { +) : MutableHistogram> { + + + private val strides = DefaultStrides(IntArray(binNums.size) { binNums[it] + 2 }) + + private val values: NDStructure = ndStructure(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()) 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") + if ((upper - lower).asSequence().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 @@ -70,17 +49,60 @@ class FastHistogram( } } + private fun getIndex(point: Buffer): IntArray = IntArray(dimension) { getIndex(it, point[it]) } - override fun get(point: Buffer): MultivariateBin? { - val index = IntArray(dimension) { getIndex(it, point[it]) } - return bins[index] + private fun getValue(index: IntArray): Long { + return values[index].sum() } - override fun put(point: Buffer) { - this[point]?.inc() ?: error("Could not find appropriate bin (should not be possible)") + fun getValue(point: Buffer): Long { + return getValue(getIndex(point)) } - override fun iterator(): Iterator = bins.asSequence().map { it.second }.iterator() + private fun getTemplate(index: IntArray): BinTemplate { + val center = index.mapIndexed { axis, i -> + when (i) { + 0 -> Double.NEGATIVE_INFINITY + strides.shape[axis] - 1 -> Double.POSITIVE_INFINITY + else -> lower[axis] + (i.toDouble() - 0.5) * binSize[axis] + } + }.toVector() + return BinTemplate(center, binSize) + } + + fun getTemplate(point: Buffer): BinTemplate { + return getTemplate(getIndex(point)) + } + + override fun get(point: Buffer): PhantomBin? { + val index = getIndex(point) + return PhantomBin(getTemplate(index), getValue(index)) + } + + override fun put(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()) + }.iterator() + + /** + * Convert this histogram into NDStructure containing bin values but not bin descriptions + */ + fun asNDStructure(): NDStructure { + return ndStructure(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()) + } companion object { @@ -108,8 +130,8 @@ class FastHistogram( */ fun fromRanges(vararg ranges: Pair, Int>): FastHistogram { return FastHistogram( - ranges.map { it.first.start }.toVector(), - ranges.map { it.first.endInclusive }.toVector(), + 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 1b5c231b4..08214142e 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,19 @@ package scientifik.kmath.histogram -import scientifik.kmath.operations.Space import scientifik.kmath.structures.ArrayBuffer import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.DoubleBuffer + +typealias Point = Buffer + +typealias RealPoint = Buffer /** * A simple geometric domain * TODO move to geometry module */ interface Domain { - operator fun contains(vector: Buffer): Boolean + operator fun contains(vector: Point): Boolean val dimension: Int } @@ -21,7 +25,7 @@ interface Bin : Domain { * The value of this bin */ val value: Number - val center: Buffer + val center: Point } interface Histogram> : Iterable { @@ -29,34 +33,31 @@ interface Histogram> : Iterable { /** * Find existing bin, corresponding to given coordinates */ - operator fun get(point: Buffer): B? + operator fun get(point: Point): B? /** * Dimension of the histogram */ val dimension: Int +} + +interface MutableHistogram>: Histogram{ + /** * Increment appropriate bin */ - fun put(point: Buffer) + fun put(point: Point, weight: Double = 1.0) } -fun Histogram.put(vararg point: T) = put(ArrayBuffer(point)) +fun MutableHistogram.put(vararg point: T) = put(ArrayBuffer(point)) -fun Histogram.fill(sequence: Iterable>) = sequence.forEach { put(it) } +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) } /** * 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, H : Histogram> : Space { - /** - * Rules for performing operations on bins - */ - val binSpace: Space> -} \ 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 new file mode 100644 index 000000000..ffffb0d7d --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/histogram/PhantomHistogram.kt @@ -0,0 +1,63 @@ +package scientifik.kmath.histogram + +import scientifik.kmath.linear.Vector +import scientifik.kmath.operations.Space +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.asSequence + +data class BinTemplate>(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 map 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> { + return 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-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt index 0b71405c6..223e63419 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LUDecomposition.kt @@ -1,5 +1,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.genericNdStructure @@ -9,7 +11,7 @@ import kotlin.math.absoluteValue /** * Implementation based on Apache common-maths LU-decomposition */ -abstract class LUDecomposition>(val matrix: Matrix) { +abstract class LUDecomposition, F : Field>(val matrix: Matrix) { private val field get() = matrix.context.field /** Entries of LU decomposition. */ @@ -31,7 +33,7 @@ abstract class LUDecomposition>(val matrix: Matrix) { * L is a lower-triangular matrix * @return the L matrix (or null if decomposed matrix is singular) */ - val l: Matrix by lazy { + val l: Matrix by lazy { matrix.context.produce { i, j -> when { j < i -> lu[i, j] @@ -48,7 +50,7 @@ abstract class LUDecomposition>(val matrix: Matrix) { * U is an upper-triangular matrix * @return the U matrix (or null if decomposed matrix is singular) */ - val u: Matrix by lazy { + val u: Matrix by lazy { matrix.context.produce { i, j -> if (j >= i) lu[i, j] else field.zero } @@ -64,7 +66,7 @@ abstract class LUDecomposition>(val matrix: Matrix) { * @return the P rows permutation matrix (or null if decomposed matrix is singular) * @see .getPivot */ - val p: Matrix by lazy { + val p: Matrix by lazy { matrix.context.produce { i, j -> //TODO ineffective. Need sparse matrix for that if (j == pivot[i]) field.one else field.zero @@ -181,7 +183,7 @@ abstract class LUDecomposition>(val matrix: Matrix) { } -class RealLUDecomposition(matrix: Matrix, 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 } @@ -194,12 +196,12 @@ class RealLUDecomposition(matrix: Matrix, private val singularityThresho /** Specialized solver. */ -object RealLUSolver : LinearSolver { +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: Matrix, b: Matrix): Matrix { + override fun solve(a: RealMatrix, b: RealMatrix): RealMatrix { val decomposition = decompose(a, a.context.field.zero) if (b.rows != a.rows) { 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 e585b884d..acd27dbd9 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt @@ -2,282 +2,16 @@ package scientifik.kmath.linear import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field -import scientifik.kmath.operations.Space -import scientifik.kmath.operations.SpaceElement -import scientifik.kmath.structures.* +import scientifik.kmath.operations.Norm -/** - * 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: Field) : Space> { - - /** - * Produce the element of this space - */ - abstract fun produce(initializer: (Int, 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 - */ - abstract fun produceSpace(rows: Int, columns: Int): MatrixSpace - - override val zero: Matrix by lazy { - produce { _, _ -> field.zero } - } - -// val one: Matrix by lazy { -// produce { i, j -> if (i == j) field.one else field.zero } -// } - - override fun add(a: Matrix, b: Matrix): Matrix { - return produce { i, j -> with(field) { 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 } } - } - - /** - * 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 - - if (rows != other.rows) return false - if (columns != other.columns) return false - if (field != other.field) return false - - return true - } - - override fun hashCode(): Int { - var result = rows - result = 31 * result + columns - result = 31 * result + field.hashCode() - return result - } - - -} - -infix fun Matrix.dot(b: Matrix): Matrix = this.context.multiply(this, b) - -/** - * 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] - } - } - - companion object { - - /** - * Create [ArrayMatrix] with custom field - */ - fun of(rows: Int, columns: Int, field: Field, initializer: (Int, Int) -> T) = - ArrayMatrix(ArrayMatrixSpace(rows, columns, field), initializer) - - /** - * Create [ArrayMatrix] of doubles. The implementation in general should be faster than generic one due to boxing. - */ - fun ofReal(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = - ArrayMatrix(ArrayMatrixSpace(rows, columns, DoubleField, realNDFieldFactory), initializer) - - /** - * Create a diagonal value matrix. By default value equals [Field.one]. - */ - fun diagonal(rows: Int, columns: Int, field: Field, values: (Int) -> T = { field.one }): Matrix { - return of(rows, columns, field) { i, j -> if (i == j) values(i) else field.zero } - } - - /** - * Equality check on two generic matrices - */ - fun equals(mat1: Matrix<*>, mat2: Matrix<*>): Boolean { - if (mat1 === mat2) return true - if (mat1.context != mat2.context) return false - for (i in 0 until mat1.rows) { - for (j in 0 until mat2.columns) { - if (mat1[i, j] != mat2[i, j]) return false - } - } - return true - } - } -} - - -/** - * A linear space for vectors - */ -abstract class VectorSpace(val size: Int, val field: Field) : Space> { - - abstract fun produce(initializer: (Int) -> T): Vector - - override val zero: Vector by lazy { produce { field.zero } } - - override fun add(a: Vector, b: Vector): Vector = produce { with(field) { a[it] + b[it] } } - - override fun multiply(a: Vector, k: Double): Vector = produce { with(field) { a[it] * k } } -} - - -interface Vector : SpaceElement, VectorSpace>, Buffer, Iterable { - override val size: Int get() = context.size - - companion object { - /** - * Create vector with custom field - */ - fun of(size: Int, field: Field, initializer: (Int) -> T) = - ArrayVector(ArrayVectorSpace(size, field), initializer) - - /** - * Create vector of [Double] - */ - fun ofReal(size: Int, initializer: (Int) -> Double) = - ArrayVector(ArrayVectorSpace(size, DoubleField, realNDFieldFactory), initializer) - - fun ofReal(vararg point: Double) = point.toVector() - - fun equals(v1: Vector<*>, v2: Vector<*>): Boolean { - if (v1 === v2) return true - if (v1.context != v2.context) return false - for (i in 0 until v2.size) { - if (v1[i] != v2[i]) return false - } - return true - } - } -} - -typealias NDFieldFactory = (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. - */ -class ArrayMatrixSpace( - rows: Int, - columns: Int, - field: Field, - val ndFactory: NDFieldFactory = genericNDFieldFactory(field) -) : MatrixSpace(rows, columns, field) { - - val ndField by lazy { - ndFactory(intArrayOf(rows, columns)) - } - - override fun produce(initializer: (Int, Int) -> T): Matrix = ArrayMatrix(this, initializer) - - override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace { - return ArrayMatrixSpace(rows, columns, field, ndFactory) - } -} - -class ArrayVectorSpace( - size: Int, - field: Field, - 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) -} - -/** - * Member of [ArrayMatrixSpace] which wraps 2-D array - */ -class ArrayMatrix internal constructor(override val context: ArrayMatrixSpace, val array: NDArray) : 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 array[i, j] - } - - override val self: ArrayMatrix get() = this -} - - -class ArrayVector internal constructor(override val context: ArrayVectorSpace, val array: NDArray) : Vector { - - constructor(context: ArrayVectorSpace, initializer: (Int) -> T) : this(context, context.ndField.produce { list -> initializer(list[0]) }) - - init { - if (context.size != array.shape[0]) { - error("Array dimension mismatch") - } - } - - override fun get(index: Int): T { - return array[index] - } - - override val self: ArrayVector get() = this - - override fun iterator(): Iterator = (0 until size).map { array[it] }.iterator() - - override fun copy(): ArrayVector = ArrayVector(context, array) - - override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } -} - -typealias RealVector = Vector /** * 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, Matrix.diagonal(a.rows, a.columns, a.context.field)) +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)) } /** @@ -291,7 +25,7 @@ fun List.toVector() = Vector.ofReal(this.size) { this[it] } /** * Convert matrix to vector if it is possible */ -fun Matrix.toVector(): Vector { +fun > Matrix.toVector(): Vector { return when { this.columns == 1 -> { // if (this is ArrayMatrix) { @@ -307,7 +41,7 @@ fun Matrix.toVector(): Vector { } } -fun Vector.toMatrix(): Matrix { +fun > Vector.toMatrix(): Matrix { // return if (this is ArrayVector) { // //Reuse existing underlying array // ArrayMatrix(ArrayMatrixSpace(size, 1, context.field, context.ndFactory), array) @@ -315,6 +49,14 @@ fun Vector.toMatrix(): Matrix { // //Generic vector // matrix(size, 1, context.field) { i, j -> get(i) } // } - return Matrix.of(size, 1, context.field) { i, _ -> get(i) } + return Matrix.of(size, 1, context.space) { i, _ -> get(i) } } +object VectorL2Norm : Norm, Double> { + override fun norm(arg: Vector): Double { + return kotlin.math.sqrt(arg.sumByDouble { it.toDouble() }) + } +} + +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 new file mode 100644 index 000000000..243eb2d6c --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Matrix.kt @@ -0,0 +1,187 @@ +package scientifik.kmath.linear + +import scientifik.kmath.operations.* +import scientifik.kmath.structures.* + +/** + * The space for linear elements. Supports scalar product alongside with standard linear operations. + * @param T type of individual element of the vector or matrix + * @param V the type of vector space element + */ +abstract class MatrixSpace>(val rows: Int, val columns: Int, val field: F) : Space> { + + /** + * Produce the element of this space + */ + abstract fun produce(initializer: (Int, 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 + */ + abstract fun produceSpace(rows: Int, columns: Int): MatrixSpace + + override val zero: Matrix by lazy { + produce { _, _ -> field.zero } + } + +// val one: Matrix by lazy { +// produce { i, j -> if (i == j) field.one else field.zero } +// } + + override fun add(a: Matrix, b: Matrix): Matrix { + return produce { i, j -> with(field) { 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 } } + } + + /** + * 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 + + if (rows != other.rows) return false + if (columns != other.columns) return false + if (field != other.field) return false + + return true + } + + override fun hashCode(): Int { + var result = rows + result = 31 * result + columns + result = 31 * result + field.hashCode() + return result + } +} + +infix fun > Matrix.dot(b: Matrix): Matrix = this.context.multiply(this, b) + +/** + * 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] + } + } + + companion object { + + /** + * Create [ArrayMatrix] with custom field + */ + fun > of(rows: Int, columns: Int, field: F, initializer: (Int, Int) -> T) = + ArrayMatrix(ArrayMatrixSpace(rows, columns, field), initializer) + + /** + * Create [ArrayMatrix] of doubles. The implementation in general should be faster than generic one due to boxing. + */ + fun ofReal(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = + ArrayMatrix(ArrayMatrixSpace(rows, columns, DoubleField, realNDFieldFactory), initializer) + + /** + * Create a diagonal value matrix. By default value equals [Field.one]. + */ + fun > 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 } + } + + /** + * Equality check on two generic matrices + */ + fun equals(mat1: Matrix<*, *>, mat2: Matrix<*, *>): Boolean { + if (mat1 === mat2) return true + if (mat1.context != mat2.context) return false + for (i in 0 until mat1.rows) { + for (j in 0 until mat2.columns) { + if (mat1[i, j] != mat2[i, j]) return false + } + } + return true + } + } +} + + + + +typealias NDFieldFactory = (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)) + } + + override fun produce(initializer: (Int, Int) -> T): Matrix = ArrayMatrix(this, initializer) + + override fun produceSpace(rows: Int, columns: Int): ArrayMatrixSpace { + return ArrayMatrixSpace(rows, columns, field, ndFactory) + } +} + +/** + * 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 +} diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt new file mode 100644 index 000000000..5ab9907de --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/Vector.kt @@ -0,0 +1,103 @@ +package scientifik.kmath.linear + +import scientifik.kmath.histogram.Point +import scientifik.kmath.operations.DoubleField +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.structures.NDElement +import scientifik.kmath.structures.get + +/** + * A linear space for vectors. + * Could be used on any point-like structure + */ +abstract class VectorSpace>(val size: Int, val space: S) : Space> { + + abstract fun produce(initializer: (Int) -> T): Vector + + override val zero: Vector by lazy { 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 } } +} + + +/** + * A point coupled to the linear space + */ +interface Vector> : SpaceElement, VectorSpace>, Point, Iterable { + 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()) + + companion object { + /** + * Create vector with custom field + */ + fun > of(size: Int, field: F, initializer: (Int) -> T) = + ArrayVector(ArrayVectorSpace(size, field), initializer) + + private val realSpaceCache = HashMap>() + + 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) +} + + +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]) }) + + init { + if (context.size != element.shape[0]) { + error("Array dimension mismatch") + } + } + + override fun get(index: Int): T { + return element[index] + } + + override val self: ArrayVector get() = this + + override fun iterator(): Iterator = (0 until size).map { element[it] }.iterator() + + override fun toString(): String = this.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.toString() } +} + 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 dd3be256e..8d3c01d28 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -38,4 +38,8 @@ data class Complex(val re: Double, val im: Double) : FieldElement : + Field, + TrigonometricOperations, + PowerOperations, + ExponentialOperations + + /** * Field for real values */ -object RealField : Field, TrigonometricOperations, PowerOperations, ExponentialOperations { +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) @@ -13,33 +23,24 @@ object RealField : Field, TrigonometricOperations, PowerOperations { - /* - * The class uses composition instead of inheritance since Double is final - */ - - override fun toByte(): Byte = value.toByte() - override fun toChar(): Char = value.toChar() - override fun toDouble(): Double = value - override fun toFloat(): Float = value.toFloat() - override fun toInt(): Int = value.toInt() - override fun toLong(): Long = value.toLong() - override fun toShort(): Short = value.toShort() +inline class Real(val value: Double) : FieldElement { //values are dynamically calculated to save memory override val self @@ -48,24 +49,41 @@ data class Real(val value: Double) : Number(), FieldElement { override val context get() = RealField + companion object { + + } } /** * A field for double without boxing. Does not produce appropriate field element */ -object DoubleField : Field, TrigonometricOperations, PowerOperations, ExponentialOperations { +object DoubleField : 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 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 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: Double): Double = arg.pow(pow) - override fun exp(arg: Double): Double =kotlin.math.exp(arg) + override fun exp(arg: Double): Double = kotlin.math.exp(arg) - override fun ln(arg: Double): Double = kotlin.math.ln(arg) + override fun ln(arg: Double): Double = kotlin.math.ln(arg) + + override fun norm(arg: Double): Double = kotlin.math.abs(arg) +} + +/** + * A field for double without boxing. Does not produce appropriate field element + */ +object IntField : Field{ + 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 } \ 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 7628cb4fc..9e5c8a801 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -45,4 +45,10 @@ interface ExponentialOperations { } fun >> exp(arg: T): T = arg.context.exp(arg) -fun >> ln(arg: T): T = arg.context.ln(arg) \ No newline at end of file +fun >> ln(arg: T): T = arg.context.ln(arg) + +interface Norm { + fun norm(arg: T): R +} + +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/Buffers.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt index 04983ef85..69e8163c6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -2,30 +2,40 @@ package scientifik.kmath.structures /** - * A generic linear buffer for both primitives and objects + * A generic random access structure for both primitives and objects */ -interface Buffer : Iterable { +interface Buffer { val size: Int operator fun get(index: Int): T - /** - * A shallow copy of the buffer - */ - fun copy(): Buffer + operator fun iterator(): Iterator } +fun Buffer.asSequence(): Sequence = iterator().asSequence() + interface MutableBuffer : Buffer { operator fun set(index: Int, value: T) /** * A shallow copy of the buffer */ - override fun copy(): MutableBuffer + fun copy(): MutableBuffer } -inline class ListBuffer(private val list: MutableList) : MutableBuffer { + +inline class ListBuffer(private val list: List) : Buffer { + + override val size: Int + get() = list.size + + override fun get(index: Int): T = list[index] + + override fun iterator(): Iterator = list.iterator() +} + +inline class MutableListBuffer(private val list: MutableList) : MutableBuffer { override val size: Int get() = list.size @@ -36,12 +46,13 @@ inline class ListBuffer(private val list: MutableList) : MutableBuffer list[index] = value } - override fun iterator(): Iterator = list.iterator() + override fun iterator(): Iterator = list.iterator() - override fun copy(): MutableBuffer = ListBuffer(ArrayList(list)) + override fun copy(): MutableBuffer = MutableListBuffer(ArrayList(list)) } class ArrayBuffer(private val array: Array) : MutableBuffer { + //Can't inline because array invariant override val size: Int get() = array.size @@ -51,12 +62,12 @@ class ArrayBuffer(private val array: Array) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = ArrayBuffer(array.copyOf()) } -class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { +inline class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { override val size: Int get() = array.size @@ -66,15 +77,63 @@ class DoubleBuffer(private val array: DoubleArray) : MutableBuffer { array[index] = value } - override fun iterator(): Iterator = array.iterator() + override fun iterator(): Iterator = array.iterator() override fun copy(): MutableBuffer = DoubleBuffer(array.copyOf()) } -inline fun buffer(size: Int, noinline initializer: (Int) -> T): Buffer { - return ArrayBuffer(Array(size, initializer)) +inline class IntBuffer(private val array: IntArray) : MutableBuffer { + override val size: Int + get() = array.size + + override fun get(index: Int): Int = array[index] + + override fun set(index: Int, value: Int) { + array[index] = value + } + + override fun iterator(): Iterator = array.iterator() + + override fun copy(): MutableBuffer = IntBuffer(array.copyOf()) } +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() +} + +/** + * Convert this buffer to read-only buffer + */ +fun Buffer.asReadOnly(): Buffer = if (this is MutableBuffer) { + ReadOnlyBuffer(this) +} else { + this +} + +/** + * Create most appropriate immutable buffer for given type avoiding boxing wherever possible + */ +@Suppress("UNCHECKED_CAST") +inline fun buffer(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)) + } +} + +/** + * 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 { - return ArrayBuffer(Array(size, initializer)) + 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)) + } } \ 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 new file mode 100644 index 000000000..93ffad5a3 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -0,0 +1,42 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.ExponentialOperations +import scientifik.kmath.operations.ExtendedField +import scientifik.kmath.operations.PowerOperations +import scientifik.kmath.operations.TrigonometricOperations + + +/** + * NDField that supports [ExtendedField] operations on its elements + */ +class ExtendedNDField>(shape: IntArray, field: F) : NDField(shape, field), + TrigonometricOperations>, + PowerOperations>, + ExponentialOperations> { + + override fun produceStructure(initializer: F.(IntArray) -> N): NDStructure { + return genericNdStructure(shape) { field.initializer(it) } + } + + override fun power(arg: NDElement, pow: Double): NDElement { + return arg.transform { d -> with(field) { power(d, pow) } } + } + + override fun exp(arg: NDElement): NDElement { + return arg.transform { d -> with(field) { exp(d) } } + } + + override fun ln(arg: NDElement): NDElement { + return arg.transform { d -> with(field) { ln(d) } } + } + + override fun sin(arg: NDElement): NDElement { + return arg.transform { d -> with(field) { sin(d) } } + } + + override fun cos(arg: NDElement): NDElement { + return arg.transform { d -> with(field) { cos(d) } } + } +} + + diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt new file mode 100644 index 000000000..0428535f9 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/LazyStructure.kt @@ -0,0 +1,20 @@ +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-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt index 9461bee85..cddac5801 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDField.kt @@ -15,25 +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: Field) : Field> { +abstract class NDField>(val shape: IntArray, val field: F) : Field> { - abstract fun produceStructure(initializer: (IntArray) -> T): NDStructure + abstract fun produceStructure(initializer: F.(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 */ - fun produce(initializer: (IntArray) -> T): NDArray = NDArray(this, produceStructure(initializer)) + fun produce(initializer: F.(IntArray) -> T): NDElement = NDStructureElement(this, produceStructure(initializer)) - override val zero: NDArray by lazy { - produce { this.field.zero } + override val zero: NDElement by lazy { + produce { zero } } /** * Check the shape of given NDArray and throw exception if it does not coincide with shape of the field */ - private fun checkShape(vararg arrays: NDArray) { - arrays.forEach { + private fun checkShape(vararg elements: NDElement) { + elements.forEach { if (!shape.contentEquals(it.shape)) { throw ShapeMismatchException(shape, it.shape) } @@ -43,7 +43,7 @@ abstract class NDField(val shape: IntArray, val field: Field) : Field, b: NDArray): NDArray { + override fun add(a: NDElement, b: NDElement): NDElement { checkShape(a, b) return produce { with(field) { a[it] + b[it] } } } @@ -51,18 +51,18 @@ abstract class NDField(val shape: IntArray, val field: Field) : Field, k: Double): NDArray { + override fun multiply(a: NDElement, k: Double): NDElement { checkShape(a) return produce { with(field) { a[it] * k } } } - override val one: NDArray - get() = produce { this.field.one } + override val one: NDElement + get() = produce { one } /** * Element-by-element multiplication */ - override fun multiply(a: NDArray, b: NDArray): NDArray { + override fun multiply(a: NDElement, b: NDElement): NDElement { checkShape(a) return produce { with(field) { a[it] * b[it] } } } @@ -70,73 +70,77 @@ abstract class NDField(val shape: IntArray, val field: Field) : Field, b: NDArray): NDArray { + override fun divide(a: NDElement, b: NDElement): NDElement { checkShape(a) return produce { with(field) { a[it] / b[it] } } } - /** - * Reverse sum operation - */ - operator fun T.plus(arg: NDArray): NDArray = arg + this - - /** - * Reverse minus operation - */ - operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> - with(arg.context.field) { - this@minus - value - } - } - - /** - * Reverse product operation - */ - operator fun T.times(arg: NDArray): NDArray = arg * this - - /** - * Reverse division operation - */ - operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> - with(arg.context.field) { - this@div / value - } - } +// /** +// * 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 +// } +// } } + +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)) } + + /** - * Immutable [NDStructure] coupled to the context. Emulates Python ndarray + * Read-only [NDStructure] coupled to the context. */ -data class NDArray(override val context: NDField, private val structure: NDStructure) : FieldElement, NDField>, 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: NDArray - get() = this - - fun transform(action: (IntArray, T) -> T): NDArray = context.produce { action(it, get(*it)) } + 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(ndArray: NDArray): NDArray = ndArray.transform { _, value -> this(value) } +operator fun > Function1.invoke(ndElement: NDElement): NDElement = ndElement.transform {value -> this@invoke(value) } /* plus and minus */ /** - * Summation operation for [NDArray] and single element + * Summation operation for [NDElement] and single element */ -operator fun NDArray.plus(arg: T): NDArray = transform { _, value -> +operator fun > NDElement.plus(arg: T): NDElement = transform {value -> with(context.field) { arg + value } } /** - * Subtraction operation between [NDArray] and single element + * Subtraction operation between [NDElement] and single element */ -operator fun NDArray.minus(arg: T): NDArray = transform { _, value -> +operator fun > NDElement.minus(arg: T): NDElement = transform {value -> with(context.field) { arg - value } @@ -145,25 +149,25 @@ operator fun NDArray.minus(arg: T): NDArray = transform { _, value -> /* prod and div */ /** - * Product operation for [NDArray] and single element + * Product operation for [NDElement] and single element */ -operator fun NDArray.times(arg: T): NDArray = transform { _, value -> +operator fun > NDElement.times(arg: T): NDElement = transform { value -> with(context.field) { arg * value } } /** - * Division operation between [NDArray] and single element + * Division operation between [NDElement] and single element */ -operator fun NDArray.div(arg: T): NDArray = transform { _, value -> +operator fun > NDElement.div(arg: T): NDElement = transform { value -> with(context.field) { arg / value } } -class GenericNDField(shape: IntArray, field: Field) : NDField(shape, field) { - override fun produceStructure(initializer: (IntArray) -> T): NDStructure = genericNdStructure(shape, initializer) +class GenericNDField>(shape: IntArray, field: F) : NDField(shape, field) { + override fun produceStructure(initializer: F.(IntArray) -> T): NDStructure = genericNdStructure(shape) { field.initializer(it) } } //typealias NDFieldFactory = (IntArray)->NDField @@ -172,22 +176,25 @@ 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 realNDArray(shape: IntArray, initializer: DoubleField.(IntArray) -> Double = { 0.0 }): NDElement { + return ExtendedNDField(shape, DoubleField).produce(initializer) } - fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDArray { + fun real1DArray(dim: Int, initializer: (Int) -> Double = { _ -> 0.0 }): NDElement { return realNDArray(intArrayOf(dim)) { initializer(it[0]) } } - fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDArray { + fun real2DArray(dim1: Int, dim2: Int, initializer: (Int, Int) -> Double = { _, _ -> 0.0 }): NDElement { 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 { + 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]) } } + inline fun produceReal(shape: IntArray, block: ExtendedNDField.() -> NDElement) = + ExtendedNDField(shape, DoubleField).run(block) + // /** // * Simple boxing NDField // */ @@ -196,7 +203,7 @@ object NDArrays { /** * Simple boxing NDArray */ - fun create(field: Field, shape: IntArray, initializer: (IntArray) -> T): NDArray { + fun > create(field: F, shape: IntArray, initializer: (IntArray) -> T): NDElement { 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 index a6880362d..f9521cf93 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/NDStructure.kt @@ -1,7 +1,7 @@ package scientifik.kmath.structures -interface NDStructure : Iterable> { +interface NDStructure { val shape: IntArray @@ -9,6 +9,8 @@ interface NDStructure : Iterable> { get() = shape.size operator fun get(index: IntArray): T + + fun elements(): Sequence> } operator fun NDStructure.get(vararg index: Int): T = get(index) @@ -18,7 +20,7 @@ interface MutableNDStructure : NDStructure { } fun MutableNDStructure.transformInPlace(action: (IntArray, T) -> T) { - for ((index, oldValue) in this) { + elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) } } @@ -76,22 +78,22 @@ 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 $value out of shape bounds: (0,${shape[i]})") + throw RuntimeException("Index $value out of shape bounds: (0,${this.shape[i]})") } 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() + val res = IntArray(shape.size) + var current = offset + var strideIndex = strides.size - 2 + while (strideIndex >= 0) { + res[strideIndex] = (current / strides[strideIndex]) + current %= strides[strideIndex] + strideIndex-- + } + return res } override val linearSize: Int @@ -107,8 +109,8 @@ abstract class GenericNDStructure> : NDStructure { override val shape: IntArray get() = strides.shape - override fun iterator(): Iterator> = - strides.indices().map { it to this[it] }.iterator() + override fun elements()= + strides.indices().map { it to this[it] } } /** @@ -126,10 +128,10 @@ class BufferNDStructure( } } -inline fun ndStructure(strides: Strides, noinline initializer: (IntArray) -> T) = - BufferNDStructure(strides, buffer(strides.linearSize){ i-> initializer(strides.index(i))}) +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) = +inline fun ndStructure(shape: IntArray, noinline initializer: (IntArray) -> T) = ndStructure(DefaultStrides(shape), initializer) @@ -153,22 +155,22 @@ class MutableBufferNDStructure( /** * 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(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) = +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{ +fun genericNdStructure(shape: IntArray, initializer: (IntArray) -> T): MutableBufferNDStructure { val strides = DefaultStrides(shape) - val sequence = sequence{ - strides.indices().forEach{ + val sequence = sequence { + strides.indices().forEach { yield(initializer(it)) } } - val buffer = ListBuffer(sequence.toMutableList()) + val buffer = MutableListBuffer(sequence.toMutableList()) return MutableBufferNDStructure(strides, buffer) } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt index 543e13d79..8e2845b9e 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/expressions/FieldExpressionContextTest.kt @@ -39,4 +39,15 @@ class FieldExpressionContextTest { val expression = FieldExpressionContext(DoubleField).expression() assertEquals(expression("x" to 1.0), 4.0) } + + @Test + fun valueExpression() { + val expressionBuilder: FieldExpressionContext.()->Expression = { + val x = variable("x") + x * x + 2 * x + 1.0 + } + + val expression = FieldExpressionContext(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 2fbbbc88d..842a12ae4 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/histogram/MultivariateHistogramTest.kt @@ -14,10 +14,11 @@ class MultivariateHistogramTest { (-1.0..1.0), (-1.0..1.0) ) - histogram.put(0.6, 0.6) + histogram.put(0.55, 0.55) val bin = histogram.find { it.value.toInt() > 0 }!! - assertTrue { bin.contains(Vector.ofReal(0.6, 0.6)) } - assertFalse { bin.contains(Vector.ofReal(-0.6, 0.6)) } + assertTrue { bin.contains(Vector.ofReal(0.55, 0.55)) } + assertTrue { bin.contains(Vector.ofReal(0.6, 0.5)) } + assertFalse { bin.contains(Vector.ofReal(-0.55, 0.55)) } } @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 b07158cc3..0d33204df 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -6,9 +6,11 @@ import kotlin.test.assertEquals class RealFieldTest { @Test fun testSqrt() { + + //fails because KT-27586 val sqrt = with(RealField) { - sqrt(25 * one) + sqrt( 25 * one) } - assertEquals(5.0, sqrt.toDouble()) + assertEquals(5.0, sqrt.value) } } \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt similarity index 53% rename from kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt rename to kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt index 329f13189..990adb0c7 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/structures/NumberNDFieldTest.kt @@ -1,11 +1,14 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.Norm +import scientifik.kmath.structures.NDArrays.produceReal import scientifik.kmath.structures.NDArrays.real2DArray +import kotlin.math.abs import kotlin.math.pow import kotlin.test.Test import kotlin.test.assertEquals -class RealNDFieldTest { +class NumberNDFieldTest { val array1 = real2DArray(3, 3) { i, j -> (i + j).toDouble() } val array2 = real2DArray(3, 3) { i, j -> (i - j).toDouble() } @@ -29,7 +32,7 @@ class RealNDFieldTest { for (i in 0..2) { for (j in 0..2) { val expected = (i * 10 + j).toDouble() - assertEquals(expected, array[i, j],"Error at index [$i, $j]") + assertEquals(expected, array[i, j], "Error at index [$i, $j]") } } } @@ -38,6 +41,28 @@ class RealNDFieldTest { fun testExternalFunction() { val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 } val result = function(array1) + 1.0 - assertEquals(10.0, result[1,1]) + assertEquals(10.0, result[1, 1]) + } + + @Test + fun testLibraryFunction() { + val abs: (Double) -> Double = ::abs + val result = abs(array2) + assertEquals(2.0, result[0, 2]) + } + + object L2Norm : Norm, Double> { + override fun norm(arg: NDElement): Double { + return kotlin.math.sqrt(arg.sumByDouble { it.second.toDouble() }) + } + } + + @Test + fun testInternalContext() { + produceReal(array1.shape) { + with(L2Norm) { + 1 + norm(array1) + exp(array2) + } + } } } 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 2382c379c..26ad520e6 100644 --- a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/histogram/UnivariateHistogram.kt @@ -26,7 +26,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) : MutableHistogram { private val bins: TreeMap = TreeMap() @@ -58,7 +58,10 @@ class UnivariateHistogram private constructor(private val factory: (Double) -> U (get(value) ?: createBin(value)).inc() } - override fun put(point: Buffer) = put(point[0]) + override fun put(point: Buffer, weight: Double) { + if (weight != 1.0) TODO("Implement weighting") + put(point[0]) + } companion object { fun uniform(binSize: Double, start: Double = 0.0): UnivariateHistogram { diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt new file mode 100644 index 000000000..a5cc8c5de --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/BufferSpec.kt @@ -0,0 +1,55 @@ +package scientifik.kmath.structures + +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 +} + +/** + * 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 current position + */ + fun ByteBuffer.readObject(): T { + val buffer = ByteArray(unitSize) + get(buffer) + return fromBuffer(ByteBuffer.wrap(buffer)) + } + + /** + * Read an object from buffer in given index (not buffer position + */ + fun ByteBuffer.readObject(index: Int): T { + val dup = duplicate() + dup.position(index*unitSize) + return dup.readObject() + } + + /** + * Write object to [ByteBuffer] in current buffer position + */ + fun ByteBuffer.writeObject(obj: T) { + val buffer = toBuffer(obj).apply { rewind() } + assert(buffer.limit() == unitSize) + put(buffer) + } + + /** + * Put an object in given index + */ + fun ByteBuffer.writeObject(index: Int, obj: T) { + val dup = duplicate() + dup.position(index*unitSize) + dup.writeObject(obj) + } +} \ 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 new file mode 100644 index 000000000..68e989e81 --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ComplexBufferSpec.kt @@ -0,0 +1,25 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Complex +import java.nio.ByteBuffer + +object ComplexBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 16 + + override fun fromBuffer(buffer: ByteBuffer): Complex { + val re = buffer.getDouble(0) + val im = buffer.getDouble(8) + return Complex(re, im) + } + + override fun toBuffer(value: Complex): ByteBuffer = ByteBuffer.allocate(16).apply { + putDouble(value.re) + putDouble(value.im) + } +} + +/** + * Create a mutable buffer which ignores boxing + */ +fun Complex.Companion.createBuffer(size: Int) = ObjectBuffer.create(ComplexBufferSpec, size) + diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt new file mode 100644 index 000000000..b50ed9674 --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/ObjectBuffer.kt @@ -0,0 +1,28 @@ +package scientifik.kmath.structures + +import java.nio.ByteBuffer + +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) + } +} \ 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 new file mode 100644 index 000000000..761cfc2db --- /dev/null +++ b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/structures/RealBufferSpec.kt @@ -0,0 +1,23 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Real +import java.nio.ByteBuffer + +object RealBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 8 + + override fun fromBuffer(buffer: ByteBuffer): Real = Real(buffer.double) + + override fun toBuffer(value: Real): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value.value) } +} + +object DoubleBufferSpec : FixedSizeBufferSpec { + override val unitSize: Int = 8 + + override fun fromBuffer(buffer: ByteBuffer): Double = buffer.double + + override fun toBuffer(value: Double): ByteBuffer = ByteBuffer.allocate(8).apply { putDouble(value) } +} + +fun Double.Companion.createBuffer(size: Int) = ObjectBuffer.create(DoubleBufferSpec, size) +fun Real.Companion.createBuffer(size: Int) = ObjectBuffer.create(RealBufferSpec, size) \ 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 new file mode 100644 index 000000000..350f06848 --- /dev/null +++ b/kmath-core/src/jvmTest/kotlin/scientifik/kmath/structures/ComplexBufferSpecTest.kt @@ -0,0 +1,17 @@ +package scientifik.kmath.structures + +import org.junit.Test +import scientifik.kmath.operations.Complex +import kotlin.test.assertEquals + +class ComplexBufferSpecTest { + @Test + fun testComplexBuffer() { + val buffer = Complex.createBuffer(20) + (0 until 20).forEach { + buffer[it] = Complex(it.toDouble(), -it.toDouble()) + } + + assertEquals(Complex(5.0, -5.0), buffer[5]) + } +} \ No newline at end of file diff --git a/kmath-coroutines/build.gradle b/kmath-coroutines/build.gradle new file mode 100644 index 000000000..7149e8bb9 --- /dev/null +++ b/kmath-coroutines/build.gradle @@ -0,0 +1,42 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.mingwX64, 'mingw') + } + sourceSets { + commonMain { + dependencies { + api project(":kmath-core") + api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion" + } + } + commonTest { + dependencies { + api 'org.jetbrains.kotlin:kotlin-test-common' + api 'org.jetbrains.kotlin:kotlin-test-annotations-common' + } + } + jvmMain { + dependencies { + api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" + } + } + jvmTest { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-test' + implementation 'org.jetbrains.kotlin:kotlin-test-junit' + } + } +// mingwMain { +// } +// mingwTest { +// } + } +} diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt new file mode 100644 index 000000000..a837cde01 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/CoroutinesExtra.kt @@ -0,0 +1,11 @@ +package scientifik.kmath.structures + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +expect fun 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 new file mode 100644 index 000000000..823245685 --- /dev/null +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/structures/LazyNDField.kt @@ -0,0 +1,79 @@ +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) } + + + override fun add(a: NDElement, b: NDElement): NDElement { + return LazyNDStructure(this) { index -> + val aDeferred = a.deferred(index) + val bDeferred = b.deferred(index) + aDeferred.await() + bDeferred.await() + } + } + + override fun multiply(a: NDElement, k: Double): NDElement { + return LazyNDStructure(this) { index -> a.await(index) * k } + } + + override fun multiply(a: NDElement, b: NDElement): NDElement { + return LazyNDStructure(this) { index -> + val aDeferred = a.deferred(index) + val bDeferred = b.deferred(index) + aDeferred.await() * bDeferred.await() + } + } + + override fun divide(a: NDElement, b: NDElement): NDElement { + return LazyNDStructure(this) { index -> + val aDeferred = a.deferred(index) + val bDeferred = b.deferred(index) + aDeferred.await() / bDeferred.await() + } + } +} + +class LazyNDStructure>(override val context: LazyNDField, val function: suspend F.(IntArray) -> T) : NDElement, NDStructure { + override val self: NDElement 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) } } + + 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) + return strides.indices().map { index -> index to runBlocking { await(index) } } + } +} + +fun NDElement.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) + +fun > NDElement.lazy(scope: CoroutineScope = GlobalScope): LazyNDStructure { + return if (this is LazyNDStructure) { + this + } else { + val context = LazyNDField(context.shape, context.field) + LazyNDStructure(context) { get(it) } + } +} + +inline fun > LazyNDStructure.transformIndexed(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 -> + 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 new file mode 100644 index 000000000..403fe2d31 --- /dev/null +++ b/kmath-coroutines/src/commonTest/kotlin/scientifik/kmath/structures/LazyNDFieldTest.kt @@ -0,0 +1,20 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.IntField +import kotlin.test.Test +import kotlin.test.assertEquals + + +class LazyNDFieldTest { + @Test + fun testLazyStructure() { + var counter = 0 + val regularStructure = NDArrays.create(IntField, intArrayOf(2, 2, 2)) { it[0] + it[1] - it[2] } + val result = (regularStructure.lazy() + 2).transform { + counter++ + it * it + } + assertEquals(4, result[0,0,0]) + assertEquals(1, counter) + } +} \ 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 new file mode 100644 index 000000000..72a764729 --- /dev/null +++ b/kmath-coroutines/src/jvmMain/kotlin/scientifik/kmath/structures/_CoroutinesExtra.kt @@ -0,0 +1,6 @@ +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/kmath-io/build.gradle b/kmath-io/build.gradle new file mode 100644 index 000000000..28fb7eee5 --- /dev/null +++ b/kmath-io/build.gradle @@ -0,0 +1,55 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + fromPreset(presets.js, 'js') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.mingwX64, 'mingw') + } + sourceSets { + commonMain { + dependencies { + api project(":kmath-core") + implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" + } + } + commonTest { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-test-common' + implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common' + } + } + jvmMain { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" + } + } + jvmTest { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-test' + implementation 'org.jetbrains.kotlin:kotlin-test-junit' + } + } + jsMain { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' + } + } + jsTest { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-test-js' + } + } +// mingwMain { +// } +// mingwTest { +// } + } +} diff --git a/kmath-jmh/build.gradle b/kmath-jmh/build.gradle index a16bf3022..1876d86d7 100644 --- a/kmath-jmh/build.gradle +++ b/kmath-jmh/build.gradle @@ -4,12 +4,7 @@ plugins { 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' + compile project(':kmath-core') + //jmh project(':kmath-core') } \ 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 index eb40195e0..fe10fbd75 100644 --- a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt +++ b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/ArrayBenchmark.kt @@ -4,8 +4,7 @@ import org.openjdk.jmh.annotations.* import java.nio.IntBuffer -@Fork(1) -@Warmup(iterations = 2) +@Warmup(iterations = 1) @Measurement(iterations = 5) @State(Scope.Benchmark) open class ArrayBenchmark { @@ -30,7 +29,6 @@ open class ArrayBenchmark { for (i in 1..10000) { res += array[10000 - i] } - print(res) } @Benchmark @@ -39,7 +37,6 @@ open class ArrayBenchmark { for (i in 1..10000) { res += arrayBuffer.get(10000 - i) } - print(res) } @Benchmark @@ -48,6 +45,5 @@ open class ArrayBenchmark { for (i in 1..10000) { res += nativeBuffer.get(10000 - i) } - print(res) } } \ No newline at end of file diff --git a/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt new file mode 100644 index 000000000..fd27ae3e0 --- /dev/null +++ b/kmath-jmh/src/jmh/kotlin/scientifik/kmath/structures/BufferBenchmark.kt @@ -0,0 +1,38 @@ +package scientifik.kmath.structures + +import org.openjdk.jmh.annotations.* +import scientifik.kmath.operations.Complex + +@Warmup(iterations = 1) +@Measurement(iterations = 5) +@State(Scope.Benchmark) +open class BufferBenchmark { + + @Benchmark + fun genericDoubleBufferReadWrite() { + val buffer = Double.createBuffer(size) + (0 until size).forEach { + buffer[it] = it.toDouble() + } + + (0 until size).forEach { + buffer[it] + } + } + + @Benchmark + fun complexBufferReadWrite() { + val buffer = Complex.createBuffer(size/2) + (0 until size/2).forEach { + buffer[it] = Complex(it.toDouble(), -it.toDouble()) + } + + (0 until size/2).forEach { + buffer[it] + } + } + + companion object { + const val size = 1000 + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index df4ccc99e..000000000 --- a/settings.gradle +++ /dev/null @@ -1,13 +0,0 @@ -pluginManagement { - repositories { - mavenCentral() - maven { url = 'https://plugins.gradle.org/m2/' } - } -} - -enableFeaturePreview('GRADLE_METADATA') - -rootProject.name = 'kmath' -include ':kmath-core' -include ':kmath-jmh' - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..0c91b8446 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + mavenCentral() + maven("https://plugins.gradle.org/m2/") + } +} + +//enableFeaturePreview("GRADLE_METADATA") + +rootProject.name = "kmath" +include( + ":kmath-core", + ":kmath-io", + ":kmath-coroutines", + ":kmath-jmh" +)