From 50a65524db2afbd03b298506661fa89a312fa986 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 3 May 2018 11:35:08 +0300 Subject: [PATCH 1/7] Conceptual design of linear algebra on the way --- .../kmath/structures/LinearAlgrebra.kt | 45 +++++++++++++++++++ .../scientifik/kmath/structures/NDArray.kt | 11 ++--- .../kmath/structures/RealNDArray.kt | 1 + 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt diff --git a/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt b/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt new file mode 100644 index 000000000..3de6d7560 --- /dev/null +++ b/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt @@ -0,0 +1,45 @@ +package scientifik.kmath.structures + +import scientifik.kmath.operations.Field +import scientifik.kmath.operations.Space +import scientifik.kmath.operations.SpaceElement + +abstract class LinearSpace>(val rows: Int, val columns: Int, val field: Field) : Space { + + abstract fun produce(initializer: (Int, Int) -> T): E + + override val zero: E by lazy { + produce { _, _ -> field.zero } + } + + override fun add(a: E, b: E): E { + return produce { i, j -> with(field) { a[i, j] + b[i, j] } } + } + + override fun multiply(a: E, k: Double): E { + //TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser + return produce { i, j -> with(field) { a[i, j] * k } } + } +} + +/** + * An element of linear algebra with fixed dimension. The linear space allows linear operations on objects of the same dimensions. + * Scalar product operations are performed outside space. + * + * @param T the type of linear object element type. + */ +interface LinearObject : SpaceElement> { + val rows: Int + val columns: Int + operator fun get(i: Int, j: Int): T + + /** + * Get a transposed object with switched dimensions + */ + fun transpose(): LinearObject + + /** + * Perform scalar multiplication (dot) operation, checking dimensions. The argument object and result both could be outside initial space. + */ + operator fun times(other: LinearObject): LinearObject +} diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt index 7f27222cc..4e2458fff 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt @@ -16,8 +16,9 @@ abstract class NDField(val shape: List, val field: Field) : Field) -> T): NDArray - override val zero: NDArray - get() = produce { this.field.zero } + override val zero: NDArray by lazy { + produce { this.field.zero } + } private fun checkShape(vararg arrays: NDArray) { arrays.forEach { @@ -40,7 +41,7 @@ abstract class NDField(val shape: List, val field: Field) : Field, k: Double): NDArray { checkShape(a) - return produce { with(field) {a[it] * k} } + return produce { with(field) { a[it] * k } } } override val one: NDArray @@ -51,7 +52,7 @@ abstract class NDField(val shape: List, val field: Field) : Field, b: NDArray): NDArray { checkShape(a) - return produce { with(field) {a[it] * b[it]} } + return produce { with(field) { a[it] * b[it] } } } /** @@ -59,7 +60,7 @@ abstract class NDField(val shape: List, val field: Field) : Field, b: NDArray): NDArray { checkShape(a) - return produce { with(field) {a[it] / b[it]} } + return produce { with(field) { a[it] / b[it] } } } } diff --git a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt b/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt index 3be892ced..217e89033 100644 --- a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt +++ b/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt @@ -35,6 +35,7 @@ private class RealNDField(shape: List) : NDField(shape, DoubleField override fun produce(initializer: (List) -> Double): NDArray { //TODO use sparse arrays for large capacities val buffer = DoubleBuffer.allocate(capacity) + //FIXME there could be performance degradation due to iteration procedure. Replace by straight iteration NDArray.iterateIndexes(shape).forEach { buffer.put(offset(it), initializer(it)) } -- 2.34.1 From af446d2d2c318a1395a7ecaa9dee6a6b789137e9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 6 May 2018 22:45:27 +0300 Subject: [PATCH 2/7] Abstract linear algebra and real value array implementation. Not tested. --- .../kmath/structures/LinearAlgrebra.kt | 110 +++++++++++++++--- .../scientifik/kmath/structures/NDArray.kt | 1 + 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt b/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt index 3de6d7560..416a5d477 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt +++ b/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt @@ -1,45 +1,117 @@ package scientifik.kmath.structures +import scientifik.kmath.operations.DoubleField import scientifik.kmath.operations.Field import scientifik.kmath.operations.Space import scientifik.kmath.operations.SpaceElement -abstract class LinearSpace>(val rows: Int, val columns: Int, val field: Field) : Space { +/** + * The space for linear elements. Supports scalar product alongside with standard linear operations. + */ +abstract class LinearSpace>(val rows: Int, val columns: Int, val field: Field) : Space { - abstract fun produce(initializer: (Int, Int) -> T): E + /** + * Produce the element of this space + */ + abstract fun produce(initializer: (Int, Int) -> T): V - override val zero: E by lazy { + /** + * Produce new linear space with given dimensions + */ + abstract fun produceSpace(rows: Int, columns: Int): LinearSpace + + override val zero: V by lazy { produce { _, _ -> field.zero } } - override fun add(a: E, b: E): E { + override fun add(a: V, b: V): V { return produce { i, j -> with(field) { a[i, j] + b[i, j] } } } - override fun multiply(a: E, k: Double): E { + override fun multiply(a: V, k: Double): V { //TODO it is possible to implement scalable linear elements which normed values and adjustable scale to save memory and processing poser return produce { i, j -> with(field) { a[i, j] * k } } } + + /** + * Dot product + */ + fun multiply(a: V, b: V): V { + if (a.columns != b.rows) { + //TODO replace by specific exception + throw RuntimeException("Dimension mismatch in vector dot product") + } + return produceSpace(a.rows, b.columns).produce { i, j -> + (0..a.columns).asSequence().map { k -> field.multiply(a[i, k], b[k, j]) }.reduce { first, second -> field.add(first, second) } + } + } + + infix fun V.dot(b: V): V = multiply(this, b) } /** - * An element of linear algebra with fixed dimension. The linear space allows linear operations on objects of the same dimensions. - * Scalar product operations are performed outside space. - * - * @param T the type of linear object element type. + * A matrix-like structure that is not dependent on specific space implementation */ -interface LinearObject : SpaceElement> { +interface LinearStructure { val rows: Int val columns: Int operator fun get(i: Int, j: Int): T - /** - * Get a transposed object with switched dimensions - */ - fun transpose(): LinearObject - - /** - * Perform scalar multiplication (dot) operation, checking dimensions. The argument object and result both could be outside initial space. - */ - operator fun times(other: LinearObject): LinearObject + fun transpose(): LinearStructure { + return object : LinearStructure { + override val rows: Int = this@LinearStructure.columns + override val columns: Int = this@LinearStructure.rows + override fun get(i: Int, j: Int): T = this@LinearStructure.get(j, i) + } + } } + +class RealArraySpace(rows: Int, columns: Int) : LinearSpace(rows, columns, DoubleField) { + override fun produce(initializer: (Int, Int) -> Double): RealArray { + return RealArray(this, initializer) + } + + override fun produceSpace(rows: Int, columns: Int): LinearSpace { + return RealArraySpace(rows, columns) + } +} + +class RealArray(override val context: RealArraySpace, initializer: (Int, Int) -> Double): LinearStructure, SpaceElement { + + val array: Array> = Array(context.rows) { i -> Array(context.columns) { j -> initializer(i, j) } } + + override val rows: Int = context.rows + + override val columns: Int = context.columns + + override fun get(i: Int, j: Int): Double { + return array[i][j] + } + + override val self: RealArray = this +} + + +///** +// * An element of linear algebra with fixed dimension. The linear space allows linear operations on objects of the same dimensions. +// * Scalar product operations are performed outside space. +// * +// * @param E the type of linear object element type. +// */ +//interface LinearObject : SpaceElement> { +// override val context: LinearSpace<> +// +// val rows: Int +// val columns: Int +// operator fun get(i: Int, j: Int): E +// +// /** +// * Get a transposed object with switched dimensions +// */ +// fun transpose(): LinearObject +// +// /** +// * Perform scalar multiplication (dot) operation, checking dimensions. The argument object and result both could be outside initial space. +// */ +// operator fun times(other: LinearObject): LinearObject +//} diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt index 4e2458fff..0005565c1 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt @@ -9,6 +9,7 @@ class ShapeMismatchException(val expected: List, val actual: List) : R * Field for n-dimensional arrays. * @param shape - the list of dimensions of the array * @param field - operations field defined on individual array element + * @param T the type of the element contained in NDArray */ abstract class NDField(val shape: List, val field: Field) : Field> { /** -- 2.34.1 From eb4c9a4b94f722d3dd478da047d19fc0003941b9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 18 Jul 2018 12:51:21 +0300 Subject: [PATCH 3/7] Abstract linear algebra and real value array implementation. --- build.gradle | 2 +- .../kmath/structures/LinearAlgrebra.kt | 127 +++++++++++------- .../scientifik/kmath/structures/NDArray.kt | 14 +- .../kmath/structures/RealNDArray.kt | 2 +- .../kmath/structures/ArrayMatrixTest.kt | 26 ++++ 5 files changed, 116 insertions(+), 55 deletions(-) create mode 100644 jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt diff --git a/build.gradle b/build.gradle index 9b6c2c113..226cff021 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.41' + ext.kotlin_version = '1.2.51' repositories { mavenCentral() diff --git a/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt b/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt index 416a5d477..62bad9b75 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt +++ b/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt @@ -7,8 +7,10 @@ import scientifik.kmath.operations.SpaceElement /** * The space for linear elements. Supports scalar product alongside with standard linear operations. + * @param T type of individual element of the vector or matrix + * @param V the type of vector space element */ -abstract class LinearSpace>(val rows: Int, val columns: Int, val field: Field) : Space { +abstract class LinearSpace>(val rows: Int, val columns: Int, val field: Field) : Space { /** * Produce the element of this space @@ -18,7 +20,7 @@ abstract class LinearSpace>(val rows: Int, val colu /** * Produce new linear space with given dimensions */ - abstract fun produceSpace(rows: Int, columns: Int): LinearSpace + abstract fun produceSpace(rows: Int, columns: Int): LinearSpace override val zero: V by lazy { produce { _, _ -> field.zero } @@ -37,9 +39,9 @@ abstract class LinearSpace>(val rows: Int, val colu * Dot product */ fun multiply(a: V, b: V): V { - if (a.columns != b.rows) { + if (a.rows != b.columns) { //TODO replace by specific exception - throw RuntimeException("Dimension mismatch in vector dot product") + error("Dimension mismatch in vector dot product") } return produceSpace(a.rows, b.columns).produce { i, j -> (0..a.columns).asSequence().map { k -> field.multiply(a[i, k], b[k, j]) }.reduce { first, second -> field.add(first, second) } @@ -52,9 +54,10 @@ abstract class LinearSpace>(val rows: Int, val colu /** * A matrix-like structure that is not dependent on specific space implementation */ -interface LinearStructure { +interface LinearStructure { val rows: Int val columns: Int + operator fun get(i: Int, j: Int): T fun transpose(): LinearStructure { @@ -66,52 +69,76 @@ interface LinearStructure { } } -class RealArraySpace(rows: Int, columns: Int) : LinearSpace(rows, columns, DoubleField) { - override fun produce(initializer: (Int, Int) -> Double): RealArray { - return RealArray(this, initializer) - } +interface Vector : LinearStructure { + override val columns: Int + get() = 1 - override fun produceSpace(rows: Int, columns: Int): LinearSpace { - return RealArraySpace(rows, columns) - } -} - -class RealArray(override val context: RealArraySpace, initializer: (Int, Int) -> Double): LinearStructure, SpaceElement { - - val array: Array> = Array(context.rows) { i -> Array(context.columns) { j -> initializer(i, j) } } - - override val rows: Int = context.rows - - override val columns: Int = context.columns - - override fun get(i: Int, j: Int): Double { - return array[i][j] - } - - override val self: RealArray = this + operator fun get(i: Int) = get(i, 0) } -///** -// * An element of linear algebra with fixed dimension. The linear space allows linear operations on objects of the same dimensions. -// * Scalar product operations are performed outside space. -// * -// * @param E the type of linear object element type. -// */ -//interface LinearObject : SpaceElement> { -// override val context: LinearSpace<> -// -// val rows: Int -// val columns: Int -// operator fun get(i: Int, j: Int): E -// -// /** -// * Get a transposed object with switched dimensions -// */ -// fun transpose(): LinearObject -// -// /** -// * Perform scalar multiplication (dot) operation, checking dimensions. The argument object and result both could be outside initial space. -// */ -// operator fun times(other: LinearObject): LinearObject -//} +/** + * DoubleArray-based implementation of vector space + */ +class ArraySpace(rows: Int, columns: Int, field: Field) : LinearSpace>(rows, columns, field) { + + override fun produce(initializer: (Int, Int) -> T): LinearStructure = ArrayMatrix(this, initializer) + + + override fun produceSpace(rows: Int, columns: Int): LinearSpace> { + return ArraySpace(rows, columns, field) + } +} + +/** + * Member of [ArraySpace] which wraps 2-D array + */ +class ArrayMatrix(override val context: ArraySpace, initializer: (Int, Int) -> T) : LinearStructure, SpaceElement, ArraySpace> { + + val list: List> = (0 until rows).map { i -> (0 until columns).map { j -> initializer(i, j) } } + + override val rows: Int get() = context.rows + + override val columns: Int get() = context.columns + + override fun get(i: Int, j: Int): T { + return list[i][j] + } + + override val self: ArrayMatrix get() = this +} + + +class ArrayVector(override val context: ArraySpace, initializer: (Int) -> T) : Vector, SpaceElement, ArraySpace> { + + init { + if (context.columns != 1) { + error("Vector must have single column") + } + } + + val list: List = (0 until context.rows).map(initializer) + + + override val rows: Int get() = context.rows + + override val columns: Int = 1 + + override fun get(i: Int, j: Int): T { + return list[i] + } + + override val self: ArrayVector get() = this + +} + +fun vector(size: Int, field: Field, initializer: (Int) -> T) = ArrayVector(ArraySpace(size, 1, field), initializer) +//TODO replace by primitive array version +fun realVector(size: Int, initializer: (Int) -> Double) = vector(size, DoubleField, initializer) + +fun Array.asVector(field: Field) = vector(size, field) { this[it] } +//TODO add inferred field from field element +fun DoubleArray.asVector() = realVector(this.size) { this[it] } + +fun matrix(rows: Int, columns: Int, field: Field, initializer: (Int, Int) -> T) = ArrayMatrix(ArraySpace(rows, columns, field), initializer) +fun realMatrix(rows: Int, columns: Int, initializer: (Int, Int) -> Double) = matrix(rows, columns, DoubleField, initializer) \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt index 0005565c1..4ae164fcf 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt @@ -3,6 +3,9 @@ package scientifik.kmath.structures import scientifik.kmath.operations.Field import scientifik.kmath.operations.FieldElement +/** + * An exception is thrown when the expected ans actual shape of NDArray differs + */ class ShapeMismatchException(val expected: List, val actual: List) : RuntimeException() /** @@ -11,9 +14,11 @@ class ShapeMismatchException(val expected: List, val actual: List) : R * @param field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ -abstract class NDField(val shape: List, val field: Field) : Field> { +abstract class NDField(val shape: List, private val field: Field) : Field> { + /** * Create new instance of NDArray using field shape and given initializer + * The producer takes list of indices as argument and returns contained value */ abstract fun produce(initializer: (List) -> T): NDArray @@ -21,6 +26,9 @@ abstract class NDField(val shape: List, val field: Field) : Field) { arrays.forEach { if (shape != it.shape) { @@ -66,13 +74,13 @@ abstract class NDField(val shape: List, val field: Field) : Field : FieldElement>, Iterable, T>> { +interface NDArray : FieldElement, NDField>, Iterable, T>> { /** * The list of dimensions of this NDArray */ val shape: List - get() = (context as NDField).shape + get() = context.shape /** * The number of dimentsions for this array diff --git a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt b/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt index 217e89033..2c213f91e 100644 --- a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt +++ b/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt @@ -69,7 +69,7 @@ private class RealNDField(shape: List) : NDField(shape, DoubleField //TODO generate fixed hash code for quick comparison? - override val self: NDArray = this + override val self: NDArray get() = this } } diff --git a/jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt b/jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt new file mode 100644 index 000000000..70fd8a6a9 --- /dev/null +++ b/jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt @@ -0,0 +1,26 @@ +package scientifik.kmath.structures + +import org.junit.Assert.assertEquals +import org.junit.Test + +class ArrayMatrixTest { + + @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, 0], 0.1) + } + + @Test + fun testDot() { + val vector1 = realVector(5) { it.toDouble() } + val vector2 = realVector(5) { 5 - it.toDouble() } + val product = with(vector1.context) { + vector1 dot (vector2.transpose()) + } + + assertEquals(10.0, product[1, 0], 0.1) + } +} \ No newline at end of file -- 2.34.1 From 0d1825a0444d9b5d57ef044cfaee77c97bd75e96 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 18 Jul 2018 12:52:58 +0300 Subject: [PATCH 4/7] Optional math operations. --- .../scientifik/kmath/operations/Algebra.kt | 42 +++++++++------- .../scientifik/kmath/operations/Fields.kt | 39 ++++++++++----- .../kmath/operations/OptionalOperations.kt | 48 +++++++++++++++++++ .../kmath/operations/RealFieldTest.kt | 14 ++++++ 4 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt create mode 100644 common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt b/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt index 71147d56e..6f06f5980 100644 --- a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt +++ b/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt @@ -1,5 +1,16 @@ package scientifik.kmath.operations + +/** + * The generic mathematics elements which is able to store its context + */ +interface MathElement{ + /** + * The context this element belongs to + */ + val context: S +} + /** * A general interface representing linear context of some kind. * The context defines sum operation for its elements and multiplication by real value. @@ -37,23 +48,20 @@ interface Space { /** * The element of linear context - * @param S self type of the element. Needed for static type checking + * @param T self type of the element. Needed for static type checking + * @param S the type of space */ -interface SpaceElement> { - /** - * The context this element belongs to - */ - val context: Space +interface SpaceElement>: MathElement { /** * Self value. Needed for static type checking. Needed to avoid type erasure on JVM. */ - val self: S + val self: T - operator fun plus(b: S): S = context.add(self, b) - operator fun minus(b: S): S = context.add(self, context.multiply(b, -1.0)) - operator fun times(k: Number): S = context.multiply(self, k.toDouble()) - operator fun div(k: Number): S = context.multiply(self, 1.0 / k.toDouble()) + operator fun plus(b: T): T = context.add(self, b) + operator fun minus(b: T): T = context.add(self, context.multiply(b, -1.0)) + operator fun times(k: Number): T = context.multiply(self, k.toDouble()) + operator fun div(k: Number): T = context.multiply(self, 1.0 / k.toDouble()) } /** @@ -77,10 +85,10 @@ interface Ring : Space { /** * Ring element */ -interface RingElement> : SpaceElement { - override val context: Ring +interface RingElement> : SpaceElement { + override val context: S - operator fun times(b: S): S = context.multiply(self, b) + operator fun times(b: T): T = context.multiply(self, b) } /** @@ -96,8 +104,8 @@ interface Field : Ring { /** * Field element */ -interface FieldElement> : RingElement { - override val context: Field +interface FieldElement> : RingElement { + override val context: S - operator fun div(b: S): S = context.divide(self, b) + operator fun div(b: T): T = context.divide(self, b) } \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt b/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt index 7c9e2ddb7..2f3eb99ff 100644 --- a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt +++ b/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt @@ -1,23 +1,39 @@ package scientifik.kmath.operations +import kotlin.math.pow import kotlin.math.sqrt /** * Field for real values */ -object RealField : Field { +object RealField : Field, TrigonometricOperations, PowerOperations, ExponentialOperations { override val zero: Real = Real(0.0) override fun add(a: Real, b: Real): Real = Real(a.value + b.value) override val one: Real = Real(1.0) override fun multiply(a: Real, b: Real): Real = Real(a.value * b.value) override fun multiply(a: Real, k: Double): Real = Real(a.value * k) override fun divide(a: Real, b: Real): Real = Real(a.value / b.value) + + override fun sin(arg: Real): Real = Real(kotlin.math.sin(arg.value)) + override fun cos(arg: Real): Real = Real(kotlin.math.cos(arg.value)) + + override fun power(arg: Real, pow: Double): Real = Real(arg.value.pow(pow)) + + override fun exp(arg: Real): Real = Real(kotlin.math.exp(arg.value)) + + override fun ln(arg: Real): Real = Real(kotlin.math.ln(arg.value)) } /** - * Real field element wrapping double + * Real field element wrapping double. + * + * TODO could be replaced by inline class in kotlin 1.3 if it would allow to avoid boxing */ -class Real(val value: Double) : FieldElement, Number() { +class Real(val value: Double) : Number(), FieldElement { + /* + * The class uses composition instead of inheritance since Double is final + */ + override fun toByte(): Byte = value.toByte() override fun toChar(): Char = value.toChar() override fun toDouble(): Double = value @@ -29,8 +45,10 @@ class Real(val value: Double) : FieldElement, Number() { //values are dynamically calculated to save memory override val self get() = this + override val context get() = RealField + } /** @@ -54,10 +72,9 @@ object ComplexField : Field { /** * Complex number class */ -data class Complex(val re: Double, val im: Double) : FieldElement { - override val self: Complex - get() = this - override val context: Field +data class Complex(val re: Double, val im: Double) : FieldElement { + override val self: Complex get() = this + override val context: ComplexField get() = ComplexField /** @@ -72,15 +89,15 @@ data class Complex(val re: Double, val im: Double) : FieldElement { val module: Double get() = sqrt(square) - - //TODO is it convenient? - operator fun not() = conjugate } +/** + * A field for double without boxing. Does not produce appropriate field element + */ object DoubleField : Field { override val zero: Double = 0.0 override fun add(a: Double, b: Double): Double = a + b - override fun multiply(a: Double, b: Double): Double = a * b + override fun multiply(a: Double, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") b: Double): Double = a * b override val one: Double = 1.0 override fun divide(a: Double, b: Double): Double = a / b } \ No newline at end of file diff --git a/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt new file mode 100644 index 000000000..18ef89407 --- /dev/null +++ b/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt @@ -0,0 +1,48 @@ +package scientifik.kmath.operations + + +/* Trigonometric operations */ + +/** + * A container for trigonometric operations for specific type. Trigonometric operations are limited to fields. + * + * The operations are not exposed to class directly to avoid method bloat but instead are declared in the field. + * It also allows to override behavior for optional operations + * + */ +interface TrigonometricOperations: Field { + fun sin(arg: T): T + fun cos(arg: T): T + + fun tg(arg: T): T = sin(arg) / cos(arg) + + fun ctg(arg: T): T = cos(arg) / sin(arg) +} + +fun >> sin(arg: T): T = arg.context.sin(arg) +fun >> cos(arg: T): T = arg.context.cos(arg) +fun >> tg(arg: T): T = arg.context.tg(arg) +fun >> ctg(arg: T): T = arg.context.ctg(arg) + +/* Power and roots */ + +/** + * A context extension to include power operations like square roots, etc + */ +interface PowerOperations { + fun power(arg: T, pow: Double): T +} + +infix fun >> T.pow(power: Double): T = context.power(this, power) +fun >> sqrt(arg: T): T = arg pow 0.5 +fun >> sqr(arg: T): T = arg pow 2.0 + +/* Exponential */ + +interface ExponentialOperations{ + fun exp(arg: T): T + fun ln(arg: T): T +} + +fun >> exp(arg:T): T = arg.context.exp(arg) +fun >> ln(arg:T): T = arg.context.ln(arg) \ No newline at end of file diff --git a/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt new file mode 100644 index 000000000..b07158cc3 --- /dev/null +++ b/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt @@ -0,0 +1,14 @@ +package scientifik.kmath.operations + +import kotlin.test.Test +import kotlin.test.assertEquals + +class RealFieldTest { + @Test + fun testSqrt() { + val sqrt = with(RealField) { + sqrt(25 * one) + } + assertEquals(5.0, sqrt.toDouble()) + } +} \ No newline at end of file -- 2.34.1 From f28f036fec0c6fa709b9a3337d247f9cf85a68ad Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 3 Aug 2018 09:04:39 +0300 Subject: [PATCH 5/7] Added operations with external functions and elements on NDArray. Switched to kotlin 1.2.60 --- build.gradle | 2 +- .../scientifik/kmath/structures/NDArray.kt | 81 ++++++++++++++++++- .../kmath/structures/RealNDFieldTest.kt | 20 +++-- 3 files changed, 92 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 226cff021..bce9c1492 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.2.51' + ext.kotlin_version = '1.2.60' repositories { mavenCentral() diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt index 4ae164fcf..5acac4ff1 100644 --- a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt +++ b/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt @@ -14,7 +14,7 @@ class ShapeMismatchException(val expected: List, val actual: List) : R * @param field - operations field defined on individual array element * @param T the type of the element contained in NDArray */ -abstract class NDField(val shape: List, private val field: Field) : Field> { +abstract class NDField(val shape: List, val field: Field) : Field> { /** * Create new instance of NDArray using field shape and given initializer @@ -74,7 +74,7 @@ abstract class NDField(val shape: List, private val field: Field) : F } -interface NDArray : FieldElement, NDField>, Iterable, T>> { +interface NDArray : FieldElement, NDField> { /** * The list of dimensions of this NDArray @@ -97,14 +97,14 @@ interface NDArray : FieldElement, NDField>, Iterable, T>> { + operator fun iterator(): Iterator, T>> { return iterateIndexes(shape).map { Pair(it, this[it]) }.iterator() } /** * Generate new NDArray, using given transformation for each element */ - fun transform(action: (List, T) -> T): NDArray = (context as NDField).produce { action(it, this[it]) } + fun transform(action: (List, T) -> T): NDArray = context.produce { action(it, this[it]) } companion object { /** @@ -125,6 +125,79 @@ interface NDArray : FieldElement, NDField>, Iterable Function1.invoke(ndArray: NDArray): NDArray = ndArray.transform { _, value -> this(value) } + +/* plus and minus */ + +/** + * Summation operation for [NDArray] and single element + */ +operator fun NDArray.plus(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg + value + } +} + +/** + * Reverse sum operation + */ +operator fun T.plus(arg: NDArray): NDArray = arg + this + +/** + * Subtraction operation between [NDArray] and single element + */ +operator fun NDArray.minus(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg - value + } +} + +/** + * Reverse minus operation + */ +operator fun T.minus(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field){ + this@minus - value + } +} + +/* prod and div */ + +/** + * Product operation for [NDArray] and single element + */ +operator fun NDArray.times(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg * value + } +} + +/** + * Reverse product operation + */ +operator fun T.times(arg: NDArray): NDArray = arg * this + +/** + * Division operation between [NDArray] and single element + */ +operator fun NDArray.div(arg: T): NDArray = transform { _, value -> + with(context.field){ + arg / value + } +} + +/** + * Reverse division operation + */ +operator fun T.div(arg: NDArray): NDArray = arg.transform { _, value -> + with(arg.context.field){ + this@div/ value + } +} + /** * Create a platform-specific NDArray of doubles */ diff --git a/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt b/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt index 00e1fd7b4..9bed502f9 100644 --- a/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt +++ b/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt @@ -1,6 +1,7 @@ package scientifik.kmath.structures import org.junit.Assert.assertEquals +import kotlin.math.pow import kotlin.test.Test class RealNDFieldTest { @@ -14,8 +15,8 @@ class RealNDFieldTest { } @Test - fun testProduct(){ - val product = array1*array2 + fun testProduct() { + val product = array1 * array2 assertEquals(0.0, product[2, 2], 0.1) } @@ -24,11 +25,18 @@ class RealNDFieldTest { val array = real2DArray(3, 3) { i, j -> (i * 10 + j).toDouble() } - for(i in 0..2){ - for(j in 0..2){ - val expected= (i * 10 + j).toDouble() - assertEquals("Error at index [$i, $j]", expected, array[i,j], 0.1) + for (i in 0..2) { + for (j in 0..2) { + val expected = (i * 10 + j).toDouble() + assertEquals("Error at index [$i, $j]", expected, array[i, j], 0.1) } } } + + @Test + fun testExternalFunction() { + val function: (Double) -> Double = { x -> x.pow(2) + 2 * x + 1 } + val result = function(array1) + 1.0 + assertEquals(10.0, result[1,1],0.01) + } } -- 2.34.1 From 717c4260f90b4e982388e250e99740c9500ae847 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 3 Aug 2018 09:07:35 +0300 Subject: [PATCH 6/7] Ddded kmath prefix to module names. --- {common => kmath-common}/build.gradle | 0 .../src/main/kotlin/scientifik/kmath/operations/Algebra.kt | 0 .../src/main/kotlin/scientifik/kmath/operations/Fields.kt | 0 .../kotlin/scientifik/kmath/operations/OptionalOperations.kt | 0 .../main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt | 0 .../src/main/kotlin/scientifik/kmath/structures/NDArray.kt | 0 .../test/kotlin/scientifik/kmath/operations/RealFieldTest.kt | 0 {jvm => kmath-jvm}/build.gradle | 2 +- .../main/kotlin/scientifik/kmath/structures/RealNDArray.kt | 0 .../kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt | 0 .../kotlin/scientifik/kmath/structures/RealNDFieldTest.kt | 0 settings.gradle | 4 ++-- 12 files changed, 3 insertions(+), 3 deletions(-) rename {common => kmath-common}/build.gradle (100%) rename {common => kmath-common}/src/main/kotlin/scientifik/kmath/operations/Algebra.kt (100%) rename {common => kmath-common}/src/main/kotlin/scientifik/kmath/operations/Fields.kt (100%) rename {common => kmath-common}/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt (100%) rename {common => kmath-common}/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt (100%) rename {common => kmath-common}/src/main/kotlin/scientifik/kmath/structures/NDArray.kt (100%) rename {common => kmath-common}/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt (100%) rename {jvm => kmath-jvm}/build.gradle (87%) rename {jvm => kmath-jvm}/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt (100%) rename {jvm => kmath-jvm}/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt (100%) rename {jvm => kmath-jvm}/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt (100%) diff --git a/common/build.gradle b/kmath-common/build.gradle similarity index 100% rename from common/build.gradle rename to kmath-common/build.gradle diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt b/kmath-common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt similarity index 100% rename from common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/operations/Algebra.kt diff --git a/common/src/main/kotlin/scientifik/kmath/operations/Fields.kt b/kmath-common/src/main/kotlin/scientifik/kmath/operations/Fields.kt similarity index 100% rename from common/src/main/kotlin/scientifik/kmath/operations/Fields.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/operations/Fields.kt diff --git a/common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt b/kmath-common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt similarity index 100% rename from common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/operations/OptionalOperations.kt diff --git a/common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt b/kmath-common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt similarity index 100% rename from common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/structures/LinearAlgrebra.kt diff --git a/common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt b/kmath-common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt similarity index 100% rename from common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt rename to kmath-common/src/main/kotlin/scientifik/kmath/structures/NDArray.kt diff --git a/common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt b/kmath-common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt similarity index 100% rename from common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt rename to kmath-common/src/test/kotlin/scientifik/kmath/operations/RealFieldTest.kt diff --git a/jvm/build.gradle b/kmath-jvm/build.gradle similarity index 87% rename from jvm/build.gradle rename to kmath-jvm/build.gradle index 6dc9a0303..82bda0270 100644 --- a/jvm/build.gradle +++ b/kmath-jvm/build.gradle @@ -5,7 +5,7 @@ repositories { } dependencies { - expectedBy project(":common") + expectedBy project(":kmath-common") compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile "junit:junit:4.12" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" diff --git a/jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt b/kmath-jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt similarity index 100% rename from jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt rename to kmath-jvm/src/main/kotlin/scientifik/kmath/structures/RealNDArray.kt diff --git a/jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt b/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt similarity index 100% rename from jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt rename to kmath-jvm/src/test/kotlin/scientifik/kmath/structures/ArrayMatrixTest.kt diff --git a/jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt b/kmath-jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt similarity index 100% rename from jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt rename to kmath-jvm/src/test/kotlin/scientifik/kmath/structures/RealNDFieldTest.kt diff --git a/settings.gradle b/settings.gradle index a60b00349..9dce0ac08 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'kmath' -include 'common' -include 'jvm' +include 'kmath-common' +include 'kmath-jvm' -- 2.34.1 From 80bd0e4e6a7782d5e7f67a48a570243facf07741 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 3 Aug 2018 09:31:24 +0300 Subject: [PATCH 7/7] Created a short readme --- README.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 863a3e826..13db36602 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ -# kmath -Kotlin mathematics extensions library +# KMath +Kotlin MATHematics library is intended as a kotlin based analog of numpy python library. Contrary to `numpy` +and `scipy` it is modular and has a lightweight core. + +## Features + +* **Algebra** + * Mathematical operation entities like rings, spaces and fields with (**TODO** add example to wiki) + * Basic linear algebra operations (summs products, etc) backed by `Space` API. + * [In progress] advanced linear algebra operations like matrix inversions. +* **Array-like structures** Full support of numpy-like ndarray including mixed ariphmetic operations and function operations +on arrays and numbers just like it works in python (with benefit of static type checking). + +## Multi-platform support +KMath is developed as a multi-platform library, which means that most of interfaces are declared in common module. +Implementation is also done in common module wherever it is possible. In some cases features are delegated to +platform even if they could be done in common module because of platform performance optimization. + +## Performance +The calculation performance is one of major goals of KMath in the future, but in some cases it is not possible to achieve +both performance and flexibility. We expect to firstly focus on creating convenient universal API and then work on +increasing performance for specific cases. We expect the worst KMath performance still be better than natural python, +but worse than optimized native/scipy (mostly due to boxing operations on primitive numbers). The best performance +of optimized parts should be better than scipy. + +## Releases +The project is currently in pre-release stage. Work builds could be obtained with +[![](https://jitpack.io/v/altavir/kmath.svg)](https://jitpack.io/#altavir/kmath). + +## Contributing +The project requires a lot of additional work. Please fill free to contribute in any way and propose new features. \ No newline at end of file -- 2.34.1