kmath/kmath-optimization/src/commonMain/tmp/QowFit.kt

375 lines
13 KiB
Kotlin
Raw Normal View History

2021-04-25 22:34:59 +03:00
/*
* Copyright 2018-2021 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.optimization.qow
import space.kscience.kmath.data.ColumnarData
import space.kscience.kmath.data.XYErrorColumnarData
2021-05-24 17:02:12 +03:00
import space.kscience.kmath.expressions.*
2021-04-25 22:34:59 +03:00
import space.kscience.kmath.linear.*
import space.kscience.kmath.misc.UnstableKMathAPI
import space.kscience.kmath.operations.DoubleField
import space.kscience.kmath.operations.Field
import space.kscience.kmath.optimization.OptimizationFeature
import space.kscience.kmath.optimization.OptimizationProblemFactory
import space.kscience.kmath.optimization.OptimizationResult
2021-05-25 16:42:23 +03:00
import space.kscience.kmath.optimization.XYOptimization
2021-04-25 22:34:59 +03:00
import space.kscience.kmath.structures.DoubleBuffer
import space.kscience.kmath.structures.DoubleL2Norm
import kotlin.math.pow
private typealias ParamSet = Map<Symbol, Double>
@OptIn(UnstableKMathAPI::class)
public class QowFit(
override val symbols: List<Symbol>,
private val space: LinearSpace<Double, DoubleField>,
private val solver: LinearSolver<Double>,
2021-05-25 16:42:23 +03:00
) : XYOptimization<Double>, SymbolIndexer {
2021-04-25 22:34:59 +03:00
private var logger: FitLogger? = null
private var startingPoint: Map<Symbol, Double> = TODO()
private var covariance: Matrix<Double>? = TODO()
private val prior: DifferentiableExpression<Double, Expression<Double>>? = TODO()
private var data: XYErrorColumnarData<Double, Double, Double> = TODO()
private var model: DifferentiableExpression<Double, Expression<Double>> = TODO()
private val features = HashSet<OptimizationFeature>()
override fun update(result: OptimizationResult<Double>) {
TODO("Not yet implemented")
}
override val algebra: Field<Double>
get() = TODO("Not yet implemented")
override fun data(
dataSet: ColumnarData<Double>,
xSymbol: Symbol,
ySymbol: Symbol,
xErrSymbol: Symbol?,
yErrSymbol: Symbol?,
) {
TODO("Not yet implemented")
}
override fun model(model: (Double) -> DifferentiableExpression<Double, *>) {
TODO("Not yet implemented")
}
private var x: Symbol = Symbol.x
/**
* The signed distance from the model to the [i]-th point of data.
*/
private fun distance(i: Int, parameters: Map<Symbol, Double>): Double =
model(parameters + (x to data.x[i])) - data.y[i]
/**
* The derivative of [distance]
* TODO use expressions instead
*/
private fun distanceDerivative(symbol: Symbol, i: Int, parameters: Map<Symbol, Double>): Double =
model.derivative(symbol)(parameters + (x to data.x[i]))
/**
* The dispersion of [i]-th data point
*/
private fun getDispersion(i: Int, parameters: Map<Symbol, Double>): Double = data.yErr[i].pow(2)
private fun getCovariance(weight: QoWeight): Matrix<Double> = solver.inverse(getEqDerivValues(weight))
/**
* Теоретическая ковариация весовых функций.
*
* D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2
*/
private fun covarF(weight: QoWeight): Matrix<Double> = space.buildSymmetricMatrix(symbols.size) { k, l ->
(0 until data.size).sumOf { i -> weight.derivs[k, i] * weight.derivs[l, i] / weight.dispersion[i] }
}
/**
* Экспериментальная ковариация весов. Формула (22) из
* http://arxiv.org/abs/physics/0604127
*
* @param source
* @param set
* @param fitPars
* @param weight
* @return
*/
private fun covarFExp(weight: QoWeight, theta: Map<Symbol, Double>): Matrix<Double> = space.run {
/*
* Важно! Если не делать предварителього вычисления этих производных, то
* количество вызывов функции будет dim^2 вместо dim Первый индекс -
* номер точки, второй - номер переменной, по которой берется производная
*/
val eqvalues = buildMatrix(data.size, symbols.size) { i, l ->
distance(i, theta) * weight.derivs[l, i] / weight.dispersion[i]
}
buildMatrix(symbols.size, symbols.size) { k, l ->
(0 until data.size).sumOf { i -> eqvalues[i, l] * eqvalues[i, k] }
}
}
/**
* производные уравнений для метода Ньютона
*
* @param source
* @param set
* @param fitPars
* @param weight
* @return
*/
private fun getEqDerivValues(
weight: QoWeight, theta: Map<Symbol, Double> = weight.theta,
): Matrix<Double> = space.run {
val fitDim = symbols.size
//Возвращает производную k-того Eq по l-тому параметру
val res = Array(fitDim) { DoubleArray(fitDim) }
val sderiv = buildMatrix(data.size, symbols.size) { i, l ->
distanceDerivative(symbols[l], i, theta)
}
buildMatrix(symbols.size, symbols.size) { k, l ->
val base = (0 until data.size).sumOf { i ->
require(weight.dispersion[i] > 0)
sderiv[i, l] * weight.derivs[k, i] / weight.dispersion[i]
}
prior?.let { prior ->
//Check if this one is correct
val pi = prior(theta)
val deriv1 = prior.derivative(symbols[k])(theta)
val deriv2 = prior.derivative(symbols[l])(theta)
base + deriv1 * deriv2 / pi / pi
} ?: base
}
}
/**
* Значения уравнений метода квазиоптимальных весов
*
* @param source
* @param set
* @param fitPars
* @param weight
* @return
*/
private fun getEqValues(weight: QoWeight, theta: Map<Symbol, Double> = weight.theta): Point<Double> {
val distances = DoubleBuffer(data.size) { i -> distance(i, theta) }
return DoubleBuffer(symbols.size) { k ->
val base = (0 until data.size).sumOf { i -> distances[i] * weight.derivs[k, i] / weight.dispersion[i] }
//Поправка на априорную вероятность
prior?.let { prior ->
base - prior.derivative(symbols[k])(theta) / prior(theta)
} ?: base
}
}
/**
* The state of QOW fitter
* Created by Alexander Nozik on 17-Oct-16.
*/
private inner class QoWeight(
val theta: Map<Symbol, Double>,
) {
init {
require(data.size > 0) { "The state does not contain data" }
}
/**
* Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter
*/
val derivs: Matrix<Double> by lazy {
space.buildMatrix(data.size, symbols.size) { i, k ->
distanceDerivative(symbols[k], i, theta)
}
}
/**
* Array of dispersions in each point
*/
val dispersion: Point<Double> by lazy {
DoubleBuffer(data.size) { i -> getDispersion(i, theta) }
}
}
private fun newtonianStep(
weight: QoWeight,
par: Map<Symbol, Double>,
eqvalues: Point<Double>,
): Map<Symbol, Double> = space.run {
val start = par.toPoint()
val invJacob = solver.inverse(getEqDerivValues(weight, par))
val step = invJacob.dot(eqvalues)
return par + (start - step).toMap()
}
private fun newtonianRun(
weight: QoWeight,
maxSteps: Int = 100,
tolerance: Double = 0.0,
fast: Boolean = false,
): ParamSet {
var dis: Double//норма невязки
// Для удобства работаем всегда с полным набором параметров
var par = startingPoint
logger?.log { "Starting newtonian iteration from: \n\t$par" }
var eqvalues = getEqValues(weight, par)//значения функций
dis = DoubleL2Norm.norm(eqvalues)// невязка
logger?.log { "Starting discrepancy is $dis" }
var i = 0
var flag = false
while (!flag) {
i++
logger?.log { "Starting step number $i" }
val currentSolution = if (fast) {
//Берет значения матрицы в той точке, где считается вес
newtonianStep(weight, weight.theta, eqvalues)
} else {
//Берет значения матрицы в точке par
newtonianStep(weight, par, eqvalues)
}
// здесь должен стоять учет границ параметров
logger?.log { "Parameter values after step are: \n\t$currentSolution" }
eqvalues = getEqValues(weight, currentSolution)
val currentDis = DoubleL2Norm.norm(eqvalues)// невязка после шага
logger?.log { "The discrepancy after step is: $currentDis." }
if (currentDis >= dis && i > 1) {
//дополнительно проверяем, чтобы был сделан хотя бы один шаг
flag = true
logger?.log { "The discrepancy does not decrease. Stopping iteration." }
} else {
par = currentSolution
dis = currentDis
}
if (i >= maxSteps) {
flag = true
logger?.log { "Maximum number of iterations reached. Stopping iteration." }
}
if (dis <= tolerance) {
flag = true
logger?.log { "Tolerance threshold is reached. Stopping iteration." }
}
}
return par
}
//
// override fun run(state: FitState, parentLog: History?, meta: Meta): FitResult {
// val log = Chronicle("QOW", parentLog)
// val action = meta.getString(FIT_STAGE_TYPE, TASK_RUN)
// log.report("QOW fit engine started task '{}'", action)
// return when (action) {
// TASK_SINGLE -> makeRun(state, log, meta)
// TASK_COVARIANCE -> generateErrors(state, log, meta)
// TASK_RUN -> {
// var res = makeRun(state, log, meta)
// res = makeRun(res.optState().get(), log, meta)
// generateErrors(res.optState().get(), log, meta)
// }
// else -> throw IllegalArgumentException("Unknown task")
// }
// }
// private fun makeRun(state: FitState, log: History, meta: Meta): FitResult {
// /*Инициализация объектов, задание исходных значений*/
// log.report("Starting fit using quasioptimal weights method.")
//
// val fitPars = getFitPars(state, meta)
//
// val curWeight = QoWeight(state, fitPars, state.parameters)
//
// // вычисляем вес в allPar. Потом можно будет попробовать ручное задание веса
// log.report("The starting weight is: \n\t{}",
// MathUtils.toString(curWeight.theta))
//
// //Стартовая точка такая же как и параметр веса
// /*Фитирование*/
// val res = newtonianRun(state, curWeight, log, meta)
//
// /*Генерация результата*/
//
// return FitResult.build(state.edit().setPars(res).build(), *fitPars)
// }
/**
* generateErrors.
*/
private fun generateErrors(): Matrix<Double> {
2024-03-27 09:11:12 +03:00
logger?.log {
"""
2021-04-25 22:34:59 +03:00
Starting errors estimation using quasioptimal weights method. The starting weight is:
${curWeight.theta}
2024-03-27 09:11:12 +03:00
""".trimIndent()
}
2021-04-25 22:34:59 +03:00
val curWeight = QoWeight(startingPoint)
val covar = getCovariance(curWeight)
val decomposition = EigenDecomposition(covar.matrix)
var valid = true
for (lambda in decomposition.realEigenvalues) {
if (lambda <= 0) {
log.report("The covariance matrix is not positive defined. Error estimation is not valid")
valid = false
}
}
}
override suspend fun optimize(): OptimizationResult<Double> {
val curWeight = QoWeight(startingPoint)
logger?.log {
"""
Starting fit using quasioptimal weights method. The starting weight is:
${curWeight.theta}
""".trimIndent()
}
val res = newtonianRun(curWeight)
}
companion object : OptimizationProblemFactory<Double, QowFit> {
override fun build(symbols: List<Symbol>): QowFit {
TODO("Not yet implemented")
}
/**
* Constant `QOW_ENGINE_NAME="QOW"`
*/
const val QOW_ENGINE_NAME = "QOW"
/**
* Constant `QOW_METHOD_FAST="fast"`
*/
const val QOW_METHOD_FAST = "fast"
}
}