From 00f6c027b41fae02e24e55055d821a40ce21e093 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 21 Jun 2019 12:34:04 +0300 Subject: [PATCH 01/32] Experimental type-safe dimensions --- build.gradle.kts | 2 +- buildSrc/build.gradle.kts | 2 +- kmath-dimensions/build.gradle.kts | 14 ++ .../scientifik/kmath/dimensions/Dimensions.kt | 34 +++++ .../scientifik/kmath/dimensions/Wrappers.kt | 130 ++++++++++++++++++ .../kmath/dimensions/DMatrixContextTest.kt | 27 ++++ settings.gradle.kts | 1 + 7 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 kmath-dimensions/build.gradle.kts create mode 100644 kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt create mode 100644 kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt create mode 100644 kmath-dimensions/src/commonTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index d7b7ac78a..33fe7c33a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -val kmathVersion by extra("0.1.3-dev-1") +val kmathVersion by extra("0.1.3-dev-2") allprojects { repositories { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 318189162..a2760f609 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,7 +7,7 @@ repositories { jcenter() } -val kotlinVersion = "1.3.31" +val kotlinVersion = "1.3.40" // Add plugins used in buildSrc as dependencies, also we should specify version only here dependencies { diff --git a/kmath-dimensions/build.gradle.kts b/kmath-dimensions/build.gradle.kts new file mode 100644 index 000000000..59a4c0fc3 --- /dev/null +++ b/kmath-dimensions/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + `npm-multiplatform` +} + +description = "A proof of concept module for adding typ-safe dimensions to structures" + +kotlin.sourceSets { + commonMain { + dependencies { + implementation(kotlin("reflect")) + api(project(":kmath-core")) + } + } +} \ No newline at end of file diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt new file mode 100644 index 000000000..421ccb853 --- /dev/null +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt @@ -0,0 +1,34 @@ +package scientifik.kmath.dimensions + +interface Dimension { + val dim: UInt + + companion object { + inline fun of(dim: UInt): Dimension { + return when (dim) { + 1u -> D1 + 2u -> D2 + 3u -> D3 + else -> object : Dimension { + override val dim: UInt = dim + } + } + } + + inline fun dim(): UInt{ + return D::class.objectInstance!!.dim + } + } +} + +object D1 : Dimension { + override val dim: UInt = 1u +} + +object D2 : Dimension { + override val dim: UInt = 2u +} + +object D3 : Dimension { + override val dim: UInt = 3u +} \ No newline at end of file diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt new file mode 100644 index 000000000..3138558b8 --- /dev/null +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt @@ -0,0 +1,130 @@ +package scientifik.kmath.dimensions + +import scientifik.kmath.linear.GenericMatrixContext +import scientifik.kmath.linear.MatrixContext +import scientifik.kmath.linear.Point +import scientifik.kmath.linear.transpose +import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.Structure2D + +interface DMatrix : Structure2D { + companion object { + /** + * Coerces a regular matrix to a matrix with type-safe dimensions and throws a error if coercion failed + */ + inline fun coerce(structure: Structure2D): DMatrix { + if (structure.rowNum != Dimension.dim().toInt()) error("Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}") + if (structure.colNum != Dimension.dim().toInt()) error("Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}") + return DMatrixWrapper(structure) + } + + /** + * The same as [coerce] but without dimension checks. Use with caution + */ + inline fun coerceUnsafe(structure: Structure2D): DMatrix { + return DMatrixWrapper(structure) + } + } +} + +inline class DMatrixWrapper( + val structure: Structure2D +) : DMatrix { + override val shape: IntArray get() = structure.shape + override fun get(i: Int, j: Int): T = structure[i, j] +} + +interface DPoint : Point { + companion object { + inline fun coerce(point: Point): DPoint { + if (point.size != Dimension.dim().toInt()) error("Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}") + return DPointWrapper(point) + } + + inline fun coerceUnsafe(point: Point): DPoint { + return DPointWrapper(point) + } + } +} + +inline class DPointWrapper(val point: Point) : DPoint { + override val size: Int get() = point.size + + override fun get(index: Int): T = point[index] + + override fun iterator(): Iterator = point.iterator() +} + + +/** + * Basic operations on matrices. Operates on [Matrix] + */ +inline class DMatrixContext>(val context: GenericMatrixContext) { + + inline fun Matrix.coerce(): DMatrix = + DMatrix.coerceUnsafe(this) + + /** + * Produce a matrix with this context and given dimensions + */ + inline fun produce(noinline initializer: (i: Int, j: Int) -> T): DMatrix { + val rows = Dimension.dim() + val cols = Dimension.dim() + return context.produce(rows.toInt(), cols.toInt(), initializer).coerce() + } + + inline fun point(noinline initializer: (Int) -> T): DPoint { + val size = Dimension.dim() + return DPoint.coerceUnsafe(context.point(size.toInt(), initializer)) + } + + inline infix fun DMatrix.dot( + other: DMatrix + ): DMatrix { + return context.run { this@dot dot other }.coerce() + } + + inline infix fun DMatrix.dot(vector: DPoint): DPoint { + return DPoint.coerceUnsafe(context.run { this@dot dot vector }) + } + + inline operator fun DMatrix.times(value: T): DMatrix { + return context.run { this@times.times(value) }.coerce() + } + + inline operator fun T.times(m: DMatrix): DMatrix = + m * this + + + inline operator fun DMatrix.plus(other: DMatrix): DMatrix { + return context.run { this@plus + other }.coerce() + } + + inline operator fun DMatrix.minus(other: DMatrix): DMatrix { + return context.run { this@minus + other }.coerce() + } + + inline operator fun DMatrix.unaryMinus(): DMatrix { + return context.run { this@unaryMinus.unaryMinus() }.coerce() + } + + inline fun DMatrix.transpose(): DMatrix { + return context.run { (this@transpose as Matrix).transpose() }.coerce() + } + + /** + * A square unit matrix + */ + inline fun one(): DMatrix = produce { i, j -> + if (i == j) context.elementContext.one else context.elementContext.zero + } + + inline fun zero(): DMatrix = produce { _, _ -> + context.elementContext.zero + } + + companion object { + val real = DMatrixContext(MatrixContext.real) + } +} \ No newline at end of file diff --git a/kmath-dimensions/src/commonTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt b/kmath-dimensions/src/commonTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt new file mode 100644 index 000000000..8d4bae810 --- /dev/null +++ b/kmath-dimensions/src/commonTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt @@ -0,0 +1,27 @@ +package scientifik.kmath.dimensions + +import kotlin.test.Test + + +class DMatrixContextTest { + @Test + fun testDimensionSafeMatrix() { + val res = DMatrixContext.real.run { + val m = produce { i, j -> (i + j).toDouble() } + + //The dimension of `one()` is inferred from type + (m + one()) + } + } + + @Test + fun testTypeCheck() { + val res = DMatrixContext.real.run { + val m1 = produce { i, j -> (i + j).toDouble() } + val m2 = produce { i, j -> (i + j).toDouble() } + + //Dimension-safe addition + m1.transpose() + m2 + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 004b432fd..4afae465f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,5 +29,6 @@ include( ":kmath-commons", ":kmath-koma", ":kmath-prob", + ":kmath-dimensions", ":examples" ) -- 2.34.1 From 22a3c6e4b9cc5ccb56972af66122ff0d86e77995 Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Sun, 14 Jul 2019 18:22:22 +0300 Subject: [PATCH 02/32] Initial implementation of kmath-for-real module based on https://github.com/thomasnield/numky --- kmath-for-real/build.gradle.kts | 9 ++ .../kmath/real/DoubleMatrixOperations.kt | 117 ++++++++++++++++++ settings.gradle.kts | 1 + 3 files changed, 127 insertions(+) create mode 100644 kmath-for-real/build.gradle.kts create mode 100644 kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt diff --git a/kmath-for-real/build.gradle.kts b/kmath-for-real/build.gradle.kts new file mode 100644 index 000000000..7eaa5e174 --- /dev/null +++ b/kmath-for-real/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + `npm-multiplatform` +} + +kotlin.sourceSets.commonMain { + dependencies { + api(project(":kmath-core")) + } +} \ No newline at end of file diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt new file mode 100644 index 000000000..54e711a7b --- /dev/null +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt @@ -0,0 +1,117 @@ +package scientifik.kmath.real + +import scientifik.kmath.linear.MatrixContext +import scientifik.kmath.linear.RealMatrixContext.elementContext +import scientifik.kmath.linear.VirtualMatrix +import scientifik.kmath.operations.sum +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.asSequence +import kotlin.math.pow + +// Initial implementation of these functions is taken from: +// https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt + +fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double) = MatrixContext.real.produce(rowNum, colNum, initializer) + +fun Sequence.toMatrix() = toList().let { + MatrixContext.real.produce(it.size,it[0].size) { row, col -> it[row][col] } +} + +operator fun Matrix.times(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@times[row, col] * double +} + +fun Matrix.square() = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@square[row,col].pow(2) +} + +operator fun Matrix.plus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@plus[row,col] + double +} + +operator fun Matrix.minus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@minus[row,col] - double +} + +operator fun Matrix.div(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@div[row,col] / double +} + +operator fun Double.times(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] * this +} + +operator fun Double.plus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] + this +} + +operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] - this +} + +operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> + matrix[row,col] / this +} + +operator fun Matrix.times(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@times[row,col] * other[row,col] +} + +operator fun Matrix.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> + this@minus[row,col] - other[row,col] +} + +operator fun Matrix.plus(other: Matrix) = MatrixContext.real.add(this,other) + +fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { row, col -> + get(if (row == 0) 0 else row % rowNum, col) +} + +inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = + MatrixContext.real.produce(rowNum,colNum+1) { row,col -> + if (col < colNum) + this[row,col] + else + mapper(rows[row]) + } + +fun Matrix.extractColumn(columnIndex: Int) = extractColumns(columnIndex..columnIndex) + +fun Matrix.extractColumns(columnRange: IntRange) = MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> + this@extractColumns[row, columnRange.start + col] +} + +fun Matrix.sumByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + with(elementContext) { + sum(column.asSequence()) + } +} + +fun Matrix.minByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + column.asSequence().min()?:throw Exception("Cannot produce min on empty column") +} + +fun Matrix.maxByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + column.asSequence().max()?:throw Exception("Cannot produce min on empty column") +} + +fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> + val column = columns[j] + column.asSequence().average() +} + +fun Matrix.sum() = this.elements().map { (_,value) -> value }.sum() + +fun Matrix.min() = this.elements().map { (_,value) -> value }.min() + +fun Matrix.max() = this.elements().map { (_,value) -> value }.max() + +fun Matrix.average() = this.elements().map { (_,value) -> value }.average() + +fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { i, j -> + this@pow[i,j].pow(n) +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 004b432fd..e7f27714c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,5 +29,6 @@ include( ":kmath-commons", ":kmath-koma", ":kmath-prob", + ":kmath-for-real", ":examples" ) -- 2.34.1 From c31c9a9532d86aa30768763c286b030230f8d61c Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Wed, 7 Aug 2019 21:47:50 +0300 Subject: [PATCH 03/32] Move RealVector to kmath-for-real and use scientifik plugin --- .../scientifik/kmath/linear/MatrixTest.kt | 30 ---------------- kmath-for-real/build.gradle.kts | 10 +++--- .../scientifik/kmath/linear/RealVector.kt | 0 .../scientifik/kmath/linear/VectorTest.kt | 36 +++++++++++++++++++ kmath-histograms/build.gradle.kts | 1 + 5 files changed, 43 insertions(+), 34 deletions(-) rename {kmath-core => kmath-for-real}/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt (100%) create mode 100644 kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index de13c9888..d27aa665c 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -6,21 +6,6 @@ import kotlin.test.assertEquals class MatrixTest { - @Test - fun testSum() { - val vector1 = RealVector(5) { it.toDouble() } - val vector2 = RealVector(5) { 5 - it.toDouble() } - val sum = vector1 + vector2 - assertEquals(5.0, sum[2]) - } - - @Test - fun testVectorToMatrix() { - val vector = RealVector(5) { it.toDouble() } - val matrix = vector.asMatrix() - assertEquals(4.0, matrix[4, 0]) - } - @Test fun testTranspose() { val matrix = MatrixContext.real.one(3, 3) @@ -28,21 +13,6 @@ class MatrixTest { assertEquals(matrix, transposed) } - - @Test - fun testDot() { - val vector1 = RealVector(5) { it.toDouble() } - val vector2 = RealVector(5) { 5 - it.toDouble() } - - val matrix1 = vector1.asMatrix() - val matrix2 = vector2.asMatrix().transpose() - val product = MatrixContext.real.run { matrix1 dot matrix2 } - - - assertEquals(5.0, product[1, 0]) - assertEquals(6.0, product[2, 2]) - } - @Test fun testBuilder() { val matrix = Matrix.build(2, 3)( diff --git a/kmath-for-real/build.gradle.kts b/kmath-for-real/build.gradle.kts index 7eaa5e174..a8a8975bc 100644 --- a/kmath-for-real/build.gradle.kts +++ b/kmath-for-real/build.gradle.kts @@ -1,9 +1,11 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } -kotlin.sourceSets.commonMain { - dependencies { - api(project(":kmath-core")) +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-core")) + } } } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt similarity index 100% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt rename to kmath-for-real/src/commonMain/kotlin/scientifik/kmath/linear/RealVector.kt diff --git a/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt b/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt new file mode 100644 index 000000000..0e73ee4a5 --- /dev/null +++ b/kmath-for-real/src/commonTest/kotlin/scientifik/kmath/linear/VectorTest.kt @@ -0,0 +1,36 @@ +package scientifik.kmath.linear + +import kotlin.test.Test +import kotlin.test.assertEquals + +class VectorTest { + @Test + fun testSum() { + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } + val sum = vector1 + vector2 + assertEquals(5.0, sum[2]) + } + + @Test + fun testVectorToMatrix() { + val vector = RealVector(5) { it.toDouble() } + val matrix = vector.asMatrix() + assertEquals(4.0, matrix[4, 0]) + } + + @Test + fun testDot() { + val vector1 = RealVector(5) { it.toDouble() } + val vector2 = RealVector(5) { 5 - it.toDouble() } + + val matrix1 = vector1.asMatrix() + val matrix2 = vector2.asMatrix().transpose() + val product = MatrixContext.real.run { matrix1 dot matrix2 } + + + assertEquals(5.0, product[1, 0]) + assertEquals(6.0, product[2, 2]) + } + +} \ No newline at end of file diff --git a/kmath-histograms/build.gradle.kts b/kmath-histograms/build.gradle.kts index 39aa833ad..993bfed8e 100644 --- a/kmath-histograms/build.gradle.kts +++ b/kmath-histograms/build.gradle.kts @@ -5,5 +5,6 @@ plugins { kotlin.sourceSets.commonMain { dependencies { api(project(":kmath-core")) + api(project(":kmath-for-real")) } } \ No newline at end of file -- 2.34.1 From fbe7988bd03156f5dd59b023deb0772e041cacda Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Tue, 3 Sep 2019 20:35:50 +0300 Subject: [PATCH 04/32] Some refactoring, add test file --- .../kmath/real/DoubleMatrixOperations.kt | 137 +++++++++++------- .../scientific.kmath.real/RealMatrixTest.kt | 16 ++ kmath-for-real/src/jvmMain/kotlin/.gitkeep | 0 3 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt create mode 100644 kmath-for-real/src/jvmMain/kotlin/.gitkeep diff --git a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt index 54e711a7b..d9c6b5eab 100644 --- a/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt +++ b/kmath-for-real/src/commonMain/kotlin/scientifik/kmath/real/DoubleMatrixOperations.kt @@ -9,109 +9,138 @@ import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.asSequence import kotlin.math.pow -// Initial implementation of these functions is taken from: -// https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt +/* + * Functions for convenient "numpy-like" operations with Double matrices. + * + * Initial implementation of these functions is taken from: + * https://github.com/thomasnield/numky/blob/master/src/main/kotlin/org/nield/numky/linear/DoubleOperators.kt + * + */ -fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double) = MatrixContext.real.produce(rowNum, colNum, initializer) +/* + * Functions that help create a real (Double) matrix + */ + +fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double) = + MatrixContext.real.produce(rowNum, colNum, initializer) fun Sequence.toMatrix() = toList().let { - MatrixContext.real.produce(it.size,it[0].size) { row, col -> it[row][col] } + MatrixContext.real.produce(it.size, it[0].size) { row, col -> it[row][col] } } -operator fun Matrix.times(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@times[row, col] * double +fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { + row, col -> get(if (row == 0) 0 else row % rowNum, col) } -fun Matrix.square() = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@square[row,col].pow(2) +/* + * Operations for matrix and real number + */ + +operator fun Matrix.times(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] * double } -operator fun Matrix.plus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@plus[row,col] + double +operator fun Matrix.plus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] + double } -operator fun Matrix.minus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@minus[row,col] - double +operator fun Matrix.minus(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] - double } -operator fun Matrix.div(double: Double) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@div[row,col] / double +operator fun Matrix.div(double: Double) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] / double } -operator fun Double.times(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] * this +operator fun Double.times(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { + row, col -> this * matrix[row, col] } -operator fun Double.plus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] + this +operator fun Double.plus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { + row, col -> this * matrix[row, col] } -operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] - this +operator fun Double.minus(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { + row, col -> this - matrix[row, col] } -operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { row, col -> - matrix[row,col] / this +// TODO: does this operation make sense? Should it be 'this/matrix[row, col]'? +//operator fun Double.div(matrix: Matrix) = MatrixContext.real.produce(matrix.rowNum, matrix.colNum) { +// row, col -> matrix[row, col] / this +//} + +/* + * Per-element (!) square and power operations + */ + +fun Matrix.square() = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col].pow(2) } -operator fun Matrix.times(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@times[row,col] * other[row,col] +fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { + i, j -> this[i, j].pow(n) } -operator fun Matrix.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { row, col -> - this@minus[row,col] - other[row,col] +/* + * Operations on two matrices (per-element!) + */ + +operator fun Matrix.times(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row, col] * other[row, col] } -operator fun Matrix.plus(other: Matrix) = MatrixContext.real.add(this,other) +operator fun Matrix.plus(other: Matrix) = MatrixContext.real.add(this, other) -fun Matrix.repeatStackVertical(n: Int) = VirtualMatrix(rowNum*n, colNum) { row, col -> - get(if (row == 0) 0 else row % rowNum, col) +operator fun Matrix.minus(other: Matrix) = MatrixContext.real.produce(rowNum, colNum) { + row, col -> this[row,col] - other[row,col] } -inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = - MatrixContext.real.produce(rowNum,colNum+1) { row,col -> +/* + * Operations on columns + */ + +inline fun Matrix.appendColumn(crossinline mapper: (Buffer) -> Double) = + MatrixContext.real.produce(rowNum,colNum+1) { + row, col -> if (col < colNum) - this[row,col] + this[row, col] else mapper(rows[row]) - } + } + +fun Matrix.extractColumns(columnRange: IntRange) = MatrixContext.real.produce(rowNum, columnRange.count()) { + row, col -> this[row, columnRange.first + col] +} fun Matrix.extractColumn(columnIndex: Int) = extractColumns(columnIndex..columnIndex) -fun Matrix.extractColumns(columnRange: IntRange) = MatrixContext.real.produce(rowNum, columnRange.count()) { row, col -> - this@extractColumns[row, columnRange.start + col] -} - -fun Matrix.sumByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> +fun Matrix.sumByColumn() = MatrixContext.real.produce(1, colNum) { _, j -> val column = columns[j] with(elementContext) { sum(column.asSequence()) } } -fun Matrix.minByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> - val column = columns[j] - column.asSequence().min()?:throw Exception("Cannot produce min on empty column") +fun Matrix.minByColumn() = MatrixContext.real.produce(1, colNum) { + _, j -> columns[j].asSequence().min() ?: throw Exception("Cannot produce min on empty column") } -fun Matrix.maxByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> - val column = columns[j] - column.asSequence().max()?:throw Exception("Cannot produce min on empty column") +fun Matrix.maxByColumn() = MatrixContext.real.produce(1, colNum) { + _, j -> columns[j].asSequence().max() ?: throw Exception("Cannot produce min on empty column") } -fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { i, j -> - val column = columns[j] - column.asSequence().average() +fun Matrix.averageByColumn() = MatrixContext.real.produce(1, colNum) { + _, j -> columns[j].asSequence().average() } -fun Matrix.sum() = this.elements().map { (_,value) -> value }.sum() +/* + * Operations processing all elements + */ -fun Matrix.min() = this.elements().map { (_,value) -> value }.min() +fun Matrix.sum() = elements().map { (_, value) -> value }.sum() -fun Matrix.max() = this.elements().map { (_,value) -> value }.max() +fun Matrix.min() = elements().map { (_, value) -> value }.min() -fun Matrix.average() = this.elements().map { (_,value) -> value }.average() +fun Matrix.max() = elements().map { (_, value) -> value }.max() -fun Matrix.pow(n: Int) = MatrixContext.real.produce(rowNum, colNum) { i, j -> - this@pow[i,j].pow(n) -} \ No newline at end of file +fun Matrix.average() = elements().map { (_, value) -> value }.average() diff --git a/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt new file mode 100644 index 000000000..808794442 --- /dev/null +++ b/kmath-for-real/src/commonTest/kotlin/scientific.kmath.real/RealMatrixTest.kt @@ -0,0 +1,16 @@ +package scientific.kmath.real + +import scientifik.kmath.real.average +import scientifik.kmath.real.realMatrix +import scientifik.kmath.real.sum +import kotlin.test.Test +import kotlin.test.assertEquals + +class RealMatrixTest { + @Test + fun testSum() { + val m = realMatrix(10, 10) { i, j -> (i + j).toDouble() } + assertEquals(m.sum(), 900.0) + assertEquals(m.average(), 9.0) + } +} diff --git a/kmath-for-real/src/jvmMain/kotlin/.gitkeep b/kmath-for-real/src/jvmMain/kotlin/.gitkeep new file mode 100644 index 000000000..e69de29bb -- 2.34.1 From f9836a64779c61a510716641b9f45c5ec658d80f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 8 Dec 2019 15:48:25 +0300 Subject: [PATCH 05/32] Viktor prototype --- examples/build.gradle.kts | 3 +- .../scientifik/kmath/structures/NDField.kt | 30 +++----- .../scientifik/kmath/structures/VictorTest.kt | 42 +++++++++++ kmath-commons/build.gradle.kts | 2 - .../kmath/structures/ExtendedNDField.kt | 1 - kmath-viktor/build.gradle.kts | 10 +++ .../scientifik/kmath/viktor/ViktorBuffer.kt | 20 +++++ .../kmath/viktor/ViktorNDStructure.kt | 73 +++++++++++++++++++ settings.gradle.kts | 1 + 9 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt create mode 100644 kmath-viktor/build.gradle.kts create mode 100644 kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt create mode 100644 kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index ad59afc5c..dfd530618 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java kotlin("jvm") - kotlin("plugin.allopen") version "1.3.60" + kotlin("plugin.allopen") version "1.3.61" id("kotlinx.benchmark") version "0.2.0-dev-5" } @@ -29,6 +29,7 @@ dependencies { implementation(project(":kmath-coroutines")) implementation(project(":kmath-commons")) implementation(project(":kmath-koma")) + implementation(project(":kmath-viktor")) implementation("com.kyonifer:koma-core-ejml:0.12") implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:${Scientifik.ioVersion}") diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt b/examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt index 7e8c433c0..cfd1206ff 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/NDField.kt @@ -4,7 +4,13 @@ import kotlinx.coroutines.GlobalScope import scientifik.kmath.operations.RealField import kotlin.system.measureTimeMillis -fun main(args: Array) { +internal inline fun measureAndPrint(title: String, block: () -> Unit) { + val time = measureTimeMillis(block) + println("$title completed in $time millis") +} + + +fun main() { val dim = 1000 val n = 1000 @@ -15,8 +21,7 @@ fun main(args: Array) { //A generic boxing field. It should be used for objects, not primitives. val genericField = NDField.boxing(RealField, dim, dim) - - val autoTime = measureTimeMillis { + measureAndPrint("Automatic field addition") { autoField.run { var res = one repeat(n) { @@ -25,18 +30,14 @@ fun main(args: Array) { } } - println("Automatic field addition completed in $autoTime millis") - - val elementTime = measureTimeMillis { + measureAndPrint("Element addition"){ var res = genericField.one repeat(n) { res += 1.0 } } - println("Element addition completed in $elementTime millis") - - val specializedTime = measureTimeMillis { + measureAndPrint("Specialized addition") { specializedField.run { var res: NDBuffer = one repeat(n) { @@ -45,10 +46,7 @@ fun main(args: Array) { } } - println("Specialized addition completed in $specializedTime millis") - - - val lazyTime = measureTimeMillis { + measureAndPrint("Lazy addition") { val res = specializedField.one.mapAsync(GlobalScope) { var c = 0.0 repeat(n) { @@ -60,9 +58,7 @@ fun main(args: Array) { res.elements().forEach { it.second } } - println("Lazy addition completed in $lazyTime millis") - - val genericTime = measureTimeMillis { + measureAndPrint("Generic addition") { //genericField.run(action) genericField.run { var res: NDBuffer = one @@ -72,6 +68,4 @@ fun main(args: Array) { } } - println("Generic addition completed in $genericTime millis") - } \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt b/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt new file mode 100644 index 000000000..da0c193ad --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt @@ -0,0 +1,42 @@ +package scientifik.kmath.structures + +import org.jetbrains.bio.viktor.F64Array +import scientifik.kmath.operations.RealField +import scientifik.kmath.viktor.ViktorNDField + +fun main() { + val dim = 1000 + val n = 400 + + // automatically build context most suited for given type. + val autoField = NDField.auto(RealField, dim, dim) + + val viktorField = ViktorNDField(intArrayOf(dim, dim)) + + + measureAndPrint("Automatic field addition") { + autoField.run { + var res = one + repeat(n) { + res += 1.0 + } + } + } + + measureAndPrint("Raw Viktor") { + val one = F64Array.full(init = 1.0, shape = *intArrayOf(dim, dim)) + var res = one + repeat(n) { + res = res + one + } + } + + measureAndPrint("Viktor field addition") { + viktorField.run { + var res = one + repeat(n) { + res += 1.0 + } + } + } +} \ No newline at end of file diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 9b446f872..9ac0f6e95 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -9,6 +9,4 @@ dependencies { api(project(":kmath-coroutines")) api(project(":kmath-prob")) api("org.apache.commons:commons-math3:3.6.1") - testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt index 370dc646e..3437644ff 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/ExtendedNDField.kt @@ -2,7 +2,6 @@ package scientifik.kmath.structures import scientifik.kmath.operations.* - interface ExtendedNDField> : NDField, TrigonometricOperations, diff --git a/kmath-viktor/build.gradle.kts b/kmath-viktor/build.gradle.kts new file mode 100644 index 000000000..52ee7c497 --- /dev/null +++ b/kmath-viktor/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("scientifik.jvm") +} + +description = "Binding for https://github.com/JetBrains-Research/viktor" + +dependencies { + api(project(":kmath-core")) + api("org.jetbrains.bio:viktor:1.0.1") +} \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt new file mode 100644 index 000000000..040eee951 --- /dev/null +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorBuffer.kt @@ -0,0 +1,20 @@ +package scientifik.kmath.viktor + +import org.jetbrains.bio.viktor.F64FlatArray +import scientifik.kmath.structures.MutableBuffer + +@Suppress("NOTHING_TO_INLINE", "OVERRIDE_BY_INLINE") +inline class ViktorBuffer(val flatArray: F64FlatArray) : MutableBuffer { + override val size: Int get() = flatArray.size + + override inline fun get(index: Int): Double = flatArray[index] + override inline fun set(index: Int, value: Double) { + flatArray[index] = value + } + + override fun copy(): MutableBuffer { + return ViktorBuffer(flatArray.copy().flatten()) + } + + override fun iterator(): Iterator = flatArray.data.iterator() +} \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt new file mode 100644 index 000000000..b572485ff --- /dev/null +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt @@ -0,0 +1,73 @@ +package scientifik.kmath.viktor + +import org.jetbrains.bio.viktor.F64Array +import scientifik.kmath.operations.RealField +import scientifik.kmath.structures.DefaultStrides +import scientifik.kmath.structures.MutableNDStructure +import scientifik.kmath.structures.NDField + +inline class ViktorNDStructure(val f64Buffer: F64Array) : MutableNDStructure { + + override val shape: IntArray get() = f64Buffer.shape + + override fun get(index: IntArray): Double = f64Buffer.get(*index) + + override fun set(index: IntArray, value: Double) { + f64Buffer.set(value = value, indices = *index) + } + + override fun elements(): Sequence> { + return DefaultStrides(shape).indices().map { it to get(it) } + } +} + +fun F64Array.asStructure(): ViktorNDStructure = ViktorNDStructure(this) + +class ViktorNDField(override val shape: IntArray) : NDField { + override val zero: ViktorNDStructure + get() = F64Array.full(init = 0.0, shape = *shape).asStructure() + override val one: ViktorNDStructure + get() = F64Array.full(init = 1.0, shape = *shape).asStructure() + + val strides = DefaultStrides(shape) + + override val elementContext: RealField get() = RealField + + override fun produce(initializer: RealField.(IntArray) -> Double): ViktorNDStructure = F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.initializer(index), indices = *index) + } + }.asStructure() + + override fun map(arg: ViktorNDStructure, transform: RealField.(Double) -> Double): ViktorNDStructure = + F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.transform(arg[index]), indices = *index) + } + }.asStructure() + + override fun mapIndexed( + arg: ViktorNDStructure, + transform: RealField.(index: IntArray, Double) -> Double + ): ViktorNDStructure = F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.transform(index, arg[index]), indices = *index) + } + }.asStructure() + + override fun combine( + a: ViktorNDStructure, + b: ViktorNDStructure, + transform: RealField.(Double, Double) -> Double + ): ViktorNDStructure = F64Array(*shape).apply { + this@ViktorNDField.strides.indices().forEach { index -> + set(value = RealField.transform(a[index], b[index]), indices = *index) + } + }.asStructure() + + override fun ViktorNDStructure.plus(b: ViktorNDStructure): ViktorNDStructure = + (f64Buffer + b.f64Buffer).asStructure() + + override fun ViktorNDStructure.minus(b: ViktorNDStructure): ViktorNDStructure = + (f64Buffer - b.f64Buffer).asStructure() +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f52edfac5..8c0c4420b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,6 +33,7 @@ include( ":kmath-coroutines", ":kmath-histograms", ":kmath-commons", + ":kmath-viktor", ":kmath-koma", ":kmath-prob", ":kmath-io", -- 2.34.1 From cbac4fca334bb81a434798d325747be3b03bb00f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 8 Dec 2019 17:25:36 +0300 Subject: [PATCH 06/32] Viktor prototype with compiler bug --- .../scientifik/kmath/structures/VictorTest.kt | 31 +++++++++++++------ .../kmath/viktor/ViktorNDStructure.kt | 14 ++++++--- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt b/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt index da0c193ad..8d710c584 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt @@ -13,6 +13,12 @@ fun main() { val viktorField = ViktorNDField(intArrayOf(dim, dim)) + autoField.run { + var res = one + repeat(n/2) { + res += 1.0 + } + } measureAndPrint("Automatic field addition") { autoField.run { @@ -23,6 +29,22 @@ fun main() { } } + viktorField.run { + var res = one + repeat(n/2) { + res += one + } + } + + measureAndPrint("Viktor field addition") { + viktorField.run { + var res = one + repeat(n) { + res += one + } + } + } + measureAndPrint("Raw Viktor") { val one = F64Array.full(init = 1.0, shape = *intArrayOf(dim, dim)) var res = one @@ -30,13 +52,4 @@ fun main() { res = res + one } } - - measureAndPrint("Viktor field addition") { - viktorField.run { - var res = one - repeat(n) { - res += 1.0 - } - } - } } \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt index b572485ff..b8cf688f4 100644 --- a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt @@ -65,9 +65,15 @@ class ViktorNDField(override val shape: IntArray) : NDField Date: Sun, 8 Dec 2019 19:52:47 +0300 Subject: [PATCH 07/32] Viktor prototype with inline --- .../scientifik/kmath/viktor/ViktorNDStructure.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt index b8cf688f4..9654ba283 100644 --- a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt @@ -69,9 +69,14 @@ class ViktorNDField(override val shape: IntArray) : NDField Date: Mon, 9 Dec 2019 17:37:17 +0300 Subject: [PATCH 08/32] Viktor prototype with inline --- .../scientifik/kmath/structures/VictorTest.kt | 21 +++++++++++++++ .../kmath/viktor/ViktorNDStructure.kt | 26 ++++++++++--------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt b/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt index 8d710c584..19469ca72 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt @@ -4,12 +4,14 @@ import org.jetbrains.bio.viktor.F64Array import scientifik.kmath.operations.RealField import scientifik.kmath.viktor.ViktorNDField + fun main() { val dim = 1000 val n = 400 // automatically build context most suited for given type. val autoField = NDField.auto(RealField, dim, dim) + val realField = NDField.real(dim,dim) val viktorField = ViktorNDField(intArrayOf(dim, dim)) @@ -52,4 +54,23 @@ fun main() { res = res + one } } + + measureAndPrint("Automatic field log") { + realField.run { + val fortyTwo = produce { 42.0 } + var res = one + + repeat(n) { + res = ln(fortyTwo) + } + } + } + + measureAndPrint("Raw Viktor log") { + val fortyTwo = F64Array.full(dim, dim, init = 42.0) + var res: F64Array + repeat(n) { + res = fortyTwo.log() + } + } } \ No newline at end of file diff --git a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt index 9654ba283..84e927721 100644 --- a/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt +++ b/kmath-viktor/src/main/kotlin/scientifik/kmath/viktor/ViktorNDStructure.kt @@ -6,14 +6,15 @@ import scientifik.kmath.structures.DefaultStrides import scientifik.kmath.structures.MutableNDStructure import scientifik.kmath.structures.NDField +@Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") inline class ViktorNDStructure(val f64Buffer: F64Array) : MutableNDStructure { override val shape: IntArray get() = f64Buffer.shape - override fun get(index: IntArray): Double = f64Buffer.get(*index) + override inline fun get(index: IntArray): Double = f64Buffer.get(*index) - override fun set(index: IntArray, value: Double) { - f64Buffer.set(value = value, indices = *index) + override inline fun set(index: IntArray, value: Double) { + f64Buffer.set(*index, value = value) } override fun elements(): Sequence> { @@ -23,6 +24,7 @@ inline class ViktorNDStructure(val f64Buffer: F64Array) : MutableNDStructure { override val zero: ViktorNDStructure get() = F64Array.full(init = 0.0, shape = *shape).asStructure() @@ -69,16 +71,16 @@ class ViktorNDField(override val shape: IntArray) : NDField Date: Mon, 9 Dec 2019 19:52:00 +0300 Subject: [PATCH 09/32] Examples for type-safe dimensions --- examples/build.gradle.kts | 11 ++--- .../kmath/structures/ViktorBenchmark.kt} | 47 +++++++++---------- .../kotlin/scientifik/kmath/utils/utils.kt | 8 ++++ .../kmath/structures/typeSafeDimensions.kt | 34 ++++++++++++++ kmath-dimensions/build.gradle.kts | 2 +- .../scientifik/kmath/dimensions/Dimensions.kt | 9 +++- .../scientifik/kmath/dimensions/Wrappers.kt | 26 ++++++++-- 7 files changed, 96 insertions(+), 41 deletions(-) rename examples/src/{main/kotlin/scientifik/kmath/structures/VictorTest.kt => benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt} (58%) create mode 100644 examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt create mode 100644 examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index dfd530618..47acaa5ba 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -5,7 +5,7 @@ plugins { java kotlin("jvm") kotlin("plugin.allopen") version "1.3.61" - id("kotlinx.benchmark") version "0.2.0-dev-5" + id("kotlinx.benchmark") version "0.2.0-dev-7" } configure { @@ -13,10 +13,7 @@ configure { } repositories { - maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("http://dl.bintray.com/kyonifer/maven") - maven ("https://dl.bintray.com/orangy/maven") - mavenCentral() } @@ -30,12 +27,10 @@ dependencies { implementation(project(":kmath-commons")) implementation(project(":kmath-koma")) implementation(project(":kmath-viktor")) + implementation(project(":kmath-dimensions")) implementation("com.kyonifer:koma-core-ejml:0.12") implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:${Scientifik.ioVersion}") - - implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-2") - - + implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-7") "benchmarksCompile"(sourceSets.main.get().compileClasspath) } diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt similarity index 58% rename from examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt rename to examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt index 19469ca72..be4115d81 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/VictorTest.kt +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/structures/ViktorBenchmark.kt @@ -1,28 +1,26 @@ package scientifik.kmath.structures import org.jetbrains.bio.viktor.F64Array +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State import scientifik.kmath.operations.RealField import scientifik.kmath.viktor.ViktorNDField -fun main() { - val dim = 1000 - val n = 400 +@State(Scope.Benchmark) +class ViktorBenchmark { + final val dim = 1000 + final val n = 100 // automatically build context most suited for given type. - val autoField = NDField.auto(RealField, dim, dim) - val realField = NDField.real(dim,dim) + final val autoField = NDField.auto(RealField, dim, dim) + final val realField = NDField.real(dim, dim) - val viktorField = ViktorNDField(intArrayOf(dim, dim)) + final val viktorField = ViktorNDField(intArrayOf(dim, dim)) - autoField.run { - var res = one - repeat(n/2) { - res += 1.0 - } - } - - measureAndPrint("Automatic field addition") { + @Benchmark + fun `Automatic field addition`() { autoField.run { var res = one repeat(n) { @@ -31,14 +29,8 @@ fun main() { } } - viktorField.run { - var res = one - repeat(n/2) { - res += one - } - } - - measureAndPrint("Viktor field addition") { + @Benchmark + fun `Viktor field addition`() { viktorField.run { var res = one repeat(n) { @@ -47,7 +39,8 @@ fun main() { } } - measureAndPrint("Raw Viktor") { + @Benchmark + fun `Raw Viktor`() { val one = F64Array.full(init = 1.0, shape = *intArrayOf(dim, dim)) var res = one repeat(n) { @@ -55,9 +48,10 @@ fun main() { } } - measureAndPrint("Automatic field log") { + @Benchmark + fun `Real field log`() { realField.run { - val fortyTwo = produce { 42.0 } + val fortyTwo = produce { 42.0 } var res = one repeat(n) { @@ -66,7 +60,8 @@ fun main() { } } - measureAndPrint("Raw Viktor log") { + @Benchmark + fun `Raw Viktor log`() { val fortyTwo = F64Array.full(dim, dim, init = 42.0) var res: F64Array repeat(n) { diff --git a/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt b/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt new file mode 100644 index 000000000..6ec9e9c17 --- /dev/null +++ b/examples/src/benchmarks/kotlin/scientifik/kmath/utils/utils.kt @@ -0,0 +1,8 @@ +package scientifik.kmath.utils + +import kotlin.system.measureTimeMillis + +internal inline fun measureAndPrint(title: String, block: () -> Unit) { + val time = measureTimeMillis(block) + println("$title completed in $time millis") +} \ No newline at end of file diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt new file mode 100644 index 000000000..8693308ca --- /dev/null +++ b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt @@ -0,0 +1,34 @@ +package scientifik.kmath.structures + +import scientifik.kmath.dimensions.D2 +import scientifik.kmath.dimensions.D3 +import scientifik.kmath.dimensions.DMatrixContext +import scientifik.kmath.dimensions.Dimension +import scientifik.kmath.operations.RealField + +fun DMatrixContext.simple() { + val m1 = produce { i, j -> (i + j).toDouble() } + val m2 = produce { i, j -> (i + j).toDouble() } + + //Dimension-safe addition + m1.transpose() + m2 +} + +object D5: Dimension{ + override val dim: UInt = 5u +} + +fun DMatrixContext.custom() { + val m1 = produce { i, j -> (i+j).toDouble() } + val m2 = produce { i, j -> (i-j).toDouble() } + val m3 = produce { i, j -> (i-j).toDouble() } + + (m1 dot m2) + m3 +} + +fun main() { + DMatrixContext.real.run { + simple() + custom() + } +} \ No newline at end of file diff --git a/kmath-dimensions/build.gradle.kts b/kmath-dimensions/build.gradle.kts index 59a4c0fc3..50fb41391 100644 --- a/kmath-dimensions/build.gradle.kts +++ b/kmath-dimensions/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - `npm-multiplatform` + id("scientifik.mpp") } description = "A proof of concept module for adding typ-safe dimensions to structures" diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt index 421ccb853..87511b885 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt @@ -1,9 +1,14 @@ package scientifik.kmath.dimensions +/** + * An interface which is not used in runtime. Designates a size of some structure. + * All descendants should be singleton objects. + */ interface Dimension { val dim: UInt companion object { + @Suppress("NOTHING_TO_INLINE") inline fun of(dim: UInt): Dimension { return when (dim) { 1u -> D1 @@ -15,8 +20,8 @@ interface Dimension { } } - inline fun dim(): UInt{ - return D::class.objectInstance!!.dim + inline fun dim(): UInt { + return D::class.objectInstance?.dim ?: error("Dimension object must be a singleton") } } } diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt index 3138558b8..9985888b9 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt @@ -8,14 +8,21 @@ import scientifik.kmath.operations.Ring import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.Structure2D +/** + * A matrix with compile-time controlled dimension + */ interface DMatrix : Structure2D { companion object { /** * Coerces a regular matrix to a matrix with type-safe dimensions and throws a error if coercion failed */ inline fun coerce(structure: Structure2D): DMatrix { - if (structure.rowNum != Dimension.dim().toInt()) error("Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}") - if (structure.colNum != Dimension.dim().toInt()) error("Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}") + if (structure.rowNum != Dimension.dim().toInt()) { + error("Row number mismatch: expected ${Dimension.dim()} but found ${structure.rowNum}") + } + if (structure.colNum != Dimension.dim().toInt()) { + error("Column number mismatch: expected ${Dimension.dim()} but found ${structure.colNum}") + } return DMatrixWrapper(structure) } @@ -28,6 +35,9 @@ interface DMatrix : Structure2D { } } +/** + * An inline wrapper for a Matrix + */ inline class DMatrixWrapper( val structure: Structure2D ) : DMatrix { @@ -35,10 +45,15 @@ inline class DMatrixWrapper( override fun get(i: Int, j: Int): T = structure[i, j] } +/** + * Dimension-safe point + */ interface DPoint : Point { companion object { inline fun coerce(point: Point): DPoint { - if (point.size != Dimension.dim().toInt()) error("Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}") + if (point.size != Dimension.dim().toInt()) { + error("Vector dimension mismatch: expected ${Dimension.dim()}, but found ${point.size}") + } return DPointWrapper(point) } @@ -48,6 +63,9 @@ interface DPoint : Point { } } +/** + * Dimension-safe point wrapper + */ inline class DPointWrapper(val point: Point) : DPoint { override val size: Int get() = point.size @@ -58,7 +76,7 @@ inline class DPointWrapper(val point: Point) : DPoint /** - * Basic operations on matrices. Operates on [Matrix] + * Basic operations on dimension-safe matrices. Operates on [Matrix] */ inline class DMatrixContext>(val context: GenericMatrixContext) { -- 2.34.1 From 09f2f377802f24cb522c741cf7eddf0be8ba1d0b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 9 Dec 2019 19:52:48 +0300 Subject: [PATCH 10/32] Examples for type-safe dimensions --- buildSrc/build.gradle.kts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 buildSrc/build.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index e69de29bb..000000000 -- 2.34.1 From 169801060b6c1adc8fee15820b7350a693f2e36e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 9 Dec 2019 19:57:26 +0300 Subject: [PATCH 11/32] Examples for type-safe dimensions --- README.md | 2 ++ .../scientifik/kmath/structures/typeSafeDimensions.kt | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5678fc530..0df279889 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ can be used for a wide variety of purposes from high performance calculations to * **Streaming** Streaming operations on mathematical objects and objects buffers. +* **Type-safe dimensions** Type-safe dimensions for matrix operations. + * **Commons-math wrapper** It is planned to gradually wrap most parts of [Apache commons-math](http://commons.apache.org/proper/commons-math/) library in Kotlin code and maybe rewrite some parts to better suit the Kotlin programming paradigm, however there is no fixed roadmap for that. Feel free to submit a feature request if you want something to be done first. diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt index 8693308ca..9169c7d7b 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt @@ -14,14 +14,14 @@ fun DMatrixContext.simple() { m1.transpose() + m2 } -object D5: Dimension{ +object D5 : Dimension { override val dim: UInt = 5u } fun DMatrixContext.custom() { - val m1 = produce { i, j -> (i+j).toDouble() } - val m2 = produce { i, j -> (i-j).toDouble() } - val m3 = produce { i, j -> (i-j).toDouble() } + val m1 = produce { i, j -> (i + j).toDouble() } + val m2 = produce { i, j -> (i - j).toDouble() } + val m3 = produce { i, j -> (i - j).toDouble() } (m1 dot m2) + m3 } -- 2.34.1 From 2d714523412b80e9047d11194aa6b27ab73117b1 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 9 Dec 2019 20:56:09 +0300 Subject: [PATCH 12/32] Workaround for JS reflection for dimenstions --- .../kotlin/scientifik/kmath/dimensions/Dimensions.kt | 6 ++---- .../kotlin/scientifik/kmath/dimensions/Wrappers.kt | 10 ++++++++-- .../jsMain/kotlin/scientifik/kmath/dimensions/dim.kt | 5 +++++ .../jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt | 4 ++++ .../scientifik/kmath/dimensions/DMatrixContextTest.kt | 0 5 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt create mode 100644 kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt rename kmath-dimensions/src/{commonTest => jvmTest}/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt (100%) diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt index 87511b885..2eef69a0c 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt @@ -19,13 +19,11 @@ interface Dimension { } } } - - inline fun dim(): UInt { - return D::class.objectInstance?.dim ?: error("Dimension object must be a singleton") - } } } +expect inline fun Dimension.Companion.dim(): UInt + object D1 : Dimension { override val dim: UInt = 1u } diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt index 9985888b9..c78018753 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt @@ -66,7 +66,8 @@ interface DPoint : Point { /** * Dimension-safe point wrapper */ -inline class DPointWrapper(val point: Point) : DPoint { +inline class DPointWrapper(val point: Point) : + DPoint { override val size: Int get() = point.size override fun get(index: Int): T = point[index] @@ -94,7 +95,12 @@ inline class DMatrixContext>(val context: GenericMatrixCon inline fun point(noinline initializer: (Int) -> T): DPoint { val size = Dimension.dim() - return DPoint.coerceUnsafe(context.point(size.toInt(), initializer)) + return DPoint.coerceUnsafe( + context.point( + size.toInt(), + initializer + ) + ) } inline infix fun DMatrix.dot( diff --git a/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt b/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt new file mode 100644 index 000000000..557234d84 --- /dev/null +++ b/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt @@ -0,0 +1,5 @@ +package scientifik.kmath.dimensions + +actual inline fun Dimension.Companion.dim(): UInt { + TODO("KClass::objectInstance does not work") +} \ No newline at end of file diff --git a/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt b/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt new file mode 100644 index 000000000..a16dd2266 --- /dev/null +++ b/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt @@ -0,0 +1,4 @@ +package scientifik.kmath.dimensions + +actual inline fun Dimension.Companion.dim(): UInt = + D::class.objectInstance?.dim ?: error("Dimension object must be a singleton") \ No newline at end of file diff --git a/kmath-dimensions/src/commonTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt b/kmath-dimensions/src/jvmTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt similarity index 100% rename from kmath-dimensions/src/commonTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt rename to kmath-dimensions/src/jvmTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt -- 2.34.1 From 74653e74c6a6974db3cf51b658e289173a98fbb4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 11 Dec 2019 15:06:18 +0300 Subject: [PATCH 13/32] Direct memory for file reading. --- .../kotlin/scientifik/memory/ByteBufferMemory.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt b/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt index 30a87228a..df2e9847a 100644 --- a/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt +++ b/kmath-memory/src/jvmMain/kotlin/scientifik/memory/ByteBufferMemory.kt @@ -1,6 +1,10 @@ package scientifik.memory import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption /** @@ -11,7 +15,7 @@ actual fun Memory.Companion.allocate(length: Int): Memory { return ByteBufferMemory(buffer) } -class ByteBufferMemory( +private class ByteBufferMemory( val buffer: ByteBuffer, val startOffset: Int = 0, override val size: Int = buffer.limit() @@ -90,4 +94,13 @@ class ByteBufferMemory( } override fun writer(): MemoryWriter = writer +} + +/** + * Use direct memory-mapped buffer from file to read something and close it afterwards. + */ +fun Path.readAsMemory(position: Long = 0, size: Long = Files.size(this), block: Memory.() -> R): R { + return FileChannel.open(this, StandardOpenOption.READ).use { + ByteBufferMemory(it.map(FileChannel.MapMode.READ_ONLY, position, size)).block() + } } \ No newline at end of file -- 2.34.1 From 396b31d106e9fd7eea52c45c5c278d13bab066e7 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 12 Jan 2020 10:49:42 +0300 Subject: [PATCH 14/32] Linear interpolation --- README.md | 11 ++- build.gradle.kts | 2 +- .../scientifik/kmath/operations/Algebra.kt | 6 +- .../kmath/operations/AlgebraExtensions.kt | 13 ++- .../kmath/operations/OptionalOperations.kt | 4 +- kmath-functions/build.gradle.kts | 12 --- .../scientifik.kmath.functions/Piecewise.kt | 2 - .../scientifik.kmath.functions/Polynomial.kt | 94 ++++++++++++++++++- .../scientifik.kmath.functions/functions.kt | 53 ++++------- .../kmath/interpolation/Interpolator.kt | 18 +++- .../kmath/interpolation/LinearInterpolator.kt | 22 ++++- .../interpolation/LinearInterpolatorTest.kt | 25 ++++- settings.gradle.kts | 11 ++- 13 files changed, 198 insertions(+), 75 deletions(-) delete mode 100644 kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt diff --git a/README.md b/README.md index 0df279889..34761e838 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/scientifik/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) - -Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) - +[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![DOI](https://zenodo.org/badge/129486382.svg)](https://zenodo.org/badge/latestdoi/129486382) +![Gradle build](https://github.com/mipt-npm/kmath/workflows/Gradle%20build/badge.svg) + +Bintray: [ ![Download](https://api.bintray.com/packages/mipt-npm/scientifik/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) + +Bintray-dev: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/kmath-core/images/download.svg) ](https://bintray.com/mipt-npm/scientifik/kmath-core/_latestVersion) + # KMath Could be pronounced as `key-math`. The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's `numpy` library. In contrast to `numpy` and `scipy` it is modular and has a lightweight core. diff --git a/build.gradle.kts b/build.gradle.kts index a63966dfe..f3a3c13d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("scientifik.publish") version "0.2.6" apply false + id("scientifik.publish") version "0.3.1" apply false } val kmathVersion by extra("0.1.4-dev-1") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt index 0ed769db8..485185526 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Algebra.kt @@ -6,14 +6,14 @@ annotation class KMathContext /** * Marker interface for any algebra */ -interface Algebra +interface Algebra -inline operator fun T.invoke(block: T.() -> R): R = run(block) +inline operator fun , R> T.invoke(block: T.() -> R): R = run(block) /** * Space-like operations without neutral element */ -interface SpaceOperations : Algebra { +interface SpaceOperations : Algebra { /** * Addition operation for two context elements */ diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt index 4e8dbf36d..1e0453f08 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt @@ -1,4 +1,13 @@ package scientifik.kmath.operations -fun Space.sum(data : Iterable): T = data.fold(zero) { left, right -> add(left,right) } -fun Space.sum(data : Sequence): T = data.fold(zero) { left, right -> add(left, right) } \ No newline at end of file +fun Space.sum(data: Iterable): T = data.fold(zero) { left, right -> add(left, right) } +fun Space.sum(data: Sequence): T = data.fold(zero) { left, right -> add(left, right) } + +//TODO optimized power operation +fun RingOperations.power(arg: T, power: Int): T { + var res = arg + repeat(power - 1) { + res *= arg + } + return res +} \ 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 2a77e36ef..bd83932e7 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -29,7 +29,7 @@ fun >> ctg(arg: T): T = arg.conte /** * A context extension to include power operations like square roots, etc */ -interface PowerOperations { +interface PowerOperations : Algebra { fun power(arg: T, pow: Number): T fun sqrt(arg: T) = power(arg, 0.5) @@ -42,7 +42,7 @@ fun >> sqr(arg: T): T = arg pow 2.0 /* Exponential */ -interface ExponentialOperations { +interface ExponentialOperations: Algebra { fun exp(arg: T): T fun ln(arg: T): T } diff --git a/kmath-functions/build.gradle.kts b/kmath-functions/build.gradle.kts index 373d9b8ac..4c158a32e 100644 --- a/kmath-functions/build.gradle.kts +++ b/kmath-functions/build.gradle.kts @@ -1,23 +1,11 @@ plugins { id("scientifik.mpp") - //id("scientifik.atomic") } kotlin.sourceSets { commonMain { dependencies { api(project(":kmath-core")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Scientifik.coroutinesVersion}") - } - } - jvmMain { - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Scientifik.coroutinesVersion}") - } - } - jsMain { - dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Scientifik.coroutinesVersion}") } } } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt deleted file mode 100644 index d1263e58b..000000000 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt +++ /dev/null @@ -1,2 +0,0 @@ -package scientifik.kmath.functions - diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt index 855ed26cd..a05462863 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt @@ -1,4 +1,94 @@ package scientifik.kmath.functions -interface Polynomial { -} \ No newline at end of file +import scientifik.kmath.operations.RealField +import scientifik.kmath.operations.Ring +import scientifik.kmath.operations.Space +import kotlin.jvm.JvmName +import kotlin.math.max +import kotlin.math.pow + +/** + * Polynomial coefficients without fixation on specific context they are applied to + * @param coefficients constant is the leftmost coefficient + */ +inline class Polynomial(val coefficients: List) { + constructor(vararg coefficients: T) : this(coefficients.toList()) +} + +fun Polynomial.value() = + coefficients.reduceIndexed { index: Int, acc: Double, d: Double -> acc + d.pow(index) } + + +fun > Polynomial.value(ring: C, arg: T): T = ring.run { + if( coefficients.isEmpty()) return@run zero + var res = coefficients.first() + var powerArg = arg + for( index in 1 until coefficients.size){ + res += coefficients[index]*powerArg + //recalculating power on each step to avoid power costs on long polynomials + powerArg *= arg + } + return@run res +} + +/** + * Represent a polynomial as a context-dependent function + */ +fun > Polynomial.asMathFunction(): MathFunction = object : MathFunction { + override fun C.invoke(arg: T): T = value(this, arg) +} + +/** + * Represent the polynomial as a regular context-less function + */ +fun > Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } + +@JvmName("asRealUFunction") +fun Polynomial.asFunction(): (Double) -> Double = asFunction(RealField) + +/** + * An algebra for polynomials + */ +class PolynomialSpace>(val ring: C) : Space> { + + override fun add(a: Polynomial, b: Polynomial): Polynomial { + val dim = max(a.coefficients.size, b.coefficients.size) + ring.run { + return Polynomial(List(dim) { index -> + a.coefficients.getOrElse(index) { zero } + b.coefficients.getOrElse(index) { zero } + }) + } + } + + override fun multiply(a: Polynomial, k: Number): Polynomial { + ring.run { + return Polynomial(List(a.coefficients.size) { index -> a.coefficients[index] * k }) + } + } + + override val zero: Polynomial = Polynomial(emptyList()) + + operator fun Polynomial.invoke(arg: T): T = value(ring, arg) +} + +fun , R> C.polynomial(block: PolynomialSpace.() -> R): R { + return PolynomialSpace(this).run(block) +} + +class PiecewisePolynomial> internal constructor( + val lowerBoundary: T, + val pieces: List>> +) + +private fun > PiecewisePolynomial.findPiece(arg: T): Polynomial? { + if (arg < lowerBoundary || arg > pieces.last().first) return null + return pieces.first { arg < it.first }.second +} + +/** + * Return a value of polynomial function with given [ring] an given [arg] or null if argument is outside of piecewise definition. + */ +fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = + findPiece(arg)?.value(ring, arg) + +fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt index c26443926..2b822b3ba 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt @@ -1,54 +1,33 @@ -package scientifik.kmath.misc +package scientifik.kmath.functions +import scientifik.kmath.operations.Algebra import scientifik.kmath.operations.RealField -import scientifik.kmath.operations.SpaceOperations -import kotlin.jvm.JvmName /** * A regular function that could be called only inside specific algebra context + * @param T source type + * @param C source algebra constraint + * @param R result type */ -interface UFunction> { - operator fun C.invoke(arg: T): T +interface MathFunction, R> { + operator fun C.invoke(arg: T): R } +fun MathFunction.invoke(arg: Double): R = RealField.invoke(arg) + /** - * A suspendable univariate function defined in algebraic context + * A suspendable function defined in algebraic context */ -interface USFunction> { - suspend operator fun C.invoke(arg: T): T +interface SuspendableMathFunction, R> { + suspend operator fun C.invoke(arg: T): R } -suspend fun USFunction.invoke(arg: Double) = RealField.invoke(arg) +suspend fun SuspendableMathFunction.invoke(arg: Double) = RealField.invoke(arg) -interface MFunction> { - /** - * The input dimension of the function - */ - val dimension: UInt - - operator fun C.invoke(vararg args: T): T -} - /** - * A suspendable multivariate (N->1) function defined on algebraic context + * A parametric function with parameter */ -interface MSFunction> { - /** - * The input dimension of the function - */ - val dimension: UInt - - suspend operator fun C.invoke(vararg args: T): T -} - -suspend fun MSFunction.invoke(args: DoubleArray) = RealField.invoke(*args.toTypedArray()) -@JvmName("varargInvoke") -suspend fun MSFunction.invoke(vararg args: Double) = RealField.invoke(*args.toTypedArray()) - -/** - * A suspendable parametric function with parameter - */ -interface PSFunction> { - suspend operator fun C.invoke(arg: T, parameter: P): T +interface ParametricFunction> { + operator fun C.invoke(arg: T, parameter: P): T } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt index 0b1ca7795..1df9df5d7 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt @@ -1,7 +1,21 @@ package scientifik.kmath.interpolation -import scientifik.kmath.functions.MathFunction +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.value +import scientifik.kmath.operations.Ring interface Interpolator { - fun interpolate(points: Collection>): MathFunction + fun interpolate(points: Collection>): (X) -> Y +} + +interface PolynomialInterpolator> : Interpolator { + val algebra: Ring + + fun getDefaultValue(): T = error("Out of bounds") + + fun interpolatePolynomials(points: Collection>): PiecewisePolynomial + + override fun interpolate(points: Collection>): (T) -> T = { x -> + interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() + } } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt index 4db9da7b6..2fc8aaccc 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt @@ -1,4 +1,24 @@ package scientifik.kmath.interpolation -class LinearInterpolator { +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.Polynomial +import scientifik.kmath.operations.Field + +/** + * Reference JVM implementation: https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/analysis/interpolation/LinearInterpolator.java + */ +class LinearInterpolator>(override val algebra: Field) : PolynomialInterpolator { + + override fun interpolatePolynomials(points: Collection>): PiecewisePolynomial = algebra.run { + //sorting points + val sorted = points.sortedBy { it.first } + + val pairs: List>> = (0 until points.size - 1).map { i -> + val slope = (sorted[i + 1].second - sorted[i].second) / (sorted[i + 1].first - sorted[i].first) + val const = sorted[i].second - slope * sorted[i].first + sorted[i + 1].first to Polynomial(const, slope) + } + + return PiecewisePolynomial(sorted.first().first, pairs) + } } \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt index 34202b388..71303107e 100644 --- a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt +++ b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt @@ -1,5 +1,26 @@ package scientifik.kmath.interpolation -import org.junit.Assert.* +import scientifik.kmath.functions.asFunction +import scientifik.kmath.operations.RealField +import kotlin.test.Test +import kotlin.test.assertEquals -class LinearInterpolatorTest \ No newline at end of file + +class LinearInterpolatorTest { + @Test + fun testInterpolation() { + val data = listOf( + 0.0 to 0.0, + 1.0 to 1.0, + 2.0 to 3.0, + 3.0 to 4.0 + ) + val polynomial = LinearInterpolator(RealField).interpolatePolynomials(data) + val function = polynomial.asFunction(RealField) + +// assertEquals(null, function(-1.0)) +// assertEquals(0.5, function(0.5)) + assertEquals(2.0, function(1.5)) + assertEquals(3.0, function(2.0)) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e85b32fd2..fdb140541 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,10 +1,10 @@ pluginManagement { plugins { - id("scientifik.mpp") version "0.2.5" - id("scientifik.jvm") version "0.2.5" - id("scientifik.atomic") version "0.2.5" - id("scientifik.publish") version "0.2.5" + id("scientifik.mpp") version "0.3.1" + id("scientifik.jvm") version "0.3.1" + id("scientifik.atomic") version "0.3.1" + id("scientifik.publish") version "0.3.1" } repositories { @@ -19,7 +19,7 @@ pluginManagement { resolutionStrategy { eachPlugin { when (requested.id.id) { - "scientifik.mpp", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}") + "scientifik.mpp", "scientifik.jvm", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}") } } } @@ -29,6 +29,7 @@ rootProject.name = "kmath" include( ":kmath-memory", ":kmath-core", + ":kmath-functions", // ":kmath-io", ":kmath-coroutines", ":kmath-histograms", -- 2.34.1 From d56b4148bef72f6cffe39e7426e47c5bdbed6240 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 12 Jan 2020 20:51:16 +0300 Subject: [PATCH 15/32] Generalized linear interpolation --- .../scientifik.kmath.functions/Piecewise.kt | 55 +++++++++++++++++++ .../scientifik.kmath.functions/Polynomial.kt | 31 ++--------- .../kmath/interpolation/LinearInterpolator.kt | 16 ++++-- .../interpolation/LinearInterpolatorTest.kt | 4 +- 4 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt new file mode 100644 index 000000000..c05660d52 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt @@ -0,0 +1,55 @@ +package scientifik.kmath.functions + +import scientifik.kmath.operations.Ring + +interface Piecewise { + fun findPiece(arg: T): R? +} + +interface PiecewisePolynomial : Piecewise> + +/** + * Ordered list of pieces in piecewise function + */ +class OrderedPiecewisePolynomial>(left: T) : PiecewisePolynomial { + + private val delimiters: ArrayList = arrayListOf(left) + private val pieces: ArrayList> = ArrayList() + + /** + * Dynamically add a piece to the "right" side (beyond maximum argument value of previous piece) + * @param right new rightmost position. If is less then current rightmost position, a error is thrown. + */ + fun putRight(right: T, piece: Polynomial) { + require(right > delimiters.last()) { "New delimiter should be to the right of old one" } + delimiters.add(right) + pieces.add(piece) + } + + fun putLeft(left: T, piece: Polynomial) { + require(left < delimiters.first()) { "New delimiter should be to the left of old one" } + delimiters.add(0, left) + pieces.add(0, piece) + } + + override fun findPiece(arg: T): Polynomial? { + if (arg < delimiters.first() || arg >= delimiters.last()) { + return null + } else { + for (index in 1 until delimiters.size) { + if (arg < delimiters[index]) { + return pieces[index - 1] + } + } + error("Piece not found") + } + } +} + +/** + * Return a value of polynomial function with given [ring] an given [arg] or null if argument is outside of piecewise definition. + */ +fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = + findPiece(arg)?.value(ring, arg) + +fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt index a05462863..bd5062661 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt @@ -1,9 +1,7 @@ package scientifik.kmath.functions -import scientifik.kmath.operations.RealField import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Space -import kotlin.jvm.JvmName import kotlin.math.max import kotlin.math.pow @@ -20,11 +18,11 @@ fun Polynomial.value() = fun > Polynomial.value(ring: C, arg: T): T = ring.run { - if( coefficients.isEmpty()) return@run zero + if (coefficients.isEmpty()) return@run zero var res = coefficients.first() var powerArg = arg - for( index in 1 until coefficients.size){ - res += coefficients[index]*powerArg + for (index in 1 until coefficients.size) { + res += coefficients[index] * powerArg //recalculating power on each step to avoid power costs on long polynomials powerArg *= arg } @@ -43,9 +41,6 @@ fun > Polynomial.asMathFunction(): MathFunction> Polynomial.asFunction(ring: C): (T) -> T = { value(ring, it) } -@JvmName("asRealUFunction") -fun Polynomial.asFunction(): (Double) -> Double = asFunction(RealField) - /** * An algebra for polynomials */ @@ -73,22 +68,4 @@ class PolynomialSpace>(val ring: C) : Space> fun , R> C.polynomial(block: PolynomialSpace.() -> R): R { return PolynomialSpace(this).run(block) -} - -class PiecewisePolynomial> internal constructor( - val lowerBoundary: T, - val pieces: List>> -) - -private fun > PiecewisePolynomial.findPiece(arg: T): Polynomial? { - if (arg < lowerBoundary || arg > pieces.last().first) return null - return pieces.first { arg < it.first }.second -} - -/** - * Return a value of polynomial function with given [ring] an given [arg] or null if argument is outside of piecewise definition. - */ -fun , C : Ring> PiecewisePolynomial.value(ring: C, arg: T): T? = - findPiece(arg)?.value(ring, arg) - -fun , C : Ring> PiecewisePolynomial.asFunction(ring: C): (T) -> T? = { value(ring, it) } \ No newline at end of file +} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt index 2fc8aaccc..720f1080b 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt @@ -1,5 +1,6 @@ package scientifik.kmath.interpolation +import scientifik.kmath.functions.OrderedPiecewisePolynomial import scientifik.kmath.functions.PiecewisePolynomial import scientifik.kmath.functions.Polynomial import scientifik.kmath.operations.Field @@ -10,15 +11,18 @@ import scientifik.kmath.operations.Field class LinearInterpolator>(override val algebra: Field) : PolynomialInterpolator { override fun interpolatePolynomials(points: Collection>): PiecewisePolynomial = algebra.run { + require(points.isNotEmpty()) { "Point array should not be empty" } + //sorting points val sorted = points.sortedBy { it.first } - val pairs: List>> = (0 until points.size - 1).map { i -> - val slope = (sorted[i + 1].second - sorted[i].second) / (sorted[i + 1].first - sorted[i].first) - val const = sorted[i].second - slope * sorted[i].first - sorted[i + 1].first to Polynomial(const, slope) + return@run OrderedPiecewisePolynomial(points.first().first).apply { + for (i in 0 until points.size - 1) { + val slope = (sorted[i + 1].second - sorted[i].second) / (sorted[i + 1].first - sorted[i].first) + val const = sorted[i].second - slope * sorted[i].first + val polynomial = Polynomial(const, slope) + putRight(sorted[i + 1].first, polynomial) + } } - - return PiecewisePolynomial(sorted.first().first, pairs) } } \ No newline at end of file diff --git a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt index 71303107e..6d35d19f1 100644 --- a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt +++ b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt @@ -18,8 +18,8 @@ class LinearInterpolatorTest { val polynomial = LinearInterpolator(RealField).interpolatePolynomials(data) val function = polynomial.asFunction(RealField) -// assertEquals(null, function(-1.0)) -// assertEquals(0.5, function(0.5)) + assertEquals(null, function(-1.0)) + assertEquals(0.5, function(0.5)) assertEquals(2.0, function(1.5)) assertEquals(3.0, function(2.0)) } -- 2.34.1 From 73f40105c47630b544dd44157233b3f54e00d7d6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 12 Feb 2020 21:57:21 +0300 Subject: [PATCH 16/32] Interpolation API --- kmath-commons/build.gradle.kts | 1 + .../scientifik/kmath/structures/Buffers.kt | 2 + .../kmath/functions}/Piecewise.kt | 8 +- .../kmath/functions}/Polynomial.kt | 6 +- .../kmath/functions}/functions.kt | 0 .../kmath/interpolation/Interpolator.kt | 6 +- .../kmath/interpolation/LinearInterpolator.kt | 16 +- .../kmath/interpolation/LoessInterpolator.kt | 296 ++++++++++++++++++ .../kmath/interpolation/SplineInterpolator.kt | 58 ++++ .../kmath/interpolation/XYPointSet.kt | 54 ++++ 10 files changed, 430 insertions(+), 17 deletions(-) rename kmath-functions/src/commonMain/kotlin/{scientifik.kmath.functions => scientifik/kmath/functions}/Piecewise.kt (85%) rename kmath-functions/src/commonMain/kotlin/{scientifik.kmath.functions => scientifik/kmath/functions}/Polynomial.kt (94%) rename kmath-functions/src/commonMain/kotlin/{scientifik.kmath.functions => scientifik/kmath/functions}/functions.kt (100%) create mode 100644 kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt create mode 100644 kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt create mode 100644 kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index 9ac0f6e95..5ce1b935a 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -8,5 +8,6 @@ dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) api(project(":kmath-prob")) + api(project(":kmath-functions")) api("org.apache.commons:commons-math3:3.6.1") } \ 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 c63384fb9..f02fd8dd0 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/structures/Buffers.kt @@ -73,6 +73,8 @@ fun Buffer.asSequence(): Sequence = Sequence(::iterator) fun Buffer.asIterable(): Iterable = asSequence().asIterable() +val Buffer<*>.indices: IntRange get() = IntRange(0, size - 1) + interface MutableBuffer : Buffer { operator fun set(index: Int, value: T) diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Piecewise.kt similarity index 85% rename from kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Piecewise.kt index c05660d52..16f8aa12b 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Piecewise.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Piecewise.kt @@ -6,14 +6,16 @@ interface Piecewise { fun findPiece(arg: T): R? } -interface PiecewisePolynomial : Piecewise> +interface PiecewisePolynomial : + Piecewise> /** * Ordered list of pieces in piecewise function */ -class OrderedPiecewisePolynomial>(left: T) : PiecewisePolynomial { +class OrderedPiecewisePolynomial>(delimeter: T) : + PiecewisePolynomial { - private val delimiters: ArrayList = arrayListOf(left) + private val delimiters: ArrayList = arrayListOf(delimeter) private val pieces: ArrayList> = ArrayList() /** diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Polynomial.kt similarity index 94% rename from kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Polynomial.kt index bd5062661..b747b521d 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/Polynomial.kt @@ -32,7 +32,8 @@ fun > Polynomial.value(ring: C, arg: T): T = ring.run { /** * Represent a polynomial as a context-dependent function */ -fun > Polynomial.asMathFunction(): MathFunction = object : MathFunction { +fun > Polynomial.asMathFunction(): MathFunction = object : + MathFunction { override fun C.invoke(arg: T): T = value(this, arg) } @@ -61,7 +62,8 @@ class PolynomialSpace>(val ring: C) : Space> } } - override val zero: Polynomial = Polynomial(emptyList()) + override val zero: Polynomial = + Polynomial(emptyList()) operator fun Polynomial.invoke(arg: T): T = value(ring, arg) } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/functions.kt similarity index 100% rename from kmath-functions/src/commonMain/kotlin/scientifik.kmath.functions/functions.kt rename to kmath-functions/src/commonMain/kotlin/scientifik/kmath/functions/functions.kt diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt index 1df9df5d7..9b13c92e3 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt @@ -5,7 +5,7 @@ import scientifik.kmath.functions.value import scientifik.kmath.operations.Ring interface Interpolator { - fun interpolate(points: Collection>): (X) -> Y + fun interpolate(points: XYPointSet): (X) -> Y } interface PolynomialInterpolator> : Interpolator { @@ -13,9 +13,9 @@ interface PolynomialInterpolator> : Interpolator { fun getDefaultValue(): T = error("Out of bounds") - fun interpolatePolynomials(points: Collection>): PiecewisePolynomial + fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial - override fun interpolate(points: Collection>): (T) -> T = { x -> + override fun interpolate(points: XYPointSet): (T) -> T = { x -> interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() } } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt index 720f1080b..9467da421 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt @@ -10,18 +10,16 @@ import scientifik.kmath.operations.Field */ class LinearInterpolator>(override val algebra: Field) : PolynomialInterpolator { - override fun interpolatePolynomials(points: Collection>): PiecewisePolynomial = algebra.run { - require(points.isNotEmpty()) { "Point array should not be empty" } + override fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial = algebra.run { + require(points.size > 0) { "Point array should not be empty" } + insureSorted(points) - //sorting points - val sorted = points.sortedBy { it.first } - - return@run OrderedPiecewisePolynomial(points.first().first).apply { + OrderedPiecewisePolynomial(points.x[0]).apply { for (i in 0 until points.size - 1) { - val slope = (sorted[i + 1].second - sorted[i].second) / (sorted[i + 1].first - sorted[i].first) - val const = sorted[i].second - slope * sorted[i].first + val slope = (points.y[i + 1] - points.y[i]) / (points.x[i + 1] - points.x[i]) + val const = points.x[i] - slope * points.x[i] val polynomial = Polynomial(const, slope) - putRight(sorted[i + 1].first, polynomial) + putRight(points.x[i + 1], polynomial) } } } diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt new file mode 100644 index 000000000..c29549ed0 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt @@ -0,0 +1,296 @@ +//package scientifik.kmath.interpolation +// +//import scientifik.kmath.functions.PiecewisePolynomial +//import scientifik.kmath.operations.Ring +//import scientifik.kmath.structures.Buffer +//import kotlin.math.abs +//import kotlin.math.sqrt +// +// +///** +// * Original code: https://github.com/apache/commons-math/blob/eb57d6d457002a0bb5336d789a3381a24599affe/src/main/java/org/apache/commons/math4/analysis/interpolation/LoessInterpolator.java +// */ +//class LoessInterpolator>(override val algebra: Ring) : PolynomialInterpolator { +// /** +// * The bandwidth parameter: when computing the loess fit at +// * a particular point, this fraction of source points closest +// * to the current point is taken into account for computing +// * a least-squares regression. +// * +// * +// * A sensible value is usually 0.25 to 0.5. +// */ +// private var bandwidth = 0.0 +// +// /** +// * The number of robustness iterations parameter: this many +// * robustness iterations are done. +// * +// * +// * A sensible value is usually 0 (just the initial fit without any +// * robustness iterations) to 4. +// */ +// private var robustnessIters = 0 +// +// /** +// * If the median residual at a certain robustness iteration +// * is less than this amount, no more iterations are done. +// */ +// private var accuracy = 0.0 +// +// /** +// * Constructs a new [LoessInterpolator] +// * with a bandwidth of [.DEFAULT_BANDWIDTH], +// * [.DEFAULT_ROBUSTNESS_ITERS] robustness iterations +// * and an accuracy of {#link #DEFAULT_ACCURACY}. +// * See [.LoessInterpolator] for an explanation of +// * the parameters. +// */ +// fun LoessInterpolator() { +// bandwidth = DEFAULT_BANDWIDTH +// robustnessIters = DEFAULT_ROBUSTNESS_ITERS +// accuracy = DEFAULT_ACCURACY +// } +// +// fun LoessInterpolator(bandwidth: Double, robustnessIters: Int) { +// this(bandwidth, robustnessIters, DEFAULT_ACCURACY) +// } +// +// fun LoessInterpolator(bandwidth: Double, robustnessIters: Int, accuracy: Double) { +// if (bandwidth < 0 || +// bandwidth > 1 +// ) { +// throw OutOfRangeException(LocalizedFormats.BANDWIDTH, bandwidth, 0, 1) +// } +// this.bandwidth = bandwidth +// if (robustnessIters < 0) { +// throw NotPositiveException(LocalizedFormats.ROBUSTNESS_ITERATIONS, robustnessIters) +// } +// this.robustnessIters = robustnessIters +// this.accuracy = accuracy +// } +// +// fun interpolate( +// xval: DoubleArray, +// yval: DoubleArray +// ): PolynomialSplineFunction { +// return SplineInterpolator().interpolate(xval, smooth(xval, yval)) +// } +// +// fun XYZPointSet.smooth(): XYPointSet { +// checkAllFiniteReal(x) +// checkAllFiniteReal(y) +// checkAllFiniteReal(z) +// MathArrays.checkOrder(xval) +// if (size == 1) { +// return doubleArrayOf(y[0]) +// } +// if (size == 2) { +// return doubleArrayOf(y[0], y[1]) +// } +// val bandwidthInPoints = (bandwidth * size).toInt() +// if (bandwidthInPoints < 2) { +// throw NumberIsTooSmallException( +// LocalizedFormats.BANDWIDTH, +// bandwidthInPoints, 2, true +// ) +// } +// val res = DoubleArray(size) +// val residuals = DoubleArray(size) +// val sortedResiduals = DoubleArray(size) +// val robustnessWeights = DoubleArray(size) +// // Do an initial fit and 'robustnessIters' robustness iterations. +// // This is equivalent to doing 'robustnessIters+1' robustness iterations +// // starting with all robustness weights set to 1. +// Arrays.fill(robustnessWeights, 1.0) +// for (iter in 0..robustnessIters) { +// val bandwidthInterval = intArrayOf(0, bandwidthInPoints - 1) +// // At each x, compute a local weighted linear regression +// for (i in 0 until size) { +//// val x = x[i] +// // Find out the interval of source points on which +// // a regression is to be made. +// if (i > 0) { +// updateBandwidthInterval(x, z, i, bandwidthInterval) +// } +// val ileft = bandwidthInterval[0] +// val iright = bandwidthInterval[1] +// // Compute the point of the bandwidth interval that is +// // farthest from x +// val edge: Int +// edge = if (x[i] - x[ileft] > x[iright] - x[i]) { +// ileft +// } else { +// iright +// } +// // Compute a least-squares linear fit weighted by +// // the product of robustness weights and the tricube +// // weight function. +// // See http://en.wikipedia.org/wiki/Linear_regression +// // (section "Univariate linear case") +// // and http://en.wikipedia.org/wiki/Weighted_least_squares +// // (section "Weighted least squares") +// var sumWeights = 0.0 +// var sumX = 0.0 +// var sumXSquared = 0.0 +// var sumY = 0.0 +// var sumXY = 0.0 +// val denom: Double = abs(1.0 / (x[edge] - x[i])) +// for (k in ileft..iright) { +// val xk = x[k] +// val yk = y[k] +// val dist = if (k < i) x - xk else xk - x[i] +// val w = tricube(dist * denom) * robustnessWeights[k] * z[k] +// val xkw = xk * w +// sumWeights += w +// sumX += xkw +// sumXSquared += xk * xkw +// sumY += yk * w +// sumXY += yk * xkw +// } +// val meanX = sumX / sumWeights +// val meanY = sumY / sumWeights +// val meanXY = sumXY / sumWeights +// val meanXSquared = sumXSquared / sumWeights +// val beta: Double +// beta = if (sqrt(abs(meanXSquared - meanX * meanX)) < accuracy) { +// 0.0 +// } else { +// (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX) +// } +// val alpha = meanY - beta * meanX +// res[i] = beta * x[i] + alpha +// residuals[i] = abs(y[i] - res[i]) +// } +// // No need to recompute the robustness weights at the last +// // iteration, they won't be needed anymore +// if (iter == robustnessIters) { +// break +// } +// // Recompute the robustness weights. +// // Find the median residual. +// // An arraycopy and a sort are completely tractable here, +// // because the preceding loop is a lot more expensive +// java.lang.System.arraycopy(residuals, 0, sortedResiduals, 0, size) +// Arrays.sort(sortedResiduals) +// val medianResidual = sortedResiduals[size / 2] +// if (abs(medianResidual) < accuracy) { +// break +// } +// for (i in 0 until size) { +// val arg = residuals[i] / (6 * medianResidual) +// if (arg >= 1) { +// robustnessWeights[i] = 0.0 +// } else { +// val w = 1 - arg * arg +// robustnessWeights[i] = w * w +// } +// } +// } +// return res +// } +// +// fun smooth(xval: DoubleArray, yval: DoubleArray): DoubleArray { +// if (xval.size != yval.size) { +// throw DimensionMismatchException(xval.size, yval.size) +// } +// val unitWeights = DoubleArray(xval.size) +// Arrays.fill(unitWeights, 1.0) +// return smooth(xval, yval, unitWeights) +// } +// +// /** +// * Given an index interval into xval that embraces a certain number of +// * points closest to `xval[i-1]`, update the interval so that it +// * embraces the same number of points closest to `xval[i]`, +// * ignoring zero weights. +// * +// * @param xval Arguments array. +// * @param weights Weights array. +// * @param i Index around which the new interval should be computed. +// * @param bandwidthInterval a two-element array {left, right} such that: +// * `(left==0 or xval[i] - xval[left-1] > xval[right] - xval[i])` +// * and +// * `(right==xval.length-1 or xval[right+1] - xval[i] > xval[i] - xval[left])`. +// * The array will be updated. +// */ +// private fun updateBandwidthInterval( +// xval: Buffer, weights: Buffer, +// i: Int, +// bandwidthInterval: IntArray +// ) { +// val left = bandwidthInterval[0] +// val right = bandwidthInterval[1] +// // The right edge should be adjusted if the next point to the right +// // is closer to xval[i] than the leftmost point of the current interval +// val nextRight = nextNonzero(weights, right) +// if (nextRight < xval.size && xval[nextRight] - xval[i] < xval[i] - xval[left]) { +// val nextLeft = nextNonzero(weights, bandwidthInterval[0]) +// bandwidthInterval[0] = nextLeft +// bandwidthInterval[1] = nextRight +// } +// } +// +// /** +// * Return the smallest index `j` such that +// * `j > i && (j == weights.length || weights[j] != 0)`. +// * +// * @param weights Weights array. +// * @param i Index from which to start search. +// * @return the smallest compliant index. +// */ +// private fun nextNonzero(weights: Buffer, i: Int): Int { +// var j = i + 1 +// while (j < weights.size && weights[j] == 0.0) { +// ++j +// } +// return j +// } +// +// /** +// * Compute the +// * [tricube](http://en.wikipedia.org/wiki/Local_regression#Weight_function) +// * weight function +// * +// * @param x Argument. +// * @return `(1 - |x|3)3` for |x| < 1, 0 otherwise. +// */ +// private fun tricube(x: Double): Double { +// val absX: Double = FastMath.abs(x) +// if (absX >= 1.0) { +// return 0.0 +// } +// val tmp = 1 - absX * absX * absX +// return tmp * tmp * tmp +// } +// +// /** +// * Check that all elements of an array are finite real numbers. +// * +// * @param values Values array. +// * @throws org.apache.commons.math4.exception.NotFiniteNumberException +// * if one of the values is not a finite real number. +// */ +// private fun checkAllFiniteReal(values: DoubleArray) { +// for (i in values.indices) { +// MathUtils.checkFinite(values[i]) +// } +// } +// +// override fun interpolatePolynomials(points: Collection>): PiecewisePolynomial { +// TODO("not implemented") //To change body of created functions use File | Settings | File Templates. +// } +// +// companion object { +// /** Default value of the bandwidth parameter. */ +// const val DEFAULT_BANDWIDTH = 0.3 +// +// /** Default value of the number of robustness iterations. */ +// const val DEFAULT_ROBUSTNESS_ITERS = 2 +// +// /** +// * Default value for accuracy. +// */ +// const val DEFAULT_ACCURACY = 1e-12 +// } +//} \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt new file mode 100644 index 000000000..e1af0c1a2 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/SplineInterpolator.kt @@ -0,0 +1,58 @@ +package scientifik.kmath.interpolation + +import scientifik.kmath.functions.OrderedPiecewisePolynomial +import scientifik.kmath.functions.PiecewisePolynomial +import scientifik.kmath.functions.Polynomial +import scientifik.kmath.operations.Field +import scientifik.kmath.structures.MutableBufferFactory + +/** + * Generic spline interpolator. Not recommended for performance critical places, use platform-specific and type specific ones. + * Based on https://github.com/apache/commons-math/blob/eb57d6d457002a0bb5336d789a3381a24599affe/src/main/java/org/apache/commons/math4/analysis/interpolation/SplineInterpolator.java + */ +class SplineInterpolator>( + override val algebra: Field, + val bufferFactory: MutableBufferFactory +) : PolynomialInterpolator { + + //TODO possibly optimize zeroed buffers + + override fun interpolatePolynomials(points: XYPointSet): PiecewisePolynomial = algebra.run { + if (points.size < 3) { + error("Can't use spline interpolator with less than 3 points") + } + insureSorted(points) + + // Number of intervals. The number of data points is n + 1. + val n = points.size - 1 + // Differences between knot points + val h = bufferFactory(points.size) { i -> points.x[i + 1] - points.x[i] } + val mu = bufferFactory(points.size - 1) { zero } + val z = bufferFactory(points.size) { zero } + + for (i in 1 until n) { + val g = 2.0 * (points.x[i + 1] - points.x[i - 1]) - h[i - 1] * mu[i - 1] + mu[i] = h[i] / g + z[i] = + (3.0 * (points.y[i + 1] * h[i - 1] - points.x[i] * (points.x[i + 1] - points.x[i - 1]) + points.y[i - 1] * h[i]) / (h[i - 1] * h[i]) + - h[i - 1] * z[i - 1]) / g + } + + // cubic spline coefficients -- b is linear, c quadratic, d is cubic (original y's are constants) + + OrderedPiecewisePolynomial(points.x[points.size - 1]).apply { + var cOld = zero + for (j in n - 1 downTo 0) { + val c = z[j] - mu[j] * cOld + val a = points.y[j] + val b = (points.y[j + 1] - points.y[j]) / h[j] - h[j] * (cOld + 2.0 * c) / 3.0 + val d = (cOld - c) / (3.0 * h[j]) + val polynomial = Polynomial(a, b, c, d) + cOld = c + putLeft(points.x[j], polynomial) + } + } + + } + +} diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt new file mode 100644 index 000000000..d8e10b880 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/XYPointSet.kt @@ -0,0 +1,54 @@ +package scientifik.kmath.interpolation + +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.Structure2D + +interface XYPointSet { + val size: Int + val x: Buffer + val y: Buffer +} + +interface XYZPointSet : XYPointSet { + val z: Buffer +} + +internal fun > insureSorted(points: XYPointSet) { + for (i in 0 until points.size - 1) { + if (points.x[i + 1] <= points.x[i]) error("Input data is not sorted at index $i") + } +} + +class NDStructureColumn(val structure: Structure2D, val column: Int) : Buffer { + init { + require(column < structure.colNum) { "Column index is outside of structure column range" } + } + + override val size: Int get() = structure.rowNum + + override fun get(index: Int): T = structure[index, column] + + override fun iterator(): Iterator = sequence { + repeat(size) { + yield(get(it)) + } + }.iterator() +} + +class BufferXYPointSet(override val x: Buffer, override val y: Buffer) : XYPointSet { + init { + require(x.size == y.size) { "Sizes of x and y buffers should be the same" } + } + + override val size: Int + get() = x.size +} + +fun Structure2D.asXYPointSet(): XYPointSet { + require(shape[1] == 2) { "Structure second dimension should be of size 2" } + return object : XYPointSet { + override val size: Int get() = this@asXYPointSet.shape[0] + override val x: Buffer get() = NDStructureColumn(this@asXYPointSet, 0) + override val y: Buffer get() = NDStructureColumn(this@asXYPointSet, 1) + } +} \ No newline at end of file -- 2.34.1 From 068b90e7ab9e116bf6668dd960111f71da00b35e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 12 Feb 2020 22:26:25 +0300 Subject: [PATCH 17/32] Add different ways to provide data for XY interpolator --- .../kmath/interpolation/Interpolator.kt | 24 +++++++++++++++++++ .../kmath/interpolation/LoessInterpolator.kt | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt index 9b13c92e3..8d83e4198 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/Interpolator.kt @@ -3,6 +3,8 @@ package scientifik.kmath.interpolation import scientifik.kmath.functions.PiecewisePolynomial import scientifik.kmath.functions.value import scientifik.kmath.operations.Ring +import scientifik.kmath.structures.Buffer +import scientifik.kmath.structures.asBuffer interface Interpolator { fun interpolate(points: XYPointSet): (X) -> Y @@ -18,4 +20,26 @@ interface PolynomialInterpolator> : Interpolator { override fun interpolate(points: XYPointSet): (T) -> T = { x -> interpolatePolynomials(points).value(algebra, x) ?: getDefaultValue() } +} + +fun > PolynomialInterpolator.interpolatePolynomials( + x: Buffer, + y: Buffer +): PiecewisePolynomial { + val pointSet = BufferXYPointSet(x, y) + return interpolatePolynomials(pointSet) +} + +fun > PolynomialInterpolator.interpolatePolynomials( + data: Map +): PiecewisePolynomial { + val pointSet = BufferXYPointSet(data.keys.toList().asBuffer(), data.values.toList().asBuffer()) + return interpolatePolynomials(pointSet) +} + +fun > PolynomialInterpolator.interpolatePolynomials( + data: List> +): PiecewisePolynomial { + val pointSet = BufferXYPointSet(data.map { it.first }.asBuffer(), data.map { it.second }.asBuffer()) + return interpolatePolynomials(pointSet) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt index c29549ed0..6707bd8bc 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LoessInterpolator.kt @@ -1,4 +1,4 @@ -//package scientifik.kmath.interpolation +package scientifik.kmath.interpolation // //import scientifik.kmath.functions.PiecewisePolynomial //import scientifik.kmath.operations.Ring -- 2.34.1 From e00a66ca428ccdd18b8836f6dc9c9fb200a494c4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 13 Feb 2020 16:21:41 +0300 Subject: [PATCH 18/32] Fix linear interpolator const --- .../scientifik/kmath/interpolation/LinearInterpolator.kt | 2 +- .../scientifik/kmath/interpolation/LinearInterpolatorTest.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt index 9467da421..98beb4391 100644 --- a/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/scientifik/kmath/interpolation/LinearInterpolator.kt @@ -17,7 +17,7 @@ class LinearInterpolator>(override val algebra: Field) : Po OrderedPiecewisePolynomial(points.x[0]).apply { for (i in 0 until points.size - 1) { val slope = (points.y[i + 1] - points.y[i]) / (points.x[i + 1] - points.x[i]) - val const = points.x[i] - slope * points.x[i] + val const = points.y[i] - slope * points.x[i] val polynomial = Polynomial(const, slope) putRight(points.x[i + 1], polynomial) } diff --git a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt index 6d35d19f1..23acd835c 100644 --- a/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt +++ b/kmath-functions/src/commonTest/kotlin/scientifik/kmath/interpolation/LinearInterpolatorTest.kt @@ -1,5 +1,6 @@ package scientifik.kmath.interpolation +import scientifik.kmath.functions.PiecewisePolynomial import scientifik.kmath.functions.asFunction import scientifik.kmath.operations.RealField import kotlin.test.Test @@ -15,7 +16,7 @@ class LinearInterpolatorTest { 2.0 to 3.0, 3.0 to 4.0 ) - val polynomial = LinearInterpolator(RealField).interpolatePolynomials(data) + val polynomial: PiecewisePolynomial = LinearInterpolator(RealField).interpolatePolynomials(data) val function = polynomial.asFunction(RealField) assertEquals(null, function(-1.0)) -- 2.34.1 From 9500ee0924d828a600771857197a0e369d707fcf Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Mon, 30 Mar 2020 16:30:16 +0300 Subject: [PATCH 19/32] Initial implementation of multiplatform BigInteger --- .../kmath/operations/KBigInteger.kt | 398 +++++++++++++++ .../kmath/operations/KBigIntegerTest.kt | 480 ++++++++++++++++++ 2 files changed, 878 insertions(+) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt new file mode 100644 index 000000000..0238492d2 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt @@ -0,0 +1,398 @@ +package scientifik.kmath.operations + +import kotlin.math.max +import kotlin.math.sign + +/* + * Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). + * Initial version from https://github.com/robdrynkin/kotlin-big-integer + */ + +@kotlin.ExperimentalUnsignedTypes +typealias Magnitude = UIntArray + +@kotlin.ExperimentalUnsignedTypes +typealias TBase = ULong + +object KBigIntegerRing: Ring { + override val zero: KBigInteger = KBigInteger.ZERO + override val one: KBigInteger = KBigInteger.ONE + + override fun add(a: KBigInteger, b: KBigInteger): KBigInteger = a.plus(b) + + override fun multiply(a: KBigInteger, k: Number): KBigInteger = a.times(k.toLong()) + + override fun multiply(a: KBigInteger, b: KBigInteger): KBigInteger = a.times(b) + + operator fun String.unaryPlus(): KBigInteger = KBigInteger(this)!! + + operator fun String.unaryMinus(): KBigInteger = -KBigInteger(this)!! + +} + + +@kotlin.ExperimentalUnsignedTypes +class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): + RingElement, Comparable { + + constructor(x: Int) : this(x.sign, uintArrayOf(kotlin.math.abs(x).toUInt())) + constructor(x: Long) : this(x.sign, uintArrayOf( + (kotlin.math.abs(x).toULong() and BASE).toUInt(), + ((kotlin.math.abs(x).toULong() shr BASE_SIZE) and BASE).toUInt())) + + val magnitude = stripLeadingZeros(magnitude) + val sign = if (this.magnitude.isNotEmpty()) sign else 0 + val sizeByte: Int = magnitude.size * BASE_SIZE / 4 + + override val context: KBigIntegerRing get() = KBigIntegerRing + + override fun unwrap(): KBigInteger = this + override fun KBigInteger.wrap(): KBigInteger = this + + companion object { + val BASE = 0xffffffffUL + const val BASE_SIZE: Int = 32 + val ZERO: KBigInteger = KBigInteger() + val ONE: KBigInteger = KBigInteger(1) + + private val hexMapping: HashMap = + hashMapOf( + 0U to "0", 1U to "1", 2U to "2", 3U to "3", 4U to "4", 5U to "5", 6U to "6", 7U to "7", 8U to "8", + 9U to "9", 10U to "a", 11U to "b", 12U to "c", 13U to "d", 14U to "e", 15U to "f" + ) + + private fun stripLeadingZeros(mag: Magnitude): Magnitude { + // TODO: optimize performance + if (mag.isEmpty()) { + return mag + } + var resSize: Int = mag.size - 1 + while (mag[resSize] == 0U) { + if (resSize == 0) + break + resSize -= 1 + } + return mag.sliceArray(IntRange(0, resSize)) + } + + private fun compareMagnitudes(mag1: Magnitude, mag2: Magnitude): Int { + when { + mag1.size > mag2.size -> return 1 + mag1.size < mag2.size -> return -1 + else -> { + for (i in mag1.size - 1 downTo 0) { + if (mag1[i] > mag2[i]) { + return 1 + } else if (mag1[i] < mag2[i]) { + return -1 + } + } + return 0 + } + } + } + + private fun addMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + val resultLength: Int = max(mag1.size, mag2.size) + 1 + val result = Magnitude(resultLength) + var carry: TBase = 0UL + + for (i in 0 until resultLength - 1) { + val res = when { + i >= mag1.size -> mag2[i].toULong() + carry + i >= mag2.size -> mag1[i].toULong() + carry + else -> mag1[i].toULong() + mag2[i].toULong() + carry + } + result[i] = (res and BASE).toUInt() + carry = (res shr BASE_SIZE) + } + result[resultLength - 1] = carry.toUInt() + return result + } + + private fun subtractMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + val resultLength: Int = mag1.size + val result = Magnitude(resultLength) + var carry = 0L + + for (i in 0 until resultLength) { + var res: Long = + if (i < mag2.size) mag1[i].toLong() - mag2[i].toLong() - carry + else mag1[i].toLong() - carry + + carry = if (res < 0) 1 else 0 + res += carry * (BASE + 1UL).toLong() + + result[i] = res.toUInt() + } + + return result + } + + private fun multiplyMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { + val resultLength: Int = mag.size + 1 + val result = Magnitude(resultLength) + var carry: ULong = 0UL + + for (i in mag.indices) { + val cur: ULong = carry + mag[i].toULong() * x.toULong() + result[i] = (cur and BASE.toULong()).toUInt() + carry = cur shr BASE_SIZE + } + result[resultLength - 1] = (carry and BASE).toUInt() + + return result + } + + private fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + val resultLength: Int = mag1.size + mag2.size + val result = Magnitude(resultLength) + + for (i in mag1.indices) { + var carry: ULong = 0UL + for (j in mag2.indices) { + val cur: ULong = result[i + j].toULong() + mag1[i].toULong() * mag2[j].toULong() + carry + result[i + j] = (cur and BASE.toULong()).toUInt() + carry = cur shr BASE_SIZE + } + result[i + mag2.size] = (carry and BASE).toUInt() + } + + return result + } + + internal fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { + val resultLength: Int = mag.size + val result = Magnitude(resultLength) + var carry: ULong = 0UL + + for (i in mag.size - 1 downTo 0) { + val cur: ULong = mag[i].toULong() + (carry shl BASE_SIZE) + result[i] = (cur / x).toUInt() + carry = cur % x + } + return result + } + + internal fun divideMagnitudes(mag1_: Magnitude, mag2: Magnitude): Magnitude { + val mag1 = ULongArray(mag1_.size) { mag1_[it].toULong() } + + val resultLength: Int = mag1.size - mag2.size + 1 + val result = LongArray(resultLength) + + for (i in mag1.size - 1 downTo mag2.size - 1) { + val div: ULong = mag1[i] / mag2[mag2.size - 1] + result[i - mag2.size + 1] = div.toLong() + for (j in mag2.indices) { + mag1[i - j] -= mag2[mag2.size - 1 - j] * div + } + if (i > 0) { + mag1[i - 1] += (mag1[i] shl BASE_SIZE) + } + } + + val normalizedResult = Magnitude(resultLength) + var carry = 0L + + for (i in result.indices) { + result[i] += carry + if (result[i] < 0L) { + normalizedResult[i] = (result[i] + (BASE + 1UL).toLong()).toUInt() + carry = -1 + } else { + normalizedResult[i] = result[i].toUInt() + carry = 0 + } + } + + return normalizedResult + } + } + + override fun compareTo(other: KBigInteger): Int { + return when { + (this.sign == 0) and (other.sign == 0) -> 0 + this.sign < other.sign -> -1 + this.sign > other.sign -> 1 + else -> this.sign * compareMagnitudes(this.magnitude, other.magnitude) + } + } + + override fun equals(other: Any?): Boolean { + if (other is KBigInteger) { + return this.compareTo(other) == 0 + } + else error("Can't compare KBigInteger to a different type") + } + + override fun hashCode(): Int { + return magnitude.hashCode() + this.sign + } + + operator fun unaryMinus(): KBigInteger { + return if (this.sign == 0) this else KBigInteger(-this.sign, this.magnitude) + } + + override operator fun plus(b: KBigInteger): KBigInteger { + return when { + b.sign == 0 -> this + this.sign == 0 -> b + this == -b -> ZERO + this.sign == b.sign -> KBigInteger(this.sign, addMagnitudes(this.magnitude, b.magnitude)) + else -> { + val comp: Int = compareMagnitudes(this.magnitude, b.magnitude) + + if (comp == 1) { + KBigInteger(this.sign, subtractMagnitudes(this.magnitude, b.magnitude)) + } else { + KBigInteger(-this.sign, subtractMagnitudes(b.magnitude, this.magnitude)) + } + } + } + } + + override operator fun minus(b: KBigInteger): KBigInteger { + return this + (-b) + } + + override operator fun times(b: KBigInteger): KBigInteger { + return when { + this.sign == 0 -> ZERO + b.sign == 0 -> ZERO +// TODO: Karatsuba + else -> KBigInteger(this.sign * b.sign, multiplyMagnitudes(this.magnitude, b.magnitude)) + } + } + + operator fun times(other: UInt): KBigInteger { + return when { + this.sign == 0 -> ZERO + other == 0U -> ZERO + else -> KBigInteger(this.sign, multiplyMagnitudeByUInt(this.magnitude, other)) + } + } + + operator fun times(other: Int): KBigInteger { + return if (other > 0) + this * kotlin.math.abs(other).toUInt() + else + -this * kotlin.math.abs(other).toUInt() + } + + operator fun div(other: UInt): KBigInteger { + return KBigInteger(this.sign, divideMagnitudeByUInt(this.magnitude, other)) + } + + operator fun div(other: Int): KBigInteger { + return KBigInteger(this.sign * other.sign, divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt())) + } + + operator fun div(other: KBigInteger): KBigInteger { + return when { + this < other -> ZERO + this == other -> ONE + else -> KBigInteger(this.sign * other.sign, divideMagnitudes(this.magnitude, other.magnitude)) + } + } + + operator fun rem(other: Int): Int { + val res = this - (this / other) * other + return if (res == ZERO) 0 else res.sign * res.magnitude[0].toInt() + } + + operator fun rem(other: KBigInteger): KBigInteger { + return this - (this / other) * other + } + + fun modPow(exponent: KBigInteger, m: KBigInteger): KBigInteger { + return when { + exponent == ZERO -> ONE + exponent % 2 == 1 -> (this * modPow(exponent - ONE, m)) % m + else -> { + val sqRoot = modPow(exponent / 2, m) + (sqRoot * sqRoot) % m + } + } + } + + override fun toString(): String { + if (this.sign == 0) { + return "0x0" + } + var res: String = if (this.sign == -1) "-0x" else "0x" + var numberStarted = false + + for (i in this.magnitude.size - 1 downTo 0) { + for (j in BASE_SIZE / 4 - 1 downTo 0) { + val curByte = (this.magnitude[i] shr 4 * j) and 0xfU + if (numberStarted or (curByte != 0U)) { + numberStarted = true + res += hexMapping[curByte] + } + } + } + + return res + } +} + +@kotlin.ExperimentalUnsignedTypes +fun abs(x: KBigInteger): KBigInteger { + return if (x.sign == 0) x else KBigInteger(1, x.magnitude) +} + +@kotlin.ExperimentalUnsignedTypes +// Can't put it as constructor in class due to platform declaration clash with KBigInteger(Int) +fun KBigInteger(x: UInt): KBigInteger + = KBigInteger(1, uintArrayOf(x)) + +@kotlin.ExperimentalUnsignedTypes +// Can't put it as constructor in class due to platform declaration clash with KBigInteger(Long) +fun KBigInteger(x: ULong): KBigInteger + = KBigInteger(1, uintArrayOf((x and KBigInteger.BASE).toUInt(), ((x shr KBigInteger.BASE_SIZE) and KBigInteger.BASE).toUInt())) + +val hexChToInt = hashMapOf('0' to 0, '1' to 1, '2' to 2, '3' to 3, '4' to 4, '5' to 5, '6' to 6, '7' to 7, + '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15) + +// Returns None if a valid number can not be read from a string +fun KBigInteger(s: String): KBigInteger? { + val sign: Int + val sPositive: String + when { + s[0] == '+' -> { + sign = +1 + sPositive = s.substring(1) + } + s[0] == '-' -> { + sign = -1 + sPositive = s.substring(1) + } + else -> { + sPositive = s + sign = +1 + } + } + var res = KBigInteger.ZERO + var digitValue = KBigInteger.ONE + val sPositiveUpper = sPositive.toUpperCase() + if (sPositiveUpper.startsWith("0X")) { // hex representation + val sHex = sPositiveUpper.substring(2) + for (ch in sHex.reversed()) { + if (ch == '_') continue + res += digitValue * (hexChToInt[ch] ?: return null) + digitValue *= KBigInteger(16) + } + } + else { // decimal representation + val sDecimal = sPositiveUpper + for (ch in sDecimal.reversed()) { + if (ch == '_') continue + if (ch !in '0'..'9') { + return null + } + res += digitValue * (ch.toInt() - '0'.toInt()) + digitValue *= KBigInteger(10) + } + } + return res * sign +} diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt new file mode 100644 index 000000000..10b89f28b --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt @@ -0,0 +1,480 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.test.assertEquals + +@kotlin.ExperimentalUnsignedTypes +class KBigIntegerConstructorTest { + @Test + fun testConstructorZero() { + assertEquals(KBigInteger(0), KBigInteger(0, uintArrayOf())) + } + + @Test + fun testConstructor8() { + assertEquals(KBigInteger(8), KBigInteger(1, uintArrayOf(8U))) + } + + @Test + fun testConstructor_0xffffffffaL() { + val x = KBigInteger(-0xffffffffaL) + val y = KBigInteger(-1, uintArrayOf(0xfffffffaU, 0xfU)) + assertEquals(x, y) + } +} + +@kotlin.ExperimentalUnsignedTypes +class KBigIntegerCompareTest { + @Test + fun testCompare1_2() { + val x = KBigInteger(1) + val y = KBigInteger(2) + assertTrue { x < y } + } + + @Test + fun testCompare0_0() { + val x = KBigInteger(0) + val y = KBigInteger(0) + assertEquals(x, y) + } + + @Test + fun testCompare1__2() { + val x = KBigInteger(1) + val y = KBigInteger(-2) + assertTrue { x > y } + } + + @Test + fun testCompare_1__2() { + val x = KBigInteger(-1) + val y = KBigInteger(-2) + assertTrue { x > y } + } + + @Test + fun testCompare_2__1() { + val x = KBigInteger(-2) + val y = KBigInteger(-1) + assertTrue { x < y } + } + + @Test + fun testCompare12345_12345() { + val x = KBigInteger(12345) + val y = KBigInteger(12345) + assertEquals(x, y) + } + + @Test + fun testEqualsWithLong() { + val x = KBigInteger(12345) + assertTrue { x == KBigInteger(12345L) } + } + + @Test + fun testEqualsWithULong() { + val x = KBigInteger(12345) + assertTrue { x == KBigInteger(12345UL) } + } + + @Test + fun testCompareBigNumbersGreater() { + val x = KBigInteger(0xfffffffffL) + val y = KBigInteger(0xffffffffaL) + assertTrue { x > y } + } + + @Test + fun testCompareBigNumbersEqual() { + val x = KBigInteger(0xffffffffaL) + val y = KBigInteger(0xffffffffaL) + assertEquals(x, y) + } + + @Test + fun testCompareBigNumbersLess() { + val x = KBigInteger(-0xffffffffaL) + val y = KBigInteger(0xffffffffaL) + assertTrue { x < y } + } +} + +@kotlin.ExperimentalUnsignedTypes +class KBigIntegerOperationsTest { + @Test + fun testPlus_1_1() { + val x = KBigInteger(1) + val y = KBigInteger(1) + + val res = x + y + val sum = KBigInteger(2) + + assertEquals(sum, res) + } + + @Test + fun testPlusBigNumbers() { + val x = KBigInteger(0x7fffffff) + val y = KBigInteger(0x7fffffff) + val z = KBigInteger(0x7fffffff) + + val res = x + y + z + val sum = KBigInteger(1, uintArrayOf(0x7ffffffdU, 0x1U)) + + assertEquals(sum, res) + } + + @Test + fun testUnaryMinus() { + val x = KBigInteger(1234) + val y = KBigInteger(-1234) + assertEquals(-x, y) + } + + @Test + fun testMinus_2_1() { + val x = KBigInteger(2) + val y = KBigInteger(1) + + val res = x - y + val sum = KBigInteger(1) + + assertEquals(sum, res) + } + + @Test + fun testMinus__2_1() { + val x = KBigInteger(-2) + val y = KBigInteger(1) + + val res = x - y + val sum = KBigInteger(-3) + + assertEquals(sum, res) + } + + @Test + fun testMinus___2_1() { + val x = KBigInteger(-2) + val y = KBigInteger(1) + + val res = -x - y + val sum = KBigInteger(1) + + assertEquals(sum, res) + } + + @Test + fun testMinusBigNumbers() { + val x = KBigInteger(12345) + val y = KBigInteger(0xffffffffaL) + + val res = x - y + val sum = KBigInteger(-0xfffffcfc1L) + + assertEquals(sum, res) + } + + @Test + fun testMultiply_2_3() { + val x = KBigInteger(2) + val y = KBigInteger(3) + + val res = x * y + val prod = KBigInteger(6) + + assertEquals(prod, res) + } + + @Test + fun testMultiply__2_3() { + val x = KBigInteger(-2) + val y = KBigInteger(3) + + val res = x * y + val prod = KBigInteger(-6) + + assertEquals(prod, res) + } + + @Test + fun testMultiply_0xfff123_0xfff456() { + val x = KBigInteger(0xfff123) + val y = KBigInteger(0xfff456) + + val res = x * y + val prod = KBigInteger(0xffe579ad5dc2L) + + assertEquals(prod, res) + } + + @Test + fun testMultiplyUInt_0xfff123_0xfff456() { + val x = KBigInteger(0xfff123) + val y = 0xfff456U + + val res = x * y + val prod = KBigInteger(0xffe579ad5dc2L) + + assertEquals(prod, res) + } + + @Test + fun testMultiplyInt_0xfff123__0xfff456() { + val x = KBigInteger(0xfff123) + val y = -0xfff456 + + val res = x * y + val prod = KBigInteger(-0xffe579ad5dc2L) + + assertEquals(prod, res) + } + + @Test + fun testMultiply_0xffffffff_0xffffffff() { + val x = KBigInteger(0xffffffffL) + val y = KBigInteger(0xffffffffL) + + val res = x * y + val prod = KBigInteger(0xfffffffe00000001UL) + + assertEquals(prod, res) + } + + @Test + fun test_square_0x11223344U_0xad2ffffdU_0x17eU() { + val num = KBigInteger(-1, uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU )) + println(num) + val res = num * num + assertEquals(res, KBigInteger(1, uintArrayOf(0xb0542a10U, 0xbbd85bc8U, 0x2a1fa515U, 0x5069e03bU, 0x23c09U))) + } + + @Test + fun testDivision_6_3() { + val x = KBigInteger(6) + val y = 3U + + val res = x / y + val div = KBigInteger(2) + + assertEquals(div, res) + } + + @Test + fun testBigDivision_6_3() { + val x = KBigInteger(6) + val y = KBigInteger(3) + + val res = x / y + val div = KBigInteger(2) + + assertEquals(div, res) + } + + @Test + fun testDivision_20__3() { + val x = KBigInteger(20) + val y = -3 + + val res = x / y + val div = KBigInteger(-6) + + assertEquals(div, res) + } + + @Test + fun testBigDivision_20__3() { + val x = KBigInteger(20) + val y = KBigInteger(-3) + + val res = x / y + val div = KBigInteger(-6) + + assertEquals(div, res) + } + + @Test + fun testDivision_0xfffffffe00000001_0xffffffff() { + val x = KBigInteger(0xfffffffe00000001UL) + val y = 0xffffffffU + + val res = x / y + val div = KBigInteger(0xffffffffL) + + assertEquals(div, res) + } + + @Test + fun testBigDivision_0xfffffffe00000001_0xffffffff() { + val x = KBigInteger(0xfffffffe00000001UL) + val y = KBigInteger(0xffffffffU) + + val res = x / y + val div = KBigInteger(0xffffffffL) + + assertEquals(div, res) + } + + @Test + fun testMod_20_3() { + val x = KBigInteger(20) + val y = 3 + + val res = x % y + val mod = 2 + + assertEquals(mod, res) + } + + @Test + fun testBigMod_20_3() { + val x = KBigInteger(20) + val y = KBigInteger(3) + + val res = x % y + val mod = KBigInteger(2) + + assertEquals(mod, res) + } + + @Test + fun testMod_0xfffffffe00000001_12345() { + val x = KBigInteger(0xfffffffe00000001UL) + val y = 12345 + + val res = x % y + val mod = 1980 + + assertEquals(mod, res) + } + + @Test + fun testBigMod_0xfffffffe00000001_12345() { + val x = KBigInteger(0xfffffffe00000001UL) + val y = KBigInteger(12345) + + val res = x % y + val mod = KBigInteger(1980) + + assertEquals(mod, res) + } + + @Test + fun testModPow_3_10_17() { + val x = KBigInteger(3) + val exp = KBigInteger(10) + val mod = KBigInteger(17) + + val res = KBigInteger(8) + + return assertEquals(res, x.modPow(exp, mod)) + } + + @Test + fun testModPowBigNumbers() { + val x = KBigInteger(0xfffffffeabcdef01UL) + val exp = KBigInteger(2) + val mod = KBigInteger(0xfffffffeabcUL) + + val res = KBigInteger(0x6deec7895faUL) + + return assertEquals(res, x.modPow(exp, mod)) + } + + @Test + fun testModBigNumbers() { + val x = KBigInteger(0xfffffffeabcdef01UL) + val mod = KBigInteger(0xfffffffeabcUL) + + val res = KBigInteger(0xdef01) + + return assertEquals(res, x % mod) + } +} + +@kotlin.ExperimentalUnsignedTypes +class KBigIntegerConversionsTest { + @Test + fun testToString0x10() { + val x = KBigInteger(0x10) + assertEquals("0x10", x.toString()) + } + + @Test + fun testToString0x17ffffffd() { + val x = KBigInteger(0x17ffffffdL) + assertEquals("0x17ffffffd", x.toString()) + } + + @Test + fun testToString_0x17ead2ffffd() { + val x = KBigInteger(-0x17ead2ffffdL) + assertEquals("-0x17ead2ffffd", x.toString()) + } + + @Test + fun testToString_0x17ead2ffffd11223344() { + val x = KBigInteger(-1, uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU )) + assertEquals("-0x17ead2ffffd11223344", x.toString()) + } + + @Test + fun testFromString_0x17ead2ffffd11223344() { + val x = KBigInteger("0x17ead2ffffd11223344")!! + assertEquals( "0x17ead2ffffd11223344", x.toString()) + } + + @Test + fun testFromString_7059135710711894913860() { + val x = KBigInteger("-7059135710711894913860") + assertEquals("-0x17ead2ffffd11223344", x.toString()) + } +} + +@kotlin.ExperimentalUnsignedTypes +class DivisionTests { + // TODO + @Test + fun test_0xfffffffeabcdef01UL_0xfffffffeabc() { + val res = KBigInteger(0xfffffffeabcdef01UL) / KBigInteger(0xfffffffeabc) + assertEquals(res, KBigInteger(0x100000)) + + } + +// println(KBigInteger(+1, uintArrayOf(1000U, 1000U, 1000U)) / KBigInteger(0xfffffffeabc) ) + // >>> hex((1000 + 1000*2**32 + 1000*2**64)/ 0xfffffffeabc) == 0x3e800000L +// println(KBigInteger(+1, KBigInteger.divideMagnitudeByUInt(uintArrayOf(1000U, 1000U, 1000U),456789U))) + // 0x8f789719813969L + +} + +class KBigIntegerRingTest { + @Test + fun testSum() { + val res = KBigIntegerRing { + KBigInteger(1_000L) * KBigInteger(1_000L) + } + assertEquals(res, KBigInteger(1_000_000) ) + } + + @Test + fun test_sum_100_000_000__100_000_000() { + KBigIntegerRing { + val sum = +"100_000_000" + +"100_000_000" + assertEquals(sum, KBigInteger("200_000_000")) + } + } + + @Test + fun test_mul_3__4() { + KBigIntegerRing { + val prod = +"0x3000_0000_0000" * +"0x4000_0000_0000_0000_0000" + assertEquals(prod, KBigInteger("0xc00_0000_0000_0000_0000_0000_0000_0000")) + } + } + +} + -- 2.34.1 From 19d1459a558fb19731a1ba713b184342d5c62bbd Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Wed, 1 Apr 2020 23:56:39 +0300 Subject: [PATCH 20/32] Fix division and add tests --- .../kmath/operations/KBigInteger.kt | 136 ++++++++++++------ .../kmath/operations/KBigIntegerTest.kt | 103 ++++++++++--- 2 files changed, 179 insertions(+), 60 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt index 0238492d2..036f44a91 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt @@ -1,6 +1,8 @@ package scientifik.kmath.operations +import kotlin.math.log2 import kotlin.math.max +import kotlin.math.min import kotlin.math.sign /* @@ -27,7 +29,6 @@ object KBigIntegerRing: Ring { operator fun String.unaryPlus(): KBigInteger = KBigInteger(this)!! operator fun String.unaryMinus(): KBigInteger = -KBigInteger(this)!! - } @@ -161,7 +162,7 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): return result } - internal fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { + private fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { val resultLength: Int = mag.size val result = Magnitude(resultLength) var carry: ULong = 0UL @@ -174,39 +175,6 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): return result } - internal fun divideMagnitudes(mag1_: Magnitude, mag2: Magnitude): Magnitude { - val mag1 = ULongArray(mag1_.size) { mag1_[it].toULong() } - - val resultLength: Int = mag1.size - mag2.size + 1 - val result = LongArray(resultLength) - - for (i in mag1.size - 1 downTo mag2.size - 1) { - val div: ULong = mag1[i] / mag2[mag2.size - 1] - result[i - mag2.size + 1] = div.toLong() - for (j in mag2.indices) { - mag1[i - j] -= mag2[mag2.size - 1 - j] * div - } - if (i > 0) { - mag1[i - 1] += (mag1[i] shl BASE_SIZE) - } - } - - val normalizedResult = Magnitude(resultLength) - var carry = 0L - - for (i in result.indices) { - result[i] += carry - if (result[i] < 0L) { - normalizedResult[i] = (result[i] + (BASE + 1UL).toLong()).toUInt() - carry = -1 - } else { - normalizedResult[i] = result[i].toUInt() - carry = 0 - } - } - - return normalizedResult - } } override fun compareTo(other: KBigInteger): Int { @@ -287,12 +255,100 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): return KBigInteger(this.sign * other.sign, divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt())) } - operator fun div(other: KBigInteger): KBigInteger { - return when { - this < other -> ZERO - this == other -> ONE - else -> KBigInteger(this.sign * other.sign, divideMagnitudes(this.magnitude, other.magnitude)) + private fun division(other: KBigInteger): Pair { + // Long division algorithm: + // https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder + // TODO: Implement more effective algorithm + var q: KBigInteger = ZERO + var r: KBigInteger = ZERO + + val bitSize = (BASE_SIZE * (this.magnitude.size - 1) + log2(this.magnitude.last().toFloat() + 1)).toInt() + for (i in bitSize downTo 0) { + r = r shl 1 + r = r or ((abs(this) shr i) and ONE) + if (r >= abs(other)) { + r -= abs(other) + q += (ONE shl i) + } } + + return Pair(KBigInteger(this.sign * other.sign, q.magnitude), r) + } + + operator fun div(other: KBigInteger): KBigInteger { + return this.division(other).first + } + + infix fun shl(i: Int): KBigInteger { + if (this == ZERO) return ZERO + if (i == 0) return this + + val fullShifts = i / BASE_SIZE + 1 + val relShift = i % BASE_SIZE + val shiftLeft = {x: UInt -> if (relShift >= 32) 0U else x shl relShift} + val shiftRight = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shr (BASE_SIZE - relShift)} + + val newMagnitude: Magnitude = Magnitude(this.magnitude.size + fullShifts) + + for (j in this.magnitude.indices) { + newMagnitude[j + fullShifts - 1] = shiftLeft(this.magnitude[j]) + if (j != 0) { + newMagnitude[j + fullShifts - 1] = newMagnitude[j + fullShifts - 1] or shiftRight(this.magnitude[j - 1]) + } + } + + newMagnitude[this.magnitude.size + fullShifts - 1] = shiftRight(this.magnitude.last()) + + return KBigInteger(this.sign, newMagnitude) + } + + infix fun shr(i: Int): KBigInteger { + if (this == ZERO) return ZERO + if (i == 0) return this + + val fullShifts = i / BASE_SIZE + val relShift = i % BASE_SIZE + val shiftRight = {x: UInt -> if (relShift >= 32) 0U else x shr relShift} + val shiftLeft = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shl (BASE_SIZE - relShift)} + if (this.magnitude.size - fullShifts <= 0) { + return ZERO + } + val newMagnitude: Magnitude = Magnitude(this.magnitude.size - fullShifts) + + for (j in fullShifts until this.magnitude.size) { + newMagnitude[j - fullShifts] = shiftRight(this.magnitude[j]) + if (j != this.magnitude.size - 1) { + newMagnitude[j - fullShifts] = newMagnitude[j - fullShifts] or shiftLeft(this.magnitude[j + 1]) + } + } + + return KBigInteger(this.sign, newMagnitude) + } + + infix fun or(other: KBigInteger): KBigInteger { + if (this == ZERO) return other; + if (other == ZERO) return this; + val resSize = max(this.magnitude.size, other.magnitude.size) + val newMagnitude: Magnitude = Magnitude(resSize) + for (i in 0 until resSize) { + if (i < this.magnitude.size) { + newMagnitude[i] = newMagnitude[i] or this.magnitude[i] + } + if (i < other.magnitude.size) { + newMagnitude[i] = newMagnitude[i] or other.magnitude[i] + } + } + return KBigInteger(1, newMagnitude) + } + + infix fun and(other: KBigInteger): KBigInteger { + if ((this == ZERO) or (other == ZERO)) return ZERO; + val resSize = min(this.magnitude.size, other.magnitude.size) + val newMagnitude: Magnitude = Magnitude(resSize) + for (i in 0 until resSize) { + newMagnitude[i] = this.magnitude[i] and other.magnitude[i] + } + return KBigInteger(1, newMagnitude) } operator fun rem(other: Int): Int { diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt index 10b89f28b..daece0f45 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt @@ -244,6 +244,62 @@ class KBigIntegerOperationsTest { assertEquals(prod, res) } + @Test + fun test_shr_20() { + val x = KBigInteger(20) + assertEquals(KBigInteger(10), x shr 1) + } + + @Test + fun test_shl_20() { + val x = KBigInteger(20) + assertEquals(KBigInteger(40), x shl 1) + } + + @Test + fun test_shl_1_0() { + assertEquals(KBigInteger.ONE, KBigInteger.ONE shl 0) + } + + @Test + fun test_shl_1_32() { + assertEquals(KBigInteger(0x100000000UL), KBigInteger.ONE shl 32) + } + + @Test + fun test_shl_1_33() { + assertEquals(KBigInteger(0x200000000UL), KBigInteger.ONE shl 33) + } + + @Test + fun test_shr_1_33_33() { + assertEquals(KBigInteger.ONE, (KBigInteger.ONE shl 33) shr 33) + } + + @Test + fun test_shr_1_32() { + assertEquals(KBigInteger.ZERO, KBigInteger.ONE shr 32) + } + + @Test + fun test_and_123_456() { + val x = KBigInteger(123) + val y = KBigInteger(456) + assertEquals(KBigInteger(72), x and y) + } + + @Test + fun test_or_123_456() { + val x = KBigInteger(123) + val y = KBigInteger(456) + assertEquals(KBigInteger(507), x or y) + } + + @Test + fun test_asd() { + assertEquals(KBigInteger.ONE, KBigInteger.ZERO or ((KBigInteger(20) shr 4) and KBigInteger.ONE)) + } + @Test fun test_square_0x11223344U_0xad2ffffdU_0x17eU() { val num = KBigInteger(-1, uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU )) @@ -307,6 +363,12 @@ class KBigIntegerOperationsTest { assertEquals(div, res) } + @Test + fun testBigDivision_0xfffffffeabcdef01UL_0xfffffffeabc() { + val res = KBigInteger(0xfffffffeabcdef01UL) / KBigInteger(0xfffffffeabc) + assertEquals(res, KBigInteger(0x100000)) + } + @Test fun testBigDivision_0xfffffffe00000001_0xffffffff() { val x = KBigInteger(0xfffffffe00000001UL) @@ -379,7 +441,7 @@ class KBigIntegerOperationsTest { val exp = KBigInteger(2) val mod = KBigInteger(0xfffffffeabcUL) - val res = KBigInteger(0x6deec7895faUL) + val res = KBigInteger(0xc2253cde01) return assertEquals(res, x.modPow(exp, mod)) } @@ -434,26 +496,9 @@ class KBigIntegerConversionsTest { } } -@kotlin.ExperimentalUnsignedTypes -class DivisionTests { - // TODO - @Test - fun test_0xfffffffeabcdef01UL_0xfffffffeabc() { - val res = KBigInteger(0xfffffffeabcdef01UL) / KBigInteger(0xfffffffeabc) - assertEquals(res, KBigInteger(0x100000)) - - } - -// println(KBigInteger(+1, uintArrayOf(1000U, 1000U, 1000U)) / KBigInteger(0xfffffffeabc) ) - // >>> hex((1000 + 1000*2**32 + 1000*2**64)/ 0xfffffffeabc) == 0x3e800000L -// println(KBigInteger(+1, KBigInteger.divideMagnitudeByUInt(uintArrayOf(1000U, 1000U, 1000U),456789U))) - // 0x8f789719813969L - -} - class KBigIntegerRingTest { @Test - fun testSum() { + fun testKBigIntegerRingSum() { val res = KBigIntegerRing { KBigInteger(1_000L) * KBigInteger(1_000L) } @@ -461,7 +506,7 @@ class KBigIntegerRingTest { } @Test - fun test_sum_100_000_000__100_000_000() { + fun testKBigIntegerRingSum_100_000_000__100_000_000() { KBigIntegerRing { val sum = +"100_000_000" + +"100_000_000" assertEquals(sum, KBigInteger("200_000_000")) @@ -476,5 +521,23 @@ class KBigIntegerRingTest { } } + @Test + fun test_div_big_1() { + KBigIntegerRing { + val res = +"1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000" / + +"555_000_444_000_333_000_222_000_111_000_999_001" + assertEquals(res, +"1801800360360432432518919022699") + } + } + + @Test + fun test_rem_big_1() { + KBigIntegerRing { + val res = +"1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000" % + +"555_000_444_000_333_000_222_000_111_000_999_001" + assertEquals(res, +"324121220440768000291647788404676301") + } + } + } -- 2.34.1 From 0e898ee1ea6d708c13a17ef7631144ca79195904 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 3 Apr 2020 19:01:58 +0300 Subject: [PATCH 21/32] Add strided matrix dot test to check #82 --- .../scientifik/kmath/linear/MatrixTest.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt index d27aa665c..7d1209963 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/linear/MatrixTest.kt @@ -1,6 +1,8 @@ package scientifik.kmath.linear import scientifik.kmath.structures.Matrix +import scientifik.kmath.structures.NDStructure +import scientifik.kmath.structures.as2D import kotlin.test.Test import kotlin.test.assertEquals @@ -44,4 +46,20 @@ class MatrixTest { val toTenthPower = transitionMatrix pow 10 } + + @Test + fun test2DDot() { + val firstMatrix = NDStructure.auto(2,3){ (i, j) -> (i + j).toDouble() }.as2D() + val secondMatrix = NDStructure.auto(3,2){ (i, j) -> (i + j).toDouble() }.as2D() + MatrixContext.real.run { +// val firstMatrix = produce(2, 3) { i, j -> (i + j).toDouble() } +// val secondMatrix = produce(3, 2) { i, j -> (i + j).toDouble() } + val result = firstMatrix dot secondMatrix + assertEquals(2, result.rowNum) + assertEquals(2, result.colNum) + assertEquals(8.0, result[0,1]) + assertEquals(8.0, result[1,0]) + assertEquals(14.0, result[1,1]) + } + } } \ No newline at end of file -- 2.34.1 From 0cb53792b1de5ba5589f28d03e12e554f6ac311c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 3 Apr 2020 21:44:07 +0300 Subject: [PATCH 22/32] Update plugin and fix median Statistic --- build.gradle.kts | 2 +- .../src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f3a3c13d4..1034f3f44 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("scientifik.publish") version "0.3.1" apply false + id("scientifik.publish") version "0.4.1" apply false } val kmathVersion by extra("0.1.4-dev-1") diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt index 1af2570b0..804aed089 100644 --- a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Statistic.kt @@ -11,6 +11,7 @@ import scientifik.kmath.coroutines.mapParallel import scientifik.kmath.operations.* import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.asIterable +import scientifik.kmath.structures.asSequence /** * A function, that transforms a buffer of random quantities to some resulting value @@ -83,9 +84,9 @@ class Mean(val space: Space) : ComposableStatistic, T> { /** * Non-composable median */ -class Median(comparator: Comparator) : Statistic { +class Median(private val comparator: Comparator) : Statistic { override suspend fun invoke(data: Buffer): T { - return data.asIterable().toList()[data.size / 2] //TODO check if this is correct + return data.asSequence().sortedWith(comparator).toList()[data.size / 2] //TODO check if this is correct } companion object { -- 2.34.1 From a1dd71a74b2e232ca1b55ca79f7c5ac04085081c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 3 Apr 2020 22:29:23 +0300 Subject: [PATCH 23/32] update build tools and dependencies --- build.gradle.kts | 2 +- examples/build.gradle.kts | 6 ++++-- settings.gradle.kts | 9 +++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1034f3f44..6c4536b4c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("scientifik.publish") version "0.4.1" apply false } -val kmathVersion by extra("0.1.4-dev-1") +val kmathVersion by extra("0.1.4-dev-2") val bintrayRepo by extra("scientifik") val githubProject by extra("kmath") diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 47acaa5ba..8853b78a5 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { java kotlin("jvm") - kotlin("plugin.allopen") version "1.3.61" + kotlin("plugin.allopen") version "1.3.71" id("kotlinx.benchmark") version "0.2.0-dev-7" } @@ -14,6 +14,8 @@ configure { repositories { maven("http://dl.bintray.com/kyonifer/maven") + maven("https://dl.bintray.com/mipt-npm/scientifik") + maven("https://dl.bintray.com/mipt-npm/dev") mavenCentral() } @@ -29,7 +31,7 @@ dependencies { implementation(project(":kmath-viktor")) implementation(project(":kmath-dimensions")) implementation("com.kyonifer:koma-core-ejml:0.12") - implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:${Scientifik.ioVersion}") + implementation("org.jetbrains.kotlinx:kotlinx-io-jvm:0.2.0-npm-dev-6") implementation("org.jetbrains.kotlinx:kotlinx.benchmark.runtime:0.2.0-dev-7") "benchmarksCompile"(sourceSets.main.get().compileClasspath) } diff --git a/settings.gradle.kts b/settings.gradle.kts index d7bcb249f..671db444a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,10 +1,10 @@ pluginManagement { plugins { - id("scientifik.mpp") version "0.3.1" - id("scientifik.jvm") version "0.3.1" - id("scientifik.atomic") version "0.3.1" - id("scientifik.publish") version "0.3.1" + id("scientifik.mpp") version "0.4.1" + id("scientifik.jvm") version "0.4.1" + id("scientifik.atomic") version "0.4.1" + id("scientifik.publish") version "0.4.1" } repositories { @@ -13,6 +13,7 @@ pluginManagement { gradlePluginPortal() maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("https://dl.bintray.com/mipt-npm/scientifik") + maven("https://dl.bintray.com/mipt-npm/dev") maven("https://dl.bintray.com/kotlin/kotlinx") } -- 2.34.1 From cb1156fd9051df20250856cc22ae76cb6cbd01cc Mon Sep 17 00:00:00 2001 From: Andreas Radke Date: Fri, 10 Apr 2020 20:54:10 +0200 Subject: [PATCH 24/32] Fix two simple typos in file names: Rename LinearAlgrebra.kt to LinearAlgebra.kt and BigNumers.kt to BigNumbers.kt --- .../kmath/linear/{LinearAlgrebra.kt => LinearAlgebra.kt} | 0 .../scientifik/kmath/operations/{BigNumers.kt => BigNumbers.kt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/{LinearAlgrebra.kt => LinearAlgebra.kt} (100%) rename kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/{BigNumers.kt => BigNumbers.kt} (100%) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt similarity index 100% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgrebra.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/LinearAlgebra.kt diff --git a/kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumers.kt b/kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumbers.kt similarity index 100% rename from kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumers.kt rename to kmath-core/src/jvmMain/kotlin/scientifik/kmath/operations/BigNumbers.kt -- 2.34.1 From a94440d5fbd411b28383bda5cd237c8600f62dee Mon Sep 17 00:00:00 2001 From: Aleksei Trifonov Date: Mon, 13 Apr 2020 08:55:49 +0300 Subject: [PATCH 25/32] Fix link to linear.md --- doc/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/features.md b/doc/features.md index 703dad6bd..e6a820c1e 100644 --- a/doc/features.md +++ b/doc/features.md @@ -4,7 +4,7 @@ * [NDStructures](./nd-structure.md) -* [Linear algebra](linear) - Matrices, operations and linear equations solving. To be moved to separate module. Currently supports basic +* [Linear algebra](./linear.md) - Matrices, operations and linear equations solving. To be moved to separate module. Currently supports basic api and multiple library back-ends. * [Histograms](./histograms.md) - Multidimensional histogram calculation and operations. -- 2.34.1 From 48cb683bc4dd28c1c9f509cbf04dc538952f9082 Mon Sep 17 00:00:00 2001 From: Peter Klimai Date: Wed, 15 Apr 2020 18:55:13 +0300 Subject: [PATCH 26/32] Refactoring of KBigInteger --- .../kmath/operations/KBigInteger.kt | 505 +++++++++--------- .../kmath/operations/KBigIntegerTest.kt | 8 +- 2 files changed, 259 insertions(+), 254 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt index 036f44a91..b9b2bbb81 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt @@ -26,45 +26,256 @@ object KBigIntegerRing: Ring { override fun multiply(a: KBigInteger, b: KBigInteger): KBigInteger = a.times(b) - operator fun String.unaryPlus(): KBigInteger = KBigInteger(this)!! + operator fun String.unaryPlus(): KBigInteger = this.toKBigInteger()!! - operator fun String.unaryMinus(): KBigInteger = -KBigInteger(this)!! + operator fun String.unaryMinus(): KBigInteger = -this.toKBigInteger()!! } - @kotlin.ExperimentalUnsignedTypes -class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): - RingElement, Comparable { +class KBigInteger internal constructor( + private val sign: Byte = 0, + private val magnitude: Magnitude = Magnitude(0) +): Comparable { - constructor(x: Int) : this(x.sign, uintArrayOf(kotlin.math.abs(x).toUInt())) - constructor(x: Long) : this(x.sign, uintArrayOf( + constructor(x: Int) : this(x.sign.toByte(), uintArrayOf(kotlin.math.abs(x).toUInt())) + + constructor(x: Long) : this(x.sign.toByte(), stripLeadingZeros(uintArrayOf( (kotlin.math.abs(x).toULong() and BASE).toUInt(), - ((kotlin.math.abs(x).toULong() shr BASE_SIZE) and BASE).toUInt())) + ((kotlin.math.abs(x).toULong() shr BASE_SIZE) and BASE).toUInt()))) - val magnitude = stripLeadingZeros(magnitude) - val sign = if (this.magnitude.isNotEmpty()) sign else 0 - val sizeByte: Int = magnitude.size * BASE_SIZE / 4 + override fun compareTo(other: KBigInteger): Int { + return when { + (this.sign == 0.toByte()) and (other.sign == 0.toByte()) -> 0 + this.sign < other.sign -> -1 + this.sign > other.sign -> 1 + else -> this.sign * compareMagnitudes(this.magnitude, other.magnitude) + } + } - override val context: KBigIntegerRing get() = KBigIntegerRing + override fun equals(other: Any?): Boolean { + if (other is KBigInteger) { + return this.compareTo(other) == 0 + } + else error("Can't compare KBigInteger to a different type") + } - override fun unwrap(): KBigInteger = this - override fun KBigInteger.wrap(): KBigInteger = this + override fun hashCode(): Int { + return magnitude.hashCode() + this.sign + } + + fun abs(): KBigInteger = if (sign == 0.toByte()) this else KBigInteger(1, magnitude) + + operator fun unaryMinus(): KBigInteger { + return if (this.sign == 0.toByte()) this else KBigInteger((-this.sign).toByte(), this.magnitude) + } + + operator fun plus(b: KBigInteger): KBigInteger { + return when { + b.sign == 0.toByte() -> this + this.sign == 0.toByte() -> b + this == -b -> ZERO + this.sign == b.sign -> KBigInteger(this.sign, addMagnitudes(this.magnitude, b.magnitude)) + else -> { + val comp: Int = compareMagnitudes(this.magnitude, b.magnitude) + + if (comp == 1) { + KBigInteger(this.sign, subtractMagnitudes(this.magnitude, b.magnitude)) + } else { + KBigInteger((-this.sign).toByte(), subtractMagnitudes(b.magnitude, this.magnitude)) + } + } + } + } + + operator fun minus(b: KBigInteger): KBigInteger { + return this + (-b) + } + + operator fun times(b: KBigInteger): KBigInteger { + return when { + this.sign == 0.toByte() -> ZERO + b.sign == 0.toByte() -> ZERO +// TODO: Karatsuba + else -> KBigInteger((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) + } + } + + operator fun times(other: UInt): KBigInteger { + return when { + this.sign == 0.toByte() -> ZERO + other == 0U -> ZERO + else -> KBigInteger(this.sign, multiplyMagnitudeByUInt(this.magnitude, other)) + } + } + + operator fun times(other: Int): KBigInteger { + return if (other > 0) + this * kotlin.math.abs(other).toUInt() + else + -this * kotlin.math.abs(other).toUInt() + } + + operator fun div(other: UInt): KBigInteger { + return KBigInteger(this.sign, divideMagnitudeByUInt(this.magnitude, other)) + } + + operator fun div(other: Int): KBigInteger { + return KBigInteger((this.sign * other.sign).toByte(), + divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt())) + } + + private fun division(other: KBigInteger): Pair { + // Long division algorithm: + // https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder + // TODO: Implement more effective algorithm + var q: KBigInteger = ZERO + var r: KBigInteger = ZERO + + val bitSize = (BASE_SIZE * (this.magnitude.size - 1) + log2(this.magnitude.last().toFloat() + 1)).toInt() + for (i in bitSize downTo 0) { + r = r shl 1 + r = r or ((abs(this) shr i) and ONE) + if (r >= abs(other)) { + r -= abs(other) + q += (ONE shl i) + } + } + + return Pair(KBigInteger((this.sign * other.sign).toByte(), q.magnitude), r) + } + + operator fun div(other: KBigInteger): KBigInteger { + return this.division(other).first + } + + infix fun shl(i: Int): KBigInteger { + if (this == ZERO) return ZERO + if (i == 0) return this + + val fullShifts = i / BASE_SIZE + 1 + val relShift = i % BASE_SIZE + val shiftLeft = {x: UInt -> if (relShift >= 32) 0U else x shl relShift} + val shiftRight = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shr (BASE_SIZE - relShift)} + + val newMagnitude: Magnitude = Magnitude(this.magnitude.size + fullShifts) + + for (j in this.magnitude.indices) { + newMagnitude[j + fullShifts - 1] = shiftLeft(this.magnitude[j]) + if (j != 0) { + newMagnitude[j + fullShifts - 1] = newMagnitude[j + fullShifts - 1] or shiftRight(this.magnitude[j - 1]) + } + } + + newMagnitude[this.magnitude.size + fullShifts - 1] = shiftRight(this.magnitude.last()) + + return KBigInteger(this.sign, stripLeadingZeros(newMagnitude)) + } + + infix fun shr(i: Int): KBigInteger { + if (this == ZERO) return ZERO + if (i == 0) return this + + val fullShifts = i / BASE_SIZE + val relShift = i % BASE_SIZE + val shiftRight = {x: UInt -> if (relShift >= 32) 0U else x shr relShift} + val shiftLeft = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shl (BASE_SIZE - relShift)} + if (this.magnitude.size - fullShifts <= 0) { + return ZERO + } + val newMagnitude: Magnitude = Magnitude(this.magnitude.size - fullShifts) + + for (j in fullShifts until this.magnitude.size) { + newMagnitude[j - fullShifts] = shiftRight(this.magnitude[j]) + if (j != this.magnitude.size - 1) { + newMagnitude[j - fullShifts] = newMagnitude[j - fullShifts] or shiftLeft(this.magnitude[j + 1]) + } + } + + return KBigInteger(this.sign, stripLeadingZeros(newMagnitude)) + } + + infix fun or(other: KBigInteger): KBigInteger { + if (this == ZERO) return other; + if (other == ZERO) return this; + val resSize = max(this.magnitude.size, other.magnitude.size) + val newMagnitude: Magnitude = Magnitude(resSize) + for (i in 0 until resSize) { + if (i < this.magnitude.size) { + newMagnitude[i] = newMagnitude[i] or this.magnitude[i] + } + if (i < other.magnitude.size) { + newMagnitude[i] = newMagnitude[i] or other.magnitude[i] + } + } + return KBigInteger(1, stripLeadingZeros(newMagnitude)) + } + + infix fun and(other: KBigInteger): KBigInteger { + if ((this == ZERO) or (other == ZERO)) return ZERO; + val resSize = min(this.magnitude.size, other.magnitude.size) + val newMagnitude: Magnitude = Magnitude(resSize) + for (i in 0 until resSize) { + newMagnitude[i] = this.magnitude[i] and other.magnitude[i] + } + return KBigInteger(1, stripLeadingZeros(newMagnitude)) + } + + operator fun rem(other: Int): Int { + val res = this - (this / other) * other + return if (res == ZERO) 0 else res.sign * res.magnitude[0].toInt() + } + + operator fun rem(other: KBigInteger): KBigInteger { + return this - (this / other) * other + } + + fun modPow(exponent: KBigInteger, m: KBigInteger): KBigInteger { + return when { + exponent == ZERO -> ONE + exponent % 2 == 1 -> (this * modPow(exponent - ONE, m)) % m + else -> { + val sqRoot = modPow(exponent / 2, m) + (sqRoot * sqRoot) % m + } + } + } + + override fun toString(): String { + if (this.sign == 0.toByte()) { + return "0x0" + } + var res: String = if (this.sign == (-1).toByte()) "-0x" else "0x" + var numberStarted = false + + for (i in this.magnitude.size - 1 downTo 0) { + for (j in BASE_SIZE / 4 - 1 downTo 0) { + val curByte = (this.magnitude[i] shr 4 * j) and 0xfU + if (numberStarted or (curByte != 0U)) { + numberStarted = true + res += hexMapping[curByte] + } + } + } + + return res + } companion object { - val BASE = 0xffffffffUL + const val BASE = 0xffffffffUL const val BASE_SIZE: Int = 32 val ZERO: KBigInteger = KBigInteger() val ONE: KBigInteger = KBigInteger(1) private val hexMapping: HashMap = hashMapOf( - 0U to "0", 1U to "1", 2U to "2", 3U to "3", 4U to "4", 5U to "5", 6U to "6", 7U to "7", 8U to "8", - 9U to "9", 10U to "a", 11U to "b", 12U to "c", 13U to "d", 14U to "e", 15U to "f" + 0U to "0", 1U to "1", 2U to "2", 3U to "3", + 4U to "4", 5U to "5", 6U to "6", 7U to "7", + 8U to "8", 9U to "9", 10U to "a", 11U to "b", + 12U to "c", 13U to "d", 14U to "e", 15U to "f" ) - private fun stripLeadingZeros(mag: Magnitude): Magnitude { - // TODO: optimize performance - if (mag.isEmpty()) { + internal fun stripLeadingZeros(mag: Magnitude): Magnitude { + if (mag.isEmpty() || mag.last() != 0U) { return mag } var resSize: Int = mag.size - 1 @@ -108,7 +319,7 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): carry = (res shr BASE_SIZE) } result[resultLength - 1] = carry.toUInt() - return result + return stripLeadingZeros(result) } private fun subtractMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { @@ -127,7 +338,7 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): result[i] = res.toUInt() } - return result + return stripLeadingZeros(result) } private fun multiplyMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { @@ -142,7 +353,7 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): } result[resultLength - 1] = (carry and BASE).toUInt() - return result + return stripLeadingZeros(result) } private fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { @@ -159,7 +370,7 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): result[i + mag2.size] = (carry and BASE).toUInt() } - return result + return stripLeadingZeros(result) } private fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { @@ -172,230 +383,15 @@ class KBigInteger(sign: Int = 0, magnitude: Magnitude = Magnitude(0)): result[i] = (cur / x).toUInt() carry = cur % x } - return result + return stripLeadingZeros(result) } } - override fun compareTo(other: KBigInteger): Int { - return when { - (this.sign == 0) and (other.sign == 0) -> 0 - this.sign < other.sign -> -1 - this.sign > other.sign -> 1 - else -> this.sign * compareMagnitudes(this.magnitude, other.magnitude) - } - } - - override fun equals(other: Any?): Boolean { - if (other is KBigInteger) { - return this.compareTo(other) == 0 - } - else error("Can't compare KBigInteger to a different type") - } - - override fun hashCode(): Int { - return magnitude.hashCode() + this.sign - } - - operator fun unaryMinus(): KBigInteger { - return if (this.sign == 0) this else KBigInteger(-this.sign, this.magnitude) - } - - override operator fun plus(b: KBigInteger): KBigInteger { - return when { - b.sign == 0 -> this - this.sign == 0 -> b - this == -b -> ZERO - this.sign == b.sign -> KBigInteger(this.sign, addMagnitudes(this.magnitude, b.magnitude)) - else -> { - val comp: Int = compareMagnitudes(this.magnitude, b.magnitude) - - if (comp == 1) { - KBigInteger(this.sign, subtractMagnitudes(this.magnitude, b.magnitude)) - } else { - KBigInteger(-this.sign, subtractMagnitudes(b.magnitude, this.magnitude)) - } - } - } - } - - override operator fun minus(b: KBigInteger): KBigInteger { - return this + (-b) - } - - override operator fun times(b: KBigInteger): KBigInteger { - return when { - this.sign == 0 -> ZERO - b.sign == 0 -> ZERO -// TODO: Karatsuba - else -> KBigInteger(this.sign * b.sign, multiplyMagnitudes(this.magnitude, b.magnitude)) - } - } - - operator fun times(other: UInt): KBigInteger { - return when { - this.sign == 0 -> ZERO - other == 0U -> ZERO - else -> KBigInteger(this.sign, multiplyMagnitudeByUInt(this.magnitude, other)) - } - } - - operator fun times(other: Int): KBigInteger { - return if (other > 0) - this * kotlin.math.abs(other).toUInt() - else - -this * kotlin.math.abs(other).toUInt() - } - - operator fun div(other: UInt): KBigInteger { - return KBigInteger(this.sign, divideMagnitudeByUInt(this.magnitude, other)) - } - - operator fun div(other: Int): KBigInteger { - return KBigInteger(this.sign * other.sign, divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt())) - } - - private fun division(other: KBigInteger): Pair { - // Long division algorithm: - // https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder - // TODO: Implement more effective algorithm - var q: KBigInteger = ZERO - var r: KBigInteger = ZERO - - val bitSize = (BASE_SIZE * (this.magnitude.size - 1) + log2(this.magnitude.last().toFloat() + 1)).toInt() - for (i in bitSize downTo 0) { - r = r shl 1 - r = r or ((abs(this) shr i) and ONE) - if (r >= abs(other)) { - r -= abs(other) - q += (ONE shl i) - } - } - - return Pair(KBigInteger(this.sign * other.sign, q.magnitude), r) - } - - operator fun div(other: KBigInteger): KBigInteger { - return this.division(other).first - } - - infix fun shl(i: Int): KBigInteger { - if (this == ZERO) return ZERO - if (i == 0) return this - - val fullShifts = i / BASE_SIZE + 1 - val relShift = i % BASE_SIZE - val shiftLeft = {x: UInt -> if (relShift >= 32) 0U else x shl relShift} - val shiftRight = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shr (BASE_SIZE - relShift)} - - val newMagnitude: Magnitude = Magnitude(this.magnitude.size + fullShifts) - - for (j in this.magnitude.indices) { - newMagnitude[j + fullShifts - 1] = shiftLeft(this.magnitude[j]) - if (j != 0) { - newMagnitude[j + fullShifts - 1] = newMagnitude[j + fullShifts - 1] or shiftRight(this.magnitude[j - 1]) - } - } - - newMagnitude[this.magnitude.size + fullShifts - 1] = shiftRight(this.magnitude.last()) - - return KBigInteger(this.sign, newMagnitude) - } - - infix fun shr(i: Int): KBigInteger { - if (this == ZERO) return ZERO - if (i == 0) return this - - val fullShifts = i / BASE_SIZE - val relShift = i % BASE_SIZE - val shiftRight = {x: UInt -> if (relShift >= 32) 0U else x shr relShift} - val shiftLeft = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shl (BASE_SIZE - relShift)} - if (this.magnitude.size - fullShifts <= 0) { - return ZERO - } - val newMagnitude: Magnitude = Magnitude(this.magnitude.size - fullShifts) - - for (j in fullShifts until this.magnitude.size) { - newMagnitude[j - fullShifts] = shiftRight(this.magnitude[j]) - if (j != this.magnitude.size - 1) { - newMagnitude[j - fullShifts] = newMagnitude[j - fullShifts] or shiftLeft(this.magnitude[j + 1]) - } - } - - return KBigInteger(this.sign, newMagnitude) - } - - infix fun or(other: KBigInteger): KBigInteger { - if (this == ZERO) return other; - if (other == ZERO) return this; - val resSize = max(this.magnitude.size, other.magnitude.size) - val newMagnitude: Magnitude = Magnitude(resSize) - for (i in 0 until resSize) { - if (i < this.magnitude.size) { - newMagnitude[i] = newMagnitude[i] or this.magnitude[i] - } - if (i < other.magnitude.size) { - newMagnitude[i] = newMagnitude[i] or other.magnitude[i] - } - } - return KBigInteger(1, newMagnitude) - } - - infix fun and(other: KBigInteger): KBigInteger { - if ((this == ZERO) or (other == ZERO)) return ZERO; - val resSize = min(this.magnitude.size, other.magnitude.size) - val newMagnitude: Magnitude = Magnitude(resSize) - for (i in 0 until resSize) { - newMagnitude[i] = this.magnitude[i] and other.magnitude[i] - } - return KBigInteger(1, newMagnitude) - } - - operator fun rem(other: Int): Int { - val res = this - (this / other) * other - return if (res == ZERO) 0 else res.sign * res.magnitude[0].toInt() - } - - operator fun rem(other: KBigInteger): KBigInteger { - return this - (this / other) * other - } - - fun modPow(exponent: KBigInteger, m: KBigInteger): KBigInteger { - return when { - exponent == ZERO -> ONE - exponent % 2 == 1 -> (this * modPow(exponent - ONE, m)) % m - else -> { - val sqRoot = modPow(exponent / 2, m) - (sqRoot * sqRoot) % m - } - } - } - - override fun toString(): String { - if (this.sign == 0) { - return "0x0" - } - var res: String = if (this.sign == -1) "-0x" else "0x" - var numberStarted = false - - for (i in this.magnitude.size - 1 downTo 0) { - for (j in BASE_SIZE / 4 - 1 downTo 0) { - val curByte = (this.magnitude[i] shr 4 * j) and 0xfU - if (numberStarted or (curByte != 0U)) { - numberStarted = true - res += hexMapping[curByte] - } - } - } - - return res - } } @kotlin.ExperimentalUnsignedTypes -fun abs(x: KBigInteger): KBigInteger { - return if (x.sign == 0) x else KBigInteger(1, x.magnitude) -} +fun abs(x: KBigInteger): KBigInteger = x.abs() @kotlin.ExperimentalUnsignedTypes // Can't put it as constructor in class due to platform declaration clash with KBigInteger(Int) @@ -405,26 +401,35 @@ fun KBigInteger(x: UInt): KBigInteger @kotlin.ExperimentalUnsignedTypes // Can't put it as constructor in class due to platform declaration clash with KBigInteger(Long) fun KBigInteger(x: ULong): KBigInteger - = KBigInteger(1, uintArrayOf((x and KBigInteger.BASE).toUInt(), ((x shr KBigInteger.BASE_SIZE) and KBigInteger.BASE).toUInt())) + = KBigInteger(1, + KBigInteger.stripLeadingZeros(uintArrayOf( + (x and KBigInteger.BASE).toUInt(), + ((x shr KBigInteger.BASE_SIZE) and KBigInteger.BASE).toUInt()) + ) + ) -val hexChToInt = hashMapOf('0' to 0, '1' to 1, '2' to 2, '3' to 3, '4' to 4, '5' to 5, '6' to 6, '7' to 7, - '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15) +val hexChToInt = hashMapOf( + '0' to 0, '1' to 1, '2' to 2, '3' to 3, + '4' to 4, '5' to 5, '6' to 6, '7' to 7, + '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, + 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15 +) // Returns None if a valid number can not be read from a string -fun KBigInteger(s: String): KBigInteger? { +fun String.toKBigInteger(): KBigInteger? { val sign: Int val sPositive: String when { - s[0] == '+' -> { + this[0] == '+' -> { sign = +1 - sPositive = s.substring(1) + sPositive = this.substring(1) } - s[0] == '-' -> { + this[0] == '-' -> { sign = -1 - sPositive = s.substring(1) + sPositive = this.substring(1) } else -> { - sPositive = s + sPositive = this sign = +1 } } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt index daece0f45..96e2dee9a 100644 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt @@ -485,13 +485,13 @@ class KBigIntegerConversionsTest { @Test fun testFromString_0x17ead2ffffd11223344() { - val x = KBigInteger("0x17ead2ffffd11223344")!! + val x = "0x17ead2ffffd11223344".toKBigInteger() assertEquals( "0x17ead2ffffd11223344", x.toString()) } @Test fun testFromString_7059135710711894913860() { - val x = KBigInteger("-7059135710711894913860") + val x = "-7059135710711894913860".toKBigInteger() assertEquals("-0x17ead2ffffd11223344", x.toString()) } } @@ -509,7 +509,7 @@ class KBigIntegerRingTest { fun testKBigIntegerRingSum_100_000_000__100_000_000() { KBigIntegerRing { val sum = +"100_000_000" + +"100_000_000" - assertEquals(sum, KBigInteger("200_000_000")) + assertEquals(sum, "200_000_000".toKBigInteger()) } } @@ -517,7 +517,7 @@ class KBigIntegerRingTest { fun test_mul_3__4() { KBigIntegerRing { val prod = +"0x3000_0000_0000" * +"0x4000_0000_0000_0000_0000" - assertEquals(prod, KBigInteger("0xc00_0000_0000_0000_0000_0000_0000_0000")) + assertEquals(prod, "0xc00_0000_0000_0000_0000_0000_0000_0000".toKBigInteger()) } } -- 2.34.1 From 28ef591524cb8e122f3614997e87f33d4f8fba7c Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 26 Apr 2020 20:04:15 +0300 Subject: [PATCH 27/32] Chain implements Flow --- build.gradle.kts | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 58702 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 3 +++ .../kmath/commons/expressions/AutoDiffTest.kt | 2 +- .../scientifik/kmath/linear/FeaturedMatrix.kt | 10 ---------- .../scientifik/kmath/linear/MatrixBuilder.kt | 14 ++++++++++++++ .../kotlin/scientifik/kmath/chains/Chain.kt | 13 ++++++++----- .../kmath/streaming/BufferFlowTest.kt | 9 ++++++--- .../kmath/streaming/RingBufferTest.kt | 2 +- ...istributions.kt => UniformDistribution.kt} | 0 .../scientifik/kmath/prob/StatisticTest.kt | 4 ++-- 12 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt rename kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/{distributions.kt => UniformDistribution.kt} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 6c4536b4c..c807636e6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ plugins { - id("scientifik.publish") version "0.4.1" apply false + id("scientifik.publish") version "0.4.2" apply false } -val kmathVersion by extra("0.1.4-dev-2") +val kmathVersion by extra("0.1.4-dev-3") val bintrayRepo by extra("scientifik") val githubProject by extra("kmath") diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch delta 18348 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 H|2F2obH5>< delta 18428 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f21..a4b442974 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 9618d8d96..62bd9b9cc 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" diff --git a/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt b/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt index 100f49948..c77f30eb7 100644 --- a/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt +++ b/kmath-commons/src/test/kotlin/scientifik/kmath/commons/expressions/AutoDiffTest.kt @@ -1,7 +1,7 @@ package scientifik.kmath.commons.expressions -import org.junit.Test import scientifik.kmath.expressions.invoke +import kotlin.test.Test import kotlin.test.assertEquals inline fun diff(order: Int, vararg parameters: Pair, block: DerivativeStructureField.() -> R) = diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt index 9f6b9f600..297586fc6 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/FeaturedMatrix.kt @@ -41,16 +41,6 @@ fun Structure2D.Companion.square(vararg elements: T): FeaturedMatrix Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) - -class MatrixBuilder(val rows: Int, val columns: Int) { - operator fun invoke(vararg elements: T): FeaturedMatrix { - if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns") - val buffer = elements.asBuffer() - return BufferMatrix(rows, columns, buffer) - } -} - val Matrix<*>.features get() = (this as? FeaturedMatrix)?.features?: emptySet() /** diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt new file mode 100644 index 000000000..466dbea6e --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/linear/MatrixBuilder.kt @@ -0,0 +1,14 @@ +package scientifik.kmath.linear + +import scientifik.kmath.structures.Structure2D +import scientifik.kmath.structures.asBuffer + +class MatrixBuilder(val rows: Int, val columns: Int) { + operator fun invoke(vararg elements: T): FeaturedMatrix { + if (rows * columns != elements.size) error("The number of elements ${elements.size} is not equal $rows * $columns") + val buffer = elements.asBuffer() + return BufferMatrix(rows, columns, buffer) + } +} + +fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns) \ No newline at end of file diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt index 8dba9e215..467bbe978 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt @@ -16,7 +16,9 @@ package scientifik.kmath.chains +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -25,7 +27,7 @@ import kotlinx.coroutines.sync.withLock * A not-necessary-Markov chain of some type * @param R - the chain element type */ -interface Chain { +interface Chain: Flow { /** * Generate next value, changing state if needed */ @@ -36,14 +38,15 @@ interface Chain { */ fun fork(): Chain + @InternalCoroutinesApi + override suspend fun collect(collector: FlowCollector) { + kotlinx.coroutines.flow.flow { while (true) emit(next()) }.collect(collector) + } + companion object } -/** - * Chain as a coroutine flow. The flow emit affects chain state and vice versa - */ -fun Chain.flow(): Flow = kotlinx.coroutines.flow.flow { while (true) emit(next()) } fun Iterator.asChain(): Chain = SimpleChain { next() } fun Sequence.asChain(): Chain = iterator().asChain() diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt index af2d6cd43..147f687f0 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/BufferFlowTest.kt @@ -3,11 +3,12 @@ package scientifik.kmath.streaming import kotlinx.coroutines.* import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.collect -import org.junit.Test +import org.junit.jupiter.api.Timeout import scientifik.kmath.coroutines.async import scientifik.kmath.coroutines.collect import scientifik.kmath.coroutines.mapParallel import java.util.concurrent.Executors +import kotlin.test.Test @ExperimentalCoroutinesApi @@ -17,7 +18,8 @@ class BufferFlowTest { val dispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() - @Test(timeout = 2000) + @Test + @Timeout(2000) fun map() { runBlocking { (1..20).asFlow().mapParallel( dispatcher) { @@ -31,7 +33,8 @@ class BufferFlowTest { } } - @Test(timeout = 2000) + @Test + @Timeout(2000) fun async() { runBlocking { (1..20).asFlow().async(dispatcher) { diff --git a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt index 25af1f589..c14d1a26c 100644 --- a/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt +++ b/kmath-coroutines/src/jvmTest/kotlin/scientifik/kmath/streaming/RingBufferTest.kt @@ -2,8 +2,8 @@ package scientifik.kmath.streaming import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking -import org.junit.Test import scientifik.kmath.structures.asSequence +import kotlin.test.Test import kotlin.test.assertEquals class RingBufferTest { diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/distributions.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/UniformDistribution.kt similarity index 100% rename from kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/distributions.kt rename to kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/UniformDistribution.kt diff --git a/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt index 8b46cac52..af069810f 100644 --- a/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt +++ b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/StatisticTest.kt @@ -3,7 +3,7 @@ package scientifik.kmath.prob import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import scientifik.kmath.chains.flow + import scientifik.kmath.streaming.chunked import kotlin.test.Test @@ -13,7 +13,7 @@ class StatisticTest { //Create a stateless chain from generator. val data = generator.chain { nextDouble() } //Convert a chaint to Flow and break it into chunks. - val chunked = data.flow().chunked(1000) + val chunked = data.chunked(1000) @Test fun testParallelMean() { -- 2.34.1 From fbe7363cde853b82d51448b7193a62409643b066 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 26 Apr 2020 21:47:34 +0300 Subject: [PATCH 28/32] BigInt refactoring --- build.gradle.kts | 2 +- .../operations/{KBigInteger.kt => BigInt.kt} | 275 +++++---- .../kmath/operations/BigIntAlgebraTest.kt | 50 ++ .../kmath/operations/BigIntConstructorTest.kt | 26 + .../kmath/operations/BigIntConversionsTest.kt | 43 ++ .../kmath/operations/BigIntOperationsTest.kt | 381 ++++++++++++ .../kmath/operations/KBigIntegerTest.kt | 543 ------------------ 7 files changed, 651 insertions(+), 669 deletions(-) rename kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/{KBigInteger.kt => BigInt.kt} (60%) create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt create mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt delete mode 100644 kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c807636e6..6ab33d31c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("scientifik.publish") version "0.4.2" apply false } -val kmathVersion by extra("0.1.4-dev-3") +val kmathVersion by extra("0.1.4-dev-4") val bintrayRepo by extra("scientifik") val githubProject by extra("kmath") diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/BigInt.kt similarity index 60% rename from kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt rename to kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/BigInt.kt index b9b2bbb81..1661170d3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/KBigInteger.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/BigInt.kt @@ -1,49 +1,45 @@ package scientifik.kmath.operations +import scientifik.kmath.operations.BigInt.Companion.BASE +import scientifik.kmath.operations.BigInt.Companion.BASE_SIZE import kotlin.math.log2 import kotlin.math.max import kotlin.math.min import kotlin.math.sign -/* - * Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). - * Initial version from https://github.com/robdrynkin/kotlin-big-integer - */ -@kotlin.ExperimentalUnsignedTypes typealias Magnitude = UIntArray - -@kotlin.ExperimentalUnsignedTypes typealias TBase = ULong -object KBigIntegerRing: Ring { - override val zero: KBigInteger = KBigInteger.ZERO - override val one: KBigInteger = KBigInteger.ONE +/** + * Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). + * + * @author Robert Drynkin (https://github.com/robdrynkin) and Peter Klimai (https://github.com/pklimai) + */ +object BigIntField : Field { + override val zero: BigInt = BigInt.ZERO + override val one: BigInt = BigInt.ONE - override fun add(a: KBigInteger, b: KBigInteger): KBigInteger = a.plus(b) + override fun add(a: BigInt, b: BigInt): BigInt = a.plus(b) - override fun multiply(a: KBigInteger, k: Number): KBigInteger = a.times(k.toLong()) + override fun multiply(a: BigInt, k: Number): BigInt = a.times(k.toLong()) - override fun multiply(a: KBigInteger, b: KBigInteger): KBigInteger = a.times(b) + override fun multiply(a: BigInt, b: BigInt): BigInt = a.times(b) - operator fun String.unaryPlus(): KBigInteger = this.toKBigInteger()!! + operator fun String.unaryPlus(): BigInt = this.parseBigInteger() ?: error("Can't parse $this as big integer") - operator fun String.unaryMinus(): KBigInteger = -this.toKBigInteger()!! + operator fun String.unaryMinus(): BigInt = + -(this.parseBigInteger() ?: error("Can't parse $this as big integer")) + + override fun divide(a: BigInt, b: BigInt): BigInt = a.div(b) } -@kotlin.ExperimentalUnsignedTypes -class KBigInteger internal constructor( - private val sign: Byte = 0, - private val magnitude: Magnitude = Magnitude(0) -): Comparable { +class BigInt internal constructor( + private val sign: Byte, + private val magnitude: Magnitude +) : Comparable { - constructor(x: Int) : this(x.sign.toByte(), uintArrayOf(kotlin.math.abs(x).toUInt())) - - constructor(x: Long) : this(x.sign.toByte(), stripLeadingZeros(uintArrayOf( - (kotlin.math.abs(x).toULong() and BASE).toUInt(), - ((kotlin.math.abs(x).toULong() shr BASE_SIZE) and BASE).toUInt()))) - - override fun compareTo(other: KBigInteger): Int { + override fun compareTo(other: BigInt): Int { return when { (this.sign == 0.toByte()) and (other.sign == 0.toByte()) -> 0 this.sign < other.sign -> -1 @@ -53,85 +49,87 @@ class KBigInteger internal constructor( } override fun equals(other: Any?): Boolean { - if (other is KBigInteger) { + if (other is BigInt) { return this.compareTo(other) == 0 - } - else error("Can't compare KBigInteger to a different type") + } else error("Can't compare KBigInteger to a different type") } override fun hashCode(): Int { return magnitude.hashCode() + this.sign } - fun abs(): KBigInteger = if (sign == 0.toByte()) this else KBigInteger(1, magnitude) + fun abs(): BigInt = if (sign == 0.toByte()) this else BigInt(1, magnitude) - operator fun unaryMinus(): KBigInteger { - return if (this.sign == 0.toByte()) this else KBigInteger((-this.sign).toByte(), this.magnitude) + operator fun unaryMinus(): BigInt { + return if (this.sign == 0.toByte()) this else BigInt((-this.sign).toByte(), this.magnitude) } - operator fun plus(b: KBigInteger): KBigInteger { + operator fun plus(b: BigInt): BigInt { return when { b.sign == 0.toByte() -> this this.sign == 0.toByte() -> b this == -b -> ZERO - this.sign == b.sign -> KBigInteger(this.sign, addMagnitudes(this.magnitude, b.magnitude)) + this.sign == b.sign -> BigInt(this.sign, addMagnitudes(this.magnitude, b.magnitude)) else -> { val comp: Int = compareMagnitudes(this.magnitude, b.magnitude) if (comp == 1) { - KBigInteger(this.sign, subtractMagnitudes(this.magnitude, b.magnitude)) + BigInt(this.sign, subtractMagnitudes(this.magnitude, b.magnitude)) } else { - KBigInteger((-this.sign).toByte(), subtractMagnitudes(b.magnitude, this.magnitude)) + BigInt((-this.sign).toByte(), subtractMagnitudes(b.magnitude, this.magnitude)) } } } } - operator fun minus(b: KBigInteger): KBigInteger { + operator fun minus(b: BigInt): BigInt { return this + (-b) } - operator fun times(b: KBigInteger): KBigInteger { + operator fun times(b: BigInt): BigInt { return when { this.sign == 0.toByte() -> ZERO b.sign == 0.toByte() -> ZERO // TODO: Karatsuba - else -> KBigInteger((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) + else -> BigInt((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) } } - operator fun times(other: UInt): KBigInteger { + operator fun times(other: UInt): BigInt { return when { this.sign == 0.toByte() -> ZERO other == 0U -> ZERO - else -> KBigInteger(this.sign, multiplyMagnitudeByUInt(this.magnitude, other)) + else -> BigInt(this.sign, multiplyMagnitudeByUInt(this.magnitude, other)) } } - operator fun times(other: Int): KBigInteger { + operator fun times(other: Int): BigInt { return if (other > 0) this * kotlin.math.abs(other).toUInt() else -this * kotlin.math.abs(other).toUInt() } - operator fun div(other: UInt): KBigInteger { - return KBigInteger(this.sign, divideMagnitudeByUInt(this.magnitude, other)) + operator fun div(other: UInt): BigInt { + return BigInt(this.sign, divideMagnitudeByUInt(this.magnitude, other)) } - operator fun div(other: Int): KBigInteger { - return KBigInteger((this.sign * other.sign).toByte(), - divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt())) + operator fun div(other: Int): BigInt { + return BigInt( + (this.sign * other.sign).toByte(), + divideMagnitudeByUInt(this.magnitude, kotlin.math.abs(other).toUInt()) + ) } - private fun division(other: KBigInteger): Pair { + private fun division(other: BigInt): Pair { // Long division algorithm: // https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder // TODO: Implement more effective algorithm - var q: KBigInteger = ZERO - var r: KBigInteger = ZERO + var q: BigInt = ZERO + var r: BigInt = ZERO - val bitSize = (BASE_SIZE * (this.magnitude.size - 1) + log2(this.magnitude.last().toFloat() + 1)).toInt() + val bitSize = + (BASE_SIZE * (this.magnitude.size - 1) + log2(this.magnitude.lastOrNull()?.toFloat() ?: 0f + 1)).toInt() for (i in bitSize downTo 0) { r = r shl 1 r = r or ((abs(this) shr i) and ONE) @@ -141,21 +139,21 @@ class KBigInteger internal constructor( } } - return Pair(KBigInteger((this.sign * other.sign).toByte(), q.magnitude), r) + return Pair(BigInt((this.sign * other.sign).toByte(), q.magnitude), r) } - operator fun div(other: KBigInteger): KBigInteger { + operator fun div(other: BigInt): BigInt { return this.division(other).first } - infix fun shl(i: Int): KBigInteger { + infix fun shl(i: Int): BigInt { if (this == ZERO) return ZERO if (i == 0) return this val fullShifts = i / BASE_SIZE + 1 val relShift = i % BASE_SIZE - val shiftLeft = {x: UInt -> if (relShift >= 32) 0U else x shl relShift} - val shiftRight = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shr (BASE_SIZE - relShift)} + val shiftLeft = { x: UInt -> if (relShift >= 32) 0U else x shl relShift } + val shiftRight = { x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shr (BASE_SIZE - relShift) } val newMagnitude: Magnitude = Magnitude(this.magnitude.size + fullShifts) @@ -168,17 +166,17 @@ class KBigInteger internal constructor( newMagnitude[this.magnitude.size + fullShifts - 1] = shiftRight(this.magnitude.last()) - return KBigInteger(this.sign, stripLeadingZeros(newMagnitude)) + return BigInt(this.sign, stripLeadingZeros(newMagnitude)) } - infix fun shr(i: Int): KBigInteger { + infix fun shr(i: Int): BigInt { if (this == ZERO) return ZERO if (i == 0) return this val fullShifts = i / BASE_SIZE val relShift = i % BASE_SIZE - val shiftRight = {x: UInt -> if (relShift >= 32) 0U else x shr relShift} - val shiftLeft = {x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shl (BASE_SIZE - relShift)} + val shiftRight = { x: UInt -> if (relShift >= 32) 0U else x shr relShift } + val shiftLeft = { x: UInt -> if (BASE_SIZE - relShift >= 32) 0U else x shl (BASE_SIZE - relShift) } if (this.magnitude.size - fullShifts <= 0) { return ZERO } @@ -191,10 +189,10 @@ class KBigInteger internal constructor( } } - return KBigInteger(this.sign, stripLeadingZeros(newMagnitude)) + return BigInt(this.sign, stripLeadingZeros(newMagnitude)) } - infix fun or(other: KBigInteger): KBigInteger { + infix fun or(other: BigInt): BigInt { if (this == ZERO) return other; if (other == ZERO) return this; val resSize = max(this.magnitude.size, other.magnitude.size) @@ -207,17 +205,17 @@ class KBigInteger internal constructor( newMagnitude[i] = newMagnitude[i] or other.magnitude[i] } } - return KBigInteger(1, stripLeadingZeros(newMagnitude)) + return BigInt(1, stripLeadingZeros(newMagnitude)) } - infix fun and(other: KBigInteger): KBigInteger { + infix fun and(other: BigInt): BigInt { if ((this == ZERO) or (other == ZERO)) return ZERO; val resSize = min(this.magnitude.size, other.magnitude.size) val newMagnitude: Magnitude = Magnitude(resSize) for (i in 0 until resSize) { newMagnitude[i] = this.magnitude[i] and other.magnitude[i] } - return KBigInteger(1, stripLeadingZeros(newMagnitude)) + return BigInt(1, stripLeadingZeros(newMagnitude)) } operator fun rem(other: Int): Int { @@ -225,11 +223,11 @@ class KBigInteger internal constructor( return if (res == ZERO) 0 else res.sign * res.magnitude[0].toInt() } - operator fun rem(other: KBigInteger): KBigInteger { + operator fun rem(other: BigInt): BigInt { return this - (this / other) * other } - fun modPow(exponent: KBigInteger, m: KBigInteger): KBigInteger { + fun modPow(exponent: BigInt, m: BigInt): BigInt { return when { exponent == ZERO -> ONE exponent % 2 == 1 -> (this * modPow(exponent - ONE, m)) % m @@ -263,29 +261,15 @@ class KBigInteger internal constructor( companion object { const val BASE = 0xffffffffUL const val BASE_SIZE: Int = 32 - val ZERO: KBigInteger = KBigInteger() - val ONE: KBigInteger = KBigInteger(1) + val ZERO: BigInt = BigInt(0, uintArrayOf()) + val ONE: BigInt = BigInt(1, uintArrayOf(1u)) - private val hexMapping: HashMap = - hashMapOf( - 0U to "0", 1U to "1", 2U to "2", 3U to "3", - 4U to "4", 5U to "5", 6U to "6", 7U to "7", - 8U to "8", 9U to "9", 10U to "a", 11U to "b", - 12U to "c", 13U to "d", 14U to "e", 15U to "f" - ) - - internal fun stripLeadingZeros(mag: Magnitude): Magnitude { - if (mag.isEmpty() || mag.last() != 0U) { - return mag - } - var resSize: Int = mag.size - 1 - while (mag[resSize] == 0U) { - if (resSize == 0) - break - resSize -= 1 - } - return mag.sliceArray(IntRange(0, resSize)) - } + private val hexMapping: HashMap = hashMapOf( + 0U to "0", 1U to "1", 2U to "2", 3U to "3", + 4U to "4", 5U to "5", 6U to "6", 7U to "7", + 8U to "8", 9U to "9", 10U to "a", 11U to "b", + 12U to "c", 13U to "d", 14U to "e", 15U to "f" + ) private fun compareMagnitudes(mag1: Magnitude, mag2: Magnitude): Int { when { @@ -329,8 +313,8 @@ class KBigInteger internal constructor( for (i in 0 until resultLength) { var res: Long = - if (i < mag2.size) mag1[i].toLong() - mag2[i].toLong() - carry - else mag1[i].toLong() - carry + if (i < mag2.size) mag1[i].toLong() - mag2[i].toLong() - carry + else mag1[i].toLong() - carry carry = if (res < 0) 1 else 0 res += carry * (BASE + 1UL).toLong() @@ -390,33 +374,76 @@ class KBigInteger internal constructor( } -@kotlin.ExperimentalUnsignedTypes -fun abs(x: KBigInteger): KBigInteger = x.abs() -@kotlin.ExperimentalUnsignedTypes -// Can't put it as constructor in class due to platform declaration clash with KBigInteger(Int) -fun KBigInteger(x: UInt): KBigInteger - = KBigInteger(1, uintArrayOf(x)) +private fun stripLeadingZeros(mag: Magnitude): Magnitude { + if (mag.isEmpty() || mag.last() != 0U) { + return mag + } + var resSize: Int = mag.size - 1 + while (mag[resSize] == 0U) { + if (resSize == 0) + break + resSize -= 1 + } + return mag.sliceArray(IntRange(0, resSize)) +} -@kotlin.ExperimentalUnsignedTypes -// Can't put it as constructor in class due to platform declaration clash with KBigInteger(Long) -fun KBigInteger(x: ULong): KBigInteger - = KBigInteger(1, - KBigInteger.stripLeadingZeros(uintArrayOf( - (x and KBigInteger.BASE).toUInt(), - ((x shr KBigInteger.BASE_SIZE) and KBigInteger.BASE).toUInt()) - ) +fun abs(x: BigInt): BigInt = x.abs() + +/** + * Convert this [Int] to [BigInt] + */ +fun Int.toBigInt() = BigInt(sign.toByte(), uintArrayOf(kotlin.math.abs(this).toUInt())) + +/** + * Convert this [Long] to [BigInt] + */ +fun Long.toBigInt() = BigInt( + sign.toByte(), stripLeadingZeros( + uintArrayOf( + (kotlin.math.abs(this).toULong() and BASE).toUInt(), + ((kotlin.math.abs(this).toULong() shr BASE_SIZE) and BASE).toUInt() ) - -val hexChToInt = hashMapOf( - '0' to 0, '1' to 1, '2' to 2, '3' to 3, - '4' to 4, '5' to 5, '6' to 6, '7' to 7, - '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, - 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15 + ) ) -// Returns None if a valid number can not be read from a string -fun String.toKBigInteger(): KBigInteger? { +/** + * Convert UInt to [BigInt] + */ +fun UInt.toBigInt() = BigInt(1, uintArrayOf(this)) + +/** + * Convert ULong to [BigInt] + */ +fun ULong.toBigInt() = BigInt( + 1, + stripLeadingZeros( + uintArrayOf( + (this and BigInt.BASE).toUInt(), + ((this shr BigInt.BASE_SIZE) and BigInt.BASE).toUInt() + ) + ) +) + +/** + * Create a [BigInt] with this array of magnitudes with protective copy + */ +fun UIntArray.toBigInt(sign: Byte): BigInt { + if (sign == 0.toByte() && isNotEmpty()) error("") + return BigInt(sign, this.copyOf()) +} + +val hexChToInt = hashMapOf( + '0' to 0, '1' to 1, '2' to 2, '3' to 3, + '4' to 4, '5' to 5, '6' to 6, '7' to 7, + '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, + 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15 +) + +/** + * Returns null if a valid number can not be read from a string + */ +fun String.parseBigInteger(): BigInt? { val sign: Int val sPositive: String when { @@ -433,27 +460,25 @@ fun String.toKBigInteger(): KBigInteger? { sign = +1 } } - var res = KBigInteger.ZERO - var digitValue = KBigInteger.ONE + var res = BigInt.ZERO + var digitValue = BigInt.ONE val sPositiveUpper = sPositive.toUpperCase() if (sPositiveUpper.startsWith("0X")) { // hex representation val sHex = sPositiveUpper.substring(2) for (ch in sHex.reversed()) { if (ch == '_') continue - res += digitValue * (hexChToInt[ch] ?: return null) - digitValue *= KBigInteger(16) + res += digitValue * (hexChToInt[ch] ?: return null) + digitValue *= 16.toBigInt() } - } - else { // decimal representation - val sDecimal = sPositiveUpper - for (ch in sDecimal.reversed()) { + } else { // decimal representation + for (ch in sPositiveUpper.reversed()) { if (ch == '_') continue if (ch !in '0'..'9') { return null } - res += digitValue * (ch.toInt() - '0'.toInt()) - digitValue *= KBigInteger(10) + res += digitValue * (ch.toInt() - '0'.toInt()) + digitValue *= 10.toBigInt() } } - return res * sign + return res * sign } diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt new file mode 100644 index 000000000..5ae977196 --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntAlgebraTest.kt @@ -0,0 +1,50 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +class BigIntAlgebraTest { + @Test + fun testKBigIntegerRingSum() { + val res = BigIntField { + 1_000L.toBigInt() * 1_000L.toBigInt() + } + assertEquals(res, 1_000_000.toBigInt()) + } + + @Test + fun testKBigIntegerRingSum_100_000_000__100_000_000() { + BigIntField { + val sum = +"100_000_000" + +"100_000_000" + assertEquals(sum, "200_000_000".parseBigInteger()) + } + } + + @Test + fun test_mul_3__4() { + BigIntField { + val prod = +"0x3000_0000_0000" * +"0x4000_0000_0000_0000_0000" + assertEquals(prod, "0xc00_0000_0000_0000_0000_0000_0000_0000".parseBigInteger()) + } + } + + @Test + fun test_div_big_1() { + BigIntField { + val res = +"1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000" / + +"555_000_444_000_333_000_222_000_111_000_999_001" + assertEquals(res, +"1801800360360432432518919022699") + } + } + + @Test + fun test_rem_big_1() { + BigIntField { + val res = +"1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000" % + +"555_000_444_000_333_000_222_000_111_000_999_001" + assertEquals(res, +"324121220440768000291647788404676301") + } + } + +} + diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt new file mode 100644 index 000000000..2af1b7e50 --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConstructorTest.kt @@ -0,0 +1,26 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +class BigIntConstructorTest { + @Test + fun testConstructorZero() { + assertEquals(0.toBigInt(), uintArrayOf().toBigInt(0)) + } + + @Test + fun testConstructor8() { + assertEquals( + 8.toBigInt(), + uintArrayOf(8U).toBigInt(1) + ) + } + + @Test + fun testConstructor_0xffffffffaL() { + val x = -0xffffffffaL.toBigInt() + val y = uintArrayOf(0xfffffffaU, 0xfU).toBigInt(-1) + assertEquals(x, y) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt new file mode 100644 index 000000000..51b9509e0 --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntConversionsTest.kt @@ -0,0 +1,43 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +@kotlin.ExperimentalUnsignedTypes +class BigIntConversionsTest { + @Test + fun testToString0x10() { + val x = 0x10.toBigInt() + assertEquals("0x10", x.toString()) + } + + @Test + fun testToString0x17ffffffd() { + val x = 0x17ffffffdL.toBigInt() + assertEquals("0x17ffffffd", x.toString()) + } + + @Test + fun testToString_0x17ead2ffffd() { + val x = -0x17ead2ffffdL.toBigInt() + assertEquals("-0x17ead2ffffd", x.toString()) + } + + @Test + fun testToString_0x17ead2ffffd11223344() { + val x = uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU).toBigInt(-1) + assertEquals("-0x17ead2ffffd11223344", x.toString()) + } + + @Test + fun testFromString_0x17ead2ffffd11223344() { + val x = "0x17ead2ffffd11223344".parseBigInteger() + assertEquals("0x17ead2ffffd11223344", x.toString()) + } + + @Test + fun testFromString_7059135710711894913860() { + val x = "-7059135710711894913860".parseBigInteger() + assertEquals("-0x17ead2ffffd11223344", x.toString()) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt new file mode 100644 index 000000000..72ac9f229 --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/BigIntOperationsTest.kt @@ -0,0 +1,381 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +@kotlin.ExperimentalUnsignedTypes +class BigIntOperationsTest { + @Test + fun testPlus_1_1() { + val x = 1.toBigInt() + val y = 1.toBigInt() + + val res = x + y + val sum = 2.toBigInt() + + assertEquals(sum, res) + } + + @Test + fun testPlusBigNumbers() { + val x = 0x7fffffff.toBigInt() + val y = 0x7fffffff.toBigInt() + val z = 0x7fffffff.toBigInt() + + val res = x + y + z + val sum = uintArrayOf(0x7ffffffdU, 0x1U).toBigInt(1) + + assertEquals(sum, res) + } + + @Test + fun testUnaryMinus() { + val x = 1234.toBigInt() + val y = -1234.toBigInt() + assertEquals(-x, y) + } + + @Test + fun testMinus_2_1() { + val x = 2.toBigInt() + val y = 1.toBigInt() + + val res = x - y + val sum = 1.toBigInt() + + assertEquals(sum, res) + } + + @Test + fun testMinus__2_1() { + val x = -2.toBigInt() + val y = 1.toBigInt() + + val res = x - y + val sum = -3.toBigInt() + + assertEquals(sum, res) + } + + @Test + fun testMinus___2_1() { + val x = -2.toBigInt() + val y = 1.toBigInt() + + val res = -x - y + val sum = 1.toBigInt() + + assertEquals(sum, res) + } + + @Test + fun testMinusBigNumbers() { + val x = 12345.toBigInt() + val y = 0xffffffffaL.toBigInt() + + val res = x - y + val sum = -0xfffffcfc1L.toBigInt() + + assertEquals(sum, res) + } + + @Test + fun testMultiply_2_3() { + val x = 2.toBigInt() + val y = 3.toBigInt() + + val res = x * y + val prod = 6.toBigInt() + + assertEquals(prod, res) + } + + @Test + fun testMultiply__2_3() { + val x = -2.toBigInt() + val y = 3.toBigInt() + + val res = x * y + val prod = -6.toBigInt() + + assertEquals(prod, res) + } + + @Test + fun testMultiply_0xfff123_0xfff456() { + val x = 0xfff123.toBigInt() + val y = 0xfff456.toBigInt() + + val res = x * y + val prod = 0xffe579ad5dc2L.toBigInt() + + assertEquals(prod, res) + } + + @Test + fun testMultiplyUInt_0xfff123_0xfff456() { + val x = 0xfff123.toBigInt() + val y = 0xfff456U + + val res = x * y + val prod = 0xffe579ad5dc2L.toBigInt() + + assertEquals(prod, res) + } + + @Test + fun testMultiplyInt_0xfff123__0xfff456() { + val x = 0xfff123.toBigInt() + val y = -0xfff456 + + val res = x * y + val prod = -0xffe579ad5dc2L.toBigInt() + + assertEquals(prod, res) + } + + @Test + fun testMultiply_0xffffffff_0xffffffff() { + val x = 0xffffffffL.toBigInt() + val y = 0xffffffffL.toBigInt() + + val res = x * y + val prod = 0xfffffffe00000001UL.toBigInt() + + assertEquals(prod, res) + } + + @Test + fun test_shr_20() { + val x = 20.toBigInt() + assertEquals(10.toBigInt(), x shr 1) + } + + @Test + fun test_shl_20() { + val x = 20.toBigInt() + assertEquals(40.toBigInt(), x shl 1) + } + + @Test + fun test_shl_1_0() { + assertEquals( + BigInt.ONE, + BigInt.ONE shl 0 + ) + } + + @Test + fun test_shl_1_32() { + assertEquals( + 0x100000000UL.toBigInt(), + BigInt.ONE shl 32 + ) + } + + @Test + fun test_shl_1_33() { + assertEquals( + 0x200000000UL.toBigInt(), + BigInt.ONE shl 33 + ) + } + + @Test + fun test_shr_1_33_33() { + assertEquals( + BigInt.ONE, + (BigInt.ONE shl 33) shr 33 + ) + } + + @Test + fun test_shr_1_32() { + assertEquals( + BigInt.ZERO, + BigInt.ONE shr 32 + ) + } + + @Test + fun test_and_123_456() { + val x = 123.toBigInt() + val y = 456.toBigInt() + assertEquals(72.toBigInt(), x and y) + } + + @Test + fun test_or_123_456() { + val x = 123.toBigInt() + val y = 456.toBigInt() + assertEquals(507.toBigInt(), x or y) + } + + @Test + fun test_asd() { + assertEquals( + BigInt.ONE, + BigInt.ZERO or ((20.toBigInt() shr 4) and BigInt.ONE) + ) + } + + @Test + fun test_square_0x11223344U_0xad2ffffdU_0x17eU() { + val num = + uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU).toBigInt(-1) + println(num) + val res = num * num + assertEquals( + res, + uintArrayOf(0xb0542a10U, 0xbbd85bc8U, 0x2a1fa515U, 0x5069e03bU, 0x23c09U).toBigInt(1) + ) + } + + @Test + fun testDivision_6_3() { + val x = 6.toBigInt() + val y = 3U + + val res = x / y + val div = 2.toBigInt() + + assertEquals(div, res) + } + + @Test + fun testBigDivision_6_3() { + val x = 6.toBigInt() + val y = 3.toBigInt() + + val res = x / y + val div = 2.toBigInt() + + assertEquals(div, res) + } + + @Test + fun testDivision_20__3() { + val x = 20.toBigInt() + val y = -3 + + val res = x / y + val div = -6.toBigInt() + + assertEquals(div, res) + } + + @Test + fun testBigDivision_20__3() { + val x = 20.toBigInt() + val y = -3.toBigInt() + + val res = x / y + val div = -6.toBigInt() + + assertEquals(div, res) + } + + @Test + fun testDivision_0xfffffffe00000001_0xffffffff() { + val x = 0xfffffffe00000001UL.toBigInt() + val y = 0xffffffffU + + val res = x / y + val div = 0xffffffffL.toBigInt() + + assertEquals(div, res) + } + + @Test + fun testBigDivision_0xfffffffeabcdef01UL_0xfffffffeabc() { + val res = 0xfffffffeabcdef01UL.toBigInt() / 0xfffffffeabc.toBigInt() + assertEquals(res, 0x100000.toBigInt()) + } + + @Test + fun testBigDivision_0xfffffffe00000001_0xffffffff() { + val x = 0xfffffffe00000001UL.toBigInt() + val y = 0xffffffffU.toBigInt() + + val res = x / y + val div = 0xffffffffL.toBigInt() + + assertEquals(div, res) + } + + @Test + fun testMod_20_3() { + val x = 20.toBigInt() + val y = 3 + + val res = x % y + val mod = 2 + + assertEquals(mod, res) + } + + @Test + fun testBigMod_20_3() { + val x = 20.toBigInt() + val y = 3.toBigInt() + + val res = x % y + val mod = 2.toBigInt() + + assertEquals(mod, res) + } + + @Test + fun testMod_0xfffffffe00000001_12345() { + val x = 0xfffffffe00000001UL.toBigInt() + val y = 12345 + + val res = x % y + val mod = 1980 + + assertEquals(mod, res) + } + + @Test + fun testBigMod_0xfffffffe00000001_12345() { + val x = 0xfffffffe00000001UL.toBigInt() + val y = 12345.toBigInt() + + val res = x % y + val mod = 1980.toBigInt() + + assertEquals(mod, res) + } + + @Test + fun testModPow_3_10_17() { + val x = 3.toBigInt() + val exp = 10.toBigInt() + val mod = 17.toBigInt() + + val res = 8.toBigInt() + + return assertEquals(res, x.modPow(exp, mod)) + } + + @Test + fun testModPowBigNumbers() { + val x = 0xfffffffeabcdef01UL.toBigInt() + val exp = 2.toBigInt() + val mod = 0xfffffffeabcUL.toBigInt() + + val res = 0xc2253cde01.toBigInt() + + return assertEquals(res, x.modPow(exp, mod)) + } + + @Test + fun testModBigNumbers() { + val x = 0xfffffffeabcdef01UL.toBigInt() + val mod = 0xfffffffeabcUL.toBigInt() + + val res = 0xdef01.toBigInt() + + return assertEquals(res, x % mod) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt b/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt deleted file mode 100644 index 96e2dee9a..000000000 --- a/kmath-core/src/commonTest/kotlin/scientifik/kmath/operations/KBigIntegerTest.kt +++ /dev/null @@ -1,543 +0,0 @@ -package scientifik.kmath.operations - -import kotlin.test.Test -import kotlin.test.assertTrue -import kotlin.test.assertEquals - -@kotlin.ExperimentalUnsignedTypes -class KBigIntegerConstructorTest { - @Test - fun testConstructorZero() { - assertEquals(KBigInteger(0), KBigInteger(0, uintArrayOf())) - } - - @Test - fun testConstructor8() { - assertEquals(KBigInteger(8), KBigInteger(1, uintArrayOf(8U))) - } - - @Test - fun testConstructor_0xffffffffaL() { - val x = KBigInteger(-0xffffffffaL) - val y = KBigInteger(-1, uintArrayOf(0xfffffffaU, 0xfU)) - assertEquals(x, y) - } -} - -@kotlin.ExperimentalUnsignedTypes -class KBigIntegerCompareTest { - @Test - fun testCompare1_2() { - val x = KBigInteger(1) - val y = KBigInteger(2) - assertTrue { x < y } - } - - @Test - fun testCompare0_0() { - val x = KBigInteger(0) - val y = KBigInteger(0) - assertEquals(x, y) - } - - @Test - fun testCompare1__2() { - val x = KBigInteger(1) - val y = KBigInteger(-2) - assertTrue { x > y } - } - - @Test - fun testCompare_1__2() { - val x = KBigInteger(-1) - val y = KBigInteger(-2) - assertTrue { x > y } - } - - @Test - fun testCompare_2__1() { - val x = KBigInteger(-2) - val y = KBigInteger(-1) - assertTrue { x < y } - } - - @Test - fun testCompare12345_12345() { - val x = KBigInteger(12345) - val y = KBigInteger(12345) - assertEquals(x, y) - } - - @Test - fun testEqualsWithLong() { - val x = KBigInteger(12345) - assertTrue { x == KBigInteger(12345L) } - } - - @Test - fun testEqualsWithULong() { - val x = KBigInteger(12345) - assertTrue { x == KBigInteger(12345UL) } - } - - @Test - fun testCompareBigNumbersGreater() { - val x = KBigInteger(0xfffffffffL) - val y = KBigInteger(0xffffffffaL) - assertTrue { x > y } - } - - @Test - fun testCompareBigNumbersEqual() { - val x = KBigInteger(0xffffffffaL) - val y = KBigInteger(0xffffffffaL) - assertEquals(x, y) - } - - @Test - fun testCompareBigNumbersLess() { - val x = KBigInteger(-0xffffffffaL) - val y = KBigInteger(0xffffffffaL) - assertTrue { x < y } - } -} - -@kotlin.ExperimentalUnsignedTypes -class KBigIntegerOperationsTest { - @Test - fun testPlus_1_1() { - val x = KBigInteger(1) - val y = KBigInteger(1) - - val res = x + y - val sum = KBigInteger(2) - - assertEquals(sum, res) - } - - @Test - fun testPlusBigNumbers() { - val x = KBigInteger(0x7fffffff) - val y = KBigInteger(0x7fffffff) - val z = KBigInteger(0x7fffffff) - - val res = x + y + z - val sum = KBigInteger(1, uintArrayOf(0x7ffffffdU, 0x1U)) - - assertEquals(sum, res) - } - - @Test - fun testUnaryMinus() { - val x = KBigInteger(1234) - val y = KBigInteger(-1234) - assertEquals(-x, y) - } - - @Test - fun testMinus_2_1() { - val x = KBigInteger(2) - val y = KBigInteger(1) - - val res = x - y - val sum = KBigInteger(1) - - assertEquals(sum, res) - } - - @Test - fun testMinus__2_1() { - val x = KBigInteger(-2) - val y = KBigInteger(1) - - val res = x - y - val sum = KBigInteger(-3) - - assertEquals(sum, res) - } - - @Test - fun testMinus___2_1() { - val x = KBigInteger(-2) - val y = KBigInteger(1) - - val res = -x - y - val sum = KBigInteger(1) - - assertEquals(sum, res) - } - - @Test - fun testMinusBigNumbers() { - val x = KBigInteger(12345) - val y = KBigInteger(0xffffffffaL) - - val res = x - y - val sum = KBigInteger(-0xfffffcfc1L) - - assertEquals(sum, res) - } - - @Test - fun testMultiply_2_3() { - val x = KBigInteger(2) - val y = KBigInteger(3) - - val res = x * y - val prod = KBigInteger(6) - - assertEquals(prod, res) - } - - @Test - fun testMultiply__2_3() { - val x = KBigInteger(-2) - val y = KBigInteger(3) - - val res = x * y - val prod = KBigInteger(-6) - - assertEquals(prod, res) - } - - @Test - fun testMultiply_0xfff123_0xfff456() { - val x = KBigInteger(0xfff123) - val y = KBigInteger(0xfff456) - - val res = x * y - val prod = KBigInteger(0xffe579ad5dc2L) - - assertEquals(prod, res) - } - - @Test - fun testMultiplyUInt_0xfff123_0xfff456() { - val x = KBigInteger(0xfff123) - val y = 0xfff456U - - val res = x * y - val prod = KBigInteger(0xffe579ad5dc2L) - - assertEquals(prod, res) - } - - @Test - fun testMultiplyInt_0xfff123__0xfff456() { - val x = KBigInteger(0xfff123) - val y = -0xfff456 - - val res = x * y - val prod = KBigInteger(-0xffe579ad5dc2L) - - assertEquals(prod, res) - } - - @Test - fun testMultiply_0xffffffff_0xffffffff() { - val x = KBigInteger(0xffffffffL) - val y = KBigInteger(0xffffffffL) - - val res = x * y - val prod = KBigInteger(0xfffffffe00000001UL) - - assertEquals(prod, res) - } - - @Test - fun test_shr_20() { - val x = KBigInteger(20) - assertEquals(KBigInteger(10), x shr 1) - } - - @Test - fun test_shl_20() { - val x = KBigInteger(20) - assertEquals(KBigInteger(40), x shl 1) - } - - @Test - fun test_shl_1_0() { - assertEquals(KBigInteger.ONE, KBigInteger.ONE shl 0) - } - - @Test - fun test_shl_1_32() { - assertEquals(KBigInteger(0x100000000UL), KBigInteger.ONE shl 32) - } - - @Test - fun test_shl_1_33() { - assertEquals(KBigInteger(0x200000000UL), KBigInteger.ONE shl 33) - } - - @Test - fun test_shr_1_33_33() { - assertEquals(KBigInteger.ONE, (KBigInteger.ONE shl 33) shr 33) - } - - @Test - fun test_shr_1_32() { - assertEquals(KBigInteger.ZERO, KBigInteger.ONE shr 32) - } - - @Test - fun test_and_123_456() { - val x = KBigInteger(123) - val y = KBigInteger(456) - assertEquals(KBigInteger(72), x and y) - } - - @Test - fun test_or_123_456() { - val x = KBigInteger(123) - val y = KBigInteger(456) - assertEquals(KBigInteger(507), x or y) - } - - @Test - fun test_asd() { - assertEquals(KBigInteger.ONE, KBigInteger.ZERO or ((KBigInteger(20) shr 4) and KBigInteger.ONE)) - } - - @Test - fun test_square_0x11223344U_0xad2ffffdU_0x17eU() { - val num = KBigInteger(-1, uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU )) - println(num) - val res = num * num - assertEquals(res, KBigInteger(1, uintArrayOf(0xb0542a10U, 0xbbd85bc8U, 0x2a1fa515U, 0x5069e03bU, 0x23c09U))) - } - - @Test - fun testDivision_6_3() { - val x = KBigInteger(6) - val y = 3U - - val res = x / y - val div = KBigInteger(2) - - assertEquals(div, res) - } - - @Test - fun testBigDivision_6_3() { - val x = KBigInteger(6) - val y = KBigInteger(3) - - val res = x / y - val div = KBigInteger(2) - - assertEquals(div, res) - } - - @Test - fun testDivision_20__3() { - val x = KBigInteger(20) - val y = -3 - - val res = x / y - val div = KBigInteger(-6) - - assertEquals(div, res) - } - - @Test - fun testBigDivision_20__3() { - val x = KBigInteger(20) - val y = KBigInteger(-3) - - val res = x / y - val div = KBigInteger(-6) - - assertEquals(div, res) - } - - @Test - fun testDivision_0xfffffffe00000001_0xffffffff() { - val x = KBigInteger(0xfffffffe00000001UL) - val y = 0xffffffffU - - val res = x / y - val div = KBigInteger(0xffffffffL) - - assertEquals(div, res) - } - - @Test - fun testBigDivision_0xfffffffeabcdef01UL_0xfffffffeabc() { - val res = KBigInteger(0xfffffffeabcdef01UL) / KBigInteger(0xfffffffeabc) - assertEquals(res, KBigInteger(0x100000)) - } - - @Test - fun testBigDivision_0xfffffffe00000001_0xffffffff() { - val x = KBigInteger(0xfffffffe00000001UL) - val y = KBigInteger(0xffffffffU) - - val res = x / y - val div = KBigInteger(0xffffffffL) - - assertEquals(div, res) - } - - @Test - fun testMod_20_3() { - val x = KBigInteger(20) - val y = 3 - - val res = x % y - val mod = 2 - - assertEquals(mod, res) - } - - @Test - fun testBigMod_20_3() { - val x = KBigInteger(20) - val y = KBigInteger(3) - - val res = x % y - val mod = KBigInteger(2) - - assertEquals(mod, res) - } - - @Test - fun testMod_0xfffffffe00000001_12345() { - val x = KBigInteger(0xfffffffe00000001UL) - val y = 12345 - - val res = x % y - val mod = 1980 - - assertEquals(mod, res) - } - - @Test - fun testBigMod_0xfffffffe00000001_12345() { - val x = KBigInteger(0xfffffffe00000001UL) - val y = KBigInteger(12345) - - val res = x % y - val mod = KBigInteger(1980) - - assertEquals(mod, res) - } - - @Test - fun testModPow_3_10_17() { - val x = KBigInteger(3) - val exp = KBigInteger(10) - val mod = KBigInteger(17) - - val res = KBigInteger(8) - - return assertEquals(res, x.modPow(exp, mod)) - } - - @Test - fun testModPowBigNumbers() { - val x = KBigInteger(0xfffffffeabcdef01UL) - val exp = KBigInteger(2) - val mod = KBigInteger(0xfffffffeabcUL) - - val res = KBigInteger(0xc2253cde01) - - return assertEquals(res, x.modPow(exp, mod)) - } - - @Test - fun testModBigNumbers() { - val x = KBigInteger(0xfffffffeabcdef01UL) - val mod = KBigInteger(0xfffffffeabcUL) - - val res = KBigInteger(0xdef01) - - return assertEquals(res, x % mod) - } -} - -@kotlin.ExperimentalUnsignedTypes -class KBigIntegerConversionsTest { - @Test - fun testToString0x10() { - val x = KBigInteger(0x10) - assertEquals("0x10", x.toString()) - } - - @Test - fun testToString0x17ffffffd() { - val x = KBigInteger(0x17ffffffdL) - assertEquals("0x17ffffffd", x.toString()) - } - - @Test - fun testToString_0x17ead2ffffd() { - val x = KBigInteger(-0x17ead2ffffdL) - assertEquals("-0x17ead2ffffd", x.toString()) - } - - @Test - fun testToString_0x17ead2ffffd11223344() { - val x = KBigInteger(-1, uintArrayOf(0x11223344U, 0xad2ffffdU, 0x17eU )) - assertEquals("-0x17ead2ffffd11223344", x.toString()) - } - - @Test - fun testFromString_0x17ead2ffffd11223344() { - val x = "0x17ead2ffffd11223344".toKBigInteger() - assertEquals( "0x17ead2ffffd11223344", x.toString()) - } - - @Test - fun testFromString_7059135710711894913860() { - val x = "-7059135710711894913860".toKBigInteger() - assertEquals("-0x17ead2ffffd11223344", x.toString()) - } -} - -class KBigIntegerRingTest { - @Test - fun testKBigIntegerRingSum() { - val res = KBigIntegerRing { - KBigInteger(1_000L) * KBigInteger(1_000L) - } - assertEquals(res, KBigInteger(1_000_000) ) - } - - @Test - fun testKBigIntegerRingSum_100_000_000__100_000_000() { - KBigIntegerRing { - val sum = +"100_000_000" + +"100_000_000" - assertEquals(sum, "200_000_000".toKBigInteger()) - } - } - - @Test - fun test_mul_3__4() { - KBigIntegerRing { - val prod = +"0x3000_0000_0000" * +"0x4000_0000_0000_0000_0000" - assertEquals(prod, "0xc00_0000_0000_0000_0000_0000_0000_0000".toKBigInteger()) - } - } - - @Test - fun test_div_big_1() { - KBigIntegerRing { - val res = +"1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000" / - +"555_000_444_000_333_000_222_000_111_000_999_001" - assertEquals(res, +"1801800360360432432518919022699") - } - } - - @Test - fun test_rem_big_1() { - KBigIntegerRing { - val res = +"1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000" % - +"555_000_444_000_333_000_222_000_111_000_999_001" - assertEquals(res, +"324121220440768000291647788404676301") - } - } - -} - -- 2.34.1 From 30730f1051e0ac3aa8aeb6ede6981544495ad844 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 27 Apr 2020 15:43:03 +0300 Subject: [PATCH 29/32] Dimensions for JS --- .../kmath/structures/typeSafeDimensions.kt | 1 + kmath-dimensions/build.gradle.kts | 7 +++- .../scientifik/kmath/dimensions/Dimensions.kt | 34 +++++++++---------- .../scientifik/kmath/dimensions/Wrappers.kt | 17 +++++++--- .../dimensions/DMatrixContextTest.kt | 5 ++- .../kotlin/scientifik/kmath/dimensions/dim.kt | 21 ++++++++++-- .../kotlin/scientifik/kmath/dimensions/dim.kt | 18 ++++++++-- 7 files changed, 74 insertions(+), 29 deletions(-) rename kmath-dimensions/src/{jvmTest/kotlin/scientifik/kmath => commonTest/kotlin/scientifik}/dimensions/DMatrixContextTest.kt (79%) diff --git a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt index 9169c7d7b..fdc09ed5d 100644 --- a/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt +++ b/examples/src/main/kotlin/scientifik/kmath/structures/typeSafeDimensions.kt @@ -14,6 +14,7 @@ fun DMatrixContext.simple() { m1.transpose() + m2 } + object D5 : Dimension { override val dim: UInt = 5u } diff --git a/kmath-dimensions/build.gradle.kts b/kmath-dimensions/build.gradle.kts index 50fb41391..dda6cd2f0 100644 --- a/kmath-dimensions/build.gradle.kts +++ b/kmath-dimensions/build.gradle.kts @@ -7,8 +7,13 @@ description = "A proof of concept module for adding typ-safe dimensions to struc kotlin.sourceSets { commonMain { dependencies { - implementation(kotlin("reflect")) api(project(":kmath-core")) } } + + jvmMain{ + dependencies{ + api(kotlin("reflect")) + } + } } \ No newline at end of file diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt index 2eef69a0c..37e89c111 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Dimensions.kt @@ -1,37 +1,35 @@ package scientifik.kmath.dimensions +import kotlin.reflect.KClass + /** - * An interface which is not used in runtime. Designates a size of some structure. - * All descendants should be singleton objects. + * An abstract class which is not used in runtime. Designates a size of some structure. + * Could be replaced later by fully inline constructs */ interface Dimension { - val dim: UInt + val dim: UInt companion object { - @Suppress("NOTHING_TO_INLINE") - inline fun of(dim: UInt): Dimension { - return when (dim) { - 1u -> D1 - 2u -> D2 - 3u -> D3 - else -> object : Dimension { - override val dim: UInt = dim - } - } - } + } } -expect inline fun Dimension.Companion.dim(): UInt +fun KClass.dim(): UInt = Dimension.resolve(this).dim + +expect fun Dimension.Companion.resolve(type: KClass): D + +expect fun Dimension.Companion.of(dim: UInt): Dimension + +inline fun Dimension.Companion.dim(): UInt = D::class.dim() object D1 : Dimension { - override val dim: UInt = 1u + override val dim: UInt get() = 1U } object D2 : Dimension { - override val dim: UInt = 2u + override val dim: UInt get() = 2U } object D3 : Dimension { - override val dim: UInt = 3u + override val dim: UInt get() = 31U } \ No newline at end of file diff --git a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt index c78018753..f447866c0 100644 --- a/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt +++ b/kmath-dimensions/src/commonMain/kotlin/scientifik/kmath/dimensions/Wrappers.kt @@ -29,7 +29,7 @@ interface DMatrix : Structure2D { /** * The same as [coerce] but without dimension checks. Use with caution */ - inline fun coerceUnsafe(structure: Structure2D): DMatrix { + fun coerceUnsafe(structure: Structure2D): DMatrix { return DMatrixWrapper(structure) } } @@ -57,7 +57,7 @@ interface DPoint : Point { return DPointWrapper(point) } - inline fun coerceUnsafe(point: Point): DPoint { + fun coerceUnsafe(point: Point): DPoint { return DPointWrapper(point) } } @@ -81,8 +81,15 @@ inline class DPointWrapper(val point: Point) : */ inline class DMatrixContext>(val context: GenericMatrixContext) { - inline fun Matrix.coerce(): DMatrix = - DMatrix.coerceUnsafe(this) + inline fun Matrix.coerce(): DMatrix { + if (rowNum != Dimension.dim().toInt()) { + error("Row number mismatch: expected ${Dimension.dim()} but found $rowNum") + } + if (colNum != Dimension.dim().toInt()) { + error("Column number mismatch: expected ${Dimension.dim()} but found $colNum") + } + return DMatrix.coerceUnsafe(this) + } /** * Produce a matrix with this context and given dimensions @@ -90,7 +97,7 @@ inline class DMatrixContext>(val context: GenericMatrixCon inline fun produce(noinline initializer: (i: Int, j: Int) -> T): DMatrix { val rows = Dimension.dim() val cols = Dimension.dim() - return context.produce(rows.toInt(), cols.toInt(), initializer).coerce() + return context.produce(rows.toInt(), cols.toInt(), initializer).coerce() } inline fun point(noinline initializer: (Int) -> T): DPoint { diff --git a/kmath-dimensions/src/jvmTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt b/kmath-dimensions/src/commonTest/kotlin/scientifik/dimensions/DMatrixContextTest.kt similarity index 79% rename from kmath-dimensions/src/jvmTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt rename to kmath-dimensions/src/commonTest/kotlin/scientifik/dimensions/DMatrixContextTest.kt index 8d4bae810..74d20205c 100644 --- a/kmath-dimensions/src/jvmTest/kotlin/scientifik/kmath/dimensions/DMatrixContextTest.kt +++ b/kmath-dimensions/src/commonTest/kotlin/scientifik/dimensions/DMatrixContextTest.kt @@ -1,5 +1,8 @@ -package scientifik.kmath.dimensions +package scientifik.dimensions +import scientifik.kmath.dimensions.D2 +import scientifik.kmath.dimensions.D3 +import scientifik.kmath.dimensions.DMatrixContext import kotlin.test.Test diff --git a/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt b/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt index 557234d84..bbd580629 100644 --- a/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt +++ b/kmath-dimensions/src/jsMain/kotlin/scientifik/kmath/dimensions/dim.kt @@ -1,5 +1,22 @@ package scientifik.kmath.dimensions -actual inline fun Dimension.Companion.dim(): UInt { - TODO("KClass::objectInstance does not work") +import kotlin.reflect.KClass + +private val dimensionMap = hashMapOf( + 1u to D1, + 2u to D2, + 3u to D3 +) + +@Suppress("UNCHECKED_CAST") +actual fun Dimension.Companion.resolve(type: KClass): D { + return dimensionMap.entries.find { it.value::class == type }?.value as? D ?: error("Can't resolve dimension $type") +} + +actual fun Dimension.Companion.of(dim: UInt): Dimension { + return dimensionMap.getOrPut(dim) { + object : Dimension { + override val dim: UInt get() = dim + } + } } \ No newline at end of file diff --git a/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt b/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt index a16dd2266..e8fe8f59b 100644 --- a/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt +++ b/kmath-dimensions/src/jvmMain/kotlin/scientifik/kmath/dimensions/dim.kt @@ -1,4 +1,18 @@ package scientifik.kmath.dimensions -actual inline fun Dimension.Companion.dim(): UInt = - D::class.objectInstance?.dim ?: error("Dimension object must be a singleton") \ No newline at end of file +import kotlin.reflect.KClass + +actual fun Dimension.Companion.resolve(type: KClass): D{ + return type.objectInstance ?: error("No object instance for dimension class") +} + +actual fun Dimension.Companion.of(dim: UInt): Dimension{ + return when(dim){ + 1u -> D1 + 2u -> D2 + 3u -> D3 + else -> object : Dimension { + override val dim: UInt get() = dim + } + } +} \ No newline at end of file -- 2.34.1 From 1015e238f13c17d944f2d1c215de9ef24f588556 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 29 Apr 2020 19:28:24 +0300 Subject: [PATCH 30/32] Basic geometry --- .../kmath/operations/AlgebraExtensions.kt | 2 + kmath-geometry/build.gradle.kts | 9 +++ .../kmath/geometry/Euclidean2DSpace.kt | 58 +++++++++++++++++++ .../kmath/geometry/Euclidean3DSpace.kt | 57 ++++++++++++++++++ .../kmath/geometry/GeometrySpace.kt | 17 ++++++ .../kotlin/scientifik/kmath/geometry/Line.kt | 6 ++ .../kmath/geometry/ReferenceFrame.kt | 4 ++ settings.gradle.kts | 1 + 8 files changed, 154 insertions(+) create mode 100644 kmath-geometry/build.gradle.kts create mode 100644 kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt create mode 100644 kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt create mode 100644 kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt create mode 100644 kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt create mode 100644 kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt index 1e0453f08..bfb4199a3 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/AlgebraExtensions.kt @@ -3,6 +3,8 @@ package scientifik.kmath.operations fun Space.sum(data: Iterable): T = data.fold(zero) { left, right -> add(left, right) } fun Space.sum(data: Sequence): T = data.fold(zero) { left, right -> add(left, right) } +fun > Iterable.sumWith(space: S): T = space.sum(this) + //TODO optimized power operation fun RingOperations.power(arg: T, power: Int): T { var res = arg diff --git a/kmath-geometry/build.gradle.kts b/kmath-geometry/build.gradle.kts new file mode 100644 index 000000000..39aa833ad --- /dev/null +++ b/kmath-geometry/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("scientifik.mpp") +} + +kotlin.sourceSets.commonMain { + dependencies { + api(project(":kmath-core")) + } +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt new file mode 100644 index 000000000..2313b2170 --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean2DSpace.kt @@ -0,0 +1,58 @@ +package scientifik.kmath.geometry + +import scientifik.kmath.linear.Point +import scientifik.kmath.operations.SpaceElement +import scientifik.kmath.operations.invoke +import kotlin.math.sqrt + + +interface Vector2D : Point, Vector, SpaceElement { + val x: Double + val y: Double + + override val size: Int get() = 2 + + override fun get(index: Int): Double = when (index) { + 1 -> x + 2 -> y + else -> error("Accessing outside of point bounds") + } + + override fun iterator(): Iterator = listOf(x, y).iterator() + + override val context: Euclidean2DSpace get() = Euclidean2DSpace + + override fun unwrap(): Vector2D = this + + override fun Vector2D.wrap(): Vector2D = this +} + +val Vector2D.r: Double get() = Euclidean2DSpace.run { sqrt(norm()) } + +@Suppress("FunctionName") +fun Vector2D(x: Double, y: Double): Vector2D = Vector2DImpl(x, y) + +private data class Vector2DImpl( + override val x: Double, + override val y: Double +) : Vector2D + +/** + * 2D Euclidean space + */ +object Euclidean2DSpace : GeometrySpace { + fun Vector2D.norm(): Double = sqrt(x * x + y * y) + + override fun Vector2D.distanceTo(other: Vector2D): Double = (this - other).norm() + + override fun add(a: Vector2D, b: Vector2D): Vector2D = + Vector2D(a.x + b.x, a.y + b.y) + + override fun multiply(a: Vector2D, k: Number): Vector2D = + Vector2D(a.x * k.toDouble(), a.y * k.toDouble()) + + override val zero: Vector2D = Vector2D(0.0, 0.0) + + override fun Vector2D.dot(other: Vector2D): Double = + x * other.x + y * other.y +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt new file mode 100644 index 000000000..dd1776342 --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Euclidean3DSpace.kt @@ -0,0 +1,57 @@ +package scientifik.kmath.geometry + +import scientifik.kmath.linear.Point +import scientifik.kmath.operations.SpaceElement +import kotlin.math.sqrt + + +interface Vector3D : Point, Vector, SpaceElement { + val x: Double + val y: Double + val z: Double + + override val size: Int get() = 3 + + override fun get(index: Int): Double = when (index) { + 1 -> x + 2 -> y + 3 -> z + else -> error("Accessing outside of point bounds") + } + + override fun iterator(): Iterator = listOf(x, y, z).iterator() + + override val context: Euclidean3DSpace get() = Euclidean3DSpace + + override fun unwrap(): Vector3D = this + + override fun Vector3D.wrap(): Vector3D = this +} + +@Suppress("FunctionName") +fun Vector3D(x: Double, y: Double, z: Double): Vector3D = Vector3DImpl(x, y, z) + +val Vector3D.r: Double get() = Euclidean3DSpace.run { sqrt(norm()) } + +private data class Vector3DImpl( + override val x: Double, + override val y: Double, + override val z: Double +) : Vector3D + +object Euclidean3DSpace : GeometrySpace { + override val zero: Vector3D = Vector3D(0.0, 0.0, 0.0) + + fun Vector3D.norm(): Double = sqrt(x * x + y * y + z * z) + + override fun Vector3D.distanceTo(other: Vector3D): Double = (this - other).norm() + + override fun add(a: Vector3D, b: Vector3D): Vector3D = + Vector3D(a.x + b.x, a.y + b.y, a.z + b.z) + + override fun multiply(a: Vector3D, k: Number): Vector3D = + Vector3D(a.x * k.toDouble(), a.y * k.toDouble(), a.z * k.toDouble()) + + override fun Vector3D.dot(other: Vector3D): Double = + x * other.x + y * other.y + z * other.z +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt new file mode 100644 index 000000000..b65a8dd3a --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/GeometrySpace.kt @@ -0,0 +1,17 @@ +package scientifik.kmath.geometry + +import scientifik.kmath.operations.Space + +interface Vector + +interface GeometrySpace: Space { + /** + * L2 distance + */ + fun V.distanceTo(other: V): Double + + /** + * Scalar product + */ + infix fun V.dot(other: V): Double +} \ No newline at end of file diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt new file mode 100644 index 000000000..d802a103f --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/Line.kt @@ -0,0 +1,6 @@ +package scientifik.kmath.geometry + +data class Line(val base: V, val direction: V) + +typealias Line2D = Line +typealias Line3D = Line diff --git a/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt new file mode 100644 index 000000000..420e38ce2 --- /dev/null +++ b/kmath-geometry/src/commonMain/kotlin/scientifik/kmath/geometry/ReferenceFrame.kt @@ -0,0 +1,4 @@ +package scientifik.kmath.geometry + +interface ReferenceFrame { +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 671db444a..a08d5f7ee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,5 +41,6 @@ include( ":kmath-io", ":kmath-dimensions", ":kmath-for-real", + ":kmath-geometry", ":examples" ) -- 2.34.1 From 9c21f69ec02d94ba4b3740f5ed205e000356453f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 30 Apr 2020 09:38:42 +0300 Subject: [PATCH 31/32] Fix for #87 --- .../scientifik/kmath/prob/Distribution.kt | 8 ++++++-- .../kotlin/scientifik/kmath/prob/RandomChain.kt | 3 +-- .../kotlin/scientifik/kmath/prob/SamplerTest.kt | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt index 44af84d5d..3b874adaa 100644 --- a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/Distribution.kt @@ -58,9 +58,13 @@ fun Sampler.sampleBuffer( //creating temporary storage once val tmp = ArrayList(size) return sample(generator).collect { chain -> - for (i in tmp.indices) { - tmp[i] = chain.next() + //clear list from previous run + tmp.clear() + //Fill list + repeat(size){ + tmp.add(chain.next()) } + //return new buffer with elements from tmp bufferFactory(size) { tmp[it] } } } diff --git a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt index 9b581afd7..47fc6e4c5 100644 --- a/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt +++ b/kmath-prob/src/commonMain/kotlin/scientifik/kmath/prob/RandomChain.kt @@ -11,5 +11,4 @@ class RandomChain(val generator: RandomGenerator, private val gen: suspen override fun fork(): Chain = RandomChain(generator.fork(), gen) } -fun RandomGenerator.chain(gen: suspend RandomGenerator.() -> R): RandomChain = RandomChain(this, gen) -fun RandomGenerator.flow(gen: suspend RandomGenerator.() -> R) = chain(gen).fork() \ No newline at end of file +fun RandomGenerator.chain(gen: suspend RandomGenerator.() -> R): RandomChain = RandomChain(this, gen) \ No newline at end of file diff --git a/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt new file mode 100644 index 000000000..1152f3057 --- /dev/null +++ b/kmath-prob/src/jvmTest/kotlin/scientifik/kmath/prob/SamplerTest.kt @@ -0,0 +1,17 @@ +package scientifik.kmath.prob + +import kotlinx.coroutines.runBlocking +import kotlin.test.Test + +class SamplerTest { + + @Test + fun bufferSamplerTest(){ + val sampler: Sampler = + BasicSampler { it.chain { nextDouble() } } + val data = sampler.sampleBuffer(RandomGenerator.default, 100) + runBlocking { + println(data.next()) + } + } +} \ No newline at end of file -- 2.34.1 From 2e057c409b29f6a2c7d125169d9d315dca794a03 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 30 Apr 2020 11:20:28 +0300 Subject: [PATCH 32/32] RealHistogram counts weights --- .../kotlin/scientifik/kmath/chains/Chain.kt | 15 ++++++++++++ .../kmath/histogram/RealHistogram.kt | 23 ++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt index 467bbe978..1c2872d17 100644 --- a/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt +++ b/kmath-coroutines/src/commonMain/kotlin/scientifik/kmath/chains/Chain.kt @@ -130,6 +130,21 @@ fun Chain.map(func: suspend (T) -> R): Chain = object : Chain { override fun fork(): Chain = this@map.fork().map(func) } +/** + * [block] must be a pure function or at least not use external random variables, otherwise fork could be broken + */ +fun Chain.filter(block: (T) -> Boolean): Chain = object : Chain { + override suspend fun next(): T { + var next: T + do { + next = this@filter.next() + } while (!block(next)) + return next + } + + override fun fork(): Chain = this@filter.fork().filter(block) +} + /** * Map the whole chain */ diff --git a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt index a2f0fc516..85f078fda 100644 --- a/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt +++ b/kmath-histograms/src/commonMain/kotlin/scientifik/kmath/histogram/RealHistogram.kt @@ -45,8 +45,7 @@ class RealHistogram( private val values: NDStructure = NDStructure.auto(strides) { LongCounter() } - //private val weight: NDStructure = ndStructure(strides){null} - + private val weights: NDStructure = NDStructure.auto(strides) { DoubleCounter() } override val dimension: Int get() = lower.size @@ -102,21 +101,33 @@ class RealHistogram( return MultivariateBin(getDef(index), getValue(index)) } +// fun put(point: Point){ +// val index = getIndex(point) +// values[index].increment() +// } + override fun putWithWeight(point: Buffer, weight: Double) { - if (weight != 1.0) TODO("Implement weighting") val index = getIndex(point) values[index].increment() + weights[index].add(weight) } - override fun iterator(): Iterator> = values.elements().map { (index, value) -> + override fun iterator(): Iterator> = weights.elements().map { (index, value) -> MultivariateBin(getDef(index), value.sum()) }.iterator() /** * Convert this histogram into NDStructure containing bin values but not bin descriptions */ - fun asNDStructure(): NDStructure { - return NDStructure.auto(this.values.shape) { values[it].sum() } + fun values(): NDStructure { + return NDStructure.auto(values.shape) { values[it].sum() } + } + + /** + * Sum of weights + */ + fun weights():NDStructure{ + return NDStructure.auto(weights.shape) { weights[it].sum() } } companion object { -- 2.34.1