This commit is contained in:
Alexander Nozik 2023-11-11 10:19:09 +03:00
parent 2386ecba41
commit 2f2f552648
11 changed files with 121 additions and 58 deletions

View File

@ -5,8 +5,6 @@
package space.kscience.attributes
import kotlin.reflect.KType
public interface Attribute<T>
/**

View File

@ -8,7 +8,7 @@ package space.kscience.attributes
import kotlin.jvm.JvmInline
@JvmInline
public value class Attributes internal constructor(public val content: Map<out Attribute<*>, Any>) {
public value class Attributes internal constructor(public val content: Map<out Attribute<*>, Any?>) {
public val keys: Set<Attribute<*>> get() = content.keys
@ -51,7 +51,7 @@ public inline fun <reified A : FlagAttribute> Attributes.has(): Boolean =
/**
* Create [Attributes] with an added or replaced attribute key.
*/
public fun <T : Any, A : Attribute<T>> Attributes.withAttribute(
public fun <T, A : Attribute<T>> Attributes.withAttribute(
attribute: A,
attrValue: T,
): Attributes = Attributes(content + (attribute to attrValue))
@ -101,7 +101,7 @@ public fun <T, A : SetAttribute<T>> Attributes.withoutAttributeElement(
/**
* Create [Attributes] with a single key
*/
public fun <T : Any, A : Attribute<T>> Attributes(
public fun <T, A : Attribute<T>> Attributes(
attribute: A,
attrValue: T,
): Attributes = Attributes(mapOf(attribute to attrValue))

View File

@ -10,7 +10,7 @@ package space.kscience.attributes
*
* @param O type marker of an owner object, for which these attributes are made
*/
public class TypedAttributesBuilder<in O> internal constructor(private val map: MutableMap<Attribute<*>, Any>) {
public class TypedAttributesBuilder<in O> internal constructor(private val map: MutableMap<Attribute<*>, Any?>) {
public constructor() : this(mutableMapOf())

View File

@ -5,8 +5,10 @@
package space.kscience.kmath.linear
import space.kscience.attributes.Attributes
import space.kscience.attributes.SafeType
import space.kscience.attributes.WithType
import space.kscience.attributes.withAttribute
import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.nd.*
import space.kscience.kmath.operations.BufferRingOps
@ -173,15 +175,36 @@ public interface LinearSpace<T, out A : Ring<T>> : MatrixOperations<T> {
public operator fun T.times(v: Point<T>): Point<T> = v * this
/**
* Get an attribute value for the structure in this scope. Structure attributes are preferred to computed attributes.
* Compute an [attribute] value for given [structure]. Return null if the attribute could not be computed.
*/
public fun <V, A : StructureAttribute<V>> computeAttribute(structure: StructureND<*>, attribute: A): V? = null
@UnstableKMathAPI
public fun <V, A : StructureAttribute<V>> StructureND<*>.getOrComputeAttribute(attribute: A): V? {
return attributes[attribute] ?: computeAttribute(this, attribute)
}
/**
* If the structure holds given [attribute] return itself. Otherwise, return a new [Matrix] that contains a computed attribute.
*
* @param structure the structure.
* @param attribute to be computed.
* @return a feature object or `null` if it isn't present.
* This method is used to compute and cache attribute inside the structure. If one needs an attribute only once,
* better use [StructureND.getOrComputeAttribute].
*/
@UnstableKMathAPI
public fun <T, A : StructureAttribute<T>> attributeFor(structure: StructureND<*>, attribute: A): T =
structure.attributes[attribute] ?: error("Can't compute attribute $attribute for $structure")
public fun <V : Any, A : StructureAttribute<V>> Matrix<T>.compute(
attribute: A,
): Matrix<T>? {
return if (attributes[attribute] != null) {
this
} else {
val value = computeAttribute(this, attribute) ?: return null
if (this is MatrixWrapper) {
MatrixWrapper(this, attributes.withAttribute(attribute, value))
} else {
MatrixWrapper(this, Attributes(attribute, value))
}
}
}
public companion object {

View File

@ -209,8 +209,8 @@ public fun <T : Comparable<T>, F : Field<T>> LinearSpace<T, F>.lupSolver(
singularityCheck: (T) -> Boolean,
): LinearSolver<T> = object : LinearSolver<T> {
override fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
// Use existing decomposition if it is provided by matrix
val decomposition = attributeFor(a, LUP) ?: lup(a, singularityCheck)
// Use existing decomposition if it is provided by matrix or linear space itself
val decomposition = a.getOrComputeAttribute(LUP) ?: lup(a, singularityCheck)
return solve(decomposition, b)
}

View File

@ -39,5 +39,5 @@ public abstract class EjmlLinearSpace<T : Any, out A : Ring<T>, out M : org.ejml
@UnstableKMathAPI
public fun EjmlMatrix<T, *>.inverted(): Matrix<Double> =
attributeFor(this, Float64Field.linearSpace.Inverted)
attributeForOrNull(this, Float64Field.linearSpace.Inverted)
}

View File

@ -61,9 +61,9 @@ internal class EjmlMatrixTest {
fun features() {
val m = randomMatrix
val w = EjmlDoubleMatrix(m)
val det: Determinant<Double> = EjmlLinearSpaceDDRM.attributeFor(w) ?: fail()
val det: Determinant<Double> = EjmlLinearSpaceDDRM.attributeForOrNull(w) ?: fail()
assertEquals(CommonOps_DDRM.det(m), det.determinant)
val lup: LupDecompositionAttribute<Double> = EjmlLinearSpaceDDRM.attributeFor(w) ?: fail()
val lup: LupDecompositionAttribute<Double> = EjmlLinearSpaceDDRM.attributeForOrNull(w) ?: fail()
val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows, m.numCols)
.also { it.decompose(m.copy()) }

View File

@ -12,8 +12,6 @@ import space.kscience.kmath.chains.combine
import space.kscience.kmath.random.RandomGenerator
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.BufferFactory
import space.kscience.kmath.structures.Float64Buffer
import space.kscience.kmath.structures.Int32Buffer
import kotlin.jvm.JvmName
/**
@ -36,7 +34,7 @@ public fun interface Sampler<out T : Any> {
public fun <T : Any> Sampler<T>.sampleBuffer(
generator: RandomGenerator,
size: Int,
bufferFactory: BufferFactory<T> = BufferFactory.boxing(),
bufferFactory: BufferFactory<T>
): Chain<Buffer<T>> {
require(size > 1)
//creating temporary storage once
@ -58,18 +56,11 @@ public fun <T : Any> Sampler<T>.sampleBuffer(
public suspend fun <T : Any> Sampler<T>.next(generator: RandomGenerator): T = sample(generator).first()
/**
* Generates [size] real samples and chunks them into some buffers.
* Generates [size] samples and chunks them into some buffers.
*/
@JvmName("sampleRealBuffer")
public fun Sampler<Double>.sampleBuffer(generator: RandomGenerator, size: Int): Chain<Buffer<Double>> =
sampleBuffer(generator, size, ::Float64Buffer)
/**
* Generates [size] integer samples and chunks them into some buffers.
*/
@JvmName("sampleIntBuffer")
public fun Sampler<Int>.sampleBuffer(generator: RandomGenerator, size: Int): Chain<Buffer<Int>> =
sampleBuffer(generator, size, ::Int32Buffer)
public inline fun <reified T:Any> Sampler<T>.sampleBuffer(generator: RandomGenerator, size: Int): Chain<Buffer<T>> =
sampleBuffer(generator, size, BufferFactory())
/**

View File

@ -14,6 +14,7 @@ import space.kscience.kmath.operations.ScaleOperations
import space.kscience.kmath.operations.invoke
import space.kscience.kmath.random.RandomGenerator
import space.kscience.kmath.stat.Sampler
import space.kscience.kmath.structures.MutableBufferFactory
/**
* Implements [Sampler] by sampling only certain [value].
@ -41,6 +42,8 @@ public class BasicSampler<out T : Any>(public val chainBuilder: (RandomGenerator
public class SamplerSpace<T : Any, out S>(public val algebra: S) : Group<Sampler<T>>,
ScaleOperations<Sampler<T>> where S : Group<T>, S : ScaleOperations<T> {
override val bufferFactory: MutableBufferFactory<Sampler<T>> = MutableBufferFactory()
override val zero: Sampler<T> = ConstantSampler(algebra.zero)
override fun add(left: Sampler<T>, right: Sampler<T>): Sampler<T> = BasicSampler { generator ->

View File

@ -5,10 +5,14 @@
package space.kscience.kmath.stat
import space.kscience.attributes.SafeType
import space.kscience.attributes.safeTypeOf
import space.kscience.kmath.operations.Field
import space.kscience.kmath.structures.*
import kotlin.math.pow
import kotlin.math.sqrt
/**
* A combination of a random [value] and its [dispersion].
*
@ -53,4 +57,48 @@ public object ValueAndErrorField : Field<ValueAndError> {
override fun scale(a: ValueAndError, value: Double): ValueAndError =
ValueAndError(a.value * value, a.dispersion * value.pow(2))
private class ValueAndErrorBuffer(val values: DoubleBuffer, val ds: DoubleBuffer) : MutableBuffer<ValueAndError> {
init {
require(values.size == ds.size)
}
override val type: SafeType<ValueAndError> get() = safeTypeOf()
override val size: Int
get() = values.size
override fun get(index: Int): ValueAndError = ValueAndError(values[index], ds[index])
override fun toString(): String = Buffer.toString(this)
override fun set(index: Int, value: ValueAndError) {
values[index] = value.value
values[index] = value.dispersion
}
override fun copy(): MutableBuffer<ValueAndError> = ValueAndErrorBuffer(values.copy(), ds.copy())
}
override val bufferFactory: MutableBufferFactory<ValueAndError> = object : MutableBufferFactory<ValueAndError> {
override fun invoke(
size: Int,
builder: (Int) -> ValueAndError,
): MutableBuffer<ValueAndError> {
val values: DoubleArray = DoubleArray(size)
val ds = DoubleArray(size)
repeat(size){
val (v, d) = builder(it)
values[it] = v
ds[it] = d
}
return ValueAndErrorBuffer(
values.asBuffer(),
ds.asBuffer()
)
}
override val type: SafeType<ValueAndError> get() = safeTypeOf()
}
}