Deprecated Vectors. Working on LUP optimization (not working yet)

This commit is contained in:
Alexander Nozik 2019-04-15 20:50:50 +03:00
parent 98bb72a6a0
commit f1b1010c4d
18 changed files with 347 additions and 247 deletions

View File

@ -1,10 +1,13 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import koma.matrix.ejml.EJMLMatrixFactory import koma.matrix.ejml.EJMLMatrixFactory
import scientifik.kmath.operations.RealField
import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.Matrix
import kotlin.contracts.ExperimentalContracts
import kotlin.random.Random import kotlin.random.Random
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ExperimentalContracts
fun main() { fun main() {
val random = Random(12224) val random = Random(12224)
val dim = 100 val dim = 100
@ -32,10 +35,8 @@ fun main() {
//commons-math //commons-math
val cmContext = CMLUPSolver
val commonsTime = measureTimeMillis { val commonsTime = measureTimeMillis {
cmContext.run { CMMatrixContext.run {
val cm = matrix.toCM() //avoid overhead on conversion val cm = matrix.toCM() //avoid overhead on conversion
repeat(n) { repeat(n) {
val res = inverse(cm) val res = inverse(cm)
@ -48,10 +49,8 @@ fun main() {
//koma-ejml //koma-ejml
val komaContext = KomaMatrixContext(EJMLMatrixFactory())
val komaTime = measureTimeMillis { val komaTime = measureTimeMillis {
komaContext.run { KomaMatrixContext(EJMLMatrixFactory(), RealField).run {
val km = matrix.toKoma() //avoid overhead on conversion val km = matrix.toKoma() //avoid overhead on conversion
repeat(n) { repeat(n) {
val res = inverse(km) val res = inverse(km)

View File

@ -1,6 +1,7 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import koma.matrix.ejml.EJMLMatrixFactory import koma.matrix.ejml.EJMLMatrixFactory
import scientifik.kmath.operations.RealField
import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.Matrix
import kotlin.random.Random import kotlin.random.Random
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -27,7 +28,7 @@ fun main() {
} }
KomaMatrixContext(EJMLMatrixFactory()).run { KomaMatrixContext(EJMLMatrixFactory(), RealField).run {
val komaMatrix1 = matrix1.toKoma() val komaMatrix1 = matrix1.toKoma()
val komaMatrix2 = matrix2.toKoma() val komaMatrix2 = matrix2.toKoma()

View File

@ -23,7 +23,7 @@ fun main() {
val complexTime = measureTimeMillis { val complexTime = measureTimeMillis {
complexField.run { complexField.run {
var res: ComplexNDElement = one var res = one
repeat(n) { repeat(n) {
res += 1.0 res += 1.0
} }

View File

@ -1,9 +1,6 @@
import com.moowork.gradle.node.NodeExtension import com.moowork.gradle.node.NodeExtension
import com.moowork.gradle.node.npm.NpmTask import com.moowork.gradle.node.npm.NpmTask
import com.moowork.gradle.node.task.NodeTask import com.moowork.gradle.node.task.NodeTask
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript { buildscript {
val kotlinVersion: String by rootProject.extra("1.3.21") val kotlinVersion: String by rootProject.extra("1.3.21")
@ -194,6 +191,8 @@ subprojects {
sourceSets.all { sourceSets.all {
languageSettings.progressiveMode = true languageSettings.progressiveMode = true
languageSettings.enableLanguageFeature("InlineClasses") languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("ExperimentalContracts")
//languageSettings.enableLanguageFeature("Contracts")
} }
} }

View File

@ -1 +1,19 @@
**TODO** ## Basic linear algebra layout
Kmath support for linear algebra organized in a context-oriented way. Meaning that operations are in most cases declared
in context classes, and are not the members of classes that store data. This allows more flexible approach to maintain multiple
back-ends. The new operations added as extensions to contexts instead of being member functions of data structures.
Two major contexts used for linear algebra and hyper-geometry:
* `VectorSpace` forms a mathematical space on top of array-like structure (`Buffer` and its typealias `Point` used for geometry).
* `MatrixContext` forms a space-like context for 2d-structures. It does not store matrix size and therefore does not implement
`Space` interface (it is not possible to create zero element without knowing the matrix size).
## Vector spaces
## Matrix operations
## Back-end overview

View File

@ -27,7 +27,7 @@ fun Matrix<Double>.toCM(): CMMatrix = if (this is CMMatrix) {
CMMatrix(Array2DRowRealMatrix(array)) CMMatrix(Array2DRowRealMatrix(array))
} }
fun RealMatrix.toMatrix() = CMMatrix(this) fun RealMatrix.asMatrix() = CMMatrix(this)
class CMVector(val origin: RealVector) : Point<Double> { class CMVector(val origin: RealVector) : Point<Double> {
override val size: Int get() = origin.dimension override val size: Int get() = origin.dimension
@ -47,7 +47,6 @@ fun Point<Double>.toCM(): CMVector = if (this is CMVector) {
fun RealVector.toPoint() = CMVector(this) fun RealVector.toPoint() = CMVector(this)
object CMMatrixContext : MatrixContext<Double> { object CMMatrixContext : MatrixContext<Double> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Double): CMMatrix { 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) } } val array = Array(rows) { i -> DoubleArray(columns) { j -> initializer(i, j) } }
return CMMatrix(Array2DRowRealMatrix(array)) return CMMatrix(Array2DRowRealMatrix(array))
@ -59,35 +58,19 @@ object CMMatrixContext : MatrixContext<Double> {
override fun Matrix<Double>.dot(vector: Point<Double>): CMVector = override fun Matrix<Double>.dot(vector: Point<Double>): CMVector =
CMVector(this.toCM().origin.preMultiply(vector.toCM().origin)) CMVector(this.toCM().origin.preMultiply(vector.toCM().origin))
override fun Matrix<Double>.unaryMinus(): CMMatrix = override fun Matrix<Double>.unaryMinus(): CMMatrix =
produce(rowNum, colNum) { i, j -> -get(i, j) } produce(rowNum, colNum) { i, j -> -get(i, j) }
override fun Matrix<Double>.plus(b: Matrix<Double>) = override fun add(a: Matrix<Double>, b: Matrix<Double>) =
CMMatrix(this.toCM().origin.multiply(b.toCM().origin)) CMMatrix(a.toCM().origin.multiply(b.toCM().origin))
override fun Matrix<Double>.minus(b: Matrix<Double>) = override fun Matrix<Double>.minus(b: Matrix<Double>) =
CMMatrix(this.toCM().origin.subtract(b.toCM().origin)) CMMatrix(this.toCM().origin.subtract(b.toCM().origin))
override fun Matrix<Double>.times(value: Double) = override fun multiply(a: Matrix<Double>, k: Number) =
CMMatrix(this.toCM().origin.scalarMultiply(value.toDouble())) CMMatrix(a.toCM().origin.scalarMultiply(k.toDouble()))
}
object CMLUPSolver: LinearSolver<Double>{ override fun Matrix<Double>.times(value: Double): Matrix<Double> = produce(rowNum,colNum){i,j-> get(i,j)*value}
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 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()
}
} }
operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin)) operator fun CMMatrix.plus(other: CMMatrix): CMMatrix = CMMatrix(this.origin.add(other.origin))

View File

@ -0,0 +1,39 @@
package scientifik.kmath.linear
import org.apache.commons.math3.linear.*
import scientifik.kmath.structures.Matrix
enum class CMDecomposition {
LUP,
QR,
RRQR,
EIGEN,
CHOLESKY
}
fun CMMatrixContext.solver(a: Matrix<Double>, decomposition: CMDecomposition = CMDecomposition.LUP) =
when (decomposition) {
CMDecomposition.LUP -> LUDecomposition(a.toCM().origin).solver
CMDecomposition.RRQR -> RRQRDecomposition(a.toCM().origin).solver
CMDecomposition.QR -> QRDecomposition(a.toCM().origin).solver
CMDecomposition.EIGEN -> EigenDecomposition(a.toCM().origin).solver
CMDecomposition.CHOLESKY -> CholeskyDecomposition(a.toCM().origin).solver
}
fun CMMatrixContext.solve(
a: Matrix<Double>,
b: Matrix<Double>,
decomposition: CMDecomposition = CMDecomposition.LUP
) = solver(a, decomposition).solve(b.toCM().origin).asMatrix()
fun CMMatrixContext.solve(
a: Matrix<Double>,
b: Point<Double>,
decomposition: CMDecomposition = CMDecomposition.LUP
) = solver(a, decomposition).solve(b.toCM().origin).toPoint()
fun CMMatrixContext.inverse(
a: Matrix<Double>,
decomposition: CMDecomposition = CMDecomposition.LUP
) = solver(a, decomposition).inverse.asMatrix()

View File

@ -1,6 +1,5 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Ring
import scientifik.kmath.structures.* import scientifik.kmath.structures.*

View File

@ -7,109 +7,6 @@ import scientifik.kmath.structures.*
import scientifik.kmath.structures.Buffer.Companion.boxing import scientifik.kmath.structures.Buffer.Companion.boxing
import kotlin.math.sqrt import kotlin.math.sqrt
/**
* Basic operations on matrices. Operates on [Matrix]
*/
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, Buffer.Companion::auto)
/**
* 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 point compatible with matrix space
*/
fun point(size: Int, initializer: (Int) -> T): Point<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 ->
val row = rows[i]
val column = other.columns[j]
with(elementContext) {
sum(row.asSequence().zip(column.asSequence(), ::multiply))
}
}
}
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 ->
val row = rows[i]
with(elementContext) {
sum(row.asSequence().zip(vector.asSequence(), ::multiply))
}
}
}
override operator fun Matrix<T>.unaryMinus() =
produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } }
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] } }
}
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] } }
}
operator fun Matrix<T>.times(number: Number): Matrix<T> =
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * number } }
operator fun Number.times(matrix: FeaturedMatrix<T>): Matrix<T> = matrix * this
override fun Matrix<T>.times(value: T): Matrix<T> =
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } }
}
/** /**
* A 2d structure plus optional matrix-specific features * A 2d structure plus optional matrix-specific features
*/ */

View File

@ -4,6 +4,9 @@ import scientifik.kmath.operations.Field
import scientifik.kmath.operations.RealField import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Ring import scientifik.kmath.operations.Ring
import scientifik.kmath.structures.* import scientifik.kmath.structures.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -63,7 +66,12 @@ class LUPDecomposition<T : Any>(
} }
open class BufferAccessor<T : Any>(val type: KClass<T>, val field: Field<T>, val rowNum: Int, val colNum: Int) { internal open class BufferAccessor<T : Any>(
val type: KClass<T>,
val field: Field<T>,
val rowNum: Int,
val colNum: Int
) {
open operator fun MutableBuffer<T>.get(i: Int, j: Int) = get(i + colNum * j) open operator fun MutableBuffer<T>.get(i: Int, j: Int) = get(i + colNum * j)
open operator fun MutableBuffer<T>.set(i: Int, j: Int, value: T) { open operator fun MutableBuffer<T>.set(i: Int, j: Int, value: T) {
set(i + colNum * j, value) set(i + colNum * j, value)
@ -102,7 +110,8 @@ open class BufferAccessor<T : Any>(val type: KClass<T>, val field: Field<T>, val
/** /**
* Specialized LU operations for Doubles * Specialized LU operations for Doubles
*/ */
class RealBufferAccessor(rowNum: Int, colNum: Int) : BufferAccessor<Double>(Double::class, RealField, rowNum, colNum) { private class RealBufferAccessor(rowNum: Int, colNum: Int) :
BufferAccessor<Double>(Double::class, RealField, rowNum, colNum) {
override fun MutableBuffer<Double>.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j] override fun MutableBuffer<Double>.get(i: Int, j: Int) = (this as DoubleBuffer).array[i + colNum * j]
override fun MutableBuffer<Double>.set(i: Int, j: Int, value: Double) { override fun MutableBuffer<Double>.set(i: Int, j: Int, value: Double) {
(this as DoubleBuffer).array[i + colNum * j] = value (this as DoubleBuffer).array[i + colNum * j] = value
@ -125,24 +134,33 @@ class RealBufferAccessor(rowNum: Int, colNum: Int) : BufferAccessor<Double>(Doub
} }
} }
fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.buildAccessor( @ExperimentalContracts
type:KClass<T>, private inline fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.withAccessor(
type: KClass<T>,
rowNum: Int, rowNum: Int,
colNum: Int colNum: Int,
): BufferAccessor<T> { block: BufferAccessor<T>.() -> Unit
return if (elementContext == RealField) { ) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
if (elementContext == RealField) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
RealBufferAccessor(rowNum, colNum) as BufferAccessor<T> RealBufferAccessor(rowNum, colNum) as BufferAccessor<T>
} else { } else {
BufferAccessor(type, elementContext, rowNum, colNum) BufferAccessor(type, elementContext, rowNum, colNum)
} }.run(block)
} }
fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.abs(value: T) = private fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.abs(value: T) =
if (value > elementContext.zero) value else with(elementContext) { -value } if (value > elementContext.zero) value else with(elementContext) { -value }
fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.lupDecompose( /**
* Create a lup decomposition of generic matrix
*/
@ExperimentalContracts
fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.lup(
type: KClass<T>, type: KClass<T>,
matrix: Matrix<T>, matrix: Matrix<T>,
checkSingular: (T) -> Boolean checkSingular: (T) -> Boolean
@ -155,7 +173,7 @@ fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.lupDecompose(
val m = matrix.colNum val m = matrix.colNum
val pivot = IntArray(matrix.rowNum) val pivot = IntArray(matrix.rowNum)
buildAccessor(type, matrix.rowNum, matrix.colNum).run { withAccessor(type, matrix.rowNum, matrix.colNum) {
val lu = create(matrix) val lu = create(matrix)
@ -170,21 +188,12 @@ fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.lupDecompose(
// upper // upper
for (row in 0 until col) { for (row in 0 until col) {
// var sum = lu[row, col]
// for (i in 0 until row) {
// sum -= lu[row, i] * lu[i, col]
// }
val sum = lu.innerProduct(row, col, row) val sum = lu.innerProduct(row, col, row)
lu[row, col] = field.run { lu[row, col] - sum } lu[row, col] = field.run { lu[row, col] - sum }
} }
// lower // lower
val max = (col until m).maxBy { row -> val max = (col until m).maxBy { row ->
// var sum = lu[row, col]
// for (i in 0 until col) {
// sum -= lu[row, i] * lu[i, col]
// }
// lu[row, col] = sum
val sum = lu.innerProduct(row, col, col) val sum = lu.innerProduct(row, col, col)
lu[row, col] = field.run { lu[row, col] - sum } lu[row, col] = field.run { lu[row, col] - sum }
abs(sum) abs(sum)
@ -214,14 +223,17 @@ fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.lupDecompose(
//lu[row, col] = lu[row, col] / luDiag //lu[row, col] = lu[row, col] / luDiag
} }
} }
return scientifik.kmath.linear.LUPDecomposition(elementContext, lu.collect(), pivot, even) return LUPDecomposition(elementContext, lu.collect(), pivot, even)
} }
} }
@ExperimentalContracts
fun GenericMatrixContext<Double, RealField>.lup(matrix: Matrix<Double>) = lup(Double::class, matrix) { it < 1e-11 }
/** /**
* Solve a linear equation **a*x = b** * Solve a linear equation **a*x = b**
*/ */
@ExperimentalContracts
fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.solve( fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.solve(
type: KClass<T>, type: KClass<T>,
a: Matrix<T>, a: Matrix<T>,
@ -233,9 +245,9 @@ fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.solve(
} }
// Use existing decomposition if it is provided by matrix // Use existing decomposition if it is provided by matrix
val decomposition = a.getFeature() ?: lupDecompose(type, a, checkSingular) val decomposition = a.getFeature() ?: lup(type, a, checkSingular)
buildAccessor(type, a.rowNum, a.colNum).run { withAccessor(type, a.rowNum, a.colNum) {
val lu = create(decomposition.lu) val lu = create(decomposition.lu)
@ -271,14 +283,19 @@ fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.solve(
return produce(a.rowNum, a.colNum) { i, j -> bp[i, j] } return produce(a.rowNum, a.colNum) { i, j -> bp[i, j] }
} }
} }
@ExperimentalContracts
fun GenericMatrixContext<Double, RealField>.solve(a: Matrix<Double>, b: Matrix<Double>) =
solve(Double::class, a, b) { it < 1e-11 }
@ExperimentalContracts
inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.inverse( inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F>.inverse(
matrix: Matrix<T>, matrix: Matrix<T>,
noinline checkSingular: (T) -> Boolean noinline checkSingular: (T) -> Boolean
) = ) =
solve(T::class, matrix, one(matrix.rowNum, matrix.colNum), checkSingular) solve(T::class, matrix, one(matrix.rowNum, matrix.colNum), checkSingular)
@ExperimentalContracts
fun GenericMatrixContext<Double, RealField>.inverse(matrix: Matrix<Double>) = fun GenericMatrixContext<Double, RealField>.inverse(matrix: Matrix<Double>) =
inverse(matrix) { it < 1e-11 } inverse(matrix) { it < 1e-11 }

View File

@ -13,7 +13,7 @@ import scientifik.kmath.structures.asSequence
*/ */
interface LinearSolver<T : Any> { interface LinearSolver<T : Any> {
fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T> fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T>
fun solve(a: Matrix<T>, b: Point<T>): Point<T> = solve(a, b.toMatrix()).asPoint() fun solve(a: Matrix<T>, b: Point<T>): Point<T> = solve(a, b.asMatrix()).asPoint()
fun inverse(a: Matrix<T>): Matrix<T> fun inverse(a: Matrix<T>): Matrix<T>
} }
@ -43,4 +43,4 @@ fun <T : Any> Matrix<T>.asPoint(): Point<T> =
error("Can't convert matrix with more than one column to vector") error("Can't convert matrix with more than one column to vector")
} }
fun <T : Any> Point<T>.toMatrix() = VirtualMatrix(size, 1) { i, _ -> get(i) } fun <T : Any> Point<T>.asMatrix() = VirtualMatrix(size, 1) { i, _ -> get(i) }

View File

@ -0,0 +1,106 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Ring
import scientifik.kmath.operations.SpaceOperations
import scientifik.kmath.operations.sum
import scientifik.kmath.structures.Buffer
import scientifik.kmath.structures.BufferFactory
import scientifik.kmath.structures.Matrix
import scientifik.kmath.structures.asSequence
/**
* Basic operations on matrices. Operates on [Matrix]
*/
interface MatrixContext<T : Any> : SpaceOperations<Matrix<T>> {
/**
* 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>.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, Buffer.Companion::auto)
/**
* A structured matrix with custom buffer
*/
fun <T : Any, R : Ring<T>> buffered(
ring: R,
bufferFactory: BufferFactory<T> = Buffer.Companion::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 point compatible with matrix space
*/
fun point(size: Int, initializer: (Int) -> T): Point<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 ->
val row = rows[i]
val column = other.columns[j]
with(elementContext) {
sum(row.asSequence().zip(column.asSequence(), ::multiply))
}
}
}
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 ->
val row = rows[i]
with(elementContext) {
sum(row.asSequence().zip(vector.asSequence(), ::multiply))
}
}
}
override operator fun Matrix<T>.unaryMinus() =
produce(rowNum, colNum) { i, j -> elementContext.run { -get(i, j) } }
override fun add(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
if (a.rowNum != b.rowNum || a.colNum != b.colNum) error("Matrix operation dimension mismatch. [${a.rowNum},${a.colNum}] + [${b.rowNum},${b.colNum}]")
return produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a.get(i, j) + b[i, j] } }
}
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] } }
}
override fun multiply(a: Matrix<T>, k: Number): Matrix<T> =
produce(a.rowNum, a.colNum) { i, j -> elementContext.run { a.get(i, j) * k } }
operator fun Number.times(matrix: FeaturedMatrix<T>): Matrix<T> = matrix * this
override fun Matrix<T>.times(value: T): Matrix<T> =
produce(rowNum, colNum) { i, j -> elementContext.run { get(i, j) * value } }
}

View File

@ -4,68 +4,23 @@ import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Space import scientifik.kmath.operations.Space
import scientifik.kmath.operations.SpaceElement import scientifik.kmath.operations.SpaceElement
import scientifik.kmath.structures.Buffer import scientifik.kmath.structures.Buffer
import scientifik.kmath.structures.BufferFactory
import scientifik.kmath.structures.asSequence import scientifik.kmath.structures.asSequence
import kotlin.jvm.JvmName
typealias Point<T> = Buffer<T> typealias Point<T> = Buffer<T>
/**
* A linear space for vectors.
* Could be used on any point-like structure
*/
interface VectorSpace<T : Any, S : Space<T>> : Space<Point<T>> {
val size: Int fun <T : Any, S : Space<T>> BufferVectorSpace<T, S>.produceElement(initializer: (Int) -> T): Vector<T, S> =
BufferVector(this, produce(initializer))
val space: S
fun produce(initializer: (Int) -> T): Point<T>
/**
* Produce a space-element of this vector space for expressions
*/
fun produceElement(initializer: (Int) -> T): Vector<T, S>
override val zero: Point<T> get() = produce { space.zero }
override fun add(a: Point<T>, b: Point<T>): Point<T> = produce { with(space) { a[it] + b[it] } }
override fun multiply(a: Point<T>, k: Number): Point<T> = produce { with(space) { a[it] * k } }
//TODO add basis
companion object {
private val realSpaceCache = HashMap<Int, BufferVectorSpace<Double, RealField>>()
/**
* Non-boxing double vector space
*/
fun real(size: Int): BufferVectorSpace<Double, RealField> {
return realSpaceCache.getOrPut(size) { BufferVectorSpace(size, RealField, Buffer.Companion::auto) }
}
/**
* A structured vector space with custom buffer
*/
fun <T : Any, S : Space<T>> buffered(
size: Int,
space: S,
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing
): VectorSpace<T, S> = BufferVectorSpace(size, space, bufferFactory)
/**
* Automatic buffered vector, unboxed if it is possible
*/
inline fun <reified T : Any, S : Space<T>> smart(size: Int, space: S): VectorSpace<T, S> =
buffered(size, space, Buffer.Companion::auto)
}
}
@JvmName("produceRealElement")
fun BufferVectorSpace<Double, RealField>.produceElement(initializer: (Int) -> Double): Vector<Double, RealField> =
BufferVector(this, produce(initializer))
/** /**
* A point coupled to the linear space * A point coupled to the linear space
*/ */
@Deprecated("Use VectorContext instead")
interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, Vector<T, S>, VectorSpace<T, S>>, Point<T> { interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, Vector<T, S>, VectorSpace<T, S>>, Point<T> {
override val size: Int get() = context.size override val size: Int get() = context.size
@ -90,16 +45,7 @@ interface Vector<T : Any, S : Space<T>> : SpaceElement<Point<T>, Vector<T, S>, V
} }
} }
data class BufferVectorSpace<T : Any, S : Space<T>>( @Deprecated("Use VectorContext instead")
override val size: Int,
override val space: S,
val bufferFactory: BufferFactory<T>
) : VectorSpace<T, S> {
override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer)
override fun produceElement(initializer: (Int) -> T): Vector<T, S> = BufferVector(this, produce(initializer))
}
data class BufferVector<T : Any, S : Space<T>>(override val context: VectorSpace<T, S>, val buffer: Buffer<T>) : data class BufferVector<T : Any, S : Space<T>>(override val context: VectorSpace<T, S>, val buffer: Buffer<T>) :
Vector<T, S> { Vector<T, S> {

View File

@ -0,0 +1,75 @@
package scientifik.kmath.linear
import scientifik.kmath.operations.RealField
import scientifik.kmath.operations.Space
import scientifik.kmath.structures.Buffer
import scientifik.kmath.structures.BufferFactory
/**
* A linear space for vectors.
* Could be used on any point-like structure
*/
interface VectorSpace<T : Any, S : Space<T>> : Space<Point<T>> {
val size: Int
val space: S
fun produce(initializer: (Int) -> T): Point<T>
/**
* Produce a space-element of this vector space for expressions
*/
//fun produceElement(initializer: (Int) -> T): Vector<T, S>
override val zero: Point<T> get() = produce { space.zero }
override fun add(a: Point<T>, b: Point<T>): Point<T> = produce { with(space) { a[it] + b[it] } }
override fun multiply(a: Point<T>, k: Number): Point<T> = produce { with(space) { a[it] * k } }
//TODO add basis
companion object {
private val realSpaceCache = HashMap<Int, BufferVectorSpace<Double, RealField>>()
/**
* Non-boxing double vector space
*/
fun real(size: Int): BufferVectorSpace<Double, RealField> {
return realSpaceCache.getOrPut(size) {
BufferVectorSpace(
size,
RealField,
Buffer.Companion::auto
)
}
}
/**
* A structured vector space with custom buffer
*/
fun <T : Any, S : Space<T>> buffered(
size: Int,
space: S,
bufferFactory: BufferFactory<T> = Buffer.Companion::boxing
) = BufferVectorSpace(size, space, bufferFactory)
/**
* Automatic buffered vector, unboxed if it is possible
*/
inline fun <reified T : Any, S : Space<T>> auto(size: Int, space: S): VectorSpace<T, S> =
buffered(size, space, Buffer.Companion::auto)
}
}
class BufferVectorSpace<T : Any, S : Space<T>>(
override val size: Int,
override val space: S,
val bufferFactory: BufferFactory<T>
) : VectorSpace<T, S> {
override fun produce(initializer: (Int) -> T) = bufferFactory(size, initializer)
//override fun produceElement(initializer: (Int) -> T): Vector<T, S> = BufferVector(this, produce(initializer))
}

View File

@ -73,6 +73,7 @@ interface NDSpace<T, S : Space<T>, N : NDStructure<T>> : Space<N>, NDAlgebra<T,
*/ */
override fun multiply(a: N, k: Number): N = map(a) { multiply(it, k) } override fun multiply(a: N, k: Number): N = map(a) { multiply(it, k) }
//TODO move to extensions after KEEP-176
operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) } operator fun N.plus(arg: T) = map(this) { value -> add(arg, value) }
operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) } operator fun N.minus(arg: T) = map(this) { value -> add(arg, -value) }
@ -90,6 +91,7 @@ interface NDRing<T, R : Ring<T>, N : NDStructure<T>> : Ring<N>, NDSpace<T, R, N>
*/ */
override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) } override fun multiply(a: N, b: N): N = combine(a, b) { aValue, bValue -> multiply(aValue, bValue) }
//TODO move to extensions after KEEP-176
operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) } operator fun N.times(arg: T) = map(this) { value -> multiply(arg, value) }
operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) } operator fun T.times(arg: N) = map(arg) { value -> multiply(this@times, value) }
} }
@ -109,6 +111,7 @@ interface NDField<T, F : Field<T>, N : NDStructure<T>> : Field<N>, NDRing<T, F,
*/ */
override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) } override fun divide(a: N, b: N): N = combine(a, b) { aValue, bValue -> divide(aValue, bValue) }
//TODO move to extensions after KEEP-176
operator fun N.div(arg: T) = map(this) { value -> divide(arg, value) } operator fun N.div(arg: T) = map(this) { value -> divide(arg, value) }
operator fun T.div(arg: N) = map(arg) { divide(it, this@div) } operator fun T.div(arg: N) = map(arg) { divide(it, this@div) }

View File

@ -1,5 +1,6 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import scientifik.kmath.structures.Matrix
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -16,7 +17,7 @@ class MatrixTest {
@Test @Test
fun testVectorToMatrix() { fun testVectorToMatrix() {
val vector = Vector.real(5) { it.toDouble() } val vector = Vector.real(5) { it.toDouble() }
val matrix = vector.toMatrix() val matrix = vector.asMatrix()
assertEquals(4.0, matrix[4, 0]) assertEquals(4.0, matrix[4, 0])
} }
@ -33,8 +34,8 @@ class MatrixTest {
val vector1 = Vector.real(5) { it.toDouble() } val vector1 = Vector.real(5) { it.toDouble() }
val vector2 = Vector.real(5) { 5 - it.toDouble() } val vector2 = Vector.real(5) { 5 - it.toDouble() }
val matrix1 = vector1.toMatrix() val matrix1 = vector1.asMatrix()
val matrix2 = vector2.toMatrix().transpose() val matrix2 = vector2.asMatrix().transpose()
val product = MatrixContext.real.run { matrix1 dot matrix2 } val product = MatrixContext.real.run { matrix1 dot matrix2 }
@ -44,7 +45,7 @@ class MatrixTest {
@Test @Test
fun testBuilder() { fun testBuilder() {
val matrix = FeaturedMatrix.build<Double>(2, 3)( val matrix = Matrix.build<Double>(2, 3)(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 2.0 0.0, 1.0, 2.0
) )

View File

@ -1,25 +1,28 @@
package scientifik.kmath.linear package scientifik.kmath.linear
import scientifik.kmath.structures.Matrix
import kotlin.contracts.ExperimentalContracts
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ExperimentalContracts
class RealLUSolverTest { class RealLUSolverTest {
@Test @Test
fun testInvertOne() { fun testInvertOne() {
val matrix = MatrixContext.real.one(2, 2) val matrix = MatrixContext.real.one(2, 2)
val inverted = LUSolver.real.inverse(matrix) val inverted = MatrixContext.real.inverse(matrix)
assertEquals(matrix, inverted) assertEquals(matrix, inverted)
} }
@Test @Test
fun testInvert() { fun testInvert() {
val matrix = FeaturedMatrix.square( val matrix = Matrix.square(
3.0, 1.0, 3.0, 1.0,
1.0, 3.0 1.0, 3.0
) )
val decomposed = LUSolver.real.decompose(matrix) val decomposition = MatrixContext.real.lup(matrix)
val decomposition = decomposed.getFeature<LUPDecomposition<Double>>()!!
//Check determinant //Check determinant
assertEquals(8.0, decomposition.determinant) assertEquals(8.0, decomposition.determinant)
@ -29,9 +32,9 @@ class RealLUSolverTest {
assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u) assertEquals(decomposition.p dot matrix, decomposition.l dot decomposition.u)
} }
val inverted = LUSolver.real.inverse(decomposed) val inverted = MatrixContext.real.inverse(matrix)
val expected = FeaturedMatrix.square( val expected = Matrix.square(
0.375, -0.125, 0.375, -0.125,
-0.125, 0.375 -0.125, 0.375
) )

View File

@ -2,10 +2,14 @@ package scientifik.kmath.linear
import koma.extensions.fill import koma.extensions.fill
import koma.matrix.MatrixFactory import koma.matrix.MatrixFactory
import scientifik.kmath.operations.Space
import scientifik.kmath.structures.Matrix import scientifik.kmath.structures.Matrix
class KomaMatrixContext<T : Any>(val factory: MatrixFactory<koma.matrix.Matrix<T>>) : MatrixContext<T>, class KomaMatrixContext<T : Any>(
LinearSolver<T> { private val factory: MatrixFactory<koma.matrix.Matrix<T>>,
private val space: Space<T>
) :
MatrixContext<T> {
override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T) = override fun produce(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> T) =
KomaMatrix(factory.zeros(rows, columns).fill(initializer)) KomaMatrix(factory.zeros(rows, columns).fill(initializer))
@ -32,28 +36,38 @@ class KomaMatrixContext<T : Any>(val factory: MatrixFactory<koma.matrix.Matrix<T
override fun Matrix<T>.unaryMinus() = override fun Matrix<T>.unaryMinus() =
KomaMatrix(this.toKoma().origin.unaryMinus()) KomaMatrix(this.toKoma().origin.unaryMinus())
override fun Matrix<T>.plus(b: Matrix<T>) = override fun add(a: Matrix<T>, b: Matrix<T>) =
KomaMatrix(this.toKoma().origin + b.toKoma().origin) KomaMatrix(a.toKoma().origin + b.toKoma().origin)
override fun Matrix<T>.minus(b: Matrix<T>) = override fun Matrix<T>.minus(b: Matrix<T>) =
KomaMatrix(this.toKoma().origin - b.toKoma().origin) KomaMatrix(this.toKoma().origin - b.toKoma().origin)
override fun multiply(a: Matrix<T>, k: Number): Matrix<T> =
produce(a.rowNum, a.colNum) { i, j -> space.run { a[i, j] * k } }
override fun Matrix<T>.times(value: T) = override fun Matrix<T>.times(value: T) =
KomaMatrix(this.toKoma().origin * value) KomaMatrix(this.toKoma().origin * value)
companion object {
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())
} }
fun <T : Any> KomaMatrixContext<T>.solve(a: Matrix<T>, b: Matrix<T>) =
KomaMatrix(a.toKoma().origin.solve(b.toKoma().origin))
fun <T : Any> KomaMatrixContext<T>.solve(a: Matrix<T>, b: Point<T>) =
KomaVector(a.toKoma().origin.solve(b.toKoma().origin))
fun <T : Any> KomaMatrixContext<T>.inverse(a: Matrix<T>) =
KomaMatrix(a.toKoma().origin.inv())
class KomaMatrix<T : Any>(val origin: koma.matrix.Matrix<T>, features: Set<MatrixFeature>? = null) : FeaturedMatrix<T> { class KomaMatrix<T : Any>(val origin: koma.matrix.Matrix<T>, features: Set<MatrixFeature>? = null) : FeaturedMatrix<T> {
override val rowNum: Int get() = origin.numRows() override val rowNum: Int get() = origin.numRows()
override val colNum: Int get() = origin.numCols() override val colNum: Int get() = origin.numCols()
override val shape: IntArray get() = intArrayOf(origin.numRows(),origin.numCols()) override val shape: IntArray get() = intArrayOf(origin.numRows(), origin.numCols())
override val features: Set<MatrixFeature> = features ?: setOf( override val features: Set<MatrixFeature> = features ?: setOf(
object : DeterminantFeature<T> { object : DeterminantFeature<T> {