diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index ed6452ad8..f0b20e82f 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -7,6 +7,6 @@ dependencies { api(project(":kmath-core")) api(project(":kmath-coroutines")) api(project(":kmath-prob")) -// api(project(":kmath-functions")) + api(project(":kmath-functions")) api("org.apache.commons:commons-math3:3.6.1") } diff --git a/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt b/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt index ff5542235..d9fc5ebef 100644 --- a/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt +++ b/kmath-commons/src/test/kotlin/kscience/kmath/commons/optimization/OptimizeTest.kt @@ -46,7 +46,7 @@ internal class OptimizeTest { val sigma = 1.0 val generator = Distribution.normal(0.0, sigma) - val chain = generator.sample(RandomGenerator.default(1126)) + val chain = generator.sample(RandomGenerator.default(112667)) val x = (1..100).map { it.toDouble() } val y = x.map { it -> it.pow(2) + it + 1 + chain.nextDouble() @@ -54,7 +54,8 @@ internal class OptimizeTest { val yErr = x.map { sigma } with(CMFit) { val chi2 = chiSquared(x.asBuffer(), y.asBuffer(), yErr.asBuffer()) { x -> - bind(a) * x.pow(2) + bind(b) * x + bind(c) + val cWithDefault = bindOrNull(c)?: one + bind(a) * x.pow(2) + bind(b) * x + cWithDefault } val result = chi2.minimize(a to 1.5, b to 0.9, c to 1.0) diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt b/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt index c513a6889..820076c4c 100644 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt +++ b/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/Polynomial.kt @@ -8,13 +8,13 @@ import kotlin.contracts.contract import kotlin.math.max import kotlin.math.pow -// TODO make `inline`, when KT-41771 gets fixed /** * Polynomial coefficients without fixation on specific context they are applied to * @param coefficients constant is the leftmost coefficient */ public inline class Polynomial(public val coefficients: List) +@Suppress("FunctionName") public fun Polynomial(vararg coefficients: T): Polynomial = Polynomial(coefficients.toList()) public fun Polynomial.value(): Double = coefficients.reduceIndexed { index, acc, d -> acc + d.pow(index) } @@ -33,14 +33,6 @@ public fun > Polynomial.value(ring: C, arg: T): T = ring res } -/** - * Represent a polynomial as a context-dependent function - */ -public fun > Polynomial.asMathFunction(): MathFunction = - object : MathFunction { - override fun C.invoke(arg: T): T = value(this, arg) - } - /** * Represent the polynomial as a regular context-less function */ @@ -49,7 +41,7 @@ public fun > Polynomial.asFunction(ring: C): (T) -> T = /** * An algebra for polynomials */ -public class PolynomialSpace>(public val ring: C) : Space> { +public class PolynomialSpace>(private val ring: C) : Space> { public override val zero: Polynomial = Polynomial(emptyList()) public override fun add(a: Polynomial, b: Polynomial): Polynomial { diff --git a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/functions.kt b/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/functions.kt deleted file mode 100644 index d780c16f3..000000000 --- a/kmath-functions/src/commonMain/kotlin/kscience/kmath/functions/functions.kt +++ /dev/null @@ -1,34 +0,0 @@ -package kscience.kmath.functions - -import kscience.kmath.operations.Algebra -import kscience.kmath.operations.RealField - -// TODO make fun interface when KT-41770 is fixed -/** - * A regular function that could be called only inside specific algebra context - * @param T source type - * @param C source algebra constraint - * @param R result type - */ -public /*fun*/ interface MathFunction, R> { - public operator fun C.invoke(arg: T): R -} - -public fun MathFunction.invoke(arg: Double): R = RealField.invoke(arg) - -/** - * A suspendable function defined in algebraic context - */ -// TODO make fun interface, when the new JVM IR is enabled -public interface SuspendableMathFunction, R> { - public suspend operator fun C.invoke(arg: T): R -} - -public suspend fun SuspendableMathFunction.invoke(arg: Double): R = RealField.invoke(arg) - -/** - * A parametric function with parameter - */ -public fun interface ParametricFunction> { - public operator fun C.invoke(arg: T, parameter: P): T -} diff --git a/kmath-prob/src/commonMain/kotlin/kscience/kmath/prob/Fit.kt b/kmath-prob/src/commonMain/kotlin/kscience/kmath/prob/Fit.kt new file mode 100644 index 000000000..efe582212 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/kscience/kmath/prob/Fit.kt @@ -0,0 +1,36 @@ +package kscience.kmath.prob + +import kscience.kmath.expressions.AutoDiffProcessor +import kscience.kmath.expressions.DifferentiableExpression +import kscience.kmath.expressions.ExpressionAlgebra +import kscience.kmath.operations.ExtendedField +import kscience.kmath.structures.Buffer +import kscience.kmath.structures.indices + +public object Fit { + + /** + * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation + */ + public fun chiSquared( + autoDiff: AutoDiffProcessor, + x: Buffer, + y: Buffer, + yErr: Buffer, + model: A.(I) -> I, + ): DifferentiableExpression where A : ExtendedField, A : ExpressionAlgebra { + require(x.size == y.size) { "X and y buffers should be of the same size" } + require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } + return autoDiff.process { + var sum = zero + x.indices.forEach { + val xValue = const(x[it]) + val yValue = const(y[it]) + val yErrValue = const(yErr[it]) + val modelValue = model(xValue) + sum += ((yValue - modelValue) / yErrValue).pow(2) + } + sum + } + } +} \ No newline at end of file diff --git a/kmath-prob/src/commonMain/kotlin/kscience/kmath/prob/OptimizationProblem.kt b/kmath-prob/src/commonMain/kotlin/kscience/kmath/prob/OptimizationProblem.kt new file mode 100644 index 000000000..c5fb3fa54 --- /dev/null +++ b/kmath-prob/src/commonMain/kotlin/kscience/kmath/prob/OptimizationProblem.kt @@ -0,0 +1,91 @@ +package kscience.kmath.commons.optimization + +import kscience.kmath.expressions.DifferentiableExpression +import kscience.kmath.expressions.Expression +import kscience.kmath.expressions.Symbol + +public interface OptimizationFeature + +public class OptimizationResult( + public val point: Map, + public val value: T, + public val features: Set = emptySet(), +) { + override fun toString(): String { + return "OptimizationResult(point=$point, value=$value)" + } +} + +public operator fun OptimizationResult.plus( + feature: OptimizationFeature, +): OptimizationResult = OptimizationResult(point, value, features + feature) + +/** + * A configuration builder for optimization problem + */ +public interface OptimizationProblem { + /** + * Define the initial guess for the optimization problem + */ + public fun initialGuess(map: Map): Unit + + /** + * Set an objective function expression + */ + public fun expression(expression: Expression): Unit + + /** + * Set a differentiable expression as objective function as function and gradient provider + */ + public fun diffExpression(expression: DifferentiableExpression): Unit + + /** + * Update the problem from previous optimization run + */ + public fun update(result: OptimizationResult) + + /** + * Make an optimization run + */ + public fun optimize(): OptimizationResult +} + +public interface OptimizationProblemFactory> { + public fun build(symbols: List): P + +} + +public operator fun > OptimizationProblemFactory.invoke( + symbols: List, + block: P.() -> Unit, +): P = build(symbols).apply(block) + + +/** + * Optimize expression without derivatives using specific [OptimizationProblemFactory] + */ +public fun > Expression.optimizeWith( + factory: OptimizationProblemFactory, + vararg symbols: Symbol, + configuration: F.() -> Unit, +): OptimizationResult { + require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } + val problem = factory(symbols.toList(),configuration) + problem.expression(this) + return problem.optimize() +} + +/** + * Optimize differentiable expression using specific [OptimizationProblemFactory] + */ +public fun > DifferentiableExpression.optimizeWith( + factory: OptimizationProblemFactory, + vararg symbols: Symbol, + configuration: F.() -> Unit, +): OptimizationResult { + require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } + val problem = factory(symbols.toList(), configuration) + problem.diffExpression(this) + return problem.optimize() +} +