Merge branch 'dev' into commandertvis/hermitian-conjugate

# Conflicts:
#	kmath-core/src/commonMain/kotlin/kscience/kmath/linear/FeaturedMatrix.kt
#	kmath-core/src/commonMain/kotlin/kscience/kmath/linear/VirtualMatrix.kt
This commit is contained in:
Iaroslav Postovalov 2021-01-24 02:02:20 +07:00
commit 35a1b91329
No known key found for this signature in database
GPG Key ID: 46E15E4A31B3BCD7
46 changed files with 632 additions and 575 deletions

View File

@ -4,27 +4,28 @@
### Added
- `fun` annotation for SAM interfaces in library
- Explicit `public` visibility for all public APIs
- Better trigonometric and hyperbolic functions for `AutoDiffField` (https://github.com/mipt-npm/kmath/pull/140).
- Better trigonometric and hyperbolic functions for `AutoDiffField` (https://github.com/mipt-npm/kmath/pull/140)
- Automatic README generation for features (#139)
- Native support for `memory`, `core` and `dimensions`
- `kmath-ejml` to supply EJML SimpleMatrix wrapper (https://github.com/mipt-npm/kmath/pull/136).
- `kmath-ejml` to supply EJML SimpleMatrix wrapper (https://github.com/mipt-npm/kmath/pull/136)
- A separate `Symbol` entity, which is used for global unbound symbol.
- A `Symbol` indexing scope.
- Basic optimization API for Commons-math.
- Chi squared optimization for array-like data in CM
- `Fitting` utility object in prob/stat
- ND4J support module submitting `NDStructure` and `NDAlgebra` over `INDArray`.
- Coroutine-deterministic Monte-Carlo scope with a random number generator.
- Some minor utilities to `kmath-for-real`.
- ND4J support module submitting `NDStructure` and `NDAlgebra` over `INDArray`
- Coroutine-deterministic Monte-Carlo scope with a random number generator
- Some minor utilities to `kmath-for-real`
- Generic operation result parameter to `MatrixContext`
- New `MatrixFeature` interfaces for matrix decompositions
### Changed
- Package changed from `scientifik` to `kscience.kmath`.
- Gradle version: 6.6 -> 6.7.1
- Package changed from `scientifik` to `kscience.kmath`
- Gradle version: 6.6 -> 6.8
- Minor exceptions refactor (throwing `IllegalArgumentException` by argument checks instead of `IllegalStateException`)
- `Polynomial` secondary constructor made function.
- Kotlin version: 1.3.72 -> 1.4.20
- `kmath-ast` doesn't depend on heavy `kotlin-reflect` library.
- `Polynomial` secondary constructor made function
- Kotlin version: 1.3.72 -> 1.4.21
- `kmath-ast` doesn't depend on heavy `kotlin-reflect` library
- Full autodiff refactoring based on `Symbol`
- `kmath-prob` renamed to `kmath-stat`
- Grid generators moved to `kmath-for-real`
@ -32,6 +33,8 @@
- Optimized dot product for buffer matrices moved to `kmath-for-real`
- EjmlMatrix context is an object
- Matrix LUP `inverse` renamed to `inverseWithLUP`
- `NumericAlgebra` moved outside of regular algebra chain (`Ring` no longer implements it).
- Features moved to NDStructure and became transparent.
### Deprecated

View File

@ -4,7 +4,7 @@ plugins {
id("ru.mipt.npm.project")
}
internal val kmathVersion: String by extra("0.2.0-dev-4")
internal val kmathVersion: String by extra("0.2.0-dev-5")
internal val bintrayRepo: String by extra("kscience")
internal val githubProject: String by extra("kmath")

View File

@ -28,7 +28,7 @@ internal class ExpressionsInterpretersBenchmark {
@Benchmark
fun mstExpression() {
val expr = algebra.mstInField {
symbol("x") * number(2.0) + number(2.0) / symbol("x") - number(16.0)
symbol("x") * 2.0 + 2.0 / symbol("x") - 16.0
}
invokeAndSum(expr)
@ -37,7 +37,7 @@ internal class ExpressionsInterpretersBenchmark {
@Benchmark
fun asmExpression() {
val expr = algebra.mstInField {
symbol("x") * number(2.0) + number(2.0) / symbol("x") - number(16.0)
symbol("x") * 2.0 + 2.0 / symbol("x") - 16.0
}.compile()
invokeAndSum(expr)

View File

@ -2,9 +2,8 @@ package kscience.kmath.benchmarks
import kotlinx.benchmark.Benchmark
import kscience.kmath.commons.linear.CMMatrixContext
import kscience.kmath.commons.linear.toCM
import kscience.kmath.ejml.EjmlMatrixContext
import kscience.kmath.ejml.toEjml
import kscience.kmath.linear.BufferMatrixContext
import kscience.kmath.linear.RealMatrixContext
import kscience.kmath.linear.real
@ -26,11 +25,11 @@ class DotBenchmark {
val matrix1 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
val matrix2 = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
val cmMatrix1 = matrix1.toCM()
val cmMatrix2 = matrix2.toCM()
val cmMatrix1 = CMMatrixContext { matrix1.toCM() }
val cmMatrix2 = CMMatrixContext { matrix2.toCM() }
val ejmlMatrix1 = matrix1.toEjml()
val ejmlMatrix2 = matrix2.toEjml()
val ejmlMatrix1 = EjmlMatrixContext { matrix1.toEjml() }
val ejmlMatrix2 = EjmlMatrixContext { matrix2.toEjml() }
}
@Benchmark
@ -49,9 +48,10 @@ class DotBenchmark {
@Benchmark
fun ejmlMultiplicationwithConversion() {
EjmlMatrixContext {
val ejmlMatrix1 = matrix1.toEjml()
val ejmlMatrix2 = matrix2.toEjml()
EjmlMatrixContext {
ejmlMatrix1 dot ejmlMatrix2
}
}

View File

@ -5,10 +5,8 @@ import kotlinx.benchmark.Benchmark
import kscience.kmath.commons.linear.CMMatrixContext
import kscience.kmath.commons.linear.CMMatrixContext.dot
import kscience.kmath.commons.linear.inverse
import kscience.kmath.commons.linear.toCM
import kscience.kmath.ejml.EjmlMatrixContext
import kscience.kmath.ejml.inverse
import kscience.kmath.ejml.toEjml
import kscience.kmath.operations.invoke
import kscience.kmath.structures.Matrix
import org.openjdk.jmh.annotations.Scope
@ -35,16 +33,14 @@ class LinearAlgebraBenchmark {
@Benchmark
fun cmLUPInversion() {
CMMatrixContext {
val cm = matrix.toCM() //avoid overhead on conversion
inverse(cm)
inverse(matrix)
}
}
@Benchmark
fun ejmlInverse() {
EjmlMatrixContext {
val km = matrix.toEjml() //avoid overhead on conversion
inverse(km)
inverse(matrix)
}
}
}

View File

@ -3,7 +3,6 @@ package kscience.kmath.commons.prob
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kscience.kmath.chains.BlockingRealChain
import kscience.kmath.stat.*
import org.apache.commons.rng.sampling.distribution.ZigguratNormalizedGaussianSampler
import org.apache.commons.rng.simple.RandomSource
@ -13,7 +12,7 @@ import java.time.Instant
private fun runChain(): Duration {
val generator = RandomGenerator.fromSource(RandomSource.MT, 123L)
val normal = Distribution.normal(NormalSamplerMethod.Ziggurat)
val chain = normal.sample(generator) as BlockingRealChain
val chain = normal.sample(generator)
val startTime = Instant.now()
var sum = 0.0

View File

@ -11,7 +11,7 @@ fun main() {
val n = 1000
val realField = NDField.real(dim, dim)
val complexField = NDField.complex(dim, dim)
val complexField: ComplexNDField = NDField.complex(dim, dim)
val realTime = measureTimeMillis {
realField {

View File

@ -33,7 +33,7 @@ fun main() {
measureAndPrint("Automatic field addition") {
autoField {
var res: NDBuffer<Double> = one
repeat(n) { res += number(1.0) }
repeat(n) { res += 1.0 }
}
}
@ -52,7 +52,7 @@ fun main() {
measureAndPrint("Nd4j specialized addition") {
nd4jField {
var res = one
repeat(n) { res += 1.0 as Number }
repeat(n) { res += 1.0 }
}
}
@ -73,7 +73,7 @@ fun main() {
genericField {
var res: NDBuffer<Double> = one
repeat(n) {
res += one // couldn't avoid using `one` due to resolution ambiguity }
res += 1.0 // couldn't avoid using `one` due to resolution ambiguity }
}
}
}

View File

@ -1,5 +1,6 @@
package kscience.kmath.ast
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
/**
@ -25,8 +26,11 @@ public object MstSpace : Space<MST>, NumericAlgebra<MST> {
public override fun number(value: Number): MST.Numeric = MstAlgebra.number(value)
public override fun symbol(value: String): MST.Symbolic = MstAlgebra.symbol(value)
public override fun add(a: MST, b: MST): MST.Binary = binaryOperationFunction(SpaceOperations.PLUS_OPERATION)(a, b)
public override operator fun MST.unaryPlus(): MST.Unary = unaryOperationFunction(SpaceOperations.PLUS_OPERATION)(this)
public override operator fun MST.unaryMinus(): MST.Unary = unaryOperationFunction(SpaceOperations.MINUS_OPERATION)(this)
public override operator fun MST.unaryPlus(): MST.Unary =
unaryOperationFunction(SpaceOperations.PLUS_OPERATION)(this)
public override operator fun MST.unaryMinus(): MST.Unary =
unaryOperationFunction(SpaceOperations.MINUS_OPERATION)(this)
public override operator fun MST.minus(b: MST): MST.Binary =
binaryOperationFunction(SpaceOperations.MINUS_OPERATION)(this, b)
@ -44,7 +48,8 @@ public object MstSpace : Space<MST>, NumericAlgebra<MST> {
/**
* [Ring] over [MST] nodes.
*/
public object MstRing : Ring<MST>, NumericAlgebra<MST> {
@OptIn(UnstableKMathAPI::class)
public object MstRing : Ring<MST>, RingWithNumbers<MST> {
public override val zero: MST.Numeric
get() = MstSpace.zero
@ -54,7 +59,9 @@ public object MstRing : Ring<MST>, NumericAlgebra<MST> {
public override fun symbol(value: String): MST.Symbolic = MstSpace.symbol(value)
public override fun add(a: MST, b: MST): MST.Binary = MstSpace.add(a, b)
public override fun multiply(a: MST, k: Number): MST.Binary = MstSpace.multiply(a, k)
public override fun multiply(a: MST, b: MST): MST.Binary = binaryOperationFunction(RingOperations.TIMES_OPERATION)(a, b)
public override fun multiply(a: MST, b: MST): MST.Binary =
binaryOperationFunction(RingOperations.TIMES_OPERATION)(a, b)
public override operator fun MST.unaryPlus(): MST.Unary = MstSpace { +this@unaryPlus }
public override operator fun MST.unaryMinus(): MST.Unary = MstSpace { -this@unaryMinus }
public override operator fun MST.minus(b: MST): MST.Binary = MstSpace { this@minus - b }
@ -69,7 +76,8 @@ public object MstRing : Ring<MST>, NumericAlgebra<MST> {
/**
* [Field] over [MST] nodes.
*/
public object MstField : Field<MST> {
@OptIn(UnstableKMathAPI::class)
public object MstField : Field<MST>, RingWithNumbers<MST> {
public override val zero: MST.Numeric
get() = MstRing.zero
@ -81,7 +89,9 @@ public object MstField : Field<MST> {
public override fun add(a: MST, b: MST): MST.Binary = MstRing.add(a, b)
public override fun multiply(a: MST, k: Number): MST.Binary = MstRing.multiply(a, k)
public override fun multiply(a: MST, b: MST): MST.Binary = MstRing.multiply(a, b)
public override fun divide(a: MST, b: MST): MST.Binary = binaryOperationFunction(FieldOperations.DIV_OPERATION)(a, b)
public override fun divide(a: MST, b: MST): MST.Binary =
binaryOperationFunction(FieldOperations.DIV_OPERATION)(a, b)
public override operator fun MST.unaryPlus(): MST.Unary = MstRing { +this@unaryPlus }
public override operator fun MST.unaryMinus(): MST.Unary = MstRing { -this@unaryMinus }
public override operator fun MST.minus(b: MST): MST.Binary = MstRing { this@minus - b }
@ -89,13 +99,14 @@ public object MstField : Field<MST> {
public override fun binaryOperationFunction(operation: String): (left: MST, right: MST) -> MST.Binary =
MstRing.binaryOperationFunction(operation)
public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary = MstRing.unaryOperationFunction(operation)
public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary =
MstRing.unaryOperationFunction(operation)
}
/**
* [ExtendedField] over [MST] nodes.
*/
public object MstExtendedField : ExtendedField<MST> {
public object MstExtendedField : ExtendedField<MST>, NumericAlgebra<MST> {
public override val zero: MST.Numeric
get() = MstField.zero
@ -103,6 +114,7 @@ public object MstExtendedField : ExtendedField<MST> {
get() = MstField.one
public override fun symbol(value: String): MST.Symbolic = MstField.symbol(value)
public override fun number(value: Number): MST.Numeric = MstRing.number(value)
public override fun sin(arg: MST): MST.Unary = unaryOperationFunction(TrigonometricOperations.SIN_OPERATION)(arg)
public override fun cos(arg: MST): MST.Unary = unaryOperationFunction(TrigonometricOperations.COS_OPERATION)(arg)
public override fun tan(arg: MST): MST.Unary = unaryOperationFunction(TrigonometricOperations.TAN_OPERATION)(arg)
@ -132,5 +144,6 @@ public object MstExtendedField : ExtendedField<MST> {
public override fun binaryOperationFunction(operation: String): (left: MST, right: MST) -> MST.Binary =
MstField.binaryOperationFunction(operation)
public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary = MstField.unaryOperationFunction(operation)
public override fun unaryOperationFunction(operation: String): (arg: MST) -> MST.Unary =
MstField.unaryOperationFunction(operation)
}

View File

@ -1,7 +1,9 @@
package kscience.kmath.commons.expressions
import kscience.kmath.expressions.*
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.ExtendedField
import kscience.kmath.operations.RingWithNumbers
import org.apache.commons.math3.analysis.differentiation.DerivativeStructure
/**
@ -10,15 +12,18 @@ import org.apache.commons.math3.analysis.differentiation.DerivativeStructure
* @property order The derivation order.
* @property bindings The map of bindings values. All bindings are considered free parameters
*/
@OptIn(UnstableKMathAPI::class)
public class DerivativeStructureField(
public val order: Int,
bindings: Map<Symbol, Double>,
) : ExtendedField<DerivativeStructure>, ExpressionAlgebra<Double, DerivativeStructure> {
) : ExtendedField<DerivativeStructure>, ExpressionAlgebra<Double, DerivativeStructure>, RingWithNumbers<DerivativeStructure> {
public val numberOfVariables: Int = bindings.size
public override val zero: DerivativeStructure by lazy { DerivativeStructure(numberOfVariables, order) }
public override val one: DerivativeStructure by lazy { DerivativeStructure(numberOfVariables, order, 1.0) }
override fun number(value: Number): DerivativeStructure = const(value.toDouble())
/**
* A class that implements both [DerivativeStructure] and a [Symbol]
*/

View File

@ -1,42 +1,28 @@
package kscience.kmath.commons.linear
import kscience.kmath.linear.*
import kscience.kmath.linear.DiagonalFeature
import kscience.kmath.linear.MatrixContext
import kscience.kmath.linear.Point
import kscience.kmath.linear.origin
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.NDStructure
import org.apache.commons.math3.linear.*
import kotlin.reflect.KClass
import kotlin.reflect.cast
public class CMMatrix(public val origin: RealMatrix, features: Set<MatrixFeature>? = null) : FeaturedMatrix<Double> {
public inline class CMMatrix(public val origin: RealMatrix) : Matrix<Double> {
public override val rowNum: Int get() = origin.rowDimension
public override val colNum: Int get() = origin.columnDimension
public override val features: Set<MatrixFeature> = features ?: sequence<MatrixFeature> {
if (origin is DiagonalMatrix) yield(DiagonalFeature)
}.toHashSet()
public override fun suggestFeature(vararg features: MatrixFeature): CMMatrix =
CMMatrix(origin, this.features + features)
@UnstableKMathAPI
override fun <T : Any> getFeature(type: KClass<T>): T? = when (type) {
DiagonalFeature::class -> if (origin is DiagonalMatrix) DiagonalFeature else null
else -> null
}?.let { type.cast(it) }
public override operator fun get(i: Int, j: Int): Double = origin.getEntry(i, j)
public override fun equals(other: Any?): Boolean {
return NDStructure.equals(this, other as? NDStructure<*> ?: return false)
}
public override fun hashCode(): Int {
var result = origin.hashCode()
result = 31 * result + features.hashCode()
return result
}
}
//TODO move inside context
public fun Matrix<Double>.toCM(): CMMatrix = if (this is CMMatrix) {
this
} else {
//TODO add feature analysis
val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } }
CMMatrix(Array2DRowRealMatrix(array))
}
public fun RealMatrix.asMatrix(): CMMatrix = CMMatrix(this)
@ -61,6 +47,16 @@ public object CMMatrixContext : MatrixContext<Double, CMMatrix> {
return CMMatrix(Array2DRowRealMatrix(array))
}
@OptIn(UnstableKMathAPI::class)
public fun Matrix<Double>.toCM(): CMMatrix = when (val matrix = origin) {
is CMMatrix -> matrix
else -> {
//TODO add feature analysis
val array = Array(rowNum) { i -> DoubleArray(colNum) { j -> get(i, j) } }
CMMatrix(Array2DRowRealMatrix(array))
}
}
public override fun Matrix<Double>.dot(other: Matrix<Double>): CMMatrix =
CMMatrix(toCM().origin.multiply(other.toCM().origin))

View File

@ -7,8 +7,9 @@ import kscience.kmath.operations.*
*
* @param algebra The algebra to provide for Expressions built.
*/
public abstract class FunctionalExpressionAlgebra<T, A : Algebra<T>>(public val algebra: A) :
ExpressionAlgebra<T, Expression<T>> {
public abstract class FunctionalExpressionAlgebra<T, A : Algebra<T>>(
public val algebra: A,
) : ExpressionAlgebra<T, Expression<T>> {
/**
* Builds an Expression of constant expression which does not depend on arguments.
*/
@ -42,8 +43,9 @@ public abstract class FunctionalExpressionAlgebra<T, A : Algebra<T>>(public val
/**
* A context class for [Expression] construction for [Space] algebras.
*/
public open class FunctionalExpressionSpace<T, A : Space<T>>(algebra: A) :
FunctionalExpressionAlgebra<T, A>(algebra), Space<Expression<T>> {
public open class FunctionalExpressionSpace<T, A : Space<T>>(
algebra: A,
) : FunctionalExpressionAlgebra<T, A>(algebra), Space<Expression<T>> {
public override val zero: Expression<T> get() = const(algebra.zero)
/**
@ -71,8 +73,9 @@ public open class FunctionalExpressionSpace<T, A : Space<T>>(algebra: A) :
super<FunctionalExpressionAlgebra>.binaryOperationFunction(operation)
}
public open class FunctionalExpressionRing<T, A>(algebra: A) : FunctionalExpressionSpace<T, A>(algebra),
Ring<Expression<T>> where A : Ring<T>, A : NumericAlgebra<T> {
public open class FunctionalExpressionRing<T, A : Ring<T>>(
algebra: A,
) : FunctionalExpressionSpace<T, A>(algebra), Ring<Expression<T>> {
public override val one: Expression<T>
get() = const(algebra.one)
@ -92,9 +95,9 @@ public open class FunctionalExpressionRing<T, A>(algebra: A) : FunctionalExpress
super<FunctionalExpressionSpace>.binaryOperationFunction(operation)
}
public open class FunctionalExpressionField<T, A>(algebra: A) :
FunctionalExpressionRing<T, A>(algebra), Field<Expression<T>>
where A : Field<T>, A : NumericAlgebra<T> {
public open class FunctionalExpressionField<T, A : Field<T>>(
algebra: A,
) : FunctionalExpressionRing<T, A>(algebra), Field<Expression<T>> {
/**
* Builds an Expression of division an expression by another one.
*/
@ -111,9 +114,12 @@ public open class FunctionalExpressionField<T, A>(algebra: A) :
super<FunctionalExpressionRing>.binaryOperationFunction(operation)
}
public open class FunctionalExpressionExtendedField<T, A>(algebra: A) :
FunctionalExpressionField<T, A>(algebra),
ExtendedField<Expression<T>> where A : ExtendedField<T>, A : NumericAlgebra<T> {
public open class FunctionalExpressionExtendedField<T, A : ExtendedField<T>>(
algebra: A,
) : FunctionalExpressionField<T, A>(algebra), ExtendedField<Expression<T>> {
override fun number(value: Number): Expression<T> = const(algebra.number(value))
public override fun sin(arg: Expression<T>): Expression<T> =
unaryOperationFunction(TrigonometricOperations.SIN_OPERATION)(arg)
@ -135,7 +141,8 @@ public open class FunctionalExpressionExtendedField<T, A>(algebra: A) :
public override fun exp(arg: Expression<T>): Expression<T> =
unaryOperationFunction(ExponentialOperations.EXP_OPERATION)(arg)
public override fun ln(arg: Expression<T>): Expression<T> = unaryOperationFunction(ExponentialOperations.LN_OPERATION)(arg)
public override fun ln(arg: Expression<T>): Expression<T> =
unaryOperationFunction(ExponentialOperations.LN_OPERATION)(arg)
public override fun unaryOperationFunction(operation: String): (arg: Expression<T>) -> Expression<T> =
super<FunctionalExpressionField>.unaryOperationFunction(operation)

View File

@ -1,6 +1,7 @@
package kscience.kmath.expressions
import kscience.kmath.linear.Point
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
import kscience.kmath.structures.asBuffer
import kotlin.contracts.InvocationKind
@ -79,10 +80,11 @@ public fun <T : Any, F : Field<T>> F.simpleAutoDiff(
/**
* Represents field in context of which functions can be derived.
*/
@OptIn(UnstableKMathAPI::class)
public open class SimpleAutoDiffField<T : Any, F : Field<T>>(
public val context: F,
bindings: Map<Symbol, T>,
) : Field<AutoDiffValue<T>>, ExpressionAlgebra<T, AutoDiffValue<T>> {
) : Field<AutoDiffValue<T>>, ExpressionAlgebra<T, AutoDiffValue<T>>, RingWithNumbers<AutoDiffValue<T>> {
public override val zero: AutoDiffValue<T>
get() = const(context.zero)

View File

@ -1,10 +1,7 @@
package kscience.kmath.linear
import kscience.kmath.operations.Ring
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.NDStructure
import kscience.kmath.structures.asSequence
import kscience.kmath.structures.*
/**
* Basic implementation of Matrix space based on [NDStructure]
@ -27,8 +24,7 @@ public class BufferMatrix<T : Any>(
public override val rowNum: Int,
public override val colNum: Int,
public val buffer: Buffer<out T>,
public override val features: Set<MatrixFeature> = emptySet(),
) : FeaturedMatrix<T> {
) : Matrix<T> {
init {
require(buffer.size == rowNum * colNum) { "Dimension mismatch for matrix structure" }
@ -36,9 +32,6 @@ public class BufferMatrix<T : Any>(
override val shape: IntArray get() = intArrayOf(rowNum, colNum)
public override fun suggestFeature(vararg features: MatrixFeature): BufferMatrix<T> =
BufferMatrix(rowNum, colNum, buffer, this.features + features)
public override operator fun get(index: IntArray): T = get(index[0], index[1])
public override operator fun get(i: Int, j: Int): T = buffer[i * colNum + j]
@ -50,23 +43,26 @@ public class BufferMatrix<T : Any>(
if (this === other) return true
return when (other) {
is NDStructure<*> -> return NDStructure.equals(this, other)
is NDStructure<*> -> NDStructure.contentEquals(this, other)
else -> false
}
}
public override fun hashCode(): Int {
var result = buffer.hashCode()
result = 31 * result + features.hashCode()
override fun hashCode(): Int {
var result = rowNum
result = 31 * result + colNum
result = 31 * result + buffer.hashCode()
return result
}
public override fun toString(): String {
return if (rowNum <= 5 && colNum <= 5)
"Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)\n" +
"Matrix(rowsNum = $rowNum, colNum = $colNum)\n" +
rows.asSequence().joinToString(prefix = "(", postfix = ")", separator = "\n ") { buffer ->
buffer.asSequence().joinToString(separator = "\t") { it.toString() }
}
else "Matrix(rowsNum = $rowNum, colNum = $colNum, features=$features)"
else "Matrix(rowsNum = $rowNum, colNum = $colNum)"
}
}

View File

@ -1,123 +0,0 @@
package kscience.kmath.linear
import kscience.kmath.operations.Complex
import kscience.kmath.operations.Ring
import kscience.kmath.operations.conjugate
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.Structure2D
import kscience.kmath.structures.asBuffer
import kotlin.jvm.JvmName
import kotlin.math.sqrt
/**
* A [Matrix] that holds [MatrixFeature] objects.
*
* @param T the type of items.
*/
public interface FeaturedMatrix<T : Any> : Matrix<T> {
public override val shape: IntArray get() = intArrayOf(rowNum, colNum)
/**
* The set of features this matrix possesses.
*/
public val features: Set<MatrixFeature>
/**
* Suggest new feature for this matrix. The result is the new matrix that may or may not reuse existing data structure.
*
* The implementation does not guarantee to check that matrix actually have the feature, so one should be careful to
* add only those features that are valid.
*/
public fun suggestFeature(vararg features: MatrixFeature): FeaturedMatrix<T>
public companion object
}
public inline fun Structure2D.Companion.real(
rows: Int,
columns: Int,
initializer: (Int, Int) -> Double,
): BufferMatrix<Double> = MatrixContext.real.produce(rows, columns, initializer)
/**
* Build a square matrix from given elements.
*/
public fun <T : Any> Structure2D.Companion.square(vararg elements: T): FeaturedMatrix<T> {
val size: Int = sqrt(elements.size.toDouble()).toInt()
require(size * size == elements.size) { "The number of elements ${elements.size} is not a full square" }
val buffer = elements.asBuffer()
return BufferMatrix(size, size, buffer)
}
public val Matrix<*>.features: Set<MatrixFeature> get() = (this as? FeaturedMatrix)?.features ?: emptySet()
/**
* Check if matrix has the given feature class
*/
public inline fun <reified T : Any> Matrix<*>.hasFeature(): Boolean =
features.find { it is T } != null
/**
* Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria
*/
@Suppress("UNCHECKED_CAST")
public inline fun <reified T : Any> Matrix<*>.getFeature(): T? =
features.find { it is T }?.let { it as T }
/**
* Diagonal matrix of ones. The matrix is virtual no actual matrix is created
*/
public fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R, *>.one(rows: Int, columns: Int): FeaturedMatrix<T> =
VirtualMatrix(rows, columns, DiagonalFeature) { i, j ->
if (i == j) elementContext.one else elementContext.zero
}
/**
* Returns a [VirtualMatrix] of zeroes.
*
* @param T the type of matrix's items.
* @param R the type of ring over the matrix's items.
* @receiver the matrix context to provide the [R] ring.
* @param rows the count of rows.
* @param columns the count of columns.
* @return a new virtual matrix.
*/
public fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R, *>.zero(rows: Int, columns: Int): FeaturedMatrix<T> =
VirtualMatrix(rows, columns) { _, _ -> elementContext.zero }
/**
* Matrices with this feature were transposed previously and hold the reference to their original.
*
* @param T the type of matrices' items.
* @property original the matrix before transposition.
*/
public inline class TransposedFeature<T : Any>(public val original: Matrix<T>) : MatrixFeature
/**
* Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A`.
*/
public fun <T : Any> Matrix<T>.transpose(): Matrix<T> = getFeature<TransposedFeature<T>>()?.original ?: VirtualMatrix(
colNum,
rowNum,
setOf(TransposedFeature(this)),
) { i, j -> get(j, i) }
/**
* Returns Hermitian conjugate of this matrix (i.e., just transposes it).
*
*
*/
@JvmName("transposeConjugateDouble")
public fun Matrix<Double>.transposeConjugate(): Matrix<Double> = transpose()
/**
* Returns Hermitian conjugate of this matrix (i.e., transposes it and replaces each element with its conjugate).
*
* @return the Hermitian conjugate of this matrix.
*/
@JvmName("transposeConjugateComplex")
public fun Matrix<Complex>.transposeConjugate(): Matrix<Complex> {
val t = transpose()
return VirtualMatrix(t.rowNum, t.colNum) { i, j -> t[i, j].conjugate }
}

View File

@ -1,13 +1,14 @@
package kscience.kmath.linear
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
import kscience.kmath.structures.*
/**
* Common implementation of [LupDecompositionFeature].
*/
public class LUPDecomposition<T : Any>(
public val context: MatrixContext<T, FeaturedMatrix<T>>,
public class LupDecomposition<T : Any>(
public val context: MatrixContext<T, Matrix<T>>,
public val elementContext: Field<T>,
public val lu: Matrix<T>,
public val pivot: IntArray,
@ -18,13 +19,13 @@ public class LUPDecomposition<T : Any>(
*
* L is a lower-triangular matrix with [Ring.one] in diagonal
*/
override val l: FeaturedMatrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(LFeature)) { i, j ->
override val l: Matrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j ->
when {
j < i -> lu[i, j]
j == i -> elementContext.one
else -> elementContext.zero
}
}
} + LFeature
/**
@ -32,9 +33,9 @@ public class LUPDecomposition<T : Any>(
*
* U is an upper-triangular matrix including the diagonal
*/
override val u: FeaturedMatrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1], setOf(UFeature)) { i, j ->
override val u: Matrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j ->
if (j >= i) lu[i, j] else elementContext.zero
}
} + UFeature
/**
* Returns the P rows permutation matrix.
@ -42,7 +43,7 @@ public class LUPDecomposition<T : Any>(
* P is a sparse matrix with exactly one element set to [Ring.one] in
* each row and each column, all other elements being set to [Ring.zero].
*/
override val p: FeaturedMatrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j ->
override val p: Matrix<T> = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j ->
if (j == pivot[i]) elementContext.one else elementContext.zero
}
@ -63,12 +64,12 @@ internal fun <T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, *>.abs
/**
* Create a lup decomposition of generic matrix.
*/
public fun <T : Comparable<T>> MatrixContext<T, FeaturedMatrix<T>>.lup(
public fun <T : Comparable<T>> MatrixContext<T, Matrix<T>>.lup(
factory: MutableBufferFactory<T>,
elementContext: Field<T>,
matrix: Matrix<T>,
checkSingular: (T) -> Boolean,
): LUPDecomposition<T> {
): LupDecomposition<T> {
require(matrix.rowNum == matrix.colNum) { "LU decomposition supports only square matrices" }
val m = matrix.colNum
val pivot = IntArray(matrix.rowNum)
@ -137,23 +138,23 @@ public fun <T : Comparable<T>> MatrixContext<T, FeaturedMatrix<T>>.lup(
for (row in col + 1 until m) lu[row, col] /= luDiag
}
return LUPDecomposition(this@lup, elementContext, lu.collect(), pivot, even)
return LupDecomposition(this@lup, elementContext, lu.collect(), pivot, even)
}
}
}
public inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, FeaturedMatrix<T>>.lup(
public inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, Matrix<T>>.lup(
matrix: Matrix<T>,
noinline checkSingular: (T) -> Boolean,
): LUPDecomposition<T> = lup(MutableBuffer.Companion::auto, elementContext, matrix, checkSingular)
): LupDecomposition<T> = lup(MutableBuffer.Companion::auto, elementContext, matrix, checkSingular)
public fun MatrixContext<Double, FeaturedMatrix<Double>>.lup(matrix: Matrix<Double>): LUPDecomposition<Double> =
public fun MatrixContext<Double, Matrix<Double>>.lup(matrix: Matrix<Double>): LupDecomposition<Double> =
lup(Buffer.Companion::real, RealField, matrix) { it < 1e-11 }
public fun <T : Any> LUPDecomposition<T>.solveWithLUP(
public fun <T : Any> LupDecomposition<T>.solveWithLUP(
factory: MutableBufferFactory<T>,
matrix: Matrix<T>
): FeaturedMatrix<T> {
matrix: Matrix<T>,
): Matrix<T> {
require(matrix.rowNum == pivot.size) { "Matrix dimension mismatch. Expected ${pivot.size}, but got ${matrix.colNum}" }
BufferAccessor2D(matrix.rowNum, matrix.colNum, factory).run {
@ -198,25 +199,41 @@ public fun <T : Any> LUPDecomposition<T>.solveWithLUP(
}
}
public inline fun <reified T : Any> LUPDecomposition<T>.solveWithLUP(matrix: Matrix<T>): Matrix<T> =
public inline fun <reified T : Any> LupDecomposition<T>.solveWithLUP(matrix: Matrix<T>): Matrix<T> =
solveWithLUP(MutableBuffer.Companion::auto, matrix)
/**
* Solve a linear equation **a*x = b** using LUP decomposition
*/
public inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, FeaturedMatrix<T>>.solveWithLUP(
@OptIn(UnstableKMathAPI::class)
public inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, Matrix<T>>.solveWithLUP(
a: Matrix<T>,
b: Matrix<T>,
noinline bufferFactory: MutableBufferFactory<T> = MutableBuffer.Companion::auto,
noinline checkSingular: (T) -> Boolean,
): FeaturedMatrix<T> {
): Matrix<T> {
// Use existing decomposition if it is provided by matrix
val decomposition = a.getFeature() ?: lup(bufferFactory, elementContext, a, checkSingular)
return decomposition.solveWithLUP(bufferFactory, b)
}
public inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, FeaturedMatrix<T>>.inverseWithLUP(
public inline fun <reified T : Comparable<T>, F : Field<T>> GenericMatrixContext<T, F, Matrix<T>>.inverseWithLUP(
matrix: Matrix<T>,
noinline bufferFactory: MutableBufferFactory<T> = MutableBuffer.Companion::auto,
noinline checkSingular: (T) -> Boolean,
): FeaturedMatrix<T> = solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular)
): Matrix<T> = solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular)
@OptIn(UnstableKMathAPI::class)
public fun RealMatrixContext.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, RealField, a) { it < 1e-11 }
return decomposition.solveWithLUP(bufferFactory, b)
}
/**
* Inverses a square matrix using LUP decomposition. Non square matrix will throw a error.
*/
public fun RealMatrixContext.inverseWithLUP(matrix: Matrix<Double>): Matrix<Double> =
solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum))

View File

@ -1,12 +1,9 @@
package kscience.kmath.linear
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.BufferFactory
import kscience.kmath.structures.Structure2D
import kscience.kmath.structures.asBuffer
import kscience.kmath.structures.*
public class MatrixBuilder(public val rows: Int, public val columns: Int) {
public operator fun <T : Any> invoke(vararg elements: T): FeaturedMatrix<T> {
public operator fun <T : Any> invoke(vararg elements: T): Matrix<T> {
require(rows * columns == elements.size) { "The number of elements ${elements.size} is not equal $rows * $columns" }
val buffer = elements.asBuffer()
return BufferMatrix(rows, columns, buffer)
@ -17,7 +14,7 @@ public class MatrixBuilder(public val rows: Int, public val columns: Int) {
public fun Structure2D.Companion.build(rows: Int, columns: Int): MatrixBuilder = MatrixBuilder(rows, columns)
public fun <T : Any> Structure2D.Companion.row(vararg values: T): FeaturedMatrix<T> {
public fun <T : Any> Structure2D.Companion.row(vararg values: T): Matrix<T> {
val buffer = values.asBuffer()
return BufferMatrix(1, values.size, buffer)
}
@ -26,12 +23,12 @@ public inline fun <reified T : Any> Structure2D.Companion.row(
size: Int,
factory: BufferFactory<T> = Buffer.Companion::auto,
noinline builder: (Int) -> T
): FeaturedMatrix<T> {
): Matrix<T> {
val buffer = factory(size, builder)
return BufferMatrix(1, size, buffer)
}
public fun <T : Any> Structure2D.Companion.column(vararg values: T): FeaturedMatrix<T> {
public fun <T : Any> Structure2D.Companion.column(vararg values: T): Matrix<T> {
val buffer = values.asBuffer()
return BufferMatrix(values.size, 1, buffer)
}
@ -40,7 +37,7 @@ public inline fun <reified T : Any> Structure2D.Companion.column(
size: Int,
factory: BufferFactory<T> = Buffer.Companion::auto,
noinline builder: (Int) -> T
): FeaturedMatrix<T> {
): Matrix<T> {
val buffer = factory(size, builder)
return BufferMatrix(size, 1, buffer)
}

View File

@ -1,5 +1,7 @@
package kscience.kmath.linear
import kscience.kmath.structures.Matrix
/**
* A marker interface representing some properties of matrices or additional transformations of them. Features are used
* to optimize matrix operations performance in some cases or retrieve the APIs.
@ -9,17 +11,19 @@ public interface MatrixFeature
/**
* Matrices with this feature are considered to have only diagonal non-null elements.
*/
public object DiagonalFeature : MatrixFeature
public interface DiagonalFeature : MatrixFeature{
public companion object: DiagonalFeature
}
/**
* Matrices with this feature have all zero elements.
*/
public object ZeroFeature : MatrixFeature
public object ZeroFeature : DiagonalFeature
/**
* Matrices with this feature have unit elements on diagonal and zero elements in all other places.
*/
public object UnitFeature : MatrixFeature
public object UnitFeature : DiagonalFeature
/**
* Matrices with this feature can be inverted: [inverse] = `a`<sup>-1</sup> where `a` is the owning matrix.
@ -30,7 +34,7 @@ public interface InverseMatrixFeature<T : Any> : MatrixFeature {
/**
* The inverse matrix of the matrix that owns this feature.
*/
public val inverse: FeaturedMatrix<T>
public val inverse: Matrix<T>
}
/**
@ -74,17 +78,17 @@ public interface LupDecompositionFeature<T : Any> : MatrixFeature {
/**
* The lower triangular matrix in this decomposition. It may have [LFeature].
*/
public val l: FeaturedMatrix<T>
public val l: Matrix<T>
/**
* The upper triangular matrix in this decomposition. It may have [UFeature].
*/
public val u: FeaturedMatrix<T>
public val u: Matrix<T>
/**
* The permutation matrix in this decomposition.
*/
public val p: FeaturedMatrix<T>
public val p: Matrix<T>
}
/**
@ -102,12 +106,12 @@ public interface QRDecompositionFeature<T : Any> : MatrixFeature {
/**
* The orthogonal matrix in this decomposition. It may have [OrthogonalFeature].
*/
public val q: FeaturedMatrix<T>
public val q: Matrix<T>
/**
* The upper triangular matrix in this decomposition. It may have [UFeature].
*/
public val r: FeaturedMatrix<T>
public val r: Matrix<T>
}
/**
@ -120,7 +124,7 @@ public interface CholeskyDecompositionFeature<T : Any> : MatrixFeature {
/**
* The triangular matrix in this decomposition. It may have either [UFeature] or [LFeature].
*/
public val l: FeaturedMatrix<T>
public val l: Matrix<T>
}
/**
@ -133,17 +137,17 @@ public interface SingularValueDecompositionFeature<T : Any> : MatrixFeature {
/**
* The matrix in this decomposition. It is unitary, and it consists from left singular vectors.
*/
public val u: FeaturedMatrix<T>
public val u: Matrix<T>
/**
* The matrix in this decomposition. Its main diagonal elements are singular values.
*/
public val s: FeaturedMatrix<T>
public val s: Matrix<T>
/**
* The matrix in this decomposition. It is unitary, and it consists from right singular vectors.
*/
public val v: FeaturedMatrix<T>
public val v: Matrix<T>
/**
* The buffer of singular values of this SVD.

View File

@ -0,0 +1,105 @@
package kscience.kmath.linear
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.Ring
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.Structure2D
import kscience.kmath.structures.asBuffer
import kscience.kmath.structures.getFeature
import kotlin.math.sqrt
import kotlin.reflect.KClass
import kotlin.reflect.safeCast
/**
* A [Matrix] that holds [MatrixFeature] objects.
*
* @param T the type of items.
*/
public class MatrixWrapper<T : Any> internal constructor(
public val origin: Matrix<T>,
public val features: Set<MatrixFeature>,
) : Matrix<T> by origin {
/**
* Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the criteria
*/
@UnstableKMathAPI
override fun <T : Any> getFeature(type: KClass<T>): T? = type.safeCast(features.find { type.isInstance(it) })
?: origin.getFeature(type)
override fun equals(other: Any?): Boolean = origin == other
override fun hashCode(): Int = origin.hashCode()
override fun toString(): String {
return "MatrixWrapper(matrix=$origin, features=$features)"
}
}
/**
* Return the original matrix. If this is a wrapper, return its origin. If not, this matrix.
* Origin does not necessary store all features.
*/
@UnstableKMathAPI
public val <T : Any> Matrix<T>.origin: Matrix<T> get() = (this as? MatrixWrapper)?.origin ?: this
/**
* Add a single feature to a [Matrix]
*/
public operator fun <T : Any> Matrix<T>.plus(newFeature: MatrixFeature): MatrixWrapper<T> = if (this is MatrixWrapper) {
MatrixWrapper(origin, features + newFeature)
} else {
MatrixWrapper(this, setOf(newFeature))
}
/**
* Add a collection of features to a [Matrix]
*/
public operator fun <T : Any> Matrix<T>.plus(newFeatures: Collection<MatrixFeature>): MatrixWrapper<T> =
if (this is MatrixWrapper) {
MatrixWrapper(origin, features + newFeatures)
} else {
MatrixWrapper(this, newFeatures.toSet())
}
public inline fun Structure2D.Companion.real(
rows: Int,
columns: Int,
initializer: (Int, Int) -> Double,
): BufferMatrix<Double> = MatrixContext.real.produce(rows, columns, initializer)
/**
* Build a square matrix from given elements.
*/
public fun <T : Any> Structure2D.Companion.square(vararg elements: T): Matrix<T> {
val size: Int = sqrt(elements.size.toDouble()).toInt()
require(size * size == elements.size) { "The number of elements ${elements.size} is not a full square" }
val buffer = elements.asBuffer()
return BufferMatrix(size, size, buffer)
}
/**
* Diagonal matrix of ones. The matrix is virtual no actual matrix is created
*/
public fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R, *>.one(rows: Int, columns: Int): Matrix<T> =
VirtualMatrix(rows, columns) { i, j ->
if (i == j) elementContext.one else elementContext.zero
} + UnitFeature
/**
* A virtual matrix of zeroes
*/
public fun <T : Any, R : Ring<T>> GenericMatrixContext<T, R, *>.zero(rows: Int, columns: Int): Matrix<T> =
VirtualMatrix(rows, columns) { _, _ -> elementContext.zero } + ZeroFeature
public class TransposedFeature<T : Any>(public val original: Matrix<T>) : MatrixFeature
/**
* Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A`
*/
@OptIn(UnstableKMathAPI::class)
public fun <T : Any> Matrix<T>.transpose(): Matrix<T> {
return getFeature<TransposedFeature<T>>()?.original ?: VirtualMatrix(
colNum,
rowNum,
) { i, j -> get(j, i) } + TransposedFeature(this)
}

View File

@ -1,9 +1,6 @@
package kscience.kmath.linear
import kscience.kmath.operations.RealField
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.MutableBuffer
import kscience.kmath.structures.MutableBufferFactory
import kscience.kmath.structures.RealBuffer
@Suppress("OVERRIDE_BY_INLINE")
@ -22,9 +19,9 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
produce(rowNum, colNum) { i, j -> get(i, j) }
}
public fun one(rows: Int, columns: Int): FeaturedMatrix<Double> = VirtualMatrix(rows, columns, DiagonalFeature) { i, j ->
public fun one(rows: Int, columns: Int): Matrix<Double> = VirtualMatrix(rows, columns) { i, j ->
if (i == j) 1.0 else 0.0
}
} + DiagonalFeature
public override infix fun Matrix<Double>.dot(other: Matrix<Double>): BufferMatrix<Double> {
require(colNum == other.rowNum) { "Matrix dot operation dimension mismatch: ($rowNum, $colNum) x (${other.rowNum}, ${other.colNum})" }
@ -61,7 +58,7 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
override fun multiply(a: Matrix<Double>, k: Number): BufferMatrix<Double> =
produce(a.rowNum, a.colNum) { i, j -> a.get(i, j) * k.toDouble() }
produce(a.rowNum, a.colNum) { i, j -> a[i, j] * k.toDouble() }
}
@ -69,16 +66,3 @@ public object RealMatrixContext : MatrixContext<Double, BufferMatrix<Double>> {
* Partially optimized real-valued matrix
*/
public val MatrixContext.Companion.real: RealMatrixContext get() = RealMatrixContext
public fun RealMatrixContext.solveWithLUP(a: Matrix<Double>, b: Matrix<Double>): FeaturedMatrix<Double> {
// Use existing decomposition if it is provided by matrix
val bufferFactory: MutableBufferFactory<Double> = MutableBuffer.Companion::real
val decomposition = a.getFeature() ?: lup(bufferFactory, RealField, a) { it < 1e-11 }
return decomposition.solveWithLUP(bufferFactory, b)
}
/**
* Inverses a square matrix using LUP decomposition. Non square matrix will throw a error.
*/
public fun RealMatrixContext.inverseWithLUP(matrix: Matrix<Double>): FeaturedMatrix<Double> =
solveWithLUP(matrix, one(matrix.rowNum, matrix.colNum))

View File

@ -10,31 +10,16 @@ import kscience.kmath.structures.Matrix
public class VirtualMatrix<T : Any>(
override val rowNum: Int,
override val colNum: Int,
override val features: Set<MatrixFeature> = emptySet(),
public val generator: (i: Int, j: Int) -> T,
) : FeaturedMatrix<T> {
public constructor(
rowNum: Int,
colNum: Int,
vararg features: MatrixFeature,
generator: (i: Int, j: Int) -> T,
) : this(
rowNum,
colNum,
setOf(*features),
generator
)
public val generator: (i: Int, j: Int) -> T
) : Matrix<T> {
override val shape: IntArray get() = intArrayOf(rowNum, colNum)
override operator fun get(i: Int, j: Int): T = generator(i, j)
override fun suggestFeature(vararg features: MatrixFeature): VirtualMatrix<T> =
VirtualMatrix(rowNum, colNum, this.features + features, generator)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is FeaturedMatrix<*>) return false
if (other !is Matrix<*>) return false
if (rowNum != other.rowNum) return false
if (colNum != other.colNum) return false
@ -45,21 +30,9 @@ public class VirtualMatrix<T : Any>(
override fun hashCode(): Int {
var result = rowNum
result = 31 * result + colNum
result = 31 * result + features.hashCode()
result = 31 * result + generator.hashCode()
return result
}
public companion object {
/**
* Wrap a matrix adding additional features to it
*/
public fun <T : Any> wrap(matrix: Matrix<T>, vararg features: MatrixFeature): FeaturedMatrix<T> {
return if (matrix is VirtualMatrix)
VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features, matrix.generator)
else
VirtualMatrix(matrix.rowNum, matrix.colNum, matrix.features + features) { i, j -> matrix[i, j] }
}
}
}

View File

@ -88,90 +88,11 @@ public interface Algebra<T> {
public fun binaryOperation(operation: String, left: T, right: T): T = binaryOperationFunction(operation)(left, right)
}
/**
* An algebraic structure where elements can have numeric representation.
*
* @param T the type of element of this structure.
*/
public interface NumericAlgebra<T> : Algebra<T> {
/**
* Wraps a number to [T] object.
*
* @param value the number to wrap.
* @return an object.
*/
public fun number(value: Number): T
/**
* Dynamically dispatches a binary operation with the certain name with numeric first argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with the other [leftSideNumberOperation] overload:
* i.e. `leftSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b)`.
*
* @param operation the name of operation.
* @return an operation.
*/
public fun leftSideNumberOperationFunction(operation: String): (left: Number, right: T) -> T =
{ l, r -> binaryOperationFunction(operation)(number(l), r) }
/**
* Dynamically invokes a binary operation with the certain name with numeric first argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with second [leftSideNumberOperation] overload:
* i.e. `leftSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b, c)`.
*
* @param operation the name of operation.
* @param left the first argument of operation.
* @param right the second argument of operation.
* @return a result of operation.
*/
public fun leftSideNumberOperation(operation: String, left: Number, right: T): T =
leftSideNumberOperationFunction(operation)(left, right)
/**
* Dynamically dispatches a binary operation with the certain name with numeric first argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with the other [rightSideNumberOperationFunction] overload:
* i.e. `rightSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b, c)`.
*
* @param operation the name of operation.
* @return an operation.
*/
public fun rightSideNumberOperationFunction(operation: String): (left: T, right: Number) -> T =
{ l, r -> binaryOperationFunction(operation)(l, number(r)) }
/**
* Dynamically invokes a binary operation with the certain name with numeric second argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with the other [rightSideNumberOperationFunction] overload:
* i.e. `rightSideNumberOperationFunction(a)(b, c) == rightSideNumberOperation(a, b, c)`.
*
* @param operation the name of operation.
* @param left the first argument of operation.
* @param right the second argument of operation.
* @return a result of operation.
*/
public fun rightSideNumberOperation(operation: String, left: T, right: Number): T =
rightSideNumberOperationFunction(operation)(left, right)
}
/**
* Call a block with an [Algebra] as receiver.
*/
// TODO add contract when KT-32313 is fixed
public inline operator fun <A : Algebra<*>, R> A.invoke(block: A.() -> R): R = block()
public inline operator fun <A : Algebra<*>, R> A.invoke(block: A.() -> R): R = run(block)
/**
* Represents "semispace", i.e. algebraic structure with associative binary operation called "addition" as well as
@ -341,47 +262,11 @@ public interface RingOperations<T> : SpaceOperations<T> {
*
* @param T the type of element of this ring.
*/
public interface Ring<T> : Space<T>, RingOperations<T>, NumericAlgebra<T> {
public interface Ring<T> : Space<T>, RingOperations<T> {
/**
* neutral operation for multiplication
*/
public val one: T
public override fun number(value: Number): T = one * value.toDouble()
/**
* Addition of element and scalar.
*
* @receiver the addend.
* @param b the augend.
*/
public operator fun T.plus(b: Number): T = this + number(b)
/**
* Addition of scalar and element.
*
* @receiver the addend.
* @param b the augend.
*/
public operator fun Number.plus(b: T): T = b + this
/**
* Subtraction of element from number.
*
* @receiver the minuend.
* @param b the subtrahend.
* @receiver the difference.
*/
public operator fun T.minus(b: Number): T = this - number(b)
/**
* Subtraction of number from element.
*
* @receiver the minuend.
* @param b the subtrahend.
* @receiver the difference.
*/
public operator fun Number.minus(b: T): T = -b + this
}
/**

View File

@ -1,5 +1,6 @@
package kscience.kmath.operations
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.BigInt.Companion.BASE
import kscience.kmath.operations.BigInt.Companion.BASE_SIZE
import kscience.kmath.structures.*
@ -16,7 +17,8 @@ public typealias TBase = ULong
*
* @author Robert Drynkin (https://github.com/robdrynkin) and Peter Klimai (https://github.com/pklimai)
*/
public object BigIntField : Field<BigInt> {
@OptIn(UnstableKMathAPI::class)
public object BigIntField : Field<BigInt>, RingWithNumbers<BigInt> {
override val zero: BigInt = BigInt.ZERO
override val one: BigInt = BigInt.ONE

View File

@ -3,6 +3,7 @@ package kscience.kmath.operations
import kscience.kmath.memory.MemoryReader
import kscience.kmath.memory.MemorySpec
import kscience.kmath.memory.MemoryWriter
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.MemoryBuffer
import kscience.kmath.structures.MutableBuffer
@ -41,7 +42,8 @@ private val PI_DIV_2 = Complex(PI / 2, 0)
/**
* A field of [Complex].
*/
public object ComplexField : ExtendedField<Complex>, Norm<Complex, Complex> {
@OptIn(UnstableKMathAPI::class)
public object ComplexField : ExtendedField<Complex>, Norm<Complex, Complex>, RingWithNumbers<Complex> {
override val zero: Complex = 0.0.toComplex()
override val one: Complex = 1.0.toComplex()
@ -156,7 +158,7 @@ public object ComplexField : ExtendedField<Complex>, Norm<Complex, Complex> {
override fun norm(arg: Complex): Complex = sqrt(arg.conjugate * arg)
override fun symbol(value: String): Complex = if (value == "i") i else super.symbol(value)
override fun symbol(value: String): Complex = if (value == "i") i else super<ExtendedField>.symbol(value)
}
/**

View File

@ -0,0 +1,125 @@
package kscience.kmath.operations
import kscience.kmath.misc.UnstableKMathAPI
/**
* An algebraic structure where elements can have numeric representation.
*
* @param T the type of element of this structure.
*/
public interface NumericAlgebra<T> : Algebra<T> {
/**
* Wraps a number to [T] object.
*
* @param value the number to wrap.
* @return an object.
*/
public fun number(value: Number): T
/**
* Dynamically dispatches a binary operation with the certain name with numeric first argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with the other [leftSideNumberOperation] overload:
* i.e. `leftSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b)`.
*
* @param operation the name of operation.
* @return an operation.
*/
public fun leftSideNumberOperationFunction(operation: String): (left: Number, right: T) -> T =
{ l, r -> binaryOperationFunction(operation)(number(l), r) }
/**
* Dynamically invokes a binary operation with the certain name with numeric first argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with second [leftSideNumberOperation] overload:
* i.e. `leftSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b, c)`.
*
* @param operation the name of operation.
* @param left the first argument of operation.
* @param right the second argument of operation.
* @return a result of operation.
*/
public fun leftSideNumberOperation(operation: String, left: Number, right: T): T =
leftSideNumberOperationFunction(operation)(left, right)
/**
* Dynamically dispatches a binary operation with the certain name with numeric first argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with the other [rightSideNumberOperationFunction] overload:
* i.e. `rightSideNumberOperationFunction(a)(b, c) == leftSideNumberOperation(a, b, c)`.
*
* @param operation the name of operation.
* @return an operation.
*/
public fun rightSideNumberOperationFunction(operation: String): (left: T, right: Number) -> T =
{ l, r -> binaryOperationFunction(operation)(l, number(r)) }
/**
* Dynamically invokes a binary operation with the certain name with numeric second argument.
*
* This function must follow two properties:
*
* 1. In case if operation is not defined in the structure, the function throws [kotlin.IllegalStateException].
* 2. This function is symmetric with the other [rightSideNumberOperationFunction] overload:
* i.e. `rightSideNumberOperationFunction(a)(b, c) == rightSideNumberOperation(a, b, c)`.
*
* @param operation the name of operation.
* @param left the first argument of operation.
* @param right the second argument of operation.
* @return a result of operation.
*/
public fun rightSideNumberOperation(operation: String, left: T, right: Number): T =
rightSideNumberOperationFunction(operation)(left, right)
}
/**
* A combination of [NumericAlgebra] and [Ring] that adds intrinsic simple operations on numbers like `T+1`
* TODO to be removed and replaced by extensions after multiple receivers are there
*/
@UnstableKMathAPI
public interface RingWithNumbers<T>: Ring<T>, NumericAlgebra<T>{
public override fun number(value: Number): T = one * value
/**
* Addition of element and scalar.
*
* @receiver the addend.
* @param b the augend.
*/
public operator fun T.plus(b: Number): T = this + number(b)
/**
* Addition of scalar and element.
*
* @receiver the addend.
* @param b the augend.
*/
public operator fun Number.plus(b: T): T = b + this
/**
* Subtraction of element from number.
*
* @receiver the minuend.
* @param b the subtrahend.
* @receiver the difference.
*/
public operator fun T.minus(b: Number): T = this - number(b)
/**
* Subtraction of number from element.
*
* @receiver the minuend.
* @param b the subtrahend.
* @receiver the difference.
*/
public operator fun Number.minus(b: T): T = -b + this
}

View File

@ -37,7 +37,7 @@ public interface ExtendedFieldOperations<T> :
/**
* Advanced Number-like field that implements basic operations.
*/
public interface ExtendedField<T> : ExtendedFieldOperations<T>, Field<T> {
public interface ExtendedField<T> : ExtendedFieldOperations<T>, Field<T>, NumericAlgebra<T> {
public override fun sinh(arg: T): T = (exp(arg) - exp(-arg)) / 2
public override fun cosh(arg: T): T = (exp(arg) + exp(-arg)) / 2
public override fun tanh(arg: T): T = (exp(arg) - exp(-arg)) / (exp(-arg) + exp(arg))
@ -80,6 +80,8 @@ public object RealField : ExtendedField<Double>, Norm<Double, Double> {
public override val one: Double
get() = 1.0
override fun number(value: Number): Double = value.toDouble()
public override fun binaryOperationFunction(operation: String): (left: Double, right: Double) -> Double =
when (operation) {
PowerOperations.POW_OPERATION -> ::power
@ -131,7 +133,10 @@ public object FloatField : ExtendedField<Float>, Norm<Float, Float> {
public override val one: Float
get() = 1.0f
public override fun binaryOperationFunction(operation: String): (left: Float, right: Float) -> Float = when (operation) {
override fun number(value: Number): Float = value.toFloat()
public override fun binaryOperationFunction(operation: String): (left: Float, right: Float) -> Float =
when (operation) {
PowerOperations.POW_OPERATION -> ::power
else -> super.binaryOperationFunction(operation)
}
@ -174,13 +179,15 @@ public object FloatField : ExtendedField<Float>, Norm<Float, Float> {
* A field for [Int] without boxing. Does not produce corresponding ring element.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
public object IntRing : Ring<Int>, Norm<Int, Int> {
public object IntRing : Ring<Int>, Norm<Int, Int>, NumericAlgebra<Int> {
public override val zero: Int
get() = 0
public override val one: Int
get() = 1
override fun number(value: Number): Int = value.toInt()
public override inline fun add(a: Int, b: Int): Int = a + b
public override inline fun multiply(a: Int, k: Number): Int = k.toInt() * a
@ -198,13 +205,15 @@ public object IntRing : Ring<Int>, Norm<Int, Int> {
* A field for [Short] without boxing. Does not produce appropriate ring element.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
public object ShortRing : Ring<Short>, Norm<Short, Short> {
public object ShortRing : Ring<Short>, Norm<Short, Short>, NumericAlgebra<Short> {
public override val zero: Short
get() = 0
public override val one: Short
get() = 1
override fun number(value: Number): Short = value.toShort()
public override inline fun add(a: Short, b: Short): Short = (a + b).toShort()
public override inline fun multiply(a: Short, k: Number): Short = (a * k.toShort()).toShort()
@ -222,13 +231,15 @@ public object ShortRing : Ring<Short>, Norm<Short, Short> {
* A field for [Byte] without boxing. Does not produce appropriate ring element.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
public object ByteRing : Ring<Byte>, Norm<Byte, Byte> {
public object ByteRing : Ring<Byte>, Norm<Byte, Byte>, NumericAlgebra<Byte> {
public override val zero: Byte
get() = 0
public override val one: Byte
get() = 1
override fun number(value: Number): Byte = value.toByte()
public override inline fun add(a: Byte, b: Byte): Byte = (a + b).toByte()
public override inline fun multiply(a: Byte, k: Number): Byte = (a * k.toByte()).toByte()
@ -246,13 +257,15 @@ public object ByteRing : Ring<Byte>, Norm<Byte, Byte> {
* A field for [Double] without boxing. Does not produce appropriate ring element.
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
public object LongRing : Ring<Long>, Norm<Long, Long> {
public object LongRing : Ring<Long>, Norm<Long, Long>, NumericAlgebra<Long> {
public override val zero: Long
get() = 0L
public override val one: Long
get() = 1L
override fun number(value: Number): Long = value.toLong()
public override inline fun add(a: Long, b: Long): Long = a + b
public override inline fun multiply(a: Long, k: Number): Long = a * k.toLong()

View File

@ -1,9 +1,7 @@
package kscience.kmath.structures
import kscience.kmath.operations.Complex
import kscience.kmath.operations.ComplexField
import kscience.kmath.operations.FieldElement
import kscience.kmath.operations.complex
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -12,15 +10,22 @@ public typealias ComplexNDElement = BufferedNDFieldElement<Complex, ComplexField
/**
* An optimized nd-field for complex numbers
*/
@OptIn(UnstableKMathAPI::class)
public class ComplexNDField(override val shape: IntArray) :
BufferedNDField<Complex, ComplexField>,
ExtendedNDField<Complex, ComplexField, NDBuffer<Complex>> {
ExtendedNDField<Complex, ComplexField, NDBuffer<Complex>>,
RingWithNumbers<NDBuffer<Complex>>{
override val strides: Strides = DefaultStrides(shape)
override val elementContext: ComplexField get() = ComplexField
override val zero: ComplexNDElement by lazy { produce { zero } }
override val one: ComplexNDElement by lazy { produce { one } }
override fun number(value: Number): NDBuffer<Complex> {
val c = value.toComplex()
return produce { c }
}
public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Complex): Buffer<Complex> =
Buffer.complex(size) { initializer(it) }
@ -29,7 +34,7 @@ public class ComplexNDField(override val shape: IntArray) :
*/
override fun map(
arg: NDBuffer<Complex>,
transform: ComplexField.(Complex) -> Complex
transform: ComplexField.(Complex) -> Complex,
): ComplexNDElement {
check(arg)
val array = buildBuffer(arg.strides.linearSize) { offset -> ComplexField.transform(arg.buffer[offset]) }
@ -43,7 +48,7 @@ public class ComplexNDField(override val shape: IntArray) :
override fun mapIndexed(
arg: NDBuffer<Complex>,
transform: ComplexField.(index: IntArray, Complex) -> Complex
transform: ComplexField.(index: IntArray, Complex) -> Complex,
): ComplexNDElement {
check(arg)
@ -60,7 +65,7 @@ public class ComplexNDField(override val shape: IntArray) :
override fun combine(
a: NDBuffer<Complex>,
b: NDBuffer<Complex>,
transform: ComplexField.(Complex, Complex) -> Complex
transform: ComplexField.(Complex, Complex) -> Complex,
): ComplexNDElement {
check(a, b)
@ -141,7 +146,7 @@ public fun NDField.Companion.complex(vararg shape: Int): ComplexNDField = Comple
public fun NDElement.Companion.complex(
vararg shape: Int,
initializer: ComplexField.(IntArray) -> Complex
initializer: ComplexField.(IntArray) -> Complex,
): ComplexNDElement = NDField.complex(*shape).produce(initializer)
/**

View File

@ -1,5 +1,6 @@
package kscience.kmath.structures
import kscience.kmath.misc.UnstableKMathAPI
import kotlin.jvm.JvmName
import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass
@ -38,14 +39,22 @@ public interface NDStructure<T> {
*/
public fun elements(): Sequence<Pair<IntArray, T>>
//force override equality and hash code
public override fun equals(other: Any?): Boolean
public override fun hashCode(): Int
/**
* Feature is additional property or hint that does not directly affect the structure, but could in some cases help
* optimize operations and performance. If the feature is not present, null is defined.
*/
@UnstableKMathAPI
public fun <T : Any> getFeature(type: KClass<T>): T? = null
public companion object {
/**
* Indicates whether some [NDStructure] is equal to another one.
*/
public fun equals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean {
public fun contentEquals(st1: NDStructure<*>, st2: NDStructure<*>): Boolean {
if (st1 === st2) return true
// fast comparison of buffers if possible
@ -120,6 +129,9 @@ public interface NDStructure<T> {
*/
public operator fun <T> NDStructure<T>.get(vararg index: Int): T = get(index)
@UnstableKMathAPI
public inline fun <reified T : Any> NDStructure<*>.getFeature(): T? = getFeature(T::class)
/**
* Represents mutable [NDStructure].
*/
@ -133,6 +145,9 @@ public interface MutableNDStructure<T> : NDStructure<T> {
public operator fun set(index: IntArray, value: T)
}
/**
* Transform a structure element-by element in place.
*/
public inline fun <T> MutableNDStructure<T>.mapInPlace(action: (IntArray, T) -> T): Unit =
elements().forEach { (index, oldValue) -> this[index] = action(index, oldValue) }
@ -260,7 +275,7 @@ public abstract class NDBuffer<T> : NDStructure<T> {
override fun elements(): Sequence<Pair<IntArray, T>> = strides.indices().map { it to this[it] }
override fun equals(other: Any?): Boolean {
return NDStructure.equals(this, other as? NDStructure<*> ?: return false)
return NDStructure.contentEquals(this, other as? NDStructure<*> ?: return false)
}
override fun hashCode(): Int {

View File

@ -150,6 +150,8 @@ public class RealBufferField(public val size: Int) : ExtendedField<Buffer<Double
public override val zero: Buffer<Double> by lazy { RealBuffer(size) { 0.0 } }
public override val one: Buffer<Double> by lazy { RealBuffer(size) { 1.0 } }
override fun number(value: Number): Buffer<Double> = RealBuffer(size) { value.toDouble() }
public override fun add(a: Buffer<Double>, b: Buffer<Double>): RealBuffer {
require(a.size == size) { "The buffer size ${a.size} does not match context size $size" }
return RealBufferFieldOperations.add(a, b)

View File

@ -1,13 +1,17 @@
package kscience.kmath.structures
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.FieldElement
import kscience.kmath.operations.RealField
import kscience.kmath.operations.RingWithNumbers
public typealias RealNDElement = BufferedNDFieldElement<Double, RealField>
@OptIn(UnstableKMathAPI::class)
public class RealNDField(override val shape: IntArray) :
BufferedNDField<Double, RealField>,
ExtendedNDField<Double, RealField, NDBuffer<Double>> {
ExtendedNDField<Double, RealField, NDBuffer<Double>>,
RingWithNumbers<NDBuffer<Double>> {
override val strides: Strides = DefaultStrides(shape)
@ -15,35 +19,36 @@ public class RealNDField(override val shape: IntArray) :
override val zero: RealNDElement by lazy { produce { zero } }
override val one: RealNDElement by lazy { produce { one } }
public inline fun buildBuffer(size: Int, crossinline initializer: (Int) -> Double): Buffer<Double> =
RealBuffer(DoubleArray(size) { initializer(it) })
override fun number(value: Number): NDBuffer<Double> {
val d = value.toDouble()
return produce { d }
}
/**
* Inline transform an NDStructure to
*/
override fun map(
@Suppress("OVERRIDE_BY_INLINE")
override inline fun map(
arg: NDBuffer<Double>,
transform: RealField.(Double) -> Double
transform: RealField.(Double) -> Double,
): RealNDElement {
check(arg)
val array = buildBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
val array = RealBuffer(arg.strides.linearSize) { offset -> RealField.transform(arg.buffer[offset]) }
return BufferedNDFieldElement(this, array)
}
override fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
val array = buildBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
@Suppress("OVERRIDE_BY_INLINE")
override inline fun produce(initializer: RealField.(IntArray) -> Double): RealNDElement {
val array = RealBuffer(strides.linearSize) { offset -> elementContext.initializer(strides.index(offset)) }
return BufferedNDFieldElement(this, array)
}
override fun mapIndexed(
@Suppress("OVERRIDE_BY_INLINE")
override inline fun mapIndexed(
arg: NDBuffer<Double>,
transform: RealField.(index: IntArray, Double) -> Double
transform: RealField.(index: IntArray, Double) -> Double,
): RealNDElement {
check(arg)
return BufferedNDFieldElement(
this,
buildBuffer(arg.strides.linearSize) { offset ->
RealBuffer(arg.strides.linearSize) { offset ->
elementContext.transform(
arg.strides.index(offset),
arg.buffer[offset]
@ -51,15 +56,17 @@ public class RealNDField(override val shape: IntArray) :
})
}
override fun combine(
@Suppress("OVERRIDE_BY_INLINE")
override inline fun combine(
a: NDBuffer<Double>,
b: NDBuffer<Double>,
transform: RealField.(Double, Double) -> Double
transform: RealField.(Double, Double) -> Double,
): RealNDElement {
check(a, b)
return BufferedNDFieldElement(
this,
buildBuffer(strides.linearSize) { offset -> elementContext.transform(a.buffer[offset], b.buffer[offset]) })
val buffer = RealBuffer(strides.linearSize) { offset ->
elementContext.transform(a.buffer[offset], b.buffer[offset])
}
return BufferedNDFieldElement(this, buffer)
}
override fun NDBuffer<Double>.toElement(): FieldElement<NDBuffer<Double>, *, out BufferedNDField<Double, RealField>> =

View File

@ -9,12 +9,14 @@ public interface Structure2D<T> : NDStructure<T> {
/**
* The number of rows in this structure.
*/
public val rowNum: Int get() = shape[0]
public val rowNum: Int
/**
* The number of columns in this structure.
*/
public val colNum: Int get() = shape[1]
public val colNum: Int
public override val shape: IntArray get() = intArrayOf(rowNum, colNum)
/**
* The buffer of rows of this structure. It gets elements from the structure dynamically.
@ -56,6 +58,9 @@ public interface Structure2D<T> : NDStructure<T> {
private inline class Structure2DWrapper<T>(val structure: NDStructure<T>) : Structure2D<T> {
override val shape: IntArray get() = structure.shape
override val rowNum: Int get() = shape[0]
override val colNum: Int get() = shape[1]
override operator fun get(i: Int, j: Int): T = structure[i, j]
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()

View File

@ -7,6 +7,7 @@ import kscience.kmath.structures.as2D
import kotlin.test.Test
import kotlin.test.assertEquals
@Suppress("UNUSED_VARIABLE")
class MatrixTest {
@Test
fun testTranspose() {

View File

@ -1,14 +1,13 @@
package kscience.kmath.structures
import kscience.kmath.operations.internal.FieldVerifier
import kscience.kmath.operations.invoke
import kotlin.test.Test
import kotlin.test.assertEquals
internal class NDFieldTest {
@Test
fun verify() {
(NDField.real(12, 32)) { FieldVerifier(this, one + 3, one - 23, one * 12, 6.66) }
NDField.real(12, 32).run { FieldVerifier(this, one + 3, one - 23, one * 12, 6.66) }
}
@Test

View File

@ -8,6 +8,7 @@ import kotlin.math.pow
import kotlin.test.Test
import kotlin.test.assertEquals
@Suppress("UNUSED_VARIABLE")
class NumberNDFieldTest {
val array1: RealNDElement = real2D(3, 3) { i, j -> (i + j).toDouble() }
val array2: RealNDElement = real2D(3, 3) { i, j -> (i - j).toDouble() }

View File

@ -7,7 +7,7 @@ import java.math.MathContext
/**
* A field over [BigInteger].
*/
public object JBigIntegerField : Field<BigInteger> {
public object JBigIntegerField : Field<BigInteger>, NumericAlgebra<BigInteger> {
public override val zero: BigInteger
get() = BigInteger.ZERO
@ -28,9 +28,9 @@ public object JBigIntegerField : Field<BigInteger> {
*
* @property mathContext the [MathContext] to use.
*/
public abstract class JBigDecimalFieldBase internal constructor(public val mathContext: MathContext = MathContext.DECIMAL64) :
Field<BigDecimal>,
PowerOperations<BigDecimal> {
public abstract class JBigDecimalFieldBase internal constructor(
private val mathContext: MathContext = MathContext.DECIMAL64,
) : Field<BigDecimal>, PowerOperations<BigDecimal>, NumericAlgebra<BigDecimal> {
public override val zero: BigDecimal
get() = BigDecimal.ZERO

View File

@ -24,7 +24,7 @@ public class LazyNDStructure<T>(
}
public override fun equals(other: Any?): Boolean {
return NDStructure.equals(this, other as? NDStructure<*> ?: return false)
return NDStructure.contentEquals(this, other as? NDStructure<*> ?: return false)
}
public override fun hashCode(): Int {

View File

@ -40,6 +40,8 @@ public inline class DMatrixWrapper<T, R : Dimension, C : Dimension>(
private val structure: Structure2D<T>,
) : DMatrix<T, R, C> {
override val shape: IntArray get() = structure.shape
override val rowNum: Int get() = shape[0]
override val colNum: Int get() = shape[1]
override operator fun get(i: Int, j: Int): T = structure[i, j]
}
@ -147,6 +149,7 @@ public inline fun <reified D : Dimension> DMatrixContext<Double>.one(): DMatrix<
if (i == j) 1.0 else 0.0
}
public inline fun <reified R : Dimension, reified C : Dimension> DMatrixContext<Double>.zero(): DMatrix<Double, R, C> = produce { _, _ ->
public inline fun <reified R : Dimension, reified C : Dimension> DMatrixContext<Double>.zero(): DMatrix<Double, R, C> =
produce { _, _ ->
0.0
}

View File

@ -6,6 +6,7 @@ import kscience.kmath.dimensions.DMatrixContext
import kscience.kmath.dimensions.one
import kotlin.test.Test
@Suppress("UNUSED_VARIABLE")
internal class DMatrixContextTest {
@Test
fun testDimensionSafeMatrix() {

View File

@ -1,10 +1,14 @@
package kscience.kmath.ejml
import kscience.kmath.linear.*
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.NDStructure
import kscience.kmath.structures.RealBuffer
import org.ejml.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix
import kotlin.reflect.KClass
import kotlin.reflect.cast
/**
* Represents featured matrix over EJML [SimpleMatrix].
@ -12,87 +16,75 @@ import org.ejml.simple.SimpleMatrix
* @property origin the underlying [SimpleMatrix].
* @author Iaroslav Postovalov
*/
public class EjmlMatrix(public val origin: SimpleMatrix, features: Set<MatrixFeature> = emptySet()) :
FeaturedMatrix<Double> {
public override val rowNum: Int
get() = origin.numRows()
public class EjmlMatrix(
public val origin: SimpleMatrix,
) : Matrix<Double> {
public override val rowNum: Int get() = origin.numRows()
public override val colNum: Int
get() = origin.numCols()
public override val colNum: Int get() = origin.numCols()
public override val shape: IntArray by lazy { intArrayOf(rowNum, colNum) }
public override val features: Set<MatrixFeature> = hashSetOf(
object : InverseMatrixFeature<Double> {
override val inverse: FeaturedMatrix<Double> by lazy { EjmlMatrix(origin.invert()) }
},
object : DeterminantFeature<Double> {
@UnstableKMathAPI
override fun <T : Any> getFeature(type: KClass<T>): T? = when (type) {
InverseMatrixFeature::class -> object : InverseMatrixFeature<Double> {
override val inverse: Matrix<Double> by lazy { EjmlMatrix(origin.invert()) }
}
DeterminantFeature::class -> object : DeterminantFeature<Double> {
override val determinant: Double by lazy(origin::determinant)
},
object : SingularValueDecompositionFeature<Double> {
}
SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<Double> {
private val svd by lazy {
DecompositionFactory_DDRM.svd(origin.numRows(), origin.numCols(), true, true, false)
.apply { decompose(origin.ddrm.copy()) }
}
override val u: FeaturedMatrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getU(null, false))) }
override val s: FeaturedMatrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getW(null))) }
override val v: FeaturedMatrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getV(null, false))) }
override val u: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getU(null, false))) }
override val s: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getW(null))) }
override val v: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(svd.getV(null, false))) }
override val singularValues: Point<Double> by lazy { RealBuffer(svd.singularValues) }
},
object : QRDecompositionFeature<Double> {
}
QRDecompositionFeature::class -> object : QRDecompositionFeature<Double> {
private val qr by lazy {
DecompositionFactory_DDRM.qr().apply { decompose(origin.ddrm.copy()) }
}
override val q: FeaturedMatrix<Double> by lazy { EjmlMatrix(SimpleMatrix(qr.getQ(null, false))) }
override val r: FeaturedMatrix<Double> by lazy { EjmlMatrix(SimpleMatrix(qr.getR(null, false))) }
},
object : CholeskyDecompositionFeature<Double> {
override val l: FeaturedMatrix<Double> by lazy {
override val q: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(qr.getQ(null, false))) }
override val r: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(qr.getR(null, false))) }
}
CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Double> {
override val l: Matrix<Double> by lazy {
val cholesky =
DecompositionFactory_DDRM.chol(rowNum, true).apply { decompose(origin.ddrm.copy()) }
EjmlMatrix(SimpleMatrix(cholesky.getT(null)), setOf(LFeature))
EjmlMatrix(SimpleMatrix(cholesky.getT(null))) + LFeature
}
},
object : LupDecompositionFeature<Double> {
}
LupDecompositionFeature::class -> object : LupDecompositionFeature<Double> {
private val lup by lazy {
DecompositionFactory_DDRM.lu(origin.numRows(), origin.numCols()).apply { decompose(origin.ddrm.copy()) }
}
override val l: FeaturedMatrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(lup.getLower(null)), setOf(LFeature))
override val l: Matrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(lup.getLower(null))) + LFeature
}
override val u: FeaturedMatrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(lup.getUpper(null)), setOf(UFeature))
override val u: Matrix<Double> by lazy {
EjmlMatrix(SimpleMatrix(lup.getUpper(null))) + UFeature
}
override val p: FeaturedMatrix<Double> by lazy { EjmlMatrix(SimpleMatrix(lup.getRowPivot(null))) }
},
) union features
public override fun suggestFeature(vararg features: MatrixFeature): EjmlMatrix =
EjmlMatrix(origin, this.features + features)
override val p: Matrix<Double> by lazy { EjmlMatrix(SimpleMatrix(lup.getRowPivot(null))) }
}
else -> null
}?.let { type.cast(it) }
public override operator fun get(i: Int, j: Int): Double = origin[i, j]
public override fun equals(other: Any?): Boolean {
if (other is EjmlMatrix) return origin.isIdentical(other.origin, 0.0)
return NDStructure.equals(this, other as? NDStructure<*> ?: return false)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Matrix<*>) return false
return NDStructure.contentEquals(this, other)
}
public override fun hashCode(): Int {
var result = origin.hashCode()
result = 31 * result + features.hashCode()
return result
}
override fun hashCode(): Int = origin.hashCode()
public override fun toString(): String = "EjmlMatrix(origin=$origin, features=$features)"
}

View File

@ -1,22 +1,30 @@
package kscience.kmath.ejml
import kscience.kmath.linear.InverseMatrixFeature
import kscience.kmath.linear.MatrixContext
import kscience.kmath.linear.Point
import kscience.kmath.linear.origin
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.getFeature
import org.ejml.simple.SimpleMatrix
/**
* Converts this matrix to EJML one.
*/
public fun Matrix<Double>.toEjml(): EjmlMatrix =
if (this is EjmlMatrix) this else EjmlMatrixContext.produce(rowNum, colNum) { i, j -> get(i, j) }
/**
* Represents context of basic operations operating with [EjmlMatrix].
*
* @author Iaroslav Postovalov
*/
public object EjmlMatrixContext : MatrixContext<Double, EjmlMatrix> {
/**
* Converts this matrix to EJML one.
*/
@OptIn(UnstableKMathAPI::class)
public fun Matrix<Double>.toEjml(): EjmlMatrix = when (val matrix = origin) {
is EjmlMatrix -> matrix
else -> produce(rowNum, colNum) { i, j -> get(i, j) }
}
/**
* Converts this vector to EJML one.
*/
@ -77,3 +85,8 @@ public fun EjmlMatrixContext.solve(a: Matrix<Double>, b: Matrix<Double>): EjmlMa
*/
public fun EjmlMatrixContext.solve(a: Matrix<Double>, b: Point<Double>): EjmlVector =
EjmlVector(a.toEjml().origin.solve(b.toEjml().origin))
@OptIn(UnstableKMathAPI::class)
public fun EjmlMatrix.inverted(): EjmlMatrix = getFeature<InverseMatrixFeature<Double>>()!!.inverse as EjmlMatrix
public fun EjmlMatrixContext.inverse(matrix: Matrix<Double>): Matrix<Double> = matrix.toEjml().inverted()

View File

@ -3,7 +3,9 @@ package kscience.kmath.ejml
import kscience.kmath.linear.DeterminantFeature
import kscience.kmath.linear.LupDecompositionFeature
import kscience.kmath.linear.MatrixFeature
import kscience.kmath.linear.getFeature
import kscience.kmath.linear.plus
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.getFeature
import org.ejml.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix
import kotlin.random.Random
@ -38,6 +40,7 @@ internal class EjmlMatrixTest {
assertEquals(listOf(m.numRows(), m.numCols()), w.shape.toList())
}
@OptIn(UnstableKMathAPI::class)
@Test
fun features() {
val m = randomMatrix
@ -56,9 +59,10 @@ internal class EjmlMatrixTest {
private object SomeFeature : MatrixFeature {}
@OptIn(UnstableKMathAPI::class)
@Test
fun suggestFeature() {
assertNotNull(EjmlMatrix(randomMatrix).suggestFeature(SomeFeature).getFeature<SomeFeature>())
assertNotNull((EjmlMatrix(randomMatrix) + SomeFeature).getFeature<SomeFeature>())
}
@Test

View File

@ -1,8 +1,12 @@
package kscience.kmath.real
import kscience.kmath.linear.*
import kscience.kmath.linear.MatrixContext
import kscience.kmath.linear.VirtualMatrix
import kscience.kmath.linear.inverseWithLUP
import kscience.kmath.linear.real
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.structures.Buffer
import kscience.kmath.structures.Matrix
import kscience.kmath.structures.RealBuffer
import kscience.kmath.structures.asIterable
import kotlin.math.pow
@ -19,7 +23,7 @@ import kotlin.math.pow
* Functions that help create a real (Double) matrix
*/
public typealias RealMatrix = FeaturedMatrix<Double>
public typealias RealMatrix = Matrix<Double>
public fun realMatrix(rowNum: Int, colNum: Int, initializer: (i: Int, j: Int) -> Double): RealMatrix =
MatrixContext.real.produce(rowNum, colNum, initializer)

View File

@ -1,6 +1,5 @@
package kaceince.kmath.real
import kscience.kmath.linear.VirtualMatrix
import kscience.kmath.linear.build
import kscience.kmath.real.*
import kscience.kmath.structures.Matrix
@ -42,7 +41,7 @@ internal class RealMatrixTest {
1.0, 0.0, 0.0,
0.0, 1.0, 2.0
)
assertEquals(VirtualMatrix.wrap(matrix2), matrix1.repeatStackVertical(3))
assertEquals(matrix2, matrix1.repeatStackVertical(3))
}
@Test

View File

@ -1,5 +1,6 @@
package kscience.kmath.nd4j
import kscience.kmath.misc.UnstableKMathAPI
import kscience.kmath.operations.*
import kscience.kmath.structures.NDAlgebra
import kscience.kmath.structures.NDField
@ -35,7 +36,7 @@ public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C, Nd4jArrayStructure<T>>
public override fun mapIndexed(
arg: Nd4jArrayStructure<T>,
transform: C.(index: IntArray, T) -> T
transform: C.(index: IntArray, T) -> T,
): Nd4jArrayStructure<T> {
check(arg)
val new = Nd4j.create(*shape).wrap()
@ -46,7 +47,7 @@ public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C, Nd4jArrayStructure<T>>
public override fun combine(
a: Nd4jArrayStructure<T>,
b: Nd4jArrayStructure<T>,
transform: C.(T, T) -> T
transform: C.(T, T) -> T,
): Nd4jArrayStructure<T> {
check(a, b)
val new = Nd4j.create(*shape).wrap()
@ -61,8 +62,8 @@ public interface Nd4jArrayAlgebra<T, C> : NDAlgebra<T, C, Nd4jArrayStructure<T>>
* @param T the type of the element contained in ND structure.
* @param S the type of space of structure elements.
*/
public interface Nd4jArraySpace<T, S> : NDSpace<T, S, Nd4jArrayStructure<T>>,
Nd4jArrayAlgebra<T, S> where S : Space<T> {
public interface Nd4jArraySpace<T, S : Space<T>> : NDSpace<T, S, Nd4jArrayStructure<T>>, Nd4jArrayAlgebra<T, S> {
public override val zero: Nd4jArrayStructure<T>
get() = Nd4j.zeros(*shape).wrap()
@ -103,7 +104,9 @@ public interface Nd4jArraySpace<T, S> : NDSpace<T, S, Nd4jArrayStructure<T>>,
* @param T the type of the element contained in ND structure.
* @param R the type of ring of structure elements.
*/
public interface Nd4jArrayRing<T, R> : NDRing<T, R, Nd4jArrayStructure<T>>, Nd4jArraySpace<T, R> where R : Ring<T> {
@OptIn(UnstableKMathAPI::class)
public interface Nd4jArrayRing<T, R : Ring<T>> : NDRing<T, R, Nd4jArrayStructure<T>>, Nd4jArraySpace<T, R> {
public override val one: Nd4jArrayStructure<T>
get() = Nd4j.ones(*shape).wrap()
@ -111,21 +114,21 @@ public interface Nd4jArrayRing<T, R> : NDRing<T, R, Nd4jArrayStructure<T>>, Nd4j
check(a, b)
return a.ndArray.mul(b.ndArray).wrap()
}
public override operator fun Nd4jArrayStructure<T>.minus(b: Number): Nd4jArrayStructure<T> {
check(this)
return ndArray.sub(b).wrap()
}
public override operator fun Nd4jArrayStructure<T>.plus(b: Number): Nd4jArrayStructure<T> {
check(this)
return ndArray.add(b).wrap()
}
public override operator fun Number.minus(b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> {
check(b)
return b.ndArray.rsub(this).wrap()
}
//
// public override operator fun Nd4jArrayStructure<T>.minus(b: Number): Nd4jArrayStructure<T> {
// check(this)
// return ndArray.sub(b).wrap()
// }
//
// public override operator fun Nd4jArrayStructure<T>.plus(b: Number): Nd4jArrayStructure<T> {
// check(this)
// return ndArray.add(b).wrap()
// }
//
// public override operator fun Number.minus(b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> {
// check(b)
// return b.ndArray.rsub(this).wrap()
// }
public companion object {
private val intNd4jArrayRingCache: ThreadLocal<MutableMap<IntArray, IntNd4jArrayRing>> =
@ -165,7 +168,8 @@ public interface Nd4jArrayRing<T, R> : NDRing<T, R, Nd4jArrayStructure<T>>, Nd4j
* @param N the type of ND structure.
* @param F the type field of structure elements.
*/
public interface Nd4jArrayField<T, F> : NDField<T, F, Nd4jArrayStructure<T>>, Nd4jArrayRing<T, F> where F : Field<T> {
public interface Nd4jArrayField<T, F : Field<T>> : NDField<T, F, Nd4jArrayStructure<T>>, Nd4jArrayRing<T, F> {
public override fun divide(a: Nd4jArrayStructure<T>, b: Nd4jArrayStructure<T>): Nd4jArrayStructure<T> {
check(a, b)
return a.ndArray.div(b.ndArray).wrap()

View File

@ -62,6 +62,7 @@ class MCScopeTest {
}
@OptIn(ObsoleteCoroutinesApi::class)
fun compareResult(test: ATest) {
val res1 = runBlocking(Dispatchers.Default) { test() }
val res2 = runBlocking(newSingleThreadContext("test")) { test() }

View File

@ -8,8 +8,8 @@ pluginManagement {
maven("https://dl.bintray.com/kotlin/kotlinx")
}
val toolsVersion = "0.7.1"
val kotlinVersion = "1.4.21"
val toolsVersion = "0.7.3-1.4.30-RC"
val kotlinVersion = "1.4.30-RC"
plugins {
id("kotlinx.benchmark") version "0.2.0-dev-20"