Koma integration + multiple fixes to Matrix API

This commit is contained in:
Alexander Nozik 2019-01-21 15:05:44 +03:00
parent a9e06c261a
commit baae18b223
10 changed files with 159 additions and 95 deletions

View File

@ -5,9 +5,10 @@ plugins {
}
dependencies {
api project(":kmath-core")
api project(":kmath-coroutines")
api project(":kmath-commons")
implementation project(":kmath-core")
implementation project(":kmath-coroutines")
implementation project(":kmath-commons")
implementation project(":kmath-koma")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
//jmh project(':kmath-core')
}

View File

@ -29,7 +29,7 @@ fun main() {
//commons-math
val cmSolver = CMSolver
val cmSolver = CMMatrixContext
val commonsTime = measureTimeMillis {
val cm = matrix.toCM() //avoid overhead on conversion

View File

@ -3,8 +3,6 @@ package scientifik.kmath.linear
import org.apache.commons.math3.linear.*
import org.apache.commons.math3.linear.RealMatrix
import org.apache.commons.math3.linear.RealVector
import scientifik.kmath.operations.RealField
import scientifik.kmath.structures.DoubleBuffer
inline class CMMatrix(val origin: RealMatrix) : Matrix<Double> {
override val rowNum: Int get() = origin.rowDimension
@ -40,44 +38,51 @@ fun Point<Double>.toCM(): CMVector = if (this is CMVector) {
CMVector(ArrayRealVector(array))
}
fun RealVector.toPoint() = DoubleBuffer(this.toArray())
fun RealVector.toPoint() = CMVector(this)
object CMMatrixContext : MatrixContext<Double, RealField> {
override val elementContext: RealField get() = RealField
object CMMatrixContext : MatrixContext<Double>, LinearSolver<Double> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): Matrix<Double> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix {
val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } }
return CMMatrix(Array2DRowRealMatrix(array))
}
override fun point(size: Int, initializer: (Int) -> Double): Point<Double> {
val array = DoubleArray(size, initializer)
return CMVector(ArrayRealVector(array))
override fun solve(a: Matrix<Double>, b: Matrix<Double>): CMMatrix {
val decomposition = LUDecomposition(a.toCM().origin)
return decomposition.solver.solve(b.toCM().origin).toMatrix()
}
override fun Matrix<Double>.dot(other: Matrix<Double>): Matrix<Double> = CMMatrix(this.toCM().origin.multiply(other.toCM().origin))
override fun solve(a: Matrix<Double>, b: Point<Double>): CMVector {
val decomposition = LUDecomposition(a.toCM().origin)
return decomposition.solver.solve(b.toCM().origin).toPoint()
}
override fun inverse(a: Matrix<Double>): CMMatrix {
val decomposition = LUDecomposition(a.toCM().origin)
return decomposition.solver.inverse.toMatrix()
}
override fun Matrix<Double>.dot(other: Matrix<Double>) =
CMMatrix(this.toCM().origin.multiply(other.toCM().origin))
override fun Matrix<Double>.dot(vector: Point<Double>): CMVector =
CMVector(this.toCM().origin.preMultiply(vector.toCM().origin))
override fun Matrix<Double>.unaryMinus(): CMMatrix =
produce(rowNum, colNum) { i, j -> -get(i, j) }
override fun Matrix<Double>.plus(b: Matrix<Double>) =
CMMatrix(this.toCM().origin.multiply(b.toCM().origin))
override fun Matrix<Double>.minus(b: Matrix<Double>) =
CMMatrix(this.toCM().origin.subtract(b.toCM().origin))
override fun Matrix<Double>.times(value: Double) =
CMMatrix(this.toCM().origin.scalarMultiply(value.toDouble()))
}
operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin))
operator fun CMMatrix.minus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.subtract(other.origin))
infix fun CMMatrix.dot(other: CMMatrix): CMMatrix = CMMatrix(this.origin.multiply(other.origin))
object CMSolver : LinearSolver<Double, RealField> {
override fun solve(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> {
val decomposition = LUDecomposition(a.toCM().origin)
return decomposition.solver.solve(b.toCM().origin).toMatrix()
}
override fun solve(a: Matrix<Double>, b: Point<Double>): Point<Double> {
val decomposition = LUDecomposition(a.toCM().origin)
return decomposition.solver.solve(b.toCM().origin).toPoint()
}
override fun inverse(a: Matrix<Double>): Matrix<Double> {
val decomposition = LUDecomposition(a.toCM().origin)
return decomposition.solver.inverse.toMatrix()
}
}

View File

@ -9,7 +9,7 @@ import scientifik.kmath.structures.*
class BufferMatrixContext<T : Any, R : Ring<T>>(
override val elementContext: R,
private val bufferFactory: BufferFactory<T>
) : MatrixContext<T, R> {
) : GenericMatrixContext<T, R> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): BufferMatrix<T> {
val buffer = bufferFactory(rows * columns) { offset -> initializer(offset / columns, offset % columns) }

View File

@ -65,7 +65,7 @@ class LUPDecomposition<T : Comparable<T>>(
class LUSolver<T : Comparable<T>, F : Field<T>>(
val context: MatrixContext<T, F>,
val context: GenericMatrixContext<T, F>,
val bufferFactory: MutableBufferFactory<T> = ::boxing,
val singularityCheck: (T) -> Boolean
) : LinearSolver<T, F> {
@ -201,6 +201,6 @@ class LUSolver<T : Comparable<T>, F : Field<T>>(
override fun inverse(a: Matrix<T>): Matrix<T> = solve(a, context.one(a.rowNum, a.colNum))
companion object {
val real = LUSolver(MatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 }
val real = LUSolver(GenericMatrixContext.real, MutableBuffer.Companion::auto) { it < 1e-11 }
}
}

View File

@ -3,7 +3,6 @@ package scientifik.kmath.linear
import scientifik.kmath.operations.Field
import scientifik.kmath.operations.Norm
import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Ring
import scientifik.kmath.structures.VirtualBuffer
import scientifik.kmath.structures.asSequence
@ -11,7 +10,7 @@ import scientifik.kmath.structures.asSequence
/**
* A group of methods to resolve equation A dot X = B, where A and B are matrices or vectors
*/
interface LinearSolver<T : Any, R : Ring<T>> {
interface LinearSolver<T : Any> {
fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T>
fun solve(a: Matrix<T>, b: Point<T>): Point<T> = solve(a, b.toMatrix()).toVector()
fun inverse(a: Matrix<T>): Matrix<T>

View File

@ -8,28 +8,61 @@ import scientifik.kmath.structures.Buffer.Companion.boxing
import kotlin.math.sqrt
interface MatrixContext<T : Any, R : Ring<T>> {
interface MatrixContext<T : Any> {
/**
* Produce a matrix with this context and given dimensions
*/
fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T>
infix fun Matrix<T>.dot(other: Matrix<T>): Matrix<T>
infix fun Matrix<T>.dot(vector: Point<T>): Point<T>
operator fun Matrix<T>.unaryMinus(): Matrix<T>
operator fun Matrix<T>.plus(b: Matrix<T>): Matrix<T>
operator fun Matrix<T>.minus(b: Matrix<T>): Matrix<T>
operator fun Matrix<T>.times(value: T): Matrix<T>
operator fun T.times(m: Matrix<T>): Matrix<T> = m * this
companion object {
/**
* Non-boxing double matrix
*/
val real = BufferMatrixContext(RealField, DoubleBufferFactory)
/**
* A structured matrix with custom buffer
*/
fun <T : Any, R : Ring<T>> buffered(
ring: R,
bufferFactory: BufferFactory<T> = ::boxing
): GenericMatrixContext<T, R> =
BufferMatrixContext(ring, bufferFactory)
/**
* Automatic buffered matrix, unboxed if it is possible
*/
inline fun <reified T : Any, R : Ring<T>> auto(ring: R): GenericMatrixContext<T, R> =
buffered(ring, Buffer.Companion::auto)
}
}
interface GenericMatrixContext<T : Any, R : Ring<T>> : MatrixContext<T> {
/**
* The ring context for matrix elements
*/
val elementContext: R
/**
* Produce a matrix with this context and given dimensions
*/
fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T>
/**
* Produce a point compatible with matrix space
*/
fun point(size: Int, initializer: (Int) -> T): Point<T>
fun scale(a: Matrix<T>, k: Number): Matrix<T> {
//TODO create a special wrapper class for scaled matrices
return produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a[i, j] * k } }
}
infix fun Matrix<T>.dot(other: Matrix<T>): Matrix<T> {
override infix fun Matrix<T>.dot(other: Matrix<T>): Matrix<T> {
//TODO add typed error
if (this.colNum != other.rowNum) error("Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})")
return produce(rowNum, other.colNum) { i, j ->
@ -41,7 +74,7 @@ interface MatrixContext<T : Any, R : Ring<T>> {
}
}
infix fun Matrix<T>.dot(vector: Point<T>): Point<T> {
override infix fun Matrix<T>.dot(vector: Point<T>): Point<T> {
//TODO add typed error
if (this.colNum != vector.size) error("Matrix dot vector operation dimension mismatch: ($rowNum, $colNum) x (${vector.size})")
return point(rowNum) { i ->
@ -52,15 +85,15 @@ interface MatrixContext<T : Any, R : Ring<T>> {
}
}
operator fun Matrix<T>.unaryMinus() =
override operator fun Matrix<T>.unaryMinus() =
produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } }
operator fun Matrix<T>.plus(b: Matrix<T>): Matrix<T> {
override operator fun Matrix<T>.plus(b: Matrix<T>): Matrix<T> {
if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] + [${b.rowNum},${b.colNum}]")
return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } }
}
operator fun Matrix<T>.minus(b: Matrix<T>): Matrix<T> {
override operator fun Matrix<T>.minus(b: Matrix<T>): Matrix<T> {
if (rowNum != b.rowNum || colNum != b.colNum) error("Matrix operation dimension mismatch. [$rowNum,$colNum] - [${b.rowNum},${b.colNum}]")
return produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) + b[i, j] } }
}
@ -68,27 +101,10 @@ interface MatrixContext<T : Any, R : Ring<T>> {
operator fun Matrix<T>.times(number: Number): Matrix<T> =
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } }
operator fun Number.times(m: Matrix<T>): Matrix<T> = m * this
operator fun Number.times(matrix: Matrix<T>): Matrix<T> = matrix * this
companion object {
/**
* Non-boxing double matrix
*/
val real = BufferMatrixContext(RealField, DoubleBufferFactory)
/**
* A structured matrix with custom buffer
*/
fun <T : Any, R : Ring<T>> buffered(ring: R, bufferFactory: BufferFactory<T> = ::boxing): MatrixContext<T, R> =
BufferMatrixContext(ring, bufferFactory)
/**
* Automatic buffered matrix, unboxed if it is possible
*/
inline fun <reified T : Any, R : Ring<T>> auto(ring: R): MatrixContext<T, R> =
buffered(ring, Buffer.Companion::auto)
}
override fun Matrix<T>.times(value: T): Matrix<T> =
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } }
}
/**
@ -173,7 +189,7 @@ inline fun <reified T : Any> Matrix<*>.getFeature(): T? = features.filterIsInsta
/**
* Diagonal matrix of ones. The matrix is virtual no actual matrix is created
*/
fun <T : Any, R : Ring<T>> MatrixContext<T, R>.one(rows: Int, columns: Int): Matrix<T> =
fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R>.one(rows: Int, columns: Int): Matrix<T> =
VirtualMatrix<T>(rows, columns) { i, j ->
if (i == j) elementContext.one else elementContext.zero
}
@ -182,7 +198,7 @@ fun <T : Any, R : Ring<T>> MatrixContext<T, R>.one(rows: Int, columns: Int): Mat
/**
* A virtual matrix of zeroes
*/
fun <T : Any, R : Ring<T>> MatrixContext<T, R>.zero(rows: Int, columns: Int): Matrix<T> =
fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R>.zero(rows: Int, columns: Int): Matrix<T> =
VirtualMatrix<T>(rows, columns) { i, j ->
elementContext.zero
}

View File

@ -22,7 +22,7 @@ class MatrixTest {
@Test
fun testTranspose() {
val matrix = MatrixContext.real.one(3, 3)
val matrix = GenericMatrixContext.real.one(3, 3)
val transposed = matrix.transpose()
assertEquals((matrix as BufferMatrix).buffer, (transposed as BufferMatrix).buffer)
assertEquals(matrix, transposed)
@ -36,7 +36,7 @@ class MatrixTest {
val matrix1 = vector1.toMatrix()
val matrix2 = vector2.toMatrix().transpose()
val product = MatrixContext.real.run { matrix1 dot matrix2 }
val product = GenericMatrixContext.real.run { matrix1 dot matrix2 }
assertEquals(5.0, product[1, 0])

View File

@ -6,7 +6,7 @@ import kotlin.test.assertEquals
class RealLUSolverTest {
@Test
fun testInvertOne() {
val matrix = MatrixContext.real.one(2, 2)
val matrix = GenericMatrixContext.real.one(2, 2)
val inverted = LUSolver.real.inverse(matrix)
assertEquals(matrix, inverted)
}
@ -25,7 +25,7 @@ class RealLUSolverTest {
assertEquals(8.0, decomposition.determinant)
//Check decomposition
with(MatrixContext.real) {
with(GenericMatrixContext.real) {
assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u)
}

View File

@ -1,27 +1,70 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.Ring
import koma.extensions.fill
import koma.matrix.MatrixFactory
class KomaMatrixContext<T: Any, R: Ring<T>> : MatrixContext<T,R> {
override val elementContext: R
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
class KomaMatrixContext<T : Any>(val factory: MatrixFactory<koma.matrix.Matrix<T>>) : MatrixContext<T>,
LinearSolver<T> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T): Matrix<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T) =
KomaMatrix(factory.zeros(rows, columns).fill(initializer))
fun Matrix<T>.toKoma(): KomaMatrix<T> = if (this is KomaMatrix) {
this
} else {
produce(rowNum, colNum) { i, j -> get(i, j) }
}
override fun point(size: Int, initializer: (Int) -> T): Point<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
fun Point<T>.toKoma(): KomaVector<T> = if (this is KomaVector) {
this
} else {
KomaVector(factory.zeros(size, 1).fill { i, _ -> get(i) })
}
override fun Matrix<T>.dot(other: Matrix<T>) =
KomaMatrix(this.toKoma().origin * other.toKoma().origin)
override fun Matrix<T>.dot(vector: Point<T>) =
KomaVector(this.toKoma().origin * vector.toKoma().origin)
override fun Matrix<T>.unaryMinus() =
KomaMatrix(this.toKoma().origin.unaryMinus())
override fun Matrix<T>.plus(b: Matrix<T>) =
KomaMatrix(this.toKoma().origin + b.toKoma().origin)
override fun Matrix<T>.minus(b: Matrix<T>) =
KomaMatrix(this.toKoma().origin - b.toKoma().origin)
override fun Matrix<T>.times(value: T) =
KomaMatrix(this.toKoma().origin * value)
override fun solve(a: Matrix<T>, b: Matrix<T>) =
KomaMatrix(a.toKoma().origin.solve(b.toKoma().origin))
override fun inverse(a: Matrix<T>) =
KomaMatrix(a.toKoma().origin.inv())
}
inline class KomaMatrix<T : Any>(val matrix: koma.matrix.Matrix<T>) : Matrix<T> {
override val rowNum: Int get() = matrix.numRows()
override val colNum: Int get() = matrix.numCols()
inline class KomaMatrix<T : Any>(val origin: koma.matrix.Matrix<T>) : Matrix<T> {
override val rowNum: Int get() = origin.numRows()
override val colNum: Int get() = origin.numCols()
override val features: Set<MatrixFeature> get() = emptySet()
@Suppress("OVERRIDE_BY_INLINE")
override inline fun get(i: Int, j: Int): T = matrix.getGeneric(i, j)
override fun get(i: Int, j: Int): T = origin.getGeneric(i, j)
}
class KomaVector<T : Any> internal constructor(val origin: koma.matrix.Matrix<T>) : Point<T> {
init {
if (origin.numCols() != 1) error("Only single column matrices are allowed")
}
override val size: Int get() = origin.numRows()
override fun get(index: Int): T = origin.getGeneric(index)
override fun iterator(): Iterator<T> = origin.toIterable().iterator()
}