Refactor/linear algebra #241

Merged
altavir merged 11 commits from refactor/linear-algebra into dev 2021-03-14 21:49:47 +03:00
7 changed files with 74 additions and 80 deletions
Showing only changes of commit d5ba816b7d - Show all commits

View File

@ -22,7 +22,7 @@ public typealias Vector<T> = Point<T>
* @param T the type of items in the matrices.
* @param M the type of operated matrices.
*/
public interface LinearSpace<T : Any, A : Ring<T>> {
public interface LinearSpace<T : Any, out A : Ring<T>> {
public val elementAlgebra: A
/**

View File

@ -228,7 +228,7 @@ public inline fun <reified T : Comparable<T>> LinearSpace<T, Field<T>>.inverseWi
@OptIn(UnstableKMathAPI::class)
public fun RealLinearSpace.solveWithLup(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> {
public fun LinearSpace<Double, RealField>.solveWithLup(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> {
// Use existing decomposition if it is provided by matrix
val bufferFactory: MutableBufferFactory<Double> = MutableBuffer.Companion::real
val decomposition: LupDecomposition<Double> = a.getFeature() ?: lup(bufferFactory, a) { it < 1e-11 }
@ -238,5 +238,5 @@ public fun RealLinearSpace.solveWithLup(a: Matrix<Double>, b: Matrix<Double>): M
/**
* Inverses a square matrix using LUP decomposition. Non square matrix will throw a error.
*/
public fun RealLinearSpace.inverseWithLup(matrix: Matrix<Double>): Matrix<Double> =
public fun LinearSpace<Double, RealField>.inverseWithLup(matrix: Matrix<Double>): Matrix<Double> =
solveWithLup(matrix, one(matrix.rowNum, matrix.colNum))

View File

@ -2,7 +2,6 @@ package space.kscience.kmath.linear
import space.kscience.kmath.nd.NDStructure
import space.kscience.kmath.nd.as2D
import space.kscience.kmath.operations.invoke
import kotlin.test.Test
import kotlin.test.assertEquals
@ -17,7 +16,7 @@ class MatrixTest {
@Test
fun testBuilder() {
val matrix = Matrix.build(2, 3)(
val matrix = LinearSpace.real.matrix(2, 3)(
1.0, 0.0, 0.0,
0.0, 1.0, 2.0
)

View File

@ -2,7 +2,8 @@ package space.kscience.kmath.dimensions
import space.kscience.kmath.linear.*
import space.kscience.kmath.nd.Structure2D
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.operations.RealField
import space.kscience.kmath.operations.Ring
/**
* A matrix with compile-time controlled dimension
@ -77,7 +78,7 @@ public inline class DPointWrapper<T, D : Dimension>(public val point: Point<T>)
/**
* Basic operations on dimension-safe matrices. Operates on [Matrix]
*/
public inline class DMatrixContext<T : Any>(public val context: LinearSpace<T, Matrix<T>>) {
public inline class DMatrixContext<T : Any, out A : Ring<T>>(public val context: LinearSpace<T, A>) {
public inline fun <reified R : Dimension, reified C : Dimension> Matrix<T>.coerce(): DMatrix<T, R, C> {
require(rowNum == Dimension.dim<R>().toInt()) {
"Row number mismatch: expected ${Dimension.dim<R>()} but found $rowNum"
@ -93,13 +94,15 @@ public inline class DMatrixContext<T : Any>(public val context: LinearSpace<T, M
/**
* Produce a matrix with this context and given dimensions
*/
public inline fun <reified R : Dimension, reified C : Dimension> produce(noinline initializer: (i: Int, j: Int) -> T): DMatrix<T, R, C> {
public inline fun <reified R : Dimension, reified C : Dimension> produce(
noinline initializer: A.(i: Int, j: Int) -> T
): DMatrix<T, R, C> {
val rows = Dimension.dim<R>()
val cols = Dimension.dim<C>()
return context.buildMatrix(rows.toInt(), cols.toInt(), initializer).coerce<R, C>()
}
public inline fun <reified D : Dimension> point(noinline initializer: (Int) -> T): DPoint<T, D> {
public inline fun <reified D : Dimension> point(noinline initializer: A.(Int) -> T): DPoint<T, D> {
val size = Dimension.dim<D>()
return DPoint.coerceUnsafe(
@ -112,31 +115,31 @@ public inline class DMatrixContext<T : Any>(public val context: LinearSpace<T, M
public inline infix fun <reified R1 : Dimension, reified C1 : Dimension, reified C2 : Dimension> DMatrix<T, R1, C1>.dot(
other: DMatrix<T, C1, C2>,
): DMatrix<T, R1, C2> = context { this@dot dot other }.coerce()
): DMatrix<T, R1, C2> = context.run { this@dot dot other }.coerce()
public inline infix fun <reified R : Dimension, reified C : Dimension> DMatrix<T, R, C>.dot(vector: DPoint<T, C>): DPoint<T, R> =
DPoint.coerceUnsafe(context { this@dot dot vector })
DPoint.coerceUnsafe(context.run { this@dot dot vector })
public inline operator fun <reified R : Dimension, reified C : Dimension> DMatrix<T, R, C>.times(value: T): DMatrix<T, R, C> =
context { this@times.times(value) }.coerce()
context.run { this@times.times(value) }.coerce()
public inline operator fun <reified R : Dimension, reified C : Dimension> T.times(m: DMatrix<T, R, C>): DMatrix<T, R, C> =
m * this
public inline operator fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.plus(other: DMatrix<T, C, R>): DMatrix<T, C, R> =
context { this@plus + other }.coerce()
context.run { this@plus + other }.coerce()
public inline operator fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.minus(other: DMatrix<T, C, R>): DMatrix<T, C, R> =
context { this@minus + other }.coerce()
context.run { this@minus + other }.coerce()
public inline operator fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.unaryMinus(): DMatrix<T, C, R> =
context { this@unaryMinus.unaryMinus() }.coerce()
context.run { this@unaryMinus.unaryMinus() }.coerce()
public inline fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.transpose(): DMatrix<T, R, C> =
context { (this@transpose as Matrix<T>).transpose() }.coerce()
context.run { (this@transpose as Matrix<T>).transpose() }.coerce()
public companion object {
public val real: DMatrixContext<Double> = DMatrixContext(LinearSpace.real)
public val real: DMatrixContext<Double, RealField> = DMatrixContext(LinearSpace.real)
}
}
@ -144,11 +147,11 @@ public inline class DMatrixContext<T : Any>(public val context: LinearSpace<T, M
/**
* A square unit matrix
*/
public inline fun <reified D : Dimension> DMatrixContext<Double>.one(): DMatrix<Double, D, D> = produce { i, j ->
public inline fun <reified D : Dimension> DMatrixContext<Double, RealField>.one(): DMatrix<Double, D, D> = produce { i, j ->
if (i == j) 1.0 else 0.0
}
public inline fun <reified R : Dimension, reified C : Dimension> DMatrixContext<Double>.zero(): DMatrix<Double, R, C> =
public inline fun <reified R : Dimension, reified C : Dimension> DMatrixContext<Double, RealField>.zero(): DMatrix<Double, R, C> =
produce { _, _ ->
0.0
}

View File

@ -44,7 +44,7 @@ public object EjmlLinearSpace : LinearSpace<Double, RealField>, ScaleOperations<
override fun buildVector(size: Int, initializer: RealField.(Int) -> Double): Vector<Double> =
EjmlVector(SimpleMatrix(size, 1).also {
(0 until it.numRows()).forEach { row -> it[row, 0] = initializer(row) }
(0 until it.numRows()).forEach { row -> it[row, 0] = RealField.initializer(row) }
})
private fun SimpleMatrix.wrapMatrix() = EjmlMatrix(this)
@ -59,43 +59,34 @@ public object EjmlLinearSpace : LinearSpace<Double, RealField>, ScaleOperations<
EjmlVector(toEjml().origin.mult(vector.toEjml().origin))
public override operator fun Matrix<Double>.minus(other: Matrix<Double>): EjmlMatrix =
EjmlMatrix(toEjml().origin - other.toEjml().origin)
(toEjml().origin - other.toEjml().origin).wrapMatrix()
public override fun scale(a: Matrix<Double>, value: Double): EjmlMatrix =
a.toEjml().origin.scale(value).wrapMatrix()
public override operator fun Matrix<Double>.times(value: Double): EjmlMatrix =
EjmlMatrix(toEjml().origin.scale(value))
toEjml().origin.scale(value).wrapMatrix()
override fun Vector<Double>.unaryMinus(): EjmlVector =
toEjml().origin.negative().wrapVector()
override fun Matrix<Double>.plus(other: Matrix<Double>): Matrix<Double> {
TODO("Not yet implemented")
}
override fun Matrix<Double>.plus(other: Matrix<Double>): EjmlMatrix =
(toEjml().origin + other.toEjml().origin).wrapMatrix()
override fun Vector<Double>.plus(other: Vector<Double>): Vector<Double> {
TODO("Not yet implemented")
}
override fun Vector<Double>.plus(other: Vector<Double>): EjmlVector =
(toEjml().origin + other.toEjml().origin).wrapVector()
override fun Vector<Double>.minus(other: Vector<Double>): Vector<Double> {
TODO("Not yet implemented")
}
override fun Vector<Double>.minus(other: Vector<Double>): EjmlVector =
(toEjml().origin - other.toEjml().origin).wrapVector()
override fun Double.times(m: Matrix<Double>): Matrix<Double> {
TODO("Not yet implemented")
}
override fun Double.times(m: Matrix<Double>): EjmlMatrix =
m.toEjml().origin.scale(this).wrapMatrix()
override fun Vector<Double>.times(value: Double): Vector<Double> {
TODO("Not yet implemented")
}
override fun Vector<Double>.times(value: Double): EjmlVector =
toEjml().origin.scale(value).wrapVector()
override fun Double.times(v: Vector<Double>): Vector<Double> {
TODO("Not yet implemented")
}
public override fun add(a: Matrix<Double>, b: Matrix<Double>): EjmlMatrix =
EjmlMatrix(a.toEjml().origin + b.toEjml().origin)
override fun Double.times(v: Vector<Double>): EjmlVector =
v.toEjml().origin.scale(this).wrapVector()
}
/**

View File

@ -2,6 +2,7 @@ package space.kscience.kmath.real
import space.kscience.kmath.linear.*
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.RealField
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.RealBuffer
import space.kscience.kmath.structures.asIterable
@ -21,11 +22,11 @@ import kotlin.math.pow
public typealias RealMatrix = Matrix<Double>
public fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double): RealMatrix =
public fun realMatrix(rowNum: Int, colNum: Int, initializer: RealField.(i: Int, j: Int) -> Double): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum, initializer)
public fun Array<DoubleArray>.toMatrix(): RealMatrix {
return LinearSpace.real.buildMatrix(size, this[0].size) { row, col -> this[row][col] }
return LinearSpace.real.buildMatrix(size, this[0].size) { row, col -> this@toMatrix[row][col] }
}
public fun Sequence<DoubleArray>.toMatrix(): RealMatrix = toList().let {
@ -43,37 +44,37 @@ public fun RealMatrix.repeatStackVertical(n: Int): RealMatrix =
public operator fun RealMatrix.times(double: Double): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col ->
this[row, col] * double
get(row, col) * double
}
public operator fun RealMatrix.plus(double: Double): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col ->
this[row, col] + double
get(row, col) + double
}
public operator fun RealMatrix.minus(double: Double): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col ->
this[row, col] - double
get(row, col) - double
}
public operator fun RealMatrix.div(double: Double): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col ->
this[row, col] / double
get(row, col) / double
}
public operator fun Double.times(matrix: RealMatrix): RealMatrix =
LinearSpace.real.buildMatrix(matrix.rowNum, matrix.colNum) { row, col ->
this * matrix[row, col]
this@times * matrix[row, col]
}
public operator fun Double.plus(matrix: RealMatrix): RealMatrix =
LinearSpace.real.buildMatrix(matrix.rowNum, matrix.colNum) { row, col ->
this + matrix[row, col]
this@plus + matrix[row, col]
}
public operator fun Double.minus(matrix: RealMatrix): RealMatrix =
LinearSpace.real.buildMatrix(matrix.rowNum, matrix.colNum) { row, col ->
this - matrix[row, col]
this@minus - matrix[row, col]
}
// TODO: does this operation make sense? Should it be 'this/matrix[row, col]'?
@ -87,13 +88,13 @@ public operator fun Double.minus(matrix: RealMatrix): RealMatrix =
@UnstableKMathAPI
public operator fun RealMatrix.times(other: RealMatrix): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> this[row, col] * other[row, col] }
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> this@times[row, col] * other[row, col] }
public operator fun RealMatrix.plus(other: RealMatrix): RealMatrix =
LinearSpace.real.add(this, other)
LinearSpace.real.run { this@plus + other }
public operator fun RealMatrix.minus(other: RealMatrix): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> this[row, col] - other[row, col] }
LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> this@minus[row, col] - other[row, col] }
/*
* Operations on columns
@ -102,14 +103,14 @@ public operator fun RealMatrix.minus(other: RealMatrix): RealMatrix =
public inline fun RealMatrix.appendColumn(crossinline mapper: (Buffer<Double>) -> Double): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, colNum + 1) { row, col ->
if (col < colNum)
this[row, col]
get(row, col)
else
mapper(rows[row])
}
public fun RealMatrix.extractColumns(columnRange: IntRange): RealMatrix =
LinearSpace.real.buildMatrix(rowNum, columnRange.count()) { row, col ->
this[row, columnRange.first + col]
this@extractColumns[row, columnRange.first + col]
}
public fun RealMatrix.extractColumn(columnIndex: Int): RealMatrix =

View File

@ -1,31 +1,31 @@
package space.kscience.kmath.real
import space.kscience.kmath.linear.BufferMatrix
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.RealBuffer
import space.kscience.kmath.linear.LinearSpace
import space.kscience.kmath.nd.Matrix
/**
* Optimized dot product for real matrices
*/
public infix fun BufferMatrix<Double>.dot(other: BufferMatrix<Double>): BufferMatrix<Double> {
require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" }
val resultArray = DoubleArray(this.rowNum * other.colNum)
//convert to array to insure there is no memory indirection
fun Buffer<Double>.unsafeArray() = if (this is RealBuffer)
this.array
else
DoubleArray(size) { get(it) }
val a = this.buffer.unsafeArray()
val b = other.buffer.unsafeArray()
for (i in (0 until rowNum))
for (j in (0 until other.colNum))
for (k in (0 until colNum))
resultArray[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j]
val buffer = RealBuffer(resultArray)
return BufferMatrix(rowNum, other.colNum, buffer)
public infix fun Matrix<Double>.dot(other: Matrix<Double>): Matrix<Double> = LinearSpace.real.run{
this@dot dot other
// require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" }
// val resultArray = DoubleArray(this.rowNum * other.colNum)
//
// //convert to array to insure there is no memory indirection
// fun Buffer<Double>.unsafeArray() = if (this is RealBuffer)
// this.array
// else
// DoubleArray(size) { get(it) }
//
// val a = this.buffer.unsafeArray()
// val b = other.buffer.unsafeArray()
//
// for (i in (0 until rowNum))
// for (j in (0 until other.colNum))
// for (k in (0 until colNum))
// resultArray[i * other.colNum + j] += a[i * colNum + k] * b[k * other.colNum + j]
//
// val buffer = RealBuffer(resultArray)
// return BufferMatrix(rowNum, other.colNum, buffer)
}