From 5196322b7a42ff9a629e4208fb0c0adac678ed4f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 13 Aug 2023 19:13:39 +0300 Subject: [PATCH] Update integration to use Attributes --- .../space/kscience/attributes/Attributes.kt | 8 ++ .../kscience/attributes/AttributesBuilder.kt | 7 +- .../kmath/commons/integration/CMIntegrator.kt | 19 ++--- .../kscience/kmath/commons/linear/CMMatrix.kt | 2 +- .../kscience/kmath/linear/LupDecomposition.kt | 9 +-- ...{MatrixFeatures.kt => matrixAttributes.kt} | 6 +- .../space/kscience/kmath/nd/StructureND.kt | 1 - .../kmath/functions/polynomialConstructors.kt | 7 +- .../kmath/integration/GaussIntegrator.kt | 34 +++++---- .../integration/GaussIntegratorRuleFactory.kt | 4 +- .../kscience/kmath/integration/Integrand.kt | 63 ++++++++-------- .../integration/MultivariateIntegrand.kt | 22 +++--- .../kmath/integration/SimpsonIntegrator.kt | 32 +++++--- .../kmath/integration/SplineIntegrator.kt | 22 ++++-- .../kmath/integration/UnivariateIntegrand.kt | 73 +++++++------------ .../kmath/integration/SimpsonIntegralTest.kt | 10 ++- .../kscience/kmath/geometry/testUtils.kt | 2 +- .../optimization/FunctionOptimization.kt | 2 +- .../core/LevenbergMarquardtAlgorithm.kt | 10 +-- 19 files changed, 173 insertions(+), 160 deletions(-) rename kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/{MatrixFeatures.kt => matrixAttributes.kt} (95%) diff --git a/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/Attributes.kt b/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/Attributes.kt index f93b6446a..705e61436 100644 --- a/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/Attributes.kt +++ b/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/Attributes.kt @@ -59,6 +59,14 @@ public fun > Attributes.withAttribute( public fun > Attributes.withAttribute(attribute: A): Attributes = withAttribute(attribute, Unit) +/** + * Create a new [Attributes] by modifying the current one + */ +public fun Attributes.modify(block: AttributesBuilder.() -> Unit): Attributes = Attributes { + from(this@modify) + block() +} + /** * Create new [Attributes] by removing [attribute] key */ diff --git a/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/AttributesBuilder.kt b/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/AttributesBuilder.kt index 588c789f5..d0aed08c9 100644 --- a/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/AttributesBuilder.kt +++ b/attributes-kt/src/commonMain/kotlin/space/kscience/attributes/AttributesBuilder.kt @@ -8,7 +8,9 @@ package space.kscience.attributes /** * A safe builder for [Attributes] */ -public class AttributesBuilder internal constructor(private val map: MutableMap, Any> = mutableMapOf()) { +public class AttributesBuilder internal constructor(private val map: MutableMap, Any>) { + + public constructor() : this(mutableMapOf()) @Suppress("UNCHECKED_CAST") public operator fun get(attribute: Attribute): T? = map[attribute] as? T @@ -49,4 +51,5 @@ public fun AttributesBuilder( attributes: Attributes, ): AttributesBuilder = AttributesBuilder(attributes.content.toMutableMap()) -public fun Attributes(builder: AttributesBuilder.() -> Unit): Attributes = AttributesBuilder().apply(builder).build() \ No newline at end of file +public inline fun Attributes(builder: AttributesBuilder.() -> Unit): Attributes = + AttributesBuilder().apply(builder).build() \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt index c3e581d31..82a371100 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt @@ -20,10 +20,9 @@ public class CMIntegrator( override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val integrator = integratorBuilder(integrand) - val maxCalls = integrand.getFeature()?.maxCalls ?: defaultMaxCalls + val maxCalls = integrand[IntegrandMaxCalls] ?: defaultMaxCalls val remainingCalls = maxCalls - integrand.calls - val range = integrand.getFeature()?.range - ?: error("Integration range is not provided") + val range = integrand[IntegrationRange] ?: error("Integration range is not provided") val res = integrator.integrate(remainingCalls, integrand.function, range.start, range.endInclusive) return integrand + @@ -39,11 +38,9 @@ public class CMIntegrator( * Create a Simpson integrator based on [SimpsonIntegrator] */ public fun simpson(defaultMaxCalls: Int = 200): CMIntegrator = CMIntegrator(defaultMaxCalls) { integrand -> - val absoluteAccuracy = integrand.getFeature()?.accuracy - ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val relativeAccuracy = integrand.getFeature()?.accuracy - ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val iterations = integrand.getFeature()?.range + val absoluteAccuracy = integrand[IntegrandAbsoluteAccuracy] ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY + val relativeAccuracy = integrand[IntegrandRelativeAccuracy] ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY + val iterations = integrand[IntegrandIterationsRange] ?: SimpsonIntegrator.DEFAULT_MIN_ITERATIONS_COUNT..SimpsonIntegrator.SIMPSON_MAX_ITERATIONS_COUNT @@ -55,11 +52,11 @@ public class CMIntegrator( */ public fun legandre(numPoints: Int, defaultMaxCalls: Int = numPoints * 5): CMIntegrator = CMIntegrator(defaultMaxCalls) { integrand -> - val absoluteAccuracy = integrand.getFeature()?.accuracy + val absoluteAccuracy = integrand[IntegrandAbsoluteAccuracy] ?: IterativeLegendreGaussIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val relativeAccuracy = integrand.getFeature()?.accuracy + val relativeAccuracy = integrand[IntegrandRelativeAccuracy] ?: IterativeLegendreGaussIntegrator.DEFAULT_ABSOLUTE_ACCURACY - val iterations = integrand.getFeature()?.range + val iterations = integrand[IntegrandIterationsRange] ?: IterativeLegendreGaussIntegrator.DEFAULT_MIN_ITERATIONS_COUNT..IterativeLegendreGaussIntegrator.DEFAULT_MAX_ITERATIONS_COUNT IterativeLegendreGaussIntegrator( diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt index ebf4dc65a..d29650e3f 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt @@ -135,7 +135,7 @@ public object CMLinearSpace : LinearSpace { override val r: Matrix by lazy> { CMMatrix(qr.r).withAttribute(UpperTriangular) } } - SingularValueDecompositionAttribute::class -> object : SingularValueDecompositionAttribute { + SVDAttribute::class -> object : SVDAttribute { private val sv by lazy { SingularValueDecomposition(origin) } override val u: Matrix by lazy { CMMatrix(sv.u) } override val s: Matrix by lazy { CMMatrix(sv.s) } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt index 6ccafa5f1..9fe8397d8 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt @@ -13,10 +13,6 @@ import space.kscience.attributes.safeTypeOf import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.operations.* import space.kscience.kmath.structures.* -import space.kscience.kmath.structures.BufferAccessor2D -import space.kscience.kmath.structures.Float64Buffer -import space.kscience.kmath.structures.MutableBuffer -import space.kscience.kmath.structures.MutableBufferFactory /** * Matrices with this feature support LU factorization with partial pivoting: *[p] · a = [l] · [u]* where @@ -197,8 +193,7 @@ public fun > LinearSpace>.lup( public fun LinearSpace.lup( matrix: Matrix, singularityThreshold: Double = 1e-11, -): LupDecomposition = - lup(::Float64Buffer, matrix) { it < singularityThreshold } +): LupDecomposition = lup(matrix) { it < singularityThreshold } internal fun > LinearSpace.solve( lup: LupDecomposition, @@ -265,4 +260,4 @@ public fun , F : Field> LinearSpace.lupSolver( } public fun LinearSpace.lupSolver(singularityThreshold: Double = 1e-11): LinearSolver = - lupSolver(::Float64Buffer) { it < singularityThreshold } + lupSolver { it < singularityThreshold } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt similarity index 95% rename from kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt rename to kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt index d109ef1bc..5bb35edef 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixFeatures.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/matrixAttributes.kt @@ -163,12 +163,12 @@ public interface SingularValueDecomposition { * * @param T the type of matrices' items. */ -public class SingularValueDecompositionAttribute(type: SafeType>) : +public class SVDAttribute(type: SafeType>) : PolymorphicAttribute>(type), MatrixAttribute> -public val MatrixOperations.SVD: SingularValueDecompositionAttribute - get() = SingularValueDecompositionAttribute(safeTypeOf()) +public val MatrixOperations.SVD: SVDAttribute + get() = SVDAttribute(safeTypeOf()) //TODO add sparse matrix feature diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt index 993349487..bc86f62ae 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt @@ -240,7 +240,6 @@ public interface MutableStructureND : StructureND { * Set value at specified indices */ @PerformancePitfall -@Deprecated("") public operator fun MutableStructureND.set(vararg index: Int, value: T) { set(index, value) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialConstructors.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialConstructors.kt index 4e9791a87..e07ff764c 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialConstructors.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/functions/polynomialConstructors.kt @@ -20,9 +20,4 @@ public fun Polynomial(coefficients: List, reverse: Boolean = false): Poly */ @Suppress("FunctionName") public fun Polynomial(vararg coefficients: C, reverse: Boolean = false): Polynomial = - Polynomial(with(coefficients) { if (reverse) reversed() else toList() }) - -/** - * Represents [this] constant as a [Polynomial]. - */ -public fun C.asPolynomial() : Polynomial = Polynomial(listOf(this)) \ No newline at end of file + Polynomial(with(coefficients) { if (reverse) reversed() else toList() }) \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt index f2ac0a296..f0aab2c6c 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt @@ -4,6 +4,7 @@ */ package space.kscience.kmath.integration +import space.kscience.attributes.AttributesBuilder import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.operations.Field import space.kscience.kmath.structures.Buffer @@ -11,14 +12,14 @@ import space.kscience.kmath.structures.asBuffer import space.kscience.kmath.structures.indices /** - * A simple one-pass integrator based on Gauss rule - * Following integrand features are accepted: + * A simple one-pass integrator based on Gauss rule. + * The following integrand features are accepted: * * * [GaussIntegratorRuleFactory]—a factory for computing the Gauss integration rule. By default, uses * [GaussLegendreRuleFactory]. * * [IntegrationRange]—the univariate range of integration. By default, uses `0..1` interval. * * [IntegrandMaxCalls]—the maximum number of function calls during integration. For non-iterative rules, always - * uses the maximum number of points. By default, uses 10 points. + * use the maximum number of points. By default, uses 10 points. * * [UnivariateIntegrandRanges]—set of ranges and number of points per range. Defaults to given * [IntegrationRange] and [IntegrandMaxCalls]. */ @@ -27,11 +28,11 @@ public class GaussIntegrator( ) : UnivariateIntegrator { private fun buildRule(integrand: UnivariateIntegrand): Pair, Buffer> { - val factory = integrand.getFeature() ?: GaussLegendreRuleFactory - val predefinedRanges = integrand.getFeature() + val factory = integrand[GaussIntegratorRuleFactory] ?: GaussLegendreRuleFactory + val predefinedRanges = integrand[UnivariateIntegrandRanges] if (predefinedRanges == null || predefinedRanges.ranges.isEmpty()) { - val numPoints = integrand.getFeature()?.maxCalls ?: 100 - val range = integrand.getFeature()?.range ?: 0.0..1.0 + val numPoints = integrand[IntegrandMaxCalls] ?: 100 + val range = integrand[IntegrationRange] ?: 0.0..1.0 return factory.build(numPoints, range) } else { val ranges = predefinedRanges.ranges @@ -66,7 +67,10 @@ public class GaussIntegrator( c = t - res - y res = t } - return integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + points.size) + return integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + points.size) + } } public companion object @@ -88,7 +92,7 @@ public fun GaussIntegrator.integrate( range: ClosedRange, order: Int = 10, intervals: Int = 10, - vararg features: IntegrandFeature, + attributesBuilder: AttributesBuilder.() -> Unit, function: (Double) -> T, ): UnivariateIntegrand { require(range.endInclusive > range.start) { "The range upper bound should be higher than lower bound" } @@ -100,11 +104,13 @@ public fun GaussIntegrator.integrate( ) return process( UnivariateIntegrand( - function, - IntegrationRange(range), - GaussLegendreRuleFactory, - ranges, - *features + attributeBuilder = { + IntegrationRange(range) + GaussIntegratorRuleFactory(GaussLegendreRuleFactory) + UnivariateIntegrandRanges(ranges) + attributesBuilder() + }, + function = function, ) ) } \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt index 975fcd3a8..91b3811c0 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegratorRuleFactory.kt @@ -12,10 +12,10 @@ import space.kscience.kmath.structures.asBuffer import kotlin.math.ulp import kotlin.native.concurrent.ThreadLocal -public interface GaussIntegratorRuleFactory : IntegrandFeature { +public interface GaussIntegratorRuleFactory { public fun build(numPoints: Int): Pair, Buffer> - public companion object { + public companion object: IntegrandAttribute{ public fun double(numPoints: Int, range: ClosedRange): Pair, Buffer> = GaussLegendreRuleFactory.build(numPoints, range) } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt index 40fe78898..09c32aeff 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt @@ -5,44 +5,49 @@ package space.kscience.kmath.integration -import space.kscience.kmath.misc.Feature -import space.kscience.kmath.misc.FeatureSet -import space.kscience.kmath.misc.Featured -import kotlin.reflect.KClass +import space.kscience.attributes.* +import kotlin.reflect.typeOf -public interface IntegrandFeature : Feature { - override fun toString(): String +public interface IntegrandAttribute : Attribute + +public interface Integrand : AttributeContainer { + + public fun modify(block: AttributesBuilder.() -> Unit): Integrand + + public fun withAttribute(attribute: Attribute, value: A): Integrand + + public companion object } -public interface Integrand : Featured { - public val features: FeatureSet - override fun getFeature(type: KClass): T? = features.getFeature(type) +public operator fun Integrand<*>.get(attribute: Attribute): T? = attributes[attribute] + +public class IntegrandValue(type: SafeType) : PolymorphicAttribute(type), IntegrandAttribute + +public inline val Integrand.Value: IntegrandValue get() = IntegrandValue(safeTypeOf()) + +public fun AttributesBuilder.value(value: T){ + val type: SafeType = typeOf() + IntegrandValue(type).invoke(value) } -public inline fun Integrand.getFeature(): T? = getFeature(T::class) +/** + * Value of the integrand if it is present or null + */ +public inline val Integrand.valueOrNull: T? get() = attributes[Value] -public class IntegrandValue(public val value: T) : IntegrandFeature { - override fun toString(): String = "Value($value)" -} +/** + * Value of the integrand or error + */ +public inline val Integrand.value: T get() = valueOrNull ?: error("No value in the integrand") -public class IntegrandRelativeAccuracy(public val accuracy: Double) : IntegrandFeature { - override fun toString(): String = "TargetRelativeAccuracy($accuracy)" -} +public object IntegrandRelativeAccuracy : IntegrandAttribute -public class IntegrandAbsoluteAccuracy(public val accuracy: Double) : IntegrandFeature { - override fun toString(): String = "TargetAbsoluteAccuracy($accuracy)" -} +public object IntegrandAbsoluteAccuracy : IntegrandAttribute -public class IntegrandCallsPerformed(public val calls: Int) : IntegrandFeature { - override fun toString(): String = "Calls($calls)" -} +public object IntegrandCallsPerformed : IntegrandAttribute -public val Integrand.calls: Int get() = getFeature()?.calls ?: 0 +public val Integrand<*>.calls: Int get() = attributes[IntegrandCallsPerformed] ?: 0 -public class IntegrandMaxCalls(public val maxCalls: Int) : IntegrandFeature { - override fun toString(): String = "MaxCalls($maxCalls)" -} +public object IntegrandMaxCalls : IntegrandAttribute -public class IntegrandIterationsRange(public val range: IntRange) : IntegrandFeature { - override fun toString(): String = "Iterations(${range.first}..${range.last})" -} +public object IntegrandIterationsRange : IntegrandAttribute diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt index 53a563086..a3b2ed44f 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt @@ -5,22 +5,22 @@ package space.kscience.kmath.integration +import space.kscience.attributes.* import space.kscience.kmath.linear.Point -import space.kscience.kmath.misc.FeatureSet -public class MultivariateIntegrand internal constructor( - override val features: FeatureSet, +public class MultivariateIntegrand internal constructor( + override val attributes: Attributes, public val function: (Point) -> T, -) : Integrand { +) : Integrand { - public operator fun plus(feature: F): MultivariateIntegrand = - MultivariateIntegrand(features.with(feature), function) + override fun modify(block: AttributesBuilder.() -> Unit): MultivariateIntegrand = + MultivariateIntegrand(attributes.modify(block), function) + + override fun withAttribute(attribute: Attribute, value: A): MultivariateIntegrand = + MultivariateIntegrand(attributes.withAttribute(attribute, value), function) } -@Suppress("FunctionName") public fun MultivariateIntegrand( - vararg features: IntegrandFeature, + attributeBuilder: AttributesBuilder.() -> Unit, function: (Point) -> T, -): MultivariateIntegrand = MultivariateIntegrand(FeatureSet.of(*features), function) - -public val MultivariateIntegrand.value: T? get() = getFeature>()?.value +): MultivariateIntegrand = MultivariateIntegrand(Attributes(attributeBuilder), function) diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt index 35ce82351..16d71a743 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt @@ -45,16 +45,22 @@ public class SimpsonIntegrator( } override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { - val ranges = integrand.getFeature() + val ranges = integrand[UnivariateIntegrandRanges] return if (ranges != null) { val res = algebra.sum(ranges.ranges.map { integrateRange(integrand, it.first, it.second) }) - integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + ranges.ranges.sumOf { it.second }) + integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + ranges.ranges.sumOf { it.second }) + } } else { - val numPoints = integrand.getFeature()?.maxCalls ?: 100 + val numPoints = integrand[IntegrandMaxCalls] ?: 100 require(numPoints >= 4) { "Simpson integrator requires at least 4 nodes" } - val range = integrand.getFeature()?.range ?: 0.0..1.0 + val range = integrand[IntegrationRange] ?: 0.0..1.0 val res = integrateRange(integrand, range, numPoints) - integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + numPoints) + integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + numPoints) + } } } } @@ -91,16 +97,22 @@ public object DoubleSimpsonIntegrator : UnivariateIntegrator { } override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { - val ranges = integrand.getFeature() + val ranges = integrand[UnivariateIntegrandRanges] return if (ranges != null) { val res = ranges.ranges.sumOf { integrateRange(integrand, it.first, it.second) } - integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + ranges.ranges.sumOf { it.second }) + integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + ranges.ranges.sumOf { it.second }) + } } else { - val numPoints = integrand.getFeature()?.maxCalls ?: 100 + val numPoints = integrand[IntegrandMaxCalls] ?: 100 require(numPoints >= 4) { "Simpson integrator requires at least 4 nodes" } - val range = integrand.getFeature()?.range ?: 0.0..1.0 + val range = integrand[IntegrationRange] ?: 0.0..1.0 val res = integrateRange(integrand, range, numPoints) - integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + numPoints) + integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + numPoints) + } } } } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt index 1306e501f..bbec9ff45 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt @@ -55,12 +55,12 @@ public class SplineIntegrator>( public val bufferFactory: MutableBufferFactory, ) : UnivariateIntegrator { override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand = algebra { - val range = integrand.getFeature()?.range ?: 0.0..1.0 + val range = integrand[IntegrationRange] ?: 0.0..1.0 val interpolator: PolynomialInterpolator = SplineInterpolator(algebra, bufferFactory) - val nodes: Buffer = integrand.getFeature()?.nodes ?: run { - val numPoints = integrand.getFeature()?.maxCalls ?: 100 + val nodes: Buffer = integrand[UnivariateIntegrationNodes] ?: run { + val numPoints = integrand[IntegrandMaxCalls] ?: 100 val step = (range.endInclusive - range.start) / (numPoints - 1) Float64Buffer(numPoints) { i -> range.start + i * step } } @@ -71,7 +71,10 @@ public class SplineIntegrator>( values ) val res = polynomials.integrate(algebra, number(range.start)..number(range.endInclusive)) - integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + nodes.size) + integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + nodes.size) + } } } @@ -84,11 +87,11 @@ public class SplineIntegrator>( @UnstableKMathAPI public object DoubleSplineIntegrator : UnivariateIntegrator { override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { - val range = integrand.getFeature()?.range ?: 0.0..1.0 + val range = integrand[IntegrationRange] ?: 0.0..1.0 val interpolator: PolynomialInterpolator = SplineInterpolator(Float64Field, ::Float64Buffer) - val nodes: Buffer = integrand.getFeature()?.nodes ?: run { - val numPoints = integrand.getFeature()?.maxCalls ?: 100 + val nodes: Buffer = integrand[UnivariateIntegrationNodes] ?: run { + val numPoints = integrand[IntegrandMaxCalls] ?: 100 val step = (range.endInclusive - range.start) / (numPoints - 1) Float64Buffer(numPoints) { i -> range.start + i * step } } @@ -96,7 +99,10 @@ public object DoubleSplineIntegrator : UnivariateIntegrator { val values = nodes.mapToBuffer(::Float64Buffer) { integrand.function(it) } val polynomials = interpolator.interpolatePolynomials(nodes, values) val res = polynomials.integrate(Float64Field, range) - return integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + nodes.size) + return integrand.modify { + value(res) + IntegrandCallsPerformed(integrand.calls + nodes.size) + } } } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt index e29a6c881..3c30f303e 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt @@ -5,36 +5,38 @@ package space.kscience.kmath.integration +import space.kscience.attributes.* import space.kscience.kmath.UnstableKMathAPI -import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Float64Buffer public class UnivariateIntegrand internal constructor( - override val features: FeatureSet, + override val attributes: Attributes, public val function: (Double) -> T, -) : Integrand { - public operator fun plus(feature: F): UnivariateIntegrand = - UnivariateIntegrand(features.with(feature), function) +) : Integrand { + + override fun withAttribute(attribute: Attribute, value: A): UnivariateIntegrand = + UnivariateIntegrand(attributes.withAttribute(attribute, value), function) + + override fun modify(block: AttributesBuilder.() -> Unit): UnivariateIntegrand = + UnivariateIntegrand(attributes.modify(block), function) } -@Suppress("FunctionName") public fun UnivariateIntegrand( + attributeBuilder: AttributesBuilder.() -> Unit, function: (Double) -> T, - vararg features: IntegrandFeature, -): UnivariateIntegrand = UnivariateIntegrand(FeatureSet.of(*features), function) +): UnivariateIntegrand = UnivariateIntegrand(Attributes(attributeBuilder), function) public typealias UnivariateIntegrator = Integrator> -public class IntegrationRange(public val range: ClosedRange) : IntegrandFeature { - override fun toString(): String = "Range(${range.start}..${range.endInclusive})" -} +public object IntegrationRange : IntegrandAttribute> + /** * Set of univariate integration ranges. First components correspond to the ranges themselves, second components to - * number of integration nodes per range. + * the number of integration nodes per range. */ -public class UnivariateIntegrandRanges(public val ranges: List, Int>>) : IntegrandFeature { +public class UnivariateIntegrandRanges(public val ranges: List, Int>>) { public constructor(vararg pairs: Pair, Int>) : this(pairs.toList()) override fun toString(): String { @@ -43,45 +45,25 @@ public class UnivariateIntegrandRanges(public val ranges: List } -public class UnivariateIntegrationNodes(public val nodes: Buffer) : IntegrandFeature { - public constructor(vararg nodes: Double) : this(Float64Buffer(nodes)) +public object UnivariateIntegrationNodes : IntegrandAttribute> - override fun toString(): String = "UnivariateNodes($nodes)" +public fun AttributesBuilder.integrationNodes(vararg nodes: Double) { + UnivariateIntegrationNodes(Float64Buffer(nodes)) } - -/** - * Value of the integrand if it is present or null - */ -public val UnivariateIntegrand.valueOrNull: T? get() = getFeature>()?.value - -/** - * Value of the integrand or error - */ -public val UnivariateIntegrand.value: T get() = valueOrNull ?: error("No value in the integrand") - /** * A shortcut method to integrate a [function] with additional [features]. Range must be provided in features. * The [function] is placed in the end position to allow passing a lambda. */ @UnstableKMathAPI public fun UnivariateIntegrator.integrate( - vararg features: IntegrandFeature, + attributesBuilder: AttributesBuilder.() -> Unit, function: (Double) -> T, -): UnivariateIntegrand = process(UnivariateIntegrand(function, *features)) - -/** - * A shortcut method to integrate a [function] in [range] with additional [features]. - * The [function] is placed in the end position to allow passing a lambda. - */ -@UnstableKMathAPI -public fun UnivariateIntegrator.integrate( - range: ClosedRange, - vararg features: IntegrandFeature, - function: (Double) -> T, -): UnivariateIntegrand = process(UnivariateIntegrand(function, IntegrationRange(range), *features)) +): UnivariateIntegrand = process(UnivariateIntegrand(attributesBuilder, function)) /** * A shortcut method to integrate a [function] in [range] with additional features. @@ -90,13 +72,12 @@ public fun UnivariateIntegrator.integrate( @UnstableKMathAPI public fun UnivariateIntegrator.integrate( range: ClosedRange, - featureBuilder: MutableList.() -> Unit = {}, + attributeBuilder: AttributesBuilder.() -> Unit = {}, function: (Double) -> T, ): UnivariateIntegrand { - //TODO use dedicated feature builder class instead or add extensions to MutableList - val features = buildList { - featureBuilder() - add(IntegrationRange(range)) + val attributes = Attributes { + IntegrationRange(range) + attributeBuilder() } - return process(UnivariateIntegrand(function, *features.toTypedArray())) + return process(UnivariateIntegrand(attributes, function)) } diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt index f5a79cdea..aafabf302 100644 --- a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/SimpsonIntegralTest.kt @@ -16,7 +16,10 @@ import kotlin.test.assertEquals class SimpsonIntegralTest { @Test fun gaussSin() { - val res = Float64Field.simpsonIntegrator.integrate(0.0..2 * PI, IntegrandMaxCalls(5)) { x -> + val res = Float64Field.simpsonIntegrator.integrate( + 0.0..2 * PI, + { IntegrandMaxCalls(5) } + ) { x -> sin(x) } assertEquals(0.0, res.value, 1e-2) @@ -24,7 +27,10 @@ class SimpsonIntegralTest { @Test fun gaussUniform() { - val res = Float64Field.simpsonIntegrator.integrate(35.0..100.0, IntegrandMaxCalls(20)) { x -> + val res = Float64Field.simpsonIntegrator.integrate( + 35.0..100.0, + { IntegrandMaxCalls(20) } + ) { x -> if (x in 30.0..50.0) { 1.0 } else { diff --git a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt index ac72af10c..abc57eb42 100644 --- a/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt +++ b/kmath-geometry/src/commonTest/kotlin/space/kscience/kmath/geometry/testUtils.kt @@ -27,7 +27,7 @@ fun grid( return xs.flatMap { x -> ys.map { y -> x to y } } } -fun assertVectorEquals(expected: DoubleVector2D, actual: DoubleVector2D, absoluteTolerance: Double = 1e-6) { +fun assertVectorEquals(expected: DoubleVector2D, actual: DoubleVector2D, absoluteTolerance: Double = 1e-3) { assertEquals(expected.x, actual.x, absoluteTolerance) assertEquals(expected.y, actual.y, absoluteTolerance) } diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index fe5f0ca8a..8025428e6 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -53,7 +53,7 @@ public class OptimizationPrior(type: SafeType): PolymorphicAttribute>(safeTypeOf()), Attribute> -public val FunctionOptimization.Companion.Optimization get() = +//public val FunctionOptimization.Companion.Optimization get() = public fun FunctionOptimization.withFeatures( diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/LevenbergMarquardtAlgorithm.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/LevenbergMarquardtAlgorithm.kt index fc87ad1f3..8011b332e 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/LevenbergMarquardtAlgorithm.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/LevenbergMarquardtAlgorithm.kt @@ -6,7 +6,7 @@ package space.kscience.kmath.tensors.core import space.kscience.kmath.PerformancePitfall -import space.kscience.kmath.linear.transpose +import space.kscience.kmath.linear.transposed import space.kscience.kmath.nd.* import kotlin.math.abs import kotlin.math.max @@ -139,7 +139,7 @@ public fun DoubleTensorAlgebra.levenbergMarquardt(inputData: LMInput): LMResultI if (inputData.nargin < 5) { weight = fromArray( ShapeND(intArrayOf(1, 1)), - doubleArrayOf((inputData.realValues.transpose().dot(inputData.realValues)).as1D()[0]) + doubleArrayOf((inputData.realValues.transposed dot inputData.realValues).as1D()[0]) ).as2D() } @@ -266,12 +266,12 @@ public fun DoubleTensorAlgebra.levenbergMarquardt(inputData: LMInput): LMResultI settings.funcCalls += 1 // val tmp = deltaY.times(weight) - var X2Try = deltaY.as2D().transpose().dot(deltaY.times(weight)) // Chi-squared error criteria + var X2Try = deltaY.as2D().transposed dot deltaY.times(weight) // Chi-squared error criteria val alpha = 1.0 if (updateType == 2) { // Quadratic // One step of quadratic line update in the h direction for minimum X2 - val alphaTensor = (jtWdy.transpose() dot h) / ((X2Try - x2) / 2.0 + 2 * (jtWdy.transpose() dot h)) + val alphaTensor = (jtWdy.transposed dot h) / ((X2Try - x2) / 2.0 + 2 * (jtWdy.transposed dot h)) h = h dot alphaTensor pTry = (p + h).as2D() // update only [idx] elements pTry = smallestElementComparison( @@ -289,7 +289,7 @@ public fun DoubleTensorAlgebra.levenbergMarquardt(inputData: LMInput): LMResultI ) // residual error using p_try settings.funcCalls += 1 - X2Try = deltaY.as2D().transpose() dot deltaY * weight // Chi-squared error criteria + X2Try = deltaY.as2D().transposed dot deltaY * weight // Chi-squared error criteria } val rho = when (updateType) { // Nielsen