From 52a942dbe24c9eb22fdbcd14a4038a04f2b03a98 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 12 Mar 2021 19:29:06 +0300 Subject: [PATCH 1/3] integrand --- .../kotlin/space/kscience/kmath/integration/Integrand.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt 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 new file mode 100644 index 000000000..31006cde2 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt @@ -0,0 +1,9 @@ +package space.kscience.kmath.integration + +import kotlin.reflect.KClass + +public interface IntegrandFeature + +public interface Integrand { + public fun getFeature(type: KClass): T? = null +} \ No newline at end of file -- 2.34.1 From 874875c13aaf79f6a27fa6c1f19f9f264bc53c19 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 16 Mar 2021 15:05:44 +0300 Subject: [PATCH 2/3] API for integration --- .../kmath/commons/integration/CMIntegrator.kt | 77 +++++++++++++++ .../integration/GaussRuleIntegrator.kt | 94 +++++++++++++++++++ .../commons/integration/IntegrationTest.kt | 30 ++++++ .../kscience/kmath/integration/Integrand.kt | 17 +++- .../kscience/kmath/integration/Integrator.kt | 11 +++ .../integration/MultivariateIntegrand.kt | 27 ++++++ .../kmath/integration/UnivariateIntegrand.kt | 64 +++++++++++++ 7 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt create mode 100644 kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt create mode 100644 kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt create mode 100644 kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt 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 new file mode 100644 index 000000000..e2ed84f1a --- /dev/null +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt @@ -0,0 +1,77 @@ +package space.kscience.kmath.commons.integration + +import org.apache.commons.math3.analysis.integration.IterativeLegendreGaussIntegrator +import org.apache.commons.math3.analysis.integration.SimpsonIntegrator +import space.kscience.kmath.integration.* + +/** + * Integration wrapper for Common-maths UnivariateIntegrator + */ +public class CMIntegrator( + private val defaultMaxCalls: Int = 200, + public val integratorBuilder: (Integrand) -> org.apache.commons.math3.analysis.integration.UnivariateIntegrator, +) : UnivariateIntegrator { + + public class TargetRelativeAccuracy(public val value: Double) : IntegrandFeature + public class TargetAbsoluteAccuracy(public val value: Double) : IntegrandFeature + + public class MinIterations(public val value: Int) : IntegrandFeature + public class MaxIterations(public val value: Int) : IntegrandFeature + + override fun evaluate(integrand: UnivariateIntegrand): UnivariateIntegrand { + val integrator = integratorBuilder(integrand) + val maxCalls = integrand.getFeature()?.maxCalls ?: defaultMaxCalls + val remainingCalls = maxCalls - integrand.calls + val range = integrand.getFeature>()?.range + ?: error("Integration range is not provided") + val res = integrator.integrate(remainingCalls, integrand.function, range.start, range.endInclusive) + + return integrand + + IntegrandValue(res) + + IntegrandAbsoluteAccuracy(integrator.absoluteAccuracy) + + IntegrandRelativeAccuracy(integrator.relativeAccuracy) + + IntegrandCalls(integrator.evaluations + integrand.calls) + } + + + public companion object { + /** + * Create a Simpson integrator based on [SimpsonIntegrator] + */ + public fun simpson(defaultMaxCalls: Int = 200): CMIntegrator = CMIntegrator(defaultMaxCalls) { integrand -> + val absoluteAccuracy = integrand.getFeature()?.value + ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY + val relativeAccuracy = integrand.getFeature()?.value + ?: SimpsonIntegrator.DEFAULT_ABSOLUTE_ACCURACY + val minIterations = integrand.getFeature()?.value + ?: SimpsonIntegrator.DEFAULT_MIN_ITERATIONS_COUNT + val maxIterations = integrand.getFeature()?.value + ?: SimpsonIntegrator.SIMPSON_MAX_ITERATIONS_COUNT + + SimpsonIntegrator(relativeAccuracy, absoluteAccuracy, minIterations, maxIterations) + } + + /** + * Create a Gauss-Legandre integrator based on [IterativeLegendreGaussIntegrator] + */ + public fun legandre(numPoints: Int, defaultMaxCalls: Int = numPoints * 5): CMIntegrator = + CMIntegrator(defaultMaxCalls) { integrand -> + val absoluteAccuracy = integrand.getFeature()?.value + ?: IterativeLegendreGaussIntegrator.DEFAULT_ABSOLUTE_ACCURACY + val relativeAccuracy = integrand.getFeature()?.value + ?: IterativeLegendreGaussIntegrator.DEFAULT_ABSOLUTE_ACCURACY + val minIterations = integrand.getFeature()?.value + ?: IterativeLegendreGaussIntegrator.DEFAULT_MIN_ITERATIONS_COUNT + val maxIterations = integrand.getFeature()?.value + ?: IterativeLegendreGaussIntegrator.DEFAULT_MAX_ITERATIONS_COUNT + + IterativeLegendreGaussIntegrator( + numPoints, + relativeAccuracy, + absoluteAccuracy, + minIterations, + maxIterations + ) + } + } +} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt new file mode 100644 index 000000000..29df2e787 --- /dev/null +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package space.kscience.kmath.commons.integration + +import org.apache.commons.math3.analysis.integration.gauss.GaussIntegrator +import org.apache.commons.math3.analysis.integration.gauss.GaussIntegratorFactory +import space.kscience.kmath.integration.* + +/** + * A simple one-pass integrator based on Gauss rule + */ +public class GaussRuleIntegrator( + private val numpoints: Int, + private var type: GaussRule = GaussRule.LEGANDRE, +) : UnivariateIntegrator { + + override fun evaluate(integrand: UnivariateIntegrand): UnivariateIntegrand { + val range = integrand.getFeature>()?.range + ?: error("Integration range is not provided") + val integrator: GaussIntegrator = getIntegrator(range) + //TODO check performance + val res: Double = integrator.integrate(integrand.function) + return integrand + IntegrandValue(res) + IntegrandCalls(integrand.calls + numpoints) + } + + private fun getIntegrator(range: ClosedRange): GaussIntegrator { + return when (type) { + GaussRule.LEGANDRE -> factory.legendre( + numpoints, + range.start, + range.endInclusive + ) + GaussRule.LEGANDREHP -> factory.legendreHighPrecision( + numpoints, + range.start, + range.endInclusive + ) + GaussRule.UNIFORM -> GaussIntegrator( + getUniformRule( + range.start, + range.endInclusive, + numpoints + ) + ) + } + } + + private fun getUniformRule( + min: Double, + max: Double, + numPoints: Int, + ): org.apache.commons.math3.util.Pair { + assert(numPoints > 2) + val points = DoubleArray(numPoints) + val weights = DoubleArray(numPoints) + val step = (max - min) / (numPoints - 1) + points[0] = min + for (i in 1 until numPoints) { + points[i] = points[i - 1] + step + weights[i] = step + } + return org.apache.commons.math3.util.Pair(points, weights) + } + + public enum class GaussRule { + UNIFORM, LEGANDRE, LEGANDREHP + } + + public companion object { + private val factory: GaussIntegratorFactory = GaussIntegratorFactory() + + public fun integrate( + range: ClosedRange, + numPoints: Int = 100, + type: GaussRule = GaussRule.LEGANDRE, + function: (Double) -> Double, + ): Double = GaussRuleIntegrator(numPoints, type).evaluate( + UnivariateIntegrand(function, IntegrationRange(range)) + ).value!! + } +} \ No newline at end of file diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt new file mode 100644 index 000000000..671acce07 --- /dev/null +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt @@ -0,0 +1,30 @@ +package space.kscience.kmath.commons.integration + +import org.junit.jupiter.api.Test +import space.kscience.kmath.integration.integrate +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.RealField.sin +import kotlin.math.PI +import kotlin.math.abs +import kotlin.test.assertTrue + +@UnstableKMathAPI +internal class IntegrationTest { + private val function: (Double) -> Double = { sin(it) } + + @Test + fun simpson() { + val res = CMIntegrator.simpson().integrate(0.0..2 * PI, function) + assertTrue { abs(res) < 1e-3 } + } + + @Test + fun customSimpson() { + val res = CMIntegrator.simpson().integrate(0.0..PI, function) { + add(CMIntegrator.TargetRelativeAccuracy(1e-4)) + add(CMIntegrator.TargetAbsoluteAccuracy(1e-4)) + } + assertTrue { abs(res - 2) < 1e-3 } + assertTrue { abs(res - 2) > 1e-12 } + } +} \ No newline at end of file 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 31006cde2..ae964b271 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,5 +5,18 @@ import kotlin.reflect.KClass public interface IntegrandFeature public interface Integrand { - public fun getFeature(type: KClass): T? = null -} \ No newline at end of file + public fun getFeature(type: KClass): T? +} + +public inline fun Integrand.getFeature(): T? = getFeature(T::class) + +public class IntegrandValue(public val value: T) : IntegrandFeature + +public class IntegrandRelativeAccuracy(public val accuracy: Double) : IntegrandFeature + +public class IntegrandAbsoluteAccuracy(public val accuracy: Double) : IntegrandFeature + +public class IntegrandCalls(public val calls: Int) : IntegrandFeature +public val Integrand.calls: Int get() = getFeature()?.calls ?: 0 + +public class IntegrandMaxCalls(public val maxCalls: Int) : IntegrandFeature diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt new file mode 100644 index 000000000..39330e91e --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt @@ -0,0 +1,11 @@ +package space.kscience.kmath.integration + +/** + * A general interface for all integrators + */ +public interface Integrator { + /** + * Run one integration pass and return a new [Integrand] with a new set of features + */ + public fun evaluate(integrand: I): I +} \ No newline at end of file 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 new file mode 100644 index 000000000..4b3a6deb4 --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt @@ -0,0 +1,27 @@ +package space.kscience.kmath.integration + +import space.kscience.kmath.linear.Point +import kotlin.reflect.KClass + +public class MultivariateIntegrand internal constructor( + private val features: Map, IntegrandFeature>, + public val function: (Point) -> T, +) : Integrand { + + @Suppress("UNCHECKED_CAST") + override fun getFeature(type: KClass): T? = features[type] as? T + + public operator fun plus(pair: Pair, F>): MultivariateIntegrand = + MultivariateIntegrand(features + pair, function) + + public operator fun plus(feature: F): MultivariateIntegrand = + plus(feature::class to feature) +} + +@Suppress("FunctionName") +public fun MultivariateIntegrand( + vararg features: IntegrandFeature, + function: (Point) -> T, +): MultivariateIntegrand = MultivariateIntegrand(features.associateBy { it::class }, function) + +public val MultivariateIntegrand.value: T? get() = getFeature>()?.value 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 new file mode 100644 index 000000000..ac9c570bc --- /dev/null +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt @@ -0,0 +1,64 @@ +package space.kscience.kmath.integration + +import space.kscience.kmath.misc.UnstableKMathAPI +import kotlin.reflect.KClass + +public class UnivariateIntegrand internal constructor( + private val features: Map, IntegrandFeature>, + public val function: (T) -> T, +) : Integrand { + + @Suppress("UNCHECKED_CAST") + override fun getFeature(type: KClass): T? = features[type] as? T + + public operator fun plus(pair: Pair, F>): UnivariateIntegrand = + UnivariateIntegrand(features + pair, function) + + public operator fun plus(feature: F): UnivariateIntegrand = + plus(feature::class to feature) +} + +@Suppress("FunctionName") +public fun UnivariateIntegrand( + function: (T) -> T, + vararg features: IntegrandFeature, +): UnivariateIntegrand = UnivariateIntegrand(features.associateBy { it::class }, function) + +public typealias UnivariateIntegrator = Integrator> + +public inline class IntegrationRange>(public val range: ClosedRange) : IntegrandFeature + +public val UnivariateIntegrand.value: T? get() = getFeature>()?.value + +/** + * 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) -> Double, +): Double = evaluate( + UnivariateIntegrand(function, IntegrationRange(range), *features) +).value ?: error("Unexpected: no value after integration.") + +/** + * 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, + function: (Double) -> Double, + featureBuilder: (MutableList.() -> Unit) = {}, +): Double { + //TODO use dedicated feature builder class instead or add extensions to MutableList + val features = buildList { + featureBuilder() + add(IntegrationRange(range)) + } + return evaluate( + UnivariateIntegrand(function, *features.toTypedArray()) + ).value ?: error("Unexpected: no value after integration.") +} \ No newline at end of file -- 2.34.1 From 105b84b87cd9e22a0c1f40916f053d3cf257a0fc Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 16 Mar 2021 20:44:03 +0300 Subject: [PATCH 3/3] minor cosmetics --- .../kmath/commons/integration/CMIntegrator.kt | 19 +++++++++++++++++-- .../integration/GaussRuleIntegrator.kt | 4 ++-- .../optimization/CMOptimizationProblem.kt | 2 +- .../commons/integration/IntegrationTest.kt | 4 ++-- .../kscience/kmath/integration/Integrator.kt | 2 +- .../kmath/integration/UnivariateIntegrand.kt | 4 ++-- 6 files changed, 25 insertions(+), 10 deletions(-) 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 e2ed84f1a..8511ed66e 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 @@ -3,6 +3,7 @@ package space.kscience.kmath.commons.integration import org.apache.commons.math3.analysis.integration.IterativeLegendreGaussIntegrator import org.apache.commons.math3.analysis.integration.SimpsonIntegrator import space.kscience.kmath.integration.* +import space.kscience.kmath.misc.UnstableKMathAPI /** * Integration wrapper for Common-maths UnivariateIntegrator @@ -18,7 +19,7 @@ public class CMIntegrator( public class MinIterations(public val value: Int) : IntegrandFeature public class MaxIterations(public val value: Int) : IntegrandFeature - override fun evaluate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { val integrator = integratorBuilder(integrand) val maxCalls = integrand.getFeature()?.maxCalls ?: defaultMaxCalls val remainingCalls = maxCalls - integrand.calls @@ -74,4 +75,18 @@ public class CMIntegrator( ) } } -} \ No newline at end of file +} + +@UnstableKMathAPI +public var MutableList.targetAbsoluteAccuracy: Double? + get() = filterIsInstance().lastOrNull()?.value + set(value){ + value?.let { add(CMIntegrator.TargetAbsoluteAccuracy(value))} + } + +@UnstableKMathAPI +public var MutableList.targetRelativeAccuracy: Double? + get() = filterIsInstance().lastOrNull()?.value + set(value){ + value?.let { add(CMIntegrator.TargetRelativeAccuracy(value))} + } \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt index 29df2e787..5a18756ac 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt @@ -27,7 +27,7 @@ public class GaussRuleIntegrator( private var type: GaussRule = GaussRule.LEGANDRE, ) : UnivariateIntegrator { - override fun evaluate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { val range = integrand.getFeature>()?.range ?: error("Integration range is not provided") val integrator: GaussIntegrator = getIntegrator(range) @@ -87,7 +87,7 @@ public class GaussRuleIntegrator( numPoints: Int = 100, type: GaussRule = GaussRule.LEGANDRE, function: (Double) -> Double, - ): Double = GaussRuleIntegrator(numPoints, type).evaluate( + ): Double = GaussRuleIntegrator(numPoints, type).integrate( UnivariateIntegrand(function, IntegrationRange(range)) ).value!! } diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizationProblem.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizationProblem.kt index d655b4f61..13a10475f 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizationProblem.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizationProblem.kt @@ -19,7 +19,7 @@ import kotlin.reflect.KClass public operator fun PointValuePair.component1(): DoubleArray = point public operator fun PointValuePair.component2(): Double = value -public class CMOptimizationProblem(override val symbols: List, ) : +public class CMOptimizationProblem(override val symbols: List) : OptimizationProblem, SymbolIndexer, OptimizationFeature { private val optimizationData: HashMap, OptimizationData> = HashMap() private var optimizatorBuilder: (() -> MultivariateOptimizer)? = null diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt index 671acce07..3693d5796 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/integration/IntegrationTest.kt @@ -21,8 +21,8 @@ internal class IntegrationTest { @Test fun customSimpson() { val res = CMIntegrator.simpson().integrate(0.0..PI, function) { - add(CMIntegrator.TargetRelativeAccuracy(1e-4)) - add(CMIntegrator.TargetAbsoluteAccuracy(1e-4)) + targetRelativeAccuracy = 1e-4 + targetAbsoluteAccuracy = 1e-4 } assertTrue { abs(res - 2) < 1e-3 } assertTrue { abs(res - 2) > 1e-12 } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt index 39330e91e..7027e62c7 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt @@ -7,5 +7,5 @@ public interface Integrator { /** * Run one integration pass and return a new [Integrand] with a new set of features */ - public fun evaluate(integrand: I): I + public fun integrate(integrand: I): I } \ No newline at end of file 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 ac9c570bc..9389318e8 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 @@ -39,7 +39,7 @@ public fun UnivariateIntegrator.integrate( range: ClosedRange, vararg features: IntegrandFeature, function: (Double) -> Double, -): Double = evaluate( +): Double = integrate( UnivariateIntegrand(function, IntegrationRange(range), *features) ).value ?: error("Unexpected: no value after integration.") @@ -58,7 +58,7 @@ public fun UnivariateIntegrator.integrate( featureBuilder() add(IntegrationRange(range)) } - return evaluate( + return integrate( UnivariateIntegrand(function, *features.toTypedArray()) ).value ?: error("Unexpected: no value after integration.") } \ No newline at end of file -- 2.34.1