Refactor structure features. Basic curve fitting

This commit is contained in:
Alexander Nozik 2021-03-19 11:07:27 +03:00
parent 248d42c4e0
commit 88d0c19a74
25 changed files with 311 additions and 257 deletions

View File

@ -1,5 +1,3 @@
import ru.mipt.npm.gradle.KSciencePublishingPlugin
plugins {
id("ru.mipt.npm.gradle.project")
}
@ -20,11 +18,11 @@ allprojects {
}
group = "space.kscience"
version = "0.3.0-dev-3"
version = "0.3.0-dev-4"
}
subprojects {
if (name.startsWith("kmath")) apply<KSciencePublishingPlugin>()
if (name.startsWith("kmath")) apply(plugin = "maven-publish")
}
readme {

View File

@ -8,10 +8,14 @@ import kscience.plotly.models.TraceValues
import space.kscience.kmath.commons.optimization.chiSquared
import space.kscience.kmath.commons.optimization.minimize
import space.kscience.kmath.expressions.symbol
import space.kscience.kmath.optimization.FunctionOptimization
import space.kscience.kmath.optimization.OptimizationResult
import space.kscience.kmath.real.DoubleVector
import space.kscience.kmath.real.map
import space.kscience.kmath.real.step
import space.kscience.kmath.stat.*
import space.kscience.kmath.stat.Distribution
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.normal
import space.kscience.kmath.structures.asIterable
import space.kscience.kmath.structures.toList
import kotlin.math.pow
@ -58,7 +62,7 @@ fun main() {
val yErr = y.map { sqrt(it) }//RealVector.same(x.size, sigma)
// compute differentiable chi^2 sum for given model ax^2 + bx + c
val chi2 = Fitting.chiSquared(x, y, yErr) { x1 ->
val chi2 = FunctionOptimization.chiSquared(x, y, yErr) { x1 ->
//bind variables to autodiff context
val a = bind(a)
val b = bind(b)

View File

@ -3,6 +3,7 @@ package space.kscience.kmath.commons.linear
import org.apache.commons.math3.linear.*
import space.kscience.kmath.linear.*
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureFeature
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.structures.DoubleBuffer
import kotlin.reflect.KClass
@ -89,7 +90,7 @@ public object CMLinearSpace : LinearSpace<Double, DoubleField> {
v * this
@UnstableKMathAPI
override fun <F : Any> getFeature(structure: Matrix<Double>, type: KClass<F>): F? {
override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? {
//Return the feature if it is intrinsic to the structure
structure.getFeature(type)?.let { return it }

View File

@ -10,21 +10,25 @@ import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.AbstractSimplex
import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.NelderMeadSimplex
import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer
import space.kscience.kmath.expressions.*
import space.kscience.kmath.stat.OptimizationFeature
import space.kscience.kmath.stat.OptimizationProblem
import space.kscience.kmath.stat.OptimizationProblemFactory
import space.kscience.kmath.stat.OptimizationResult
import space.kscience.kmath.optimization.FunctionOptimization
import space.kscience.kmath.optimization.OptimizationFeature
import space.kscience.kmath.optimization.OptimizationProblemFactory
import space.kscience.kmath.optimization.OptimizationResult
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<Symbol>) :
OptimizationProblem<Double>, SymbolIndexer, OptimizationFeature {
public class CMOptimization(
override val symbols: List<Symbol>,
) : FunctionOptimization<Double>, SymbolIndexer, OptimizationFeature {
private val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap()
private var optimizatorBuilder: (() -> MultivariateOptimizer)? = null
public var convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(DEFAULT_RELATIVE_TOLERANCE,
DEFAULT_ABSOLUTE_TOLERANCE, DEFAULT_MAX_ITER)
private var optimizerBuilder: (() -> MultivariateOptimizer)? = null
public var convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(
DEFAULT_RELATIVE_TOLERANCE,
DEFAULT_ABSOLUTE_TOLERANCE,
DEFAULT_MAX_ITER
)
public fun addOptimizationData(data: OptimizationData) {
optimizationData[data::class] = data
@ -57,8 +61,8 @@ public class CMOptimizationProblem(override val symbols: List<Symbol>) :
}
}
addOptimizationData(gradientFunction)
if (optimizatorBuilder == null) {
optimizatorBuilder = {
if (optimizerBuilder == null) {
optimizerBuilder = {
NonLinearConjugateGradientOptimizer(
NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES,
convergenceChecker
@ -70,8 +74,8 @@ public class CMOptimizationProblem(override val symbols: List<Symbol>) :
public fun simplex(simplex: AbstractSimplex) {
addOptimizationData(simplex)
//Set optimization builder to simplex if it is not present
if (optimizatorBuilder == null) {
optimizatorBuilder = { SimplexOptimizer(convergenceChecker) }
if (optimizerBuilder == null) {
optimizerBuilder = { SimplexOptimizer(convergenceChecker) }
}
}
@ -84,7 +88,7 @@ public class CMOptimizationProblem(override val symbols: List<Symbol>) :
}
public fun optimizer(block: () -> MultivariateOptimizer) {
optimizatorBuilder = block
optimizerBuilder = block
}
override fun update(result: OptimizationResult<Double>) {
@ -92,19 +96,19 @@ public class CMOptimizationProblem(override val symbols: List<Symbol>) :
}
override fun optimize(): OptimizationResult<Double> {
val optimizer = optimizatorBuilder?.invoke() ?: error("Optimizer not defined")
val optimizer = optimizerBuilder?.invoke() ?: error("Optimizer not defined")
val (point, value) = optimizer.optimize(*optimizationData.values.toTypedArray())
return OptimizationResult(point.toMap(), value, setOf(this))
}
public companion object : OptimizationProblemFactory<Double, CMOptimizationProblem> {
public companion object : OptimizationProblemFactory<Double, CMOptimization> {
public const val DEFAULT_RELATIVE_TOLERANCE: Double = 1e-4
public const val DEFAULT_ABSOLUTE_TOLERANCE: Double = 1e-4
public const val DEFAULT_MAX_ITER: Int = 1000
override fun build(symbols: List<Symbol>): CMOptimizationProblem = CMOptimizationProblem(symbols)
override fun build(symbols: List<Symbol>): CMOptimization = CMOptimization(symbols)
}
}
public fun CMOptimizationProblem.initialGuess(vararg pairs: Pair<Symbol, Double>): Unit = initialGuess(pairs.toMap())
public fun CMOptimizationProblem.simplexSteps(vararg pairs: Pair<Symbol, Double>): Unit = simplexSteps(pairs.toMap())
public fun CMOptimization.initialGuess(vararg pairs: Pair<Symbol, Double>): Unit = initialGuess(pairs.toMap())
public fun CMOptimization.simplexSteps(vararg pairs: Pair<Symbol, Double>): Unit = simplexSteps(pairs.toMap())

View File

@ -6,16 +6,16 @@ import space.kscience.kmath.commons.expressions.DerivativeStructureField
import space.kscience.kmath.expressions.DifferentiableExpression
import space.kscience.kmath.expressions.Expression
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.stat.Fitting
import space.kscience.kmath.stat.OptimizationResult
import space.kscience.kmath.stat.optimizeWith
import space.kscience.kmath.optimization.FunctionOptimization
import space.kscience.kmath.optimization.OptimizationResult
import space.kscience.kmath.optimization.optimizeWith
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.asBuffer
/**
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
*/
public fun Fitting.chiSquared(
public fun FunctionOptimization.Companion.chiSquared(
x: Buffer<Double>,
y: Buffer<Double>,
yErr: Buffer<Double>,
@ -25,7 +25,7 @@ public fun Fitting.chiSquared(
/**
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
*/
public fun Fitting.chiSquared(
public fun FunctionOptimization.Companion.chiSquared(
x: Iterable<Double>,
y: Iterable<Double>,
yErr: Iterable<Double>,
@ -43,23 +43,23 @@ public fun Fitting.chiSquared(
*/
public fun Expression<Double>.optimize(
vararg symbols: Symbol,
configuration: CMOptimizationProblem.() -> Unit,
): OptimizationResult<Double> = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration)
configuration: CMOptimization.() -> Unit,
): OptimizationResult<Double> = optimizeWith(CMOptimization, symbols = symbols, configuration)
/**
* Optimize differentiable expression
*/
public fun DifferentiableExpression<Double, Expression<Double>>.optimize(
vararg symbols: Symbol,
configuration: CMOptimizationProblem.() -> Unit,
): OptimizationResult<Double> = optimizeWith(CMOptimizationProblem, symbols = symbols, configuration)
configuration: CMOptimization.() -> Unit,
): OptimizationResult<Double> = optimizeWith(CMOptimization, symbols = symbols, configuration)
public fun DifferentiableExpression<Double, Expression<Double>>.minimize(
vararg startPoint: Pair<Symbol, Double>,
configuration: CMOptimizationProblem.() -> Unit = {},
configuration: CMOptimization.() -> Unit = {},
): OptimizationResult<Double> {
require(startPoint.isNotEmpty()) { "Must provide a list of symbols for optimization" }
val problem = CMOptimizationProblem(startPoint.map { it.first }).apply(configuration)
val problem = CMOptimization(startPoint.map { it.first }).apply(configuration)
problem.diffExpression(this)
problem.initialGuess(startPoint.toMap())
problem.goal(GoalType.MINIMIZE)

View File

@ -3,8 +3,8 @@ package space.kscience.kmath.commons.optimization
import org.junit.jupiter.api.Test
import space.kscience.kmath.commons.expressions.DerivativeStructureExpression
import space.kscience.kmath.expressions.symbol
import space.kscience.kmath.optimization.FunctionOptimization
import space.kscience.kmath.stat.Distribution
import space.kscience.kmath.stat.Fitting
import space.kscience.kmath.stat.RandomGenerator
import space.kscience.kmath.stat.normal
import kotlin.math.pow
@ -55,7 +55,7 @@ internal class OptimizeTest {
val yErr = List(x.size) { sigma }
val chi2 = Fitting.chiSquared(x, y, yErr) { x1 ->
val chi2 = FunctionOptimization.chiSquared(x, y, yErr) { x1 ->
val cWithDefault = bindSymbolOrNull(c) ?: one
bind(a) * x1.pow(2) + bind(b) * x1 + cWithDefault
}

View File

@ -575,7 +575,7 @@ public final class space/kscience/kmath/linear/MatrixBuilderKt {
public static final fun row (Lspace/kscience/kmath/linear/LinearSpace;[Ljava/lang/Object;)Lspace/kscience/kmath/nd/Structure2D;
}
public abstract interface class space/kscience/kmath/linear/MatrixFeature {
public abstract interface class space/kscience/kmath/linear/MatrixFeature : space/kscience/kmath/nd/StructureFeature {
}
public final class space/kscience/kmath/linear/MatrixFeaturesKt {
@ -1060,11 +1060,15 @@ public final class space/kscience/kmath/nd/Strides$DefaultImpls {
}
public abstract interface class space/kscience/kmath/nd/Structure1D : space/kscience/kmath/nd/StructureND, space/kscience/kmath/structures/Buffer {
public static final field Companion Lspace/kscience/kmath/nd/Structure1D$Companion;
public abstract fun get ([I)Ljava/lang/Object;
public abstract fun getDimension ()I
public abstract fun iterator ()Ljava/util/Iterator;
}
public final class space/kscience/kmath/nd/Structure1D$Companion {
}
public final class space/kscience/kmath/nd/Structure1D$DefaultImpls {
public static fun get (Lspace/kscience/kmath/nd/Structure1D;[I)Ljava/lang/Object;
public static fun getDimension (Lspace/kscience/kmath/nd/Structure1D;)I
@ -1104,6 +1108,9 @@ public final class space/kscience/kmath/nd/Structure2DKt {
public static final fun as2D (Lspace/kscience/kmath/nd/StructureND;)Lspace/kscience/kmath/nd/Structure2D;
}
public abstract interface class space/kscience/kmath/nd/StructureFeature {
}
public abstract interface class space/kscience/kmath/nd/StructureND {
public static final field Companion Lspace/kscience/kmath/nd/StructureND$Companion;
public abstract fun elements ()Lkotlin/sequences/Sequence;

View File

@ -164,7 +164,7 @@ public interface LinearSpace<T : Any, out A : Ring<T>> {
* @return a feature object or `null` if it isn't present.
*/
@UnstableKMathAPI
public fun <F : Any> getFeature(structure: Matrix<T>, type: KClass<F>): F? = structure.getFeature(type)
public fun <F : StructureFeature> getFeature(structure: Matrix<T>, type: KClass<out F>): F? = structure.getFeature(type)
public companion object {
@ -194,7 +194,7 @@ public interface LinearSpace<T : Any, out A : Ring<T>> {
* @return a feature object or `null` if it isn't present.
*/
@UnstableKMathAPI
public inline fun <T : Any, reified F : Any> LinearSpace<T, *>.getFeature(structure: Matrix<T>): F? =
public inline fun <T : Any, reified F : StructureFeature> LinearSpace<T, *>.getFeature(structure: Matrix<T>): F? =
getFeature(structure, F::class)

View File

@ -1,10 +1,12 @@
package space.kscience.kmath.linear
import space.kscience.kmath.nd.StructureFeature
/**
* 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
public interface MatrixFeature: StructureFeature
/**
* Matrices with this feature are considered to have only diagonal non-null elements.

View File

@ -1,6 +1,7 @@
package space.kscience.kmath.linear
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureFeature
import space.kscience.kmath.nd.getFeature
import space.kscience.kmath.operations.Ring
import kotlin.reflect.KClass
@ -20,7 +21,7 @@ public class MatrixWrapper<T : Any> internal constructor(
*/
@UnstableKMathAPI
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getFeature(type: KClass<T>): T? = features.singleOrNull { type.isInstance(it) } as? T
override fun <F : StructureFeature> getFeature(type: KClass<out F>): F? = features.singleOrNull { type.isInstance(it) } as? F
?: origin.getFeature(type)
override fun toString(): String {

View File

@ -67,7 +67,8 @@ public interface AlgebraND<T, C : Algebra<T>> {
* @return a feature object or `null` if it isn't present.
*/
@UnstableKMathAPI
public fun <F : Any> getFeature(structure: StructureND<T>, type: KClass<F>): F? = structure.getFeature(type)
public fun <F : StructureFeature> getFeature(structure: StructureND<T>, type: KClass<out F>): F? =
structure.getFeature(type)
public companion object
}
@ -81,7 +82,7 @@ public interface AlgebraND<T, C : Algebra<T>> {
* @return a feature object or `null` if it isn't present.
*/
@UnstableKMathAPI
public inline fun <T : Any, reified F : Any> AlgebraND<T, *>.getFeature(structure: StructureND<T>): F? =
public inline fun <T : Any, reified F : StructureFeature> AlgebraND<T, *>.getFeature(structure: StructureND<T>): F? =
getFeature(structure, F::class)
/**

View File

@ -15,6 +15,8 @@ public interface Structure1D<T> : StructureND<T>, Buffer<T> {
}
public override operator fun iterator(): Iterator<T> = (0 until size).asSequence().map(::get).iterator()
public companion object
}
/**

View File

@ -69,7 +69,7 @@ private inline class Structure2DWrapper<T>(val structure: StructureND<T>) : Stru
override operator fun get(i: Int, j: Int): T = structure[i, j]
@UnstableKMathAPI
override fun <F : Any> getFeature(type: KClass<F>): F? = structure.getFeature(type)
override fun <F : StructureFeature> getFeature(type: KClass<out F>): F? = structure.getFeature(type)
override fun elements(): Sequence<Pair<IntArray, T>> = structure.elements()
}

View File

@ -7,6 +7,8 @@ import kotlin.jvm.JvmName
import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass
public interface StructureFeature
/**
* Represents n-dimensional structure, i.e. multidimensional container of items of the same type and size. The number
* of dimensions and items in an array is defined by its shape, which is a sequence of non-negative integers that
@ -48,7 +50,7 @@ public interface StructureND<T> {
* If the feature is not present, null is returned.
*/
@UnstableKMathAPI
public fun <F : Any> getFeature(type: KClass<F>): F? = null
public fun <F : StructureFeature> getFeature(type: KClass<out F>): F? = null
public companion object {
/**
@ -144,7 +146,7 @@ public interface StructureND<T> {
public operator fun <T> StructureND<T>.get(vararg index: Int): T = get(index)
@UnstableKMathAPI
public inline fun <reified T : Any> StructureND<*>.getFeature(): T? = getFeature(T::class)
public inline fun <reified T : StructureFeature> StructureND<*>.getFeature(): T? = getFeature(T::class)
/**
* Represents mutable [StructureND].

View File

@ -8,5 +8,5 @@ public abstract class BlockingDoubleChain : Chain<Double> {
override suspend fun next(): Double = nextDouble()
public fun nextBlock(size: Int): DoubleArray = DoubleArray(size) { nextDouble() }
public open fun nextBlock(size: Int): DoubleArray = DoubleArray(size) { nextDouble() }
}

View File

@ -4,6 +4,7 @@ import org.ejml.dense.row.factory.DecompositionFactory_DDRM
import org.ejml.simple.SimpleMatrix
import space.kscience.kmath.linear.*
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.nd.StructureFeature
import space.kscience.kmath.nd.getFeature
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.structures.DoubleBuffer
@ -89,7 +90,7 @@ public object EjmlLinearSpace : LinearSpace<Double, DoubleField> {
v.toEjml().origin.scale(this).wrapVector()
@UnstableKMathAPI
override fun <F : Any> getFeature(structure: Matrix<Double>, type: KClass<F>): F? {
override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? {
//Return the feature if it is intrinsic to the structure
structure.getFeature(type)?.let { return it }

View File

@ -1,7 +1,43 @@
package space.kscience.kmath.real
import space.kscience.kmath.structures.asBuffer
import kotlin.math.abs
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.DoubleBuffer
import kotlin.math.floor
public val ClosedFloatingPointRange<Double>.length: Double get() = endInclusive - start
/**
* Create a Buffer-based grid with equally distributed [numberOfPoints] points. The range could be increasing or decreasing.
* If range has a zero size, then the buffer consisting of [numberOfPoints] equal values is returned.
*/
@UnstableKMathAPI
public fun Buffer.Companion.fromRange(range: ClosedFloatingPointRange<Double>, numberOfPoints: Int): DoubleBuffer {
require(numberOfPoints >= 2) { "Number of points in grid must be more than 1" }
val normalizedRange = when {
range.endInclusive > range.start -> range
range.endInclusive < range.start -> range.endInclusive..range.start
else -> return DoubleBuffer(numberOfPoints) { range.start }
}
val step = normalizedRange.length / (numberOfPoints - 1)
return DoubleBuffer(numberOfPoints) { normalizedRange.start + step * it / (numberOfPoints - 1) }
}
/**
* Create a Buffer-based grid with equally distributed points with a fixed [step]. The range could be increasing or decreasing.
* If the step is larger than the range size, single point is returned.
*/
@UnstableKMathAPI
public fun Buffer.Companion.fromRange(range: ClosedFloatingPointRange<Double>, step: Double): DoubleBuffer {
require(step > 0) { "The grid step must be positive" }
val normalizedRange = when {
range.endInclusive > range.start -> range
range.endInclusive < range.start -> range.endInclusive..range.start
else -> return DoubleBuffer(range.start)
}
val numberOfPoints = floor(normalizedRange.length / step).toInt()
return DoubleBuffer(numberOfPoints) { normalizedRange.start + step * it / (numberOfPoints - 1) }
}
/**
* Convert double range to sequence.
@ -11,35 +47,5 @@ import kotlin.math.abs
*
* If step is negative, the same goes from upper boundary downwards
*/
public fun ClosedFloatingPointRange<Double>.toSequenceWithStep(step: Double): Sequence<Double> = when {
step == 0.0 -> error("Zero step in double progression")
step > 0 -> sequence {
var current = start
while (current <= endInclusive) {
yield(current)
current += step
}
}
else -> sequence {
var current = endInclusive
while (current >= start) {
yield(current)
current += step
}
}
}
public infix fun ClosedFloatingPointRange<Double>.step(step: Double): DoubleVector =
toSequenceWithStep(step).toList().asBuffer()
/**
* Convert double range to sequence with the fixed number of points
*/
public fun ClosedFloatingPointRange<Double>.toSequenceWithPoints(numPoints: Int): Sequence<Double> {
require(numPoints > 1) { "The number of points should be more than 2" }
return toSequenceWithStep(abs(endInclusive - start) / (numPoints - 1))
}
@UnstableKMathAPI
public infix fun ClosedFloatingPointRange<Double>.step(step: Double): DoubleBuffer = Buffer.fromRange(this, step)

View File

@ -3,14 +3,6 @@ plugins {
}
kotlin.sourceSets {
all {
languageSettings.apply {
useExperimentalAnnotation("kotlinx.coroutines.FlowPreview")
useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi")
useExperimentalAnnotation("kotlinx.coroutines.ObsoleteCoroutinesApi")
}
}
commonMain {
dependencies {
api(project(":kmath-coroutines"))

View File

@ -0,0 +1,17 @@
package space.kscience.kmath.optimization
import space.kscience.kmath.expressions.DifferentiableExpression
import space.kscience.kmath.expressions.StringSymbol
import space.kscience.kmath.expressions.Symbol
import space.kscience.kmath.structures.Buffer
public interface DataFit<T : Any> : Optimization<T> {
public fun modelAndData(
x: Buffer<T>,
y: Buffer<T>,
yErr: Buffer<T>,
model: DifferentiableExpression<T, *>,
xSymbol: Symbol = StringSymbol("x"),
)
}

View File

@ -0,0 +1,122 @@
package space.kscience.kmath.optimization
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.ExtendedField
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.indices
import kotlin.math.pow
/**
* A likelihood function optimization problem
*/
public interface FunctionOptimization<T: Any>: Optimization<T>, DataFit<T> {
/**
* Define the initial guess for the optimization problem
*/
public fun initialGuess(map: Map<Symbol, T>)
/**
* Set an objective function expression
*/
public fun expression(expression: Expression<T>)
/**
* Set a differentiable expression as objective function as function and gradient provider
*/
public fun diffExpression(expression: DifferentiableExpression<T, Expression<T>>)
override fun modelAndData(
x: Buffer<T>,
y: Buffer<T>,
yErr: Buffer<T>,
model: DifferentiableExpression<T, *>,
xSymbol: Symbol,
) {
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" }
}
public companion object{
/**
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
*/
public fun <T : Any, I : Any, A> chiSquared(
autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
x: Buffer<T>,
y: Buffer<T>,
yErr: Buffer<T>,
model: A.(I) -> I,
): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
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
}
}
/**
* Generate a chi squared expression from given x-y-sigma model represented by an expression. Does not provide derivatives
*/
public fun chiSquared(
x: Buffer<Double>,
y: Buffer<Double>,
yErr: Buffer<Double>,
model: Expression<Double>,
xSymbol: Symbol = StringSymbol("x"),
): Expression<Double> {
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 Expression { arguments ->
x.indices.sumByDouble {
val xValue = x[it]
val yValue = y[it]
val yErrValue = yErr[it]
val modifiedArgs = arguments + (xSymbol to xValue)
val modelValue = model(modifiedArgs)
((yValue - modelValue) / yErrValue).pow(2)
}
}
}
}
}
/**
* Optimize expression without derivatives using specific [OptimizationProblemFactory]
*/
public fun <T : Any, F : FunctionOptimization<T>> Expression<T>.optimizeWith(
factory: OptimizationProblemFactory<T, F>,
vararg symbols: Symbol,
configuration: F.() -> Unit,
): OptimizationResult<T> {
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 <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
factory: OptimizationProblemFactory<T, F>,
vararg symbols: Symbol,
configuration: F.() -> Unit,
): OptimizationResult<T> {
require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
val problem = factory(symbols.toList(), configuration)
problem.diffExpression(this)
return problem.optimize()
}

View File

@ -0,0 +1,44 @@
package space.kscience.kmath.optimization
import space.kscience.kmath.expressions.Symbol
public interface OptimizationFeature
public class OptimizationResult<T>(
public val point: Map<Symbol, T>,
public val value: T,
public val features: Set<OptimizationFeature> = emptySet(),
) {
override fun toString(): String {
return "OptimizationResult(point=$point, value=$value)"
}
}
public operator fun <T> OptimizationResult<T>.plus(
feature: OptimizationFeature,
): OptimizationResult<T> = OptimizationResult(point, value, features + feature)
/**
* An optimization problem builder over [T] variables
*/
public interface Optimization<T : Any> {
/**
* Update the problem from previous optimization run
*/
public fun update(result: OptimizationResult<T>)
/**
* Make an optimization run
*/
public fun optimize(): OptimizationResult<T>
}
public fun interface OptimizationProblemFactory<T : Any, out P : Optimization<T>> {
public fun build(symbols: List<Symbol>): P
}
public operator fun <T : Any, P : Optimization<T>> OptimizationProblemFactory<T, P>.invoke(
symbols: List<Symbol>,
block: P.() -> Unit,
): P = build(symbols).apply(block)

View File

@ -1,63 +0,0 @@
package space.kscience.kmath.stat
import space.kscience.kmath.expressions.*
import space.kscience.kmath.operations.ExtendedField
import space.kscience.kmath.structures.Buffer
import space.kscience.kmath.structures.indices
import kotlin.math.pow
public object Fitting {
/**
* Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
*/
public fun <T : Any, I : Any, A> chiSquared(
autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
x: Buffer<T>,
y: Buffer<T>,
yErr: Buffer<T>,
model: A.(I) -> I,
): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
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
}
}
/**
* Generate a chi squared expression from given x-y-sigma model represented by an expression. Does not provide derivatives
*/
public fun chiSquared(
x: Buffer<Double>,
y: Buffer<Double>,
yErr: Buffer<Double>,
model: Expression<Double>,
xSymbol: Symbol = StringSymbol("x"),
): Expression<Double> {
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 Expression { arguments ->
x.indices.sumByDouble {
val xValue = x[it]
val yValue = y[it]
val yErrValue = yErr[it]
val modifiedArgs = arguments + (xSymbol to xValue)
val modelValue = model(modifiedArgs)
((yValue - modelValue) / yErrValue).pow(2)
}
}
}
}

View File

@ -1,88 +0,0 @@
package space.kscience.kmath.stat
import space.kscience.kmath.expressions.DifferentiableExpression
import space.kscience.kmath.expressions.Expression
import space.kscience.kmath.expressions.Symbol
public interface OptimizationFeature
public class OptimizationResult<T>(
public val point: Map<Symbol, T>,
public val value: T,
public val features: Set<OptimizationFeature> = emptySet(),
) {
override fun toString(): String {
return "OptimizationResult(point=$point, value=$value)"
}
}
public operator fun <T> OptimizationResult<T>.plus(
feature: OptimizationFeature,
): OptimizationResult<T> = OptimizationResult(point, value, features + feature)
/**
* A configuration builder for optimization problem
*/
public interface OptimizationProblem<T : Any> {
/**
* Define the initial guess for the optimization problem
*/
public fun initialGuess(map: Map<Symbol, T>)
/**
* Set an objective function expression
*/
public fun expression(expression: Expression<T>)
/**
* Set a differentiable expression as objective function as function and gradient provider
*/
public fun diffExpression(expression: DifferentiableExpression<T, Expression<T>>)
/**
* Update the problem from previous optimization run
*/
public fun update(result: OptimizationResult<T>)
/**
* Make an optimization run
*/
public fun optimize(): OptimizationResult<T>
}
public fun interface OptimizationProblemFactory<T : Any, out P : OptimizationProblem<T>> {
public fun build(symbols: List<Symbol>): P
}
public operator fun <T : Any, P : OptimizationProblem<T>> OptimizationProblemFactory<T, P>.invoke(
symbols: List<Symbol>,
block: P.() -> Unit,
): P = build(symbols).apply(block)
/**
* Optimize expression without derivatives using specific [OptimizationProblemFactory]
*/
public fun <T : Any, F : OptimizationProblem<T>> Expression<T>.optimizeWith(
factory: OptimizationProblemFactory<T, F>,
vararg symbols: Symbol,
configuration: F.() -> Unit,
): OptimizationResult<T> {
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 <T : Any, F : OptimizationProblem<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
factory: OptimizationProblemFactory<T, F>,
vararg symbols: Symbol,
configuration: F.() -> Unit,
): OptimizationResult<T> {
require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
val problem = factory(symbols.toList(), configuration)
problem.diffExpression(this)
return problem.optimize()
}

View File

@ -80,19 +80,20 @@ public fun Distribution.Companion.normal(
override fun probability(arg: Double): Double = exp(-(arg - mean).pow(2) / 2 / sigma2) / norm
}
public fun Distribution.Companion.poisson(lambda: Double): DiscreteSamplerDistribution =
object : DiscreteSamplerDistribution() {
private val computedProb: MutableMap<Int, Double> = hashMapOf(0 to exp(-lambda))
public fun Distribution.Companion.poisson(
lambda: Double,
): DiscreteSamplerDistribution = object : DiscreteSamplerDistribution() {
private val computedProb: HashMap<Int, Double> = hashMapOf(0 to exp(-lambda))
override fun buildSampler(generator: RandomGenerator): DiscreteSampler =
PoissonSampler.of(generator.asUniformRandomProvider(), lambda)
override fun buildSampler(generator: RandomGenerator): DiscreteSampler =
PoissonSampler.of(generator.asUniformRandomProvider(), lambda)
override fun probability(arg: Int): Double {
require(arg >= 0) { "The argument must be >= 0" }
override fun probability(arg: Int): Double {
require(arg >= 0) { "The argument must be >= 0" }
return if (arg > 40)
exp(-(arg - lambda).pow(2) / 2 / lambda) / sqrt(2 * PI * lambda)
else
computedProb.getOrPut(arg) { probability(arg - 1) * lambda / arg }
}
return if (arg > 40)
exp(-(arg - lambda).pow(2) / 2 / lambda) / sqrt(2 * PI * lambda)
else
computedProb.getOrPut(arg) { probability(arg - 1) * lambda / arg }
}
}