[WIP] moving from features to attributes

This commit is contained in:
Alexander Nozik 2023-06-20 19:45:21 +03:00
parent c0a7cff1d8
commit d3893ab7e6
17 changed files with 219 additions and 127 deletions

View File

@ -0,0 +1,17 @@
plugins {
id("space.kscience.gradle.mpp")
}
kscience {
jvm()
js()
native()
wasm()
}
readme {
maturity = space.kscience.gradle.Maturity.DEVELOPMENT
description = """
An API and basic implementation for arranging objects in a continuous memory block.
""".trimIndent()
}

View File

@ -0,0 +1,28 @@
/*
* 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.attributes
import kotlin.reflect.KType
public interface Attribute<T>
public interface AttributeWithDefault<T> : Attribute<T> {
public val default: T
}
public interface SetAttribute<V> : Attribute<Set<V>>
/**
* An attribute that has a type parameter for value
*/
public abstract class PolymorphicAttribute<T>(public val type: KType) : Attribute<T> {
override fun equals(other: Any?): Boolean = (other as? PolymorphicAttribute<*>)?.type == this.type
override fun hashCode(): Int {
return type.hashCode()
}
}

View File

@ -0,0 +1,13 @@
/*
* 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.attributes
/**
* A container for attributes. [attributes] could be made mutable by implementation
*/
public interface AttributeContainer {
public val attributes: Attributes
}

View File

@ -0,0 +1,71 @@
/*
* 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.attributes
import kotlin.jvm.JvmInline
@JvmInline
public value class Attributes internal constructor(public val content: Map<out Attribute<*>, Any>) {
public val keys: Set<Attribute<*>> get() = content.keys
@Suppress("UNCHECKED_CAST")
public operator fun <T> get(attribute: Attribute<T>): T? = content[attribute] as? T
override fun toString(): String = "Attributes(value=${content.entries})"
public companion object {
public val EMPTY: Attributes = Attributes(emptyMap())
}
}
public fun Attributes.isEmpty(): Boolean = content.isEmpty()
public fun <T> Attributes.getOrDefault(attribute: AttributeWithDefault<T>): T = get(attribute) ?: attribute.default
public fun <T, A : Attribute<T>> Attributes.withAttribute(
attribute: A,
attrValue: T?,
): Attributes = Attributes(
if (attrValue == null) {
content - attribute
} else {
content + (attribute to attrValue)
}
)
/**
* Add an element to a [SetAttribute]
*/
public fun <T, A : SetAttribute<T>> Attributes.withAttributeElement(
attribute: A,
attrValue: T,
): Attributes {
val currentSet: Set<T> = get(attribute) ?: emptySet()
return Attributes(
content + (attribute to (currentSet + attrValue))
)
}
/**
* Remove an element from [SetAttribute]
*/
public fun <T, A : SetAttribute<T>> Attributes.withoutAttributeElement(
attribute: A,
attrValue: T,
): Attributes {
val currentSet: Set<T> = get(attribute) ?: emptySet()
return Attributes(
content + (attribute to (currentSet - attrValue))
)
}
public fun <T : Any, A : Attribute<T>> Attributes(
attribute: A,
attrValue: T,
): Attributes = Attributes(mapOf(attribute to attrValue))
public operator fun Attributes.plus(other: Attributes): Attributes = Attributes(content + other.content)

View File

@ -0,0 +1,52 @@
/*
* 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.attributes
/**
* A safe builder for [Attributes]
*/
public class AttributesBuilder internal constructor(private val map: MutableMap<Attribute<*>, Any> = mutableMapOf()) {
@Suppress("UNCHECKED_CAST")
public operator fun <T> get(attribute: Attribute<T>): T? = map[attribute] as? T
public operator fun <V> Attribute<V>.invoke(value: V?) {
if (value == null) {
map.remove(this)
} else {
map[this] = value
}
}
public fun from(attributes: Attributes) {
map.putAll(attributes.content)
}
public fun <V> SetAttribute<V>.add(
attrValue: V,
) {
val currentSet: Set<V> = get(this) ?: emptySet()
map[this] = currentSet + attrValue
}
/**
* Remove an element from [SetAttribute]
*/
public fun <V> SetAttribute<V>.remove(
attrValue: V,
) {
val currentSet: Set<V> = get(this) ?: emptySet()
map[this] = currentSet - attrValue
}
public fun build(): Attributes = Attributes(map)
}
public fun AttributesBuilder(
attributes: Attributes,
): AttributesBuilder = AttributesBuilder(attributes.content.toMutableMap())
public fun Attributes(builder: AttributesBuilder.() -> Unit): Attributes = AttributesBuilder().apply(builder).build()

View File

@ -15,7 +15,7 @@ allprojects {
}
group = "space.kscience"
version = "0.3.2-dev-1"
version = "0.4.0-dev-1"
}
subprojects {

View File

@ -1,9 +1,3 @@
/*
* Copyright 2018-2021 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.
*/
rootProject.name = "kmath"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {

View File

@ -6,11 +6,10 @@ kotlin.code.style=official
kotlin.mpp.stability.nowarn=true
kotlin.native.ignoreDisabledTargets=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.14.8-kotlin-1.8.20
toolsVersion=0.14.9-kotlin-1.8.20
org.gradle.parallel=true
org.gradle.workers.max=4
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m

View File

@ -106,7 +106,7 @@ public object CMLinearSpace : LinearSpace<Double, DoubleField> {
val origin = structure.toCM().origin
return when (type) {
DiagonalFeature::class -> if (origin is DiagonalMatrix) DiagonalFeature else null
IsDiagonal::class -> if (origin is DiagonalMatrix) IsDiagonal else null
DeterminantFeature::class, LupDecompositionFeature::class -> object :
DeterminantFeature<Double>,

View File

@ -10,6 +10,7 @@ kscience{
dependencies {
api(projects.kmathMemory)
api(projects.attributesKt)
}
testDependencies {

View File

@ -5,54 +5,49 @@
package space.kscience.kmath.linear
import space.kscience.kmath.nd.StructureFeature
import space.kscience.attributes.Attribute
/**
* 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.
*/
public interface MatrixFeature: StructureFeature
public interface MatrixFeature<T> : Attribute<T>
/**
* Matrices with this feature are considered to have only diagonal non-null elements.
* Matrices with this feature are considered to have only diagonal non-zero elements.
*/
public interface DiagonalFeature : MatrixFeature {
public companion object : DiagonalFeature
public interface IsDiagonal : MatrixFeature<Unit> {
public companion object : IsDiagonal
}
/**
* Matrices with this feature have all zero elements.
*/
public object ZeroFeature : DiagonalFeature
public object IsZero : IsDiagonal
/**
* Matrices with this feature have unit elements on diagonal and zero elements in all other places.
*/
public object UnitFeature : DiagonalFeature
public object IsUnit : IsDiagonal
/**
* Matrices with this feature can be inverted: *[inverse] = a<sup>&minus;1</sup>* where *a* is the owning matrix.
*
* @param T the type of matrices' items.
*/
public interface InverseMatrixFeature<out T : Any> : MatrixFeature {
/**
* The inverse matrix of the matrix that owns this feature.
*/
public val inverse: Matrix<T>
public class Inverted<T> private constructor() : MatrixFeature<Matrix<T>> {
internal val instance: Inverted<Nothing> = Inverted()
}
@Suppress("UNCHECKED_CAST")
public val <T> LinearSpace<T, *>.Inverted: Inverted<T> get() = Inverted.instance as Inverted<T>
/**
* Matrices with this feature can compute their determinant.
*
* @param T the type of matrices' items.
*/
public interface DeterminantFeature<out T : Any> : MatrixFeature {
/**
* The determinant of the matrix that owns this feature.
*/
public val determinant: T
}
public class DeterminantFeature<T : Any> : MatrixFeature<T>
/**
* Produces a [DeterminantFeature] where the [DeterminantFeature.determinant] is [determinant].
@ -68,12 +63,12 @@ public fun <T : Any> DeterminantFeature(determinant: T): DeterminantFeature<T> =
/**
* Matrices with this feature are lower triangular ones.
*/
public object LFeature : MatrixFeature
public object LFeature : MatrixFeature<Unit>
/**
* Matrices with this feature are upper triangular ones.
*/
public object UFeature : MatrixFeature
public object UFeature : MatrixFeature<Unit>
/**
* Matrices with this feature support LU factorization: *a = [l] &middot; [u]* where *a* is the owning matrix.
@ -117,7 +112,7 @@ public interface LupDecompositionFeature<out T : Any> : MatrixFeature {
/**
* Matrices with this feature are orthogonal ones: *a &middot; a<sup>T</sup> = u* where *a* is the owning matrix, *u*
* is the unit matrix ([UnitFeature]).
* is the unit matrix ([IsUnit]).
*/
public object OrthogonalFeature : MatrixFeature

View File

@ -5,11 +5,10 @@
package space.kscience.kmath.linear
import space.kscience.attributes.Attributes
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.misc.FeatureSet
import space.kscience.kmath.nd.StructureFeature
import space.kscience.kmath.operations.Ring
import kotlin.reflect.KClass
/**
* A [Matrix] that holds [MatrixFeature] objects.
@ -18,18 +17,10 @@ import kotlin.reflect.KClass
*/
public class MatrixWrapper<out T : Any> internal constructor(
public val origin: Matrix<T>,
public val features: FeatureSet<StructureFeature>,
public val attributes: Attributes,
) : Matrix<T> by origin {
/**
* Get the first feature matching given class. Does not guarantee that matrix has only one feature matching the
* criteria.
*/
@Suppress("UNCHECKED_CAST")
override fun <F : StructureFeature> getFeature(type: KClass<out F>): F? =
features.getFeature(type) ?: origin.getFeature(type)
override fun toString(): String = "MatrixWrapper(matrix=$origin, features=$features)"
override fun toString(): String = "MatrixWrapper(matrix=$origin, features=$attributes)"
}
/**
@ -44,7 +35,7 @@ public val <T : Any> Matrix<T>.origin: Matrix<T>
* Add a single feature to a [Matrix]
*/
public fun <T : Any> Matrix<T>.withFeature(newFeature: MatrixFeature): MatrixWrapper<T> = if (this is MatrixWrapper) {
MatrixWrapper(origin, features.with(newFeature))
MatrixWrapper(origin, attributes.with(newFeature))
} else {
MatrixWrapper(this, FeatureSet.of(newFeature))
}
@ -57,20 +48,20 @@ public operator fun <T : Any> Matrix<T>.plus(newFeature: MatrixFeature): MatrixW
*/
public fun <T : Any> Matrix<T>.withFeatures(newFeatures: Iterable<MatrixFeature>): MatrixWrapper<T> =
if (this is MatrixWrapper) {
MatrixWrapper(origin, features.with(newFeatures))
MatrixWrapper(origin, attributes.with(newFeatures))
} else {
MatrixWrapper(this, FeatureSet.of(newFeatures))
}
/**
* Diagonal matrix of ones. The matrix is virtual no actual matrix is created.
* Diagonal matrix of ones. The matrix is virtual, no actual matrix is created.
*/
public fun <T : Any> LinearSpace<T, Ring<T>>.one(
rows: Int,
columns: Int,
): Matrix<T> = VirtualMatrix(rows, columns) { i, j ->
if (i == j) elementAlgebra.one else elementAlgebra.zero
}.withFeature(UnitFeature)
}.withFeature(IsUnit)
/**
@ -81,7 +72,7 @@ public fun <T : Any> LinearSpace<T, Ring<T>>.zero(
columns: Int,
): Matrix<T> = VirtualMatrix(rows, columns) { _, _ ->
elementAlgebra.zero
}.withFeature(ZeroFeature)
}.withFeature(IsZero)
public class TransposedFeature<out T : Any>(public val original: Matrix<T>) : MatrixFeature

View File

@ -1,61 +0,0 @@
/*
* Copyright 2018-2022 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.misc
import kotlin.jvm.JvmInline
import kotlin.reflect.KClass
/**
* An entity that contains a set of features defined by their types
*/
public interface Featured<F : Any> {
public fun <T : F> getFeature(type: FeatureKey<T>): T?
}
public typealias FeatureKey<T> = KClass<out T>
public interface Feature<F : Feature<F>> {
/**
* A key used for extraction
*/
@Suppress("UNCHECKED_CAST")
public val key: FeatureKey<F>
get() = this::class as FeatureKey<F>
}
/**
* A container for a set of features
*/
@JvmInline
public value class FeatureSet<F : Feature<F>> private constructor(public val features: Map<FeatureKey<F>, F>) : Featured<F> {
@Suppress("UNCHECKED_CAST")
override fun <T : F> getFeature(type: FeatureKey<T>): T? = features[type]?.let { it as T }
public inline fun <reified T : F> getFeature(): T? = getFeature(T::class)
public fun <T : F> with(feature: T, type: FeatureKey<F> = feature.key): FeatureSet<F> =
FeatureSet(features + (type to feature))
public fun with(other: FeatureSet<F>): FeatureSet<F> = FeatureSet(features + other.features)
public fun with(vararg otherFeatures: F): FeatureSet<F> =
FeatureSet(features + otherFeatures.associateBy { it.key })
public fun with(otherFeatures: Iterable<F>): FeatureSet<F> =
FeatureSet(features + otherFeatures.associateBy { it.key })
public operator fun iterator(): Iterator<F> = features.values.iterator()
override fun toString(): String = features.values.joinToString(prefix = "[ ", postfix = " ]")
public companion object {
public fun <F : Feature<F>> of(vararg features: F): FeatureSet<F> = FeatureSet(features.associateBy { it.key })
public fun <F : Feature<F>> of(features: Iterable<F>): FeatureSet<F> =
FeatureSet(features.associateBy { it.key })
}
}

View File

@ -114,7 +114,7 @@ public interface GroupOpsND<T, out A : GroupOps<T>> : GroupOps<StructureND<T>>,
override fun add(left: StructureND<T>, right: StructureND<T>): StructureND<T> =
zip(left, right) { aValue, bValue -> add(aValue, bValue) }
// TODO move to extensions after KEEP-176
// TODO implement using context receivers
/**
* Adds an ND structure to an element of it.
@ -181,8 +181,6 @@ public interface RingOpsND<T, out A : RingOps<T>> : RingOps<StructureND<T>>, Gro
override fun multiply(left: StructureND<T>, right: StructureND<T>): StructureND<T> =
zip(left, right) { aValue, bValue -> multiply(aValue, bValue) }
//TODO move to extensions with context receivers
/**
* Multiplies an ND structure by an element of it.
*
@ -232,7 +230,6 @@ public interface FieldOpsND<T, out A : Field<T>> :
override fun divide(left: StructureND<T>, right: StructureND<T>): StructureND<T> =
zip(left, right) { aValue, bValue -> divide(aValue, bValue) }
//TODO move to extensions after https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md
/**
* Divides an ND structure by an element of it.
*

View File

@ -5,10 +5,10 @@
package space.kscience.kmath.nd
import space.kscience.attributes.Attribute
import space.kscience.attributes.AttributeContainer
import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.linear.LinearSpace
import space.kscience.kmath.misc.Feature
import space.kscience.kmath.misc.Featured
import space.kscience.kmath.operations.Ring
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.structures.Buffer
@ -17,7 +17,7 @@ import kotlin.jvm.JvmName
import kotlin.math.abs
import kotlin.reflect.KClass
public interface StructureFeature : Feature<StructureFeature>
public interface StructureFeature<T> : Attribute<T>
/**
* Represents n-dimensional structure i.e., multidimensional container of items of the same type and size. The number
@ -28,7 +28,7 @@ public interface StructureFeature : Feature<StructureFeature>
*
* @param T the type of items.
*/
public interface StructureND<out T> : Featured<StructureFeature>, WithShape {
public interface StructureND<out T> : AttributeContainer, WithShape {
/**
* The shape of structure i.e., non-empty sequence of non-negative integers that specify sizes of dimensions of
* this structure.
@ -57,12 +57,6 @@ public interface StructureND<out T> : Featured<StructureFeature>, WithShape {
@PerformancePitfall
public fun elements(): Sequence<Pair<IntArray, T>> = indices.asSequence().map { it to get(it) }
/**
* Feature is some additional structure information that allows to access it special properties or hints.
* If the feature is not present, `null` is returned.
*/
override fun <F : StructureFeature> getFeature(type: KClass<out F>): F? = null
public companion object {
/**
* Indicates whether some [StructureND] is equal to another one.

View File

@ -10,7 +10,7 @@ plugins {
description = "Symja integration module"
dependencies {
api("org.matheclipse:matheclipse-core:2.0.0-SNAPSHOT") {
api("org.matheclipse:matheclipse-core:2.0.0") {
// Incorrect transitive dependencies
exclude("org.apfloat", "apfloat")
exclude("org.hipparchus", "hipparchus-clustering")

View File

@ -21,6 +21,7 @@ dependencyResolutionManagement {
include(
":test-utils",
":attributes-kt",
":kmath-memory",
":kmath-complex",
":kmath-core",