[WIP] Features to Attributes refactoring

This commit is contained in:
Alexander Nozik 2023-07-18 10:13:36 +03:00
parent 6da51b7794
commit 4abe25c188
24 changed files with 84 additions and 86 deletions

View File

@ -8,6 +8,7 @@
### Changed ### Changed
- Features replaced with Attributes. - Features replaced with Attributes.
- Transposed refactored.
### Deprecated ### Deprecated

View File

@ -5,6 +5,7 @@
package space.kscience.attributes package space.kscience.attributes
import kotlin.jvm.JvmInline
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
@ -14,7 +15,8 @@ import kotlin.reflect.typeOf
* *
* @param kType raw [KType] * @param kType raw [KType]
*/ */
public class SafeType<T> @PublishedApi internal constructor(public val kType: KType) @JvmInline
public value class SafeType<T> @PublishedApi internal constructor(public val kType: KType)
public inline fun <reified T> safeTypeOf(): SafeType<T> = SafeType(typeOf<T>()) public inline fun <reified T> safeTypeOf(): SafeType<T> = SafeType(typeOf<T>())

View File

@ -15,7 +15,7 @@ private fun DMatrixContext<Double, *>.simple() {
val m2 = produce<D3, D2> { i, j -> (i + j).toDouble() } val m2 = produce<D3, D2> { i, j -> (i + j).toDouble() }
//Dimension-safe addition //Dimension-safe addition
m1.transpose() + m2 m1.transposed() + m2
} }
private object D5 : Dimension { private object D5 : Dimension {

View File

@ -125,7 +125,7 @@ public object CMOptimizer : Optimizer<Double, FunctionOptimization<Double>> {
val logger = problem.getFeature<OptimizationLog>() val logger = problem.getFeature<OptimizationLog>()
for (feature in problem.features) { for (feature in problem.attributes) {
when (feature) { when (feature) {
is CMOptimizerData -> feature.data.forEach { dataBuilder -> is CMOptimizerData -> feature.data.forEach { dataBuilder ->
addOptimizationData(dataBuilder()) addOptimizationData(dataBuilder())

View File

@ -174,9 +174,8 @@ public interface LinearSpace<T, out A : Ring<T>> : MatrixOperations<T> {
/** /**
* Get an attribute value for the structure in this scope. Structure features take precedence other context features. * Get an attribute value for the structure in this scope. Structure features take precedence other context features.
* *
* @param A the type of feature.
* @param structure the structure. * @param structure the structure.
* @param attribute to be computed * @param attribute to be computed.
* @return a feature object or `null` if it isn't present. * @return a feature object or `null` if it isn't present.
*/ */
@UnstableKMathAPI @UnstableKMathAPI

View File

@ -21,12 +21,11 @@ import space.kscience.kmath.structures.*
* @param T the type of matrices' items. * @param T the type of matrices' items.
* @param l The lower triangular matrix in this decomposition. It may have [LowerTriangular]. * @param l The lower triangular matrix in this decomposition. It may have [LowerTriangular].
* @param u The upper triangular matrix in this decomposition. It may have [UpperTriangular]. * @param u The upper triangular matrix in this decomposition. It may have [UpperTriangular].
* @param p he permutation matrix in this decomposition. May have [Determinant] attribute
*/ */
public data class LupDecomposition<T>( public data class LupDecomposition<T>(
public val l: Matrix<T>, public val l: Matrix<T>,
public val u: Matrix<T>, public val u: Matrix<T>,
public val p: Matrix<T>, public val pivot: IntBuffer,
) )
@ -180,12 +179,12 @@ public fun <T : Comparable<T>> LinearSpace<T, Field<T>>.lup(
val u = VirtualMatrix(rowNum, colNum) { i, j -> val u = VirtualMatrix(rowNum, colNum) { i, j ->
if (j >= i) lu[i, j] else zero if (j >= i) lu[i, j] else zero
}.withAttribute(UpperTriangular) }.withAttribute(UpperTriangular)
//
// val p = VirtualMatrix(rowNum, colNum) { i, j ->
// if (j == pivot[i]) one else zero
// }.withAttribute(Determinant, if (even) one else -one)
val p = VirtualMatrix(rowNum, colNum) { i, j -> return LupDecomposition(l, u, pivot.asBuffer())
if (j == pivot[i]) one else zero
}.withAttribute(Determinant, if (even) one else -one)
return LupDecomposition(l, u, p)
} }
} }
} }
@ -209,7 +208,7 @@ internal fun <T : Any, A : Field<T>> LinearSpace<T, A>.solve(
for (row in 0 until rowNum) { for (row in 0 until rowNum) {
val bpRow = bp.row(row) val bpRow = bp.row(row)
val pRow = pivot[row] val pRow = lup.pivot[row]
for (col in 0 until matrix.colNum) bpRow[col] = matrix[pRow, col] for (col in 0 until matrix.colNum) bpRow[col] = matrix[pRow, col]
} }

View File

@ -11,8 +11,6 @@ package space.kscience.kmath.linear
import space.kscience.attributes.* import space.kscience.attributes.*
import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.nd.StructureAttribute import space.kscience.kmath.nd.StructureAttribute
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/** /**
* A marker interface representing some properties of matrices or additional transformations of them. Features are used * A marker interface representing some properties of matrices or additional transformations of them. Features are used
@ -53,11 +51,9 @@ public val <T> MatrixOperations<T>.Inverted: Inverted<T> get() = Inverted(safeTy
* *
* @param T the type of matrices' items. * @param T the type of matrices' items.
*/ */
public class Determinant<T>(type: SafeType<T>) : public class Determinant<T> : MatrixAttribute<T>
PolymorphicAttribute<T>(type),
MatrixAttribute<T>
public inline val <reified T> MatrixOperations<T>.Determinant: Determinant<T> get() = Determinant(safeTypeOf()) public val <T> MatrixOperations<T>.Determinant: Determinant<T> get() = Determinant()
/** /**
* Matrices with this feature are lower triangular ones. * Matrices with this feature are lower triangular ones.
@ -174,4 +170,5 @@ public class SingularValueDecompositionAttribute<T>(type: SafeType<SingularValue
public val <T> MatrixOperations<T>.SVD: SingularValueDecompositionAttribute<T> public val <T> MatrixOperations<T>.SVD: SingularValueDecompositionAttribute<T>
get() = SingularValueDecompositionAttribute(safeTypeOf()) get() = SingularValueDecompositionAttribute(safeTypeOf())
//TODO add sparse matrix feature //TODO add sparse matrix feature

View File

@ -82,13 +82,3 @@ public fun <T : Any> LinearSpace<T, Ring<T>>.zero(
): MatrixWrapper<T> = VirtualMatrix(rows, columns) { _, _ -> ): MatrixWrapper<T> = VirtualMatrix(rows, columns) { _, _ ->
elementAlgebra.zero elementAlgebra.zero
}.withAttribute(IsZero) }.withAttribute(IsZero)
public class TransposedAttribute<out T : Any>(public val original: Matrix<T>) : MatrixAttribute
/**
* Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A`
*/
@Suppress("UNCHECKED_CAST")
@OptIn(UnstableKMathAPI::class)
public fun <T : Any> Matrix<T>.transpose(): Matrix<T> = getFeature(TransposedAttribute::class)?.original as? Matrix<T>
?: VirtualMatrix(colNum, rowNum) { i, j -> get(j, i) }.withAttribute(TransposedAttribute(this))

View File

@ -0,0 +1,25 @@
/*
* Copyright 2018-2023 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.linear
import space.kscience.attributes.Attributes
public class TransposedMatrix<T>(public val origin: Matrix<T>) : Matrix<T> {
override val rowNum: Int get() = origin.colNum
override val colNum: Int get() = origin.rowNum
override fun get(i: Int, j: Int): T = origin[j, i]
override val attributes: Attributes get() = Attributes.EMPTY
}
/**
* Create a virtual transposed matrix without copying anything. `A.transpose().transpose() === A`
*/
public val <T> Matrix<T>.transposed: Matrix<T>
get() = (this as? TransposedMatrix<T>)?.origin ?: TransposedMatrix(this)

View File

@ -29,5 +29,4 @@ public class VirtualMatrix<out T : Any>(
public fun <T : Any> MatrixBuilder<T, *>.virtual( public fun <T : Any> MatrixBuilder<T, *>.virtual(
attributes: Attributes = Attributes.EMPTY, attributes: Attributes = Attributes.EMPTY,
generator: (i: Int, j: Int) -> T, generator: (i: Int, j: Int) -> T,
): VirtualMatrix<T> = ): VirtualMatrix<T> = VirtualMatrix(rows, columns, attributes, generator)
VirtualMatrix(rows, columns, attributes, generator)

View File

@ -71,31 +71,19 @@ public interface AlgebraND<T, out C : Algebra<T>> : Algebra<StructureND<T>> {
structure.map { value -> this@invoke(value) } structure.map { value -> this@invoke(value) }
/** /**
* Get a feature of the structure in this scope. Structure features take precedence other context features. * Get an attribute value for the structure in this scope. Structure features take precedence other context features.
* *
* @param F the type of feature.
* @param structure the structure. * @param structure the structure.
* @param type the [KClass] instance of [F]. * @param attribute to be computed.
* @return a feature object or `null` if it isn't present. * @return a feature object or `null` if it isn't present.
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public fun <F : StructureAttribute> getFeature(structure: StructureND<T>, type: KClass<out F>): F? = public fun <T, A : StructureAttribute<T>> attributeFor(structure: StructureND<*>, attribute: A): T? =
structure.getFeature(type) structure.attributes[attribute]
public companion object public companion object
} }
/**
* Get a feature of the structure in this scope. Structure features take precedence other context features.
*
* @param T the type of items in the matrices.
* @param F the type of feature.
* @return a feature object or `null` if it isn't present.
*/
@UnstableKMathAPI
public inline fun <T : Any, reified F : StructureAttribute> AlgebraND<T, *>.getFeature(structure: StructureND<T>): F? =
getFeature(structure, F::class)
/** /**
* Space of [StructureND]. * Space of [StructureND].
* *

View File

@ -36,8 +36,8 @@ public open class BufferND<out T>(
*/ */
public inline fun <reified T> BufferND( public inline fun <reified T> BufferND(
shape: ShapeND, shape: ShapeND,
bufferFactory: BufferFactory<T> = BufferFactory.auto(), bufferFactory: BufferFactory<T> = BufferFactory.auto<T>(),
initializer: (IntArray) -> T, crossinline initializer: (IntArray) -> T,
): BufferND<T> { ): BufferND<T> {
val strides = Strides(shape) val strides = Strides(shape)
return BufferND(strides, bufferFactory(strides.linearSize) { initializer(strides.index(it)) }) return BufferND(strides, bufferFactory(strides.linearSize) { initializer(strides.index(it)) })

View File

@ -5,6 +5,7 @@
package space.kscience.kmath.nd package space.kscience.kmath.nd
import space.kscience.attributes.Attributes
import space.kscience.kmath.PerformancePitfall import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.MutableBuffer import space.kscience.kmath.structures.MutableBuffer
@ -110,7 +111,8 @@ private value class Structure2DWrapper<out T>(val structure: StructureND<T>) : S
@PerformancePitfall @PerformancePitfall
override operator fun get(i: Int, j: Int): T = structure[i, j] override operator fun get(i: Int, j: Int): T = structure[i, j]
override fun <F : StructureAttribute> getFeature(type: KClass<out F>): F? = structure.getFeature(type) override val attributes: Attributes
get() = structure.attributes
@PerformancePitfall @PerformancePitfall
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements() override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()

View File

@ -7,13 +7,13 @@ package space.kscience.kmath.nd
import space.kscience.attributes.Attribute import space.kscience.attributes.Attribute
import space.kscience.attributes.AttributeContainer import space.kscience.attributes.AttributeContainer
import space.kscience.attributes.Attributes
import space.kscience.attributes.SafeType import space.kscience.attributes.SafeType
import space.kscience.kmath.PerformancePitfall import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.LinearSpace
import space.kscience.kmath.operations.Ring import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.invoke import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.math.abs import kotlin.math.abs
@ -57,6 +57,8 @@ public interface StructureND<out T> : AttributeContainer, WithShape {
@PerformancePitfall @PerformancePitfall
public fun elements(): Sequence<Pair<IntArray, T>> = indices.asSequence().map { it to get(it) } public fun elements(): Sequence<Pair<IntArray, T>> = indices.asSequence().map { it to get(it) }
override val attributes: Attributes get() = Attributes.EMPTY
public companion object { public companion object {
/** /**
* Indicates whether some [StructureND] is equal to another one. * Indicates whether some [StructureND] is equal to another one.

View File

@ -20,8 +20,6 @@ public interface WithSize {
public interface BufferAlgebra<T, out A : Algebra<T>> : Algebra<Buffer<T>> { public interface BufferAlgebra<T, out A : Algebra<T>> : Algebra<Buffer<T>> {
public val elementAlgebra: A public val elementAlgebra: A
public val elementType: KType
public val elementBufferFactory: MutableBufferFactory<T> get() = elementAlgebra.bufferFactory public val elementBufferFactory: MutableBufferFactory<T> get() = elementAlgebra.bufferFactory
public fun buffer(size: Int, vararg elements: T): Buffer<T> { public fun buffer(size: Int, vararg elements: T): Buffer<T> {

View File

@ -21,7 +21,7 @@ public fun interface BufferFactory<T> {
public operator fun invoke(size: Int, builder: (Int) -> T): Buffer<T> public operator fun invoke(size: Int, builder: (Int) -> T): Buffer<T>
public companion object{ public companion object{
public inline fun <reified T : Any> auto(): BufferFactory<T> = public inline fun <reified T> auto(): BufferFactory<T> =
BufferFactory(Buffer.Companion::auto) BufferFactory(Buffer.Companion::auto)
public fun <T> boxing(): BufferFactory<T> = public fun <T> boxing(): BufferFactory<T> =

View File

@ -146,8 +146,8 @@ public value class DMatrixContext<T : Any, out A : Ring<T>>(public val context:
public inline operator fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.unaryMinus(): DMatrix<T, C, R> = public inline operator fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.unaryMinus(): DMatrix<T, C, R> =
context.run { this@unaryMinus.unaryMinus() }.coerce() context.run { this@unaryMinus.unaryMinus() }.coerce()
public inline fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.transpose(): DMatrix<T, R, C> = public inline fun <reified R : Dimension, reified C : Dimension> DMatrix<T, C, R>.transposed(): DMatrix<T, R, C> =
context.run { (this@transpose as Matrix<T>).transpose() }.coerce() context.run { (this@transposed as Matrix<T>).transposed }.coerce()
public companion object { public companion object {
public val real: DMatrixContext<Double, DoubleField> = DMatrixContext(Double.algebra.linearSpace) public val real: DMatrixContext<Double, DoubleField> = DMatrixContext(Double.algebra.linearSpace)

View File

@ -30,7 +30,7 @@ internal class DMatrixContextTest {
val m2 = produce<D3, D2> { i, j -> (i + j).toDouble() } val m2 = produce<D3, D2> { i, j -> (i + j).toDouble() }
//Dimension-safe addition //Dimension-safe addition
m1.transpose() + m2 m1.transposed() + m2
} }
} }
} }

View File

@ -9,8 +9,6 @@ kscience{
wasm() wasm()
dependencies { dependencies {
api(projects.kmathCore) api(projects.kmathCore)
} }

View File

@ -68,9 +68,6 @@ public open class PolynomialSpace<C, A>(
public val ring: A, public val ring: A,
) : Ring<Polynomial<C>>, ScaleOperations<Polynomial<C>> where A : Ring<C>, A : ScaleOperations<C> { ) : Ring<Polynomial<C>>, ScaleOperations<Polynomial<C>> where A : Ring<C>, A : ScaleOperations<C> {
@UnstableKMathAPI
override val elementType: KType get() = typeOf<Polynomial<C>>()
/** /**
* Instance of zero constant (zero of the underlying ring). * Instance of zero constant (zero of the underlying ring).
*/ */

View File

@ -5,21 +5,21 @@
package space.kscience.kmath.optimization package space.kscience.kmath.optimization
import space.kscience.attributes.*
import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.DifferentiableExpression
import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.misc.FeatureSet
public class OptimizationValue<T>(public val value: T) : OptimizationFeature { public class OptimizationValue<T>(public val value: T) : OptimizationFeature {
override fun toString(): String = "Value($value)" override fun toString(): String = "Value($value)"
} }
public enum class FunctionOptimizationTarget : OptimizationFeature { public enum class FunctionOptimizationTarget {
MAXIMIZE, MAXIMIZE,
MINIMIZE MINIMIZE
} }
public class FunctionOptimization<T>( public class FunctionOptimization<T>(
override val features: FeatureSet<OptimizationFeature>, override val attributes: Attributes,
public val expression: DifferentiableExpression<T>, public val expression: DifferentiableExpression<T>,
) : OptimizationProblem<T> { ) : OptimizationProblem<T> {
@ -30,25 +30,36 @@ public class FunctionOptimization<T>(
other as FunctionOptimization<*> other as FunctionOptimization<*>
if (features != other.features) return false if (attributes != other.attributes) return false
if (expression != other.expression) return false if (expression != other.expression) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = features.hashCode() var result = attributes.hashCode()
result = 31 * result + expression.hashCode() result = 31 * result + expression.hashCode()
return result return result
} }
override fun toString(): String = "FunctionOptimization(features=$features)" override fun toString(): String = "FunctionOptimization(features=$attributes)"
public companion object
} }
public class OptimizationPrior<T>(type: SafeType<T>):
PolymorphicAttribute<DifferentiableExpression<T>>(safeTypeOf()),
Attribute<DifferentiableExpression<T>>
public val <T> FunctionOptimization.Companion.Optimization get() =
public fun <T> FunctionOptimization<T>.withFeatures( public fun <T> FunctionOptimization<T>.withFeatures(
vararg newFeature: OptimizationFeature, vararg newFeature: OptimizationFeature,
): FunctionOptimization<T> = FunctionOptimization( ): FunctionOptimization<T> = FunctionOptimization(
features.with(*newFeature), attributes.with(*newFeature),
expression, expression,
) )

View File

@ -5,21 +5,16 @@
package space.kscience.kmath.optimization package space.kscience.kmath.optimization
import space.kscience.attributes.*
import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.DifferentiableExpression
import space.kscience.kmath.expressions.NamedMatrix import space.kscience.kmath.expressions.NamedMatrix
import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.misc.* import space.kscience.kmath.misc.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
public interface OptimizationFeature : Feature<OptimizationFeature> { public interface OptimizationAttribute<T>: Attribute<T>
// enforce toString override
override fun toString(): String
}
public interface OptimizationProblem<T> : Featured<OptimizationFeature> { public interface OptimizationProblem<T> : AttributeContainer
public val features: FeatureSet<OptimizationFeature>
override fun <F : OptimizationFeature> getFeature(type: KClass<out F>): F? = features.getFeature(type)
}
public inline fun <reified F : OptimizationFeature> OptimizationProblem<*>.getFeature(): F? = getFeature(F::class) public inline fun <reified F : OptimizationFeature> OptimizationProblem<*>.getFeature(): F? = getFeature(F::class)
@ -27,11 +22,6 @@ public open class OptimizationStartPoint<T>(public val point: Map<Symbol, T>) :
override fun toString(): String = "StartPoint($point)" override fun toString(): String = "StartPoint($point)"
} }
public interface OptimizationPrior<T> : OptimizationFeature, DifferentiableExpression<T> {
override val key: FeatureKey<OptimizationFeature> get() = OptimizationPrior::class
}
/** /**
* Covariance matrix for * Covariance matrix for
*/ */

View File

@ -79,7 +79,7 @@ public interface PointWeight : OptimizationFeature {
public class XYFit( public class XYFit(
public val data: XYColumnarData<Double, Double, Double>, public val data: XYColumnarData<Double, Double, Double>,
public val model: DifferentiableExpression<Double>, public val model: DifferentiableExpression<Double>,
override val features: FeatureSet<OptimizationFeature>, override val attributes: FeatureSet<OptimizationFeature>,
internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY,
internal val pointWeight: PointWeight = PointWeight.byYSigma, internal val pointWeight: PointWeight = PointWeight.byYSigma,
public val xSymbol: Symbol = Symbol.x, public val xSymbol: Symbol = Symbol.x,
@ -90,7 +90,7 @@ public class XYFit(
} }
public fun XYFit.withFeature(vararg features: OptimizationFeature): XYFit { public fun XYFit.withFeature(vararg features: OptimizationFeature): XYFit {
return XYFit(data, model, this.features.with(*features), pointToCurveDistance, pointWeight) return XYFit(data, model, this.attributes.with(*features), pointToCurveDistance, pointWeight)
} }
public suspend fun XYColumnarData<Double, Double, Double>.fitWith( public suspend fun XYColumnarData<Double, Double, Double>.fitWith(

View File

@ -53,9 +53,9 @@ internal fun XYFit.logLikelihood(): DifferentiableExpression<Double> = object :
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public suspend fun Optimizer<Double, FunctionOptimization<Double>>.maximumLogLikelihood(problem: XYFit): XYFit { public suspend fun Optimizer<Double, FunctionOptimization<Double>>.maximumLogLikelihood(problem: XYFit): XYFit {
val functionOptimization = FunctionOptimization(problem.features, problem.logLikelihood()) val functionOptimization = FunctionOptimization(problem.attributes, problem.logLikelihood())
val result = optimize(functionOptimization.withFeatures(FunctionOptimizationTarget.MAXIMIZE)) val result = optimize(functionOptimization.withFeatures(FunctionOptimizationTarget.MAXIMIZE))
return XYFit(problem.data, problem.model, result.features) return XYFit(problem.data, problem.model, result.attributes)
} }
@UnstableKMathAPI @UnstableKMathAPI