From eb4c9a4b94f722d3dd478da047d19fc0003941b9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 18 Jul 2018 12:51:21 +0300 Subject: [PATCH] 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