diff --git a/build.gradle.kts b/build.gradle.kts index 2dae131..a0b7b5f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ allprojects { } val dataforgeVersion by extra("0.4.0-dev-2") -val kmathVersion by extra("0.2.1") +val kmathVersion by extra("0.3.0-dev-3") apiValidation{ validationDisabled = true diff --git a/numass-data-proto/src/main/proto/ru/inr/mass/data/proto/numass-proto.proto b/numass-data-proto/src/main/proto/ru/inr/mass/data/proto/numass-proto.proto index 923d6b9..e8a4397 100644 --- a/numass-data-proto/src/main/proto/ru/inr/mass/data/proto/numass-proto.proto +++ b/numass-data-proto/src/main/proto/ru/inr/mass/data/proto/numass-proto.proto @@ -10,7 +10,7 @@ message Point { // Raw data frame message Frame { uint64 time = 1; // Time in nanos from the beginning of the block - bytes data = 2; // Frame data as an array of int16 mesured in arbitrary channels + bytes data = 2; // Frame data as an array of int16 measured in arbitrary channels } // Event block obtained directly from device of from frame analysis // In order to save space, times and amplitudes are in separate arrays. diff --git a/numass-model/build.gradle.kts b/numass-model/build.gradle.kts new file mode 100644 index 0000000..3a6843f --- /dev/null +++ b/numass-model/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + kotlin("multiplatform") + id("ru.mipt.npm.gradle.common") + `maven-publish` +} + + +val dataforgeVersion: String by rootProject.extra +val kmathVersion: String by rootProject.extra + +kotlin.sourceSets { + commonMain { + dependencies { + api("space.kscience:dataforge-meta:$dataforgeVersion") + api("space.kscience:kmath-for-real:$kmathVersion") + } + } + jvmMain{ + dependencies{ + api("space.kscience:kmath-commons:$kmathVersion") + api("ch.qos.logback:logback-classic:1.2.3") + } + } +} diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINOSResult.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINOSResult.kt new file mode 100644 index 0000000..6cd365c --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINOSResult.kt @@ -0,0 +1,70 @@ +/* + * 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 hep.dataforge.stat.fit + + +/** + * Контейнер для несимметричных оценок и доверительных интервалов + * + * @author Darksnake + * @version $Id: $Id + */ +class MINOSResult +/** + * + * Constructor for MINOSResult. + * + * @param list an array of [String] objects. + */(private val names: Array, private val errl: DoubleArray?, private val errp: DoubleArray?) : + IntervalEstimate { + fun getNames(): NameList { + return NameList(names) + } + + fun getInterval(parName: String?): Pair { + val index: Int = getNames().getNumberByName(parName) + return Pair(ValueFactory.of(errl!![index]), ValueFactory.of(errp!![index])) + } + + val cL: Double + get() = 0.68 + + /** {@inheritDoc} */ + fun print(out: PrintWriter) { + if (errl != null || errp != null) { + out.println() + out.println("Assymetrical errors:") + out.println() + out.println("Name\tLower\tUpper") + for (i in 0 until getNames().size()) { + out.print(getNames().get(i)) + out.print("\t") + if (errl != null) { + out.print(errl[i]) + } else { + out.print("---") + } + out.print("\t") + if (errp != null) { + out.print(errp[i]) + } else { + out.print("---") + } + out.println() + } + } + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITFitter.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITFitter.kt new file mode 100644 index 0000000..2a19db6 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITFitter.kt @@ -0,0 +1,216 @@ +/* + * 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 hep.dataforge.stat.fit + +import ru.inr.mass.minuit.* + +/** + * + * + * MINUITFitter class. + * + * @author Darksnake + * @version $Id: $Id + */ +class MINUITFitter : Fitter { + fun run(state: FitState, parentLog: History?, meta: Meta): FitResult { + val log = Chronicle("MINUIT", parentLog) + val action: String = meta.getString("action", TASK_RUN) + log.report("MINUIT fit engine started action '{}'", action) + return when (action) { + TASK_COVARIANCE -> runHesse(state, log, meta) + TASK_SINGLE, TASK_RUN -> runFit(state, log, meta) + else -> throw IllegalArgumentException("Unknown task") + } + } + + @NotNull + fun getName(): String { + return MINUIT_ENGINE_NAME + } + + /** + * + * + * runHesse. + * + * @param state a [hep.dataforge.stat.fit.FitState] object. + * @param log + * @return a [FitResult] object. + */ + fun runHesse(state: FitState, log: History, meta: Meta?): FitResult { + val strategy: Int + strategy = Global.INSTANCE.getInt("MINUIT_STRATEGY", 2) + log.report("Generating errors using MnHesse 2-nd order gradient calculator.") + val fcn: MultiFunction + val fitPars: Array = Fitter.Companion.getFitPars(state, meta) + val pars: ParamSet = state.getParameters() + fcn = MINUITUtils.getFcn(state, pars, fitPars) + val hesse = MnHesse(strategy) + val mnState: MnUserParameterState = hesse.calculate(fcn, MINUITUtils.getFitParameters(pars, fitPars)) + val allPars: ParamSet = pars.copy() + for (fitPar in fitPars) { + allPars.setParValue(fitPar, mnState.value(fitPar)) + allPars.setParError(fitPar, mnState.error(fitPar)) + } + val newState: FitState.Builder = state.edit() + newState.setPars(allPars) + if (mnState.hasCovariance()) { + val mnCov: MnUserCovariance = mnState.covariance() + var j: Int + val cov = Array(mnState.variableParameters()) { DoubleArray(mnState.variableParameters()) } + for (i in 0 until mnState.variableParameters()) { + j = 0 + while (j < mnState.variableParameters()) { + cov[i][j] = mnCov.get(i, j) + j++ + } + } + newState.setCovariance(NamedMatrix(fitPars, cov), true) + } + return FitResult.build(newState.build(), fitPars) + } + + fun runFit(state: FitState, log: History, meta: Meta): FitResult { + val minuit: MnApplication + log.report("Starting fit using Minuit.") + val strategy: Int + strategy = Global.INSTANCE.getInt("MINUIT_STRATEGY", 2) + var force: Boolean + force = Global.INSTANCE.getBoolean("FORCE_DERIVS", false) + val fitPars: Array = Fitter.Companion.getFitPars(state, meta) + for (fitPar in fitPars) { + if (!state.modelProvidesDerivs(fitPar)) { + force = true + log.reportError("Model does not provide derivatives for parameter '{}'", fitPar) + } + } + if (force) { + log.report("Using MINUIT gradient calculator.") + } + val fcn: MultiFunction + val pars: ParamSet = state.getParameters().copy() + fcn = MINUITUtils.getFcn(state, pars, fitPars) + val method: String = meta.getString("method", MINUIT_MIGRAD) + when (method) { + MINUIT_MINOS, MINUIT_MINIMIZE -> minuit = + MnMinimize(fcn, MINUITUtils.getFitParameters(pars, fitPars), strategy) + MINUIT_SIMPLEX -> minuit = MnSimplex(fcn, MINUITUtils.getFitParameters(pars, fitPars), strategy) + else -> minuit = MnMigrad(fcn, MINUITUtils.getFitParameters(pars, fitPars), strategy) + } + if (force) { + minuit.setUseAnalyticalDerivatives(false) + log.report("Forced to use MINUIT internal derivative calculator!") + } + +// minuit.setUseAnalyticalDerivatives(true); + val minimum: FunctionMinimum + val maxSteps: Int = meta.getInt("iterations", -1) + val tolerance: Double = meta.getDouble("tolerance", -1) + minimum = if (maxSteps > 0) { + if (tolerance > 0) { + minuit.minimize(maxSteps, tolerance) + } else { + minuit.minimize(maxSteps) + } + } else { + minuit.minimize() + } + if (!minimum.isValid()) { + log.report("Minimization failed!") + } + log.report("MINUIT run completed in {} function calls.", minimum.nfcn()) + + /* + * Генерация результата + */ + val allPars: ParamSet = pars.copy() + for (fitPar in fitPars) { + allPars.setParValue(fitPar, minimum.userParameters().value(fitPar)) + allPars.setParError(fitPar, minimum.userParameters().error(fitPar)) + } + val newState: FitState.Builder = state.edit() + newState.setPars(allPars) + var valid: Boolean = minimum.isValid() + if (minimum.userCovariance().nrow() > 0) { + var j: Int + val cov = Array(minuit.variableParameters()) { DoubleArray(minuit.variableParameters()) } + if (cov[0].length == 1) { + cov[0][0] = minimum.userParameters().error(0) * minimum.userParameters().error(0) + } else { + for (i in 0 until minuit.variableParameters()) { + j = 0 + while (j < minuit.variableParameters()) { + cov[i][j] = minimum.userCovariance().get(i, j) + j++ + } + } + } + newState.setCovariance(NamedMatrix(fitPars, cov), true) + } + if (method == MINUIT_MINOS) { + log.report("Starting MINOS procedure for precise error estimation.") + val minos = MnMinos(fcn, minimum, strategy) + var mnError: MinosError + val errl = DoubleArray(fitPars.size) + val errp = DoubleArray(fitPars.size) + for (i in fitPars.indices) { + mnError = minos.minos(i) + if (mnError.isValid()) { + errl[i] = mnError.lower() + errp[i] = mnError.upper() + } else { + valid = false + } + } + val minosErrors = MINOSResult(fitPars, errl, errp) + newState.setInterval(minosErrors) + } + return FitResult.build(newState.build(), valid, fitPars) + } + + companion object { + /** + * Constant `MINUIT_MIGRAD="MIGRAD"` + */ + const val MINUIT_MIGRAD = "MIGRAD" + + /** + * Constant `MINUIT_MINIMIZE="MINIMIZE"` + */ + const val MINUIT_MINIMIZE = "MINIMIZE" + + /** + * Constant `MINUIT_SIMPLEX="SIMPLEX"` + */ + const val MINUIT_SIMPLEX = "SIMPLEX" + + /** + * Constant `MINUIT_MINOS="MINOS"` + */ + const val MINUIT_MINOS = "MINOS" //MINOS errors + + /** + * Constant `MINUIT_HESSE="HESSE"` + */ + const val MINUIT_HESSE = "HESSE" //HESSE errors + + /** + * Constant `MINUIT_ENGINE_NAME="MINUIT"` + */ + const val MINUIT_ENGINE_NAME = "MINUIT" + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITPlugin.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITPlugin.kt new file mode 100644 index 0000000..a00a46a --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITPlugin.kt @@ -0,0 +1,97 @@ +/* + * 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 hep.dataforge.stat.fit + +import hep.dataforge.context.* + +/** + * Мэнеджер для MINUITа. Пока не играет никакой активной роли кроме ведения + * внутреннего лога. + * + * @author Darksnake + * @version $Id: $Id + */ +@PluginDef(group = "hep.dataforge", + name = "MINUIT", + dependsOn = ["hep.dataforge:fitting"], + info = "The MINUIT fitter engine for DataForge fitting") +class MINUITPlugin : BasicPlugin() { + fun attach(@NotNull context: Context?) { + super.attach(context) + clearStaticLog() + } + + @Provides(Fitter.FITTER_TARGET) + fun getFitter(fitterName: String): Fitter? { + return if (fitterName == "MINUIT") { + MINUITFitter() + } else { + null + } + } + + @ProvidesNames(Fitter.FITTER_TARGET) + fun listFitters(): List { + return listOf("MINUIT") + } + + fun detach() { + clearStaticLog() + super.detach() + } + + class Factory : PluginFactory() { + fun build(meta: Meta?): Plugin { + return MINUITPlugin() + } + + fun getType(): java.lang.Class { + return MINUITPlugin::class.java + } + } + + companion object { + /** + * Constant `staticLog` + */ + private val staticLog: Chronicle? = Chronicle("MINUIT-STATIC", Global.INSTANCE.getHistory()) + + /** + * + * + * clearStaticLog. + */ + fun clearStaticLog() { + staticLog.clear() + } + + /** + * + * + * logStatic. + * + * @param str a [String] object. + * @param pars a [Object] object. + */ + fun logStatic(str: String?, vararg pars: Any?) { + checkNotNull(staticLog) { "MINUIT log is not initialized." } + staticLog.report(str, pars) + LoggerFactory.getLogger("MINUIT").info(String.format(str, *pars)) + // Out.out.printf(str,pars); +// Out.out.println(); + } + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITUtils.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITUtils.kt new file mode 100644 index 0000000..a6440cd --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/MINUITUtils.kt @@ -0,0 +1,132 @@ +/* + * 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 hep.dataforge.stat.fit + +import hep.dataforge.MINUIT.FunctionMinimum + +internal object MINUITUtils { + fun getFcn(source: FitState, allPar: ParamSet, fitPars: Array): MultiFunction { + return MnFunc(source, allPar, fitPars) + } + + fun getFitParameters(set: ParamSet, fitPars: Array): MnUserParameters { + val pars = MnUserParameters() + var i: Int + var par: Param + i = 0 + while (i < fitPars.size) { + par = set.getByName(fitPars[i]) + pars.add(fitPars[i], par.getValue(), par.getErr()) + if (par.getLowerBound() > Double.NEGATIVE_INFINITY && par.getUpperBound() < Double.POSITIVE_INFINITY) { + pars.setLimits(i, par.getLowerBound(), par.getUpperBound()) + } else if (par.getLowerBound() > Double.NEGATIVE_INFINITY) { + pars.setLowerLimit(i, par.getLowerBound()) + } else if (par.getUpperBound() < Double.POSITIVE_INFINITY) { + pars.setUpperLimit(i, par.getUpperBound()) + } + i++ + } + return pars + } + + fun getValueSet(allPar: ParamSet, names: Array, values: DoubleArray): ParamSet { + assert(values.size == names.size) + assert(allPar.getNames().contains(names)) + val vector: ParamSet = allPar.copy() + for (i in values.indices) { + vector.setParValue(names[i], values[i]) + } + return vector + } + + fun isValidArray(ar: DoubleArray): Boolean { + for (i in ar.indices) { + if (java.lang.Double.isNaN(ar[i])) { + return false + } + } + return true + } + + /** + * + * + * printMINUITResult. + * + * @param out a [PrintWriter] object. + * @param minimum a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + fun printMINUITResult(out: PrintWriter, minimum: FunctionMinimum?) { + out.println() + out.println("***MINUIT INTERNAL FIT INFORMATION***") + out.println() + MnPrint.print(out, minimum) + out.println() + out.println("***END OF MINUIT INTERNAL FIT INFORMATION***") + out.println() + } + + internal class MnFunc(source: FitState, allPar: ParamSet, fitPars: Array) : MultiFunction { + var source: FitState + var allPar: ParamSet + var fitPars: Array + fun value(doubles: DoubleArray): Double { + assert(isValidArray(doubles)) + assert(doubles.size == fitPars.size) + return -2 * source.getLogProb(getValueSet(allPar, fitPars, doubles)) + // source.getChi2(getValueSet(allPar, fitPars, doubles)); + } + + @Throws(NotDefinedException::class) + fun derivValue(n: Int, doubles: DoubleArray): Double { + assert(isValidArray(doubles)) + assert(doubles.size == getDimension()) + val set: ParamSet = getValueSet(allPar, fitPars, doubles) + +// double res; +// double d, s, deriv; +// +// res = 0; +// for (int i = 0; i < source.getDataNum(); i++) { +// d = source.getDis(i, set); +// s = source.getDispersion(i, set); +// if (source.modelProvidesDerivs(fitPars[n])) { +// deriv = source.getDisDeriv(fitPars[n], i, set); +// } else { +// throw new NotDefinedException(); +// // Такого не должно быть, поскольку мы где-то наверху должы были проверить, что производные все есть. +// } +// res += 2 * d * deriv / s; +// } + return -2 * source.getLogProbDeriv(fitPars[n], set) + } + + fun getDimension(): Int { + return fitPars.size + } + + fun providesDeriv(n: Int): Boolean { + return source.modelProvidesDerivs(fitPars[n]) + } + + init { + this.source = source + this.allPar = allPar + this.fitPars = fitPars + assert(source.getModel().getNames().contains(fitPars)) + } + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/QOWFitter.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/QOWFitter.kt new file mode 100644 index 0000000..91e545a --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/QOWFitter.kt @@ -0,0 +1,261 @@ +/* + * Copyright 2018 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 ru.inr.mass.fit + +import hep.dataforge.io.history.Chronicle +import hep.dataforge.io.history.History +import hep.dataforge.maths.MathUtils +import hep.dataforge.maths.MatrixOperations.inverse +import hep.dataforge.maths.NamedMatrix +import hep.dataforge.maths.NamedVector +import hep.dataforge.meta.Meta +import hep.dataforge.names.AbstractNamedSet +import hep.dataforge.stat.fit.FitStage.* +import hep.dataforge.stat.fit.Fitter.Companion.getFitPars +import hep.dataforge.utils.Misc +import org.apache.commons.math3.linear.ArrayRealVector +import org.apache.commons.math3.linear.EigenDecomposition + +/** + * The state of QOW fitter + * Created by darksnake on 17-Oct-16. + */ +internal class QOWeight(val source: FitState, val fitPars: Array, theta: ParamSet) : AbstractNamedSet(fitPars) { + + /** + * The set of parameters for which the weight is calculated + * TODO make paramSet immutable + */ + val theta = theta.copy() + + /** + * Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter + * + * @return the derivs + */ + val derivs: Array + + /** + * Array of dispersions in each point + * + * @return the dispersion + */ + val dispersion: DoubleArray + + init { + if (source.dataSize <= 0) { + throw IllegalStateException("The state does not contain data") + } + + dispersion = DoubleArray(source.dataSize) + derivs = Array(names.size()) { DoubleArray(source.dataSize) } + + (0 until source.dataSize).forEach { i -> + this.dispersion[i] = source.getDispersion(i, theta) + (0 until names.size()).forEach { k -> + derivs[k][i] = source.getDisDeriv(this.names.get(k), i, theta) + } + } + + } +} + + +/** + * + * + * QOWFitter class. + * + * @author Alexander Nozik + * @version $Id: $Id + */ +object QOWFitter : Fitter { + + + /** + * Constant `QOW_ENGINE_NAME="QOW"` + */ + const val QOW_ENGINE_NAME = "QOW" + /** + * Constant `QOW_METHOD_FAST="fast"` + */ + const val QOW_METHOD_FAST = "fast" + + private fun newtonianRun(state: FitState, weight: QOWeight, log: History, meta: Meta) : ParamSet { + val maxSteps = meta.getInt("iterations", 100) + val tolerance = meta.getDouble("tolerance", 0.0) + + var dis: Double//норма невязки + // Для удобства работаем всегда с полным набором параметров + var par = state.parameters.copy() + + log.report("Starting newtonian iteration from: \n\t{}", + MathUtils.toString(par, *weight.namesAsArray())) + + var eqvalues = QOWUtils.getEqValues(state, par, weight)//значения функций + + dis = eqvalues.vector.norm// невязка + log.report("Starting discrepancy is {}", dis) + var i = 0 + var flag = false + while (!flag) { + Misc.checkThread() + i++ + log.report("Starting step number {}", i) + + val currentSolution = if (meta.getString(METHOD_NAME,"").equals(QOW_METHOD_FAST, ignoreCase = true)) { + //Берет значения матрицы в той точке, где считается вес + fastNewtonianStep(state, par, eqvalues, weight) + } else { + //Берет значения матрицы в точке par + newtonianStep(state, par, eqvalues, weight) + } + // здесь должен стоять учет границ параметров + + log.report("Parameter values after step are: \n\t{}", + MathUtils.toString(currentSolution, *weight.namesAsArray())) + + eqvalues = QOWUtils.getEqValues(state, currentSolution, weight) + val currentDis = eqvalues.vector.norm// невязка после шага + + log.report("The discrepancy after step is: {}", currentDis) + if (currentDis >= dis && i > 1) { + //дополнительно проверяем, чтобы был сделан хотя бы один шаг + flag = true + log.report("The discrepancy does not decrease. Stopping iteration.") + } else { + par = currentSolution + dis = currentDis + } + if (i >= maxSteps) { + flag = true + log.report("Maximum number of iterations reached. Stopping iteration.") + } + if (dis <= tolerance) { + flag = true + log.report("Tolerance threshold is reached. Stopping iteration.") + } + } + + return par + } + + private fun newtonianStep(source: FitState, par: ParamSet, eqvalues: NamedVector, weight: QOWeight): ParamSet { + Misc.checkThread()// check if action is cacneled + val start = par.getParValues(*weight.namesAsArray()).vector + val invJacob = inverse(QOWUtils.getEqDerivValues(source, par, weight)) + + val step = invJacob.operate(ArrayRealVector(eqvalues.getArray())) + return par.copy().setParValues(NamedVector(weight.namesAsArray(), start.subtract(step))) + } + + private fun fastNewtonianStep(source: FitState, par: ParamSet, eqvalues: NamedVector, weight: QOWeight): ParamSet { + Misc.checkThread()// check if action is cacneled + val start = par.getParValues(*weight.namesAsArray()).vector + val invJacob = inverse(QOWUtils.getEqDerivValues(source, weight)) + + val step = invJacob.operate(ArrayRealVector(eqvalues.getArray())) + return par.copy().setParValues(NamedVector(weight.namesAsArray(), start.subtract(step))) + } + + 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") + } + } + + override val name: String = QOW_ENGINE_NAME + + 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 = this.newtonianRun(state, curWeight, log, meta) + + /*Генерация результата*/ + + return FitResult.build(state.edit().setPars(res).build(), *fitPars) + } + + /** + * + * + * generateErrors. + * + * @param state a [hep.dataforge.stat.fit.FitState] object. + * @param task a [hep.dataforge.stat.fit.FitStage] object. + * @param log a [History] object. + * @return a [FitResult] object. + */ + private fun generateErrors(state: FitState, log: History, meta: Meta): FitResult { + + log.report("Starting errors estimation 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)) + + // ParamSet pars = state.getParameters().copy(); + val covar = getCovariance(state, 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 + } + } + + + return FitResult.build( + state.edit().setCovariance(covar, true).build(), + valid, + *fitPars + ) + + } + + private fun getCovariance(source: FitState, weight: QOWeight): NamedMatrix { + val invH = inverse(QOWUtils.getEqDerivValues(source, weight.namesAsArray(), weight)) + return NamedMatrix(weight.namesAsArray(), invH) + } + +} diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/QOWUtils.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/QOWUtils.kt new file mode 100644 index 0000000..cee7880 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/fit/QOWUtils.kt @@ -0,0 +1,306 @@ +/* + * 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 ru.inr.mass.fit + +import hep.dataforge.maths.NamedVector +import org.apache.commons.math3.linear.Array2DRowRealMatrix +import org.apache.commons.math3.linear.RealMatrix +import java.util.logging.Logger + +/** + * + * @author Alexander Nozik + */ +internal object QOWUtils { + + fun covarFExp(source: FitState, set: ParamSet, weight: QOWeight): RealMatrix { + return covarFExp(source, set, weight.namesAsArray(), weight) + } + + /** + * Теоретическая ковариация весовых функций. + * + * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2 + * + * + * @param source + * @param weight + * @return + */ + fun covarF(source: FitState, weight: QOWeight): RealMatrix { + + val fitDim = weight.names.size() + val res = Array(fitDim) { DoubleArray(fitDim) } + + var i: Int + var k: Int + var l: Int + + var summ: Double + + k = 0 + while (k < fitDim) { + l = k + while (l < fitDim) { + summ = 0.0 + i = 0 + while (i < source.dataSize) { + summ += weight.derivs[k][i] * weight.derivs[l][i] / weight.dispersion[i] + i++ + } + res[k][l] = summ + if (l != k) { + res[l][k] = summ + } + l++ + } + k++ + } + return Array2DRowRealMatrix(res) + } + + /** + * Экспериментальная ковариация весов. Формула (22) из + * http://arxiv.org/abs/physics/0604127 + * + * @param source + * @param set + * @param fitPars + * @param weight + * @return + */ + fun covarFExp(source: FitState, set: ParamSet, fitPars: Array, weight: QOWeight): RealMatrix { + + val fitDim = fitPars.size + val res = Array(fitDim) { DoubleArray(fitDim) } + val eqvalues = Array(source.dataSize) { DoubleArray(fitDim) } + /* + * Важно! Если не делать предварителього вычисления этих производных, то + * количество вызывов функции будет dim^2 вместо dim Первый индекс - + * номер точки, второй - номер переменной, по которой берется производная + */ + var i: Int + var k: Int + var l: Int + l = 0 + while (l < fitDim) { + i = 0 + while (i < source.dataSize) { + eqvalues[i][l] = source.getDis(i, set) * weight.derivs[l][i] / weight.dispersion[i] + i++ + } + l++ + } + var summ: Double + + k = 0 + while (k < fitDim) { + l = 0 + while (l < fitDim) { + summ = 0.0 + i = 0 + while (i < source.dataSize) { + summ += eqvalues[i][l] * eqvalues[i][k] + i++ + } + res[k][l] = summ + l++ + } + k++ + } + return Array2DRowRealMatrix(res) + } + + /** + * Берет производные уравнений по параметрам, указанным в весе + * + * @param source + * @param set + * @param weight + * @return + */ + fun getEqDerivValues(source: FitState, set: ParamSet, weight: QOWeight): RealMatrix { + return getEqDerivValues(source, set, weight.namesAsArray(), weight) + } + + fun getEqDerivValues(source: FitState, weight: QOWeight): RealMatrix { + return getEqDerivValues(source, weight.namesAsArray(), weight) + } + + /** + * производные уравнений для метода Ньютона + * + * @param source + * @param set + * @param fitPars + * @param weight + * @return + */ + fun getEqDerivValues(source: FitState, set: ParamSet, fitPars: Array, weight: QOWeight): RealMatrix { + + val fitDim = fitPars.size + //Возвращает производную k-того Eq по l-тому параметру + val res = Array(fitDim) { DoubleArray(fitDim) } + val sderiv = Array(source.dataSize) { DoubleArray(fitDim) } + /* + * Важно! Если не делать предварителього вычисления этих производных, то + * количество вызывов функции будет dim^2 вместо dim Первый индекс - + * номер точки, второй - номер переменной, по которой берется производная + */ + var i: Int// номер точки из набора данных + var k: Int// номер уравнения + var l: Int// номер параметра, по короторому берется производная + l = 0 + while (l < fitDim) { + i = 0 + while (i < source.dataSize) { + sderiv[i][l] = source.getDisDeriv(fitPars[l], i, set) + i++ + + } + l++ + } + var summ: Double + + k = 0 + while (k < fitDim) { + l = 0 + while (l < fitDim) { + summ = 0.0 + i = 0 + while (i < source.dataSize) { + // Тут баг, при нулевой дисперсии скатываемся в сингулярность.!!! + assert(weight.dispersion[i] > 0) + summ += sderiv[i][l] * weight.derivs[k][i] / weight.dispersion[i] + i++ + } + res[k][l] = summ + //TODO Это правильно. Почему?? + if (source.prior != null + && source.prior.names.contains(fitPars[k]) + && source.prior.names.contains(fitPars[l])) { + val prior = source.prior + Logger.getAnonymousLogger().warning("QOW does not interpret prior probability correctly") + val pi = prior.value(set) + val deriv1 = prior.derivValue(fitPars[k], set) + val deriv2 = prior.derivValue(fitPars[l], set) + //считаем априорную вероятность независимой для разных переменных + res[k][l] += deriv1 * deriv2 / pi / pi + } + l++ + } + k++ + } + return Array2DRowRealMatrix(res) + } + + /** + * Этот метод считает матрицу производных сразу в тета-0. Сильно экономит + * вызовы функции + * + * @param source + * @param fitPars + * @param weight + * @return + */ + fun getEqDerivValues(source: FitState, fitPars: Array, weight: QOWeight): RealMatrix { + val fitDim = fitPars.size + val res = Array(fitDim) { DoubleArray(fitDim) } + var i: Int + var k: Int + var l: Int + var summ: Double + k = 0 + while (k < fitDim) { + l = 0 + while (l < fitDim) { + summ = 0.0 + i = 0 + while (i < source.dataSize) { + summ += weight.derivs[l][i] * weight.derivs[k][i] / weight.dispersion[i] + i++ + } + res[k][l] = summ + + //TODO Это правильно. Почему?? + if (source.prior != null + && source.prior.names.contains(fitPars[k]) + && source.prior.names.contains(fitPars[l])) { + Logger.getAnonymousLogger().warning("QOW does not interpret prior probability correctly") + val prior = source.prior + val pi = prior.value(weight.theta) + val deriv1 = prior.derivValue(fitPars[k], weight.theta) + val deriv2 = prior.derivValue(fitPars[l], weight.theta) + //считаем априорную вероятность независимой для разный переменных + res[k][l] += deriv1 * deriv2 / pi / pi + } + l++ + } + k++ + } + return Array2DRowRealMatrix(res) + } + + fun getEqValues(source: FitState, set: ParamSet, weight: QOWeight): NamedVector { + return getEqValues(source, set, weight.namesAsArray(), weight) + } + + /** + * Значения уравнений метода квазиоптимальных весов + * + * @param source + * @param set + * @param fitPars + * @param weight + * @return + */ + fun getEqValues(source: FitState, set: ParamSet, fitPars: Array, weight: QOWeight): NamedVector { + + val res = DoubleArray(fitPars.size) + var i: Int + var k: Int + var summ: Double + + val diss = DoubleArray(source.dataSize) + + i = 0 + while (i < diss.size) { + diss[i] = source.getDis(i, set) + i++ + + } + + k = 0 + while (k < fitPars.size) { + summ = 0.0 + i = 0 + while (i < source.dataSize) { + summ += diss[i] * weight.derivs[k][i] / weight.dispersion[i] + i++ + } + res[k] = summ + //Поправка на априорную вероятность + if (source.prior != null && source.prior.names.contains(fitPars[k])) { + Logger.getAnonymousLogger().warning("QOW does not interpret prior probability correctly") + val prior = source.prior + res[k] -= prior.derivValue(fitPars[k], set) / prior.value(set) + } + k++ + } + return NamedVector(fitPars, res) + } +} + diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/maths/functions.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/maths/functions.kt new file mode 100644 index 0000000..69b13a1 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/maths/functions.kt @@ -0,0 +1,5 @@ +package ru.inr.mass.maths + +import space.kscience.kmath.linear.Point + +public typealias MultiFunction = (Point) -> Double \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/AnalyticalGradientCalculator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/AnalyticalGradientCalculator.kt new file mode 100644 index 0000000..912fa22 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/AnalyticalGradientCalculator.kt @@ -0,0 +1,61 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction + +/** + * + * @version $Id$ + */ +internal class AnalyticalGradientCalculator(fcn: MultiFunction?, state: MnUserTransformation, checkGradient: Boolean) : + GradientCalculator { + private val function: MultiFunction? + private val theCheckGradient: Boolean + private val theTransformation: MnUserTransformation + fun checkGradient(): Boolean { + return theCheckGradient + } + + /** {@inheritDoc} */ + fun gradient(par: MinimumParameters): FunctionGradient { +// double[] grad = theGradCalc.gradientValue(theTransformation.andThen(par.vec()).data()); + val point: DoubleArray = theTransformation.transform(par.vec()).toArray() + require(!(function.getDimension() !== theTransformation.parameters().size())) { "Invalid parameter size" } + val v: RealVector = ArrayRealVector(par.vec().getDimension()) + for (i in 0 until par.vec().getDimension()) { + val ext: Int = theTransformation.extOfInt(i) + if (theTransformation.parameter(ext).hasLimits()) { + val dd: Double = theTransformation.dInt2Ext(i, par.vec().getEntry(i)) + v.setEntry(i, dd * function.derivValue(ext, point)) + } else { + v.setEntry(i, function.derivValue(ext, point)) + } + } + return FunctionGradient(v) + } + + /** {@inheritDoc} */ + fun gradient(par: MinimumParameters, grad: FunctionGradient?): FunctionGradient { + return gradient(par) + } + + init { + function = fcn + theTransformation = state + theCheckGradient = checkGradient + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/CombinedMinimizer.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/CombinedMinimizer.kt new file mode 100644 index 0000000..9363492 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/CombinedMinimizer.kt @@ -0,0 +1,32 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class CombinedMinimizer : ModularFunctionMinimizer() { + private val theMinBuilder: CombinedMinimumBuilder = CombinedMinimumBuilder() + private val theMinSeedGen: MnSeedGenerator = MnSeedGenerator() + override fun builder(): MinimumBuilder { + return theMinBuilder + } + + override fun seedGenerator(): MinimumSeedGenerator { + return theMinSeedGen + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/CombinedMinimumBuilder.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/CombinedMinimumBuilder.kt new file mode 100644 index 0000000..88fc3dd --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/CombinedMinimumBuilder.kt @@ -0,0 +1,57 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin + +/** + * + * @version $Id$ + */ +internal class CombinedMinimumBuilder : MinimumBuilder { + private val theSimplexMinimizer: SimplexMinimizer = SimplexMinimizer() + private val theVMMinimizer: VariableMetricMinimizer = VariableMetricMinimizer() + + /** {@inheritDoc} */ + override fun minimum( + fcn: MnFcn?, + gc: GradientCalculator?, + seed: MinimumSeed?, + strategy: MnStrategy?, + maxfcn: Int, + toler: Double + ): FunctionMinimum { + val min: FunctionMinimum = theVMMinimizer.minimize(fcn!!, gc, seed, strategy, maxfcn, toler) + if (!min.isValid()) { + MINUITPlugin.logStatic("CombinedMinimumBuilder: migrad method fails, will try with simplex method first.") + val str = MnStrategy(2) + val min1: FunctionMinimum = theSimplexMinimizer.minimize(fcn, gc, seed, str, maxfcn, toler) + if (!min1.isValid()) { + MINUITPlugin.logStatic("CombinedMinimumBuilder: both migrad and simplex method fail.") + return min1 + } + val seed1: MinimumSeed = theVMMinimizer.seedGenerator().generate(fcn, gc, min1.userState(), str) + val min2: FunctionMinimum = theVMMinimizer.minimize(fcn, gc, seed1, str, maxfcn, toler) + if (!min2.isValid()) { + MINUITPlugin.logStatic("CombinedMinimumBuilder: both migrad and method fails also at 2nd attempt.") + MINUITPlugin.logStatic("CombinedMinimumBuilder: return simplex minimum.") + return min1 + } + return min2 + } + return min + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ContoursError.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ContoursError.kt new file mode 100644 index 0000000..214d94c --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ContoursError.kt @@ -0,0 +1,150 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * ContoursError class. + * + * @author Darksnake + * @version $Id$ + */ +class ContoursError internal constructor( + private val theParX: Int, + private val theParY: Int, + points: List, + xmnos: MinosError, + ymnos: MinosError, + nfcn: Int +) { + private val theNFcn: Int + private val thePoints: List = points + private val theXMinos: MinosError + private val theYMinos: MinosError + + /** + * + * nfcn. + * + * @return a int. + */ + fun nfcn(): Int { + return theNFcn + } + + /** + * + * points. + * + * @return a [List] object. + */ + fun points(): List { + return thePoints + } + + /** + * {@inheritDoc} + */ + override fun toString(): String { + return MnPrint.toString(this) + } + + /** + * + * xMinosError. + * + * @return a [hep.dataforge.MINUIT.MinosError] object. + */ + fun xMinosError(): MinosError { + return theXMinos + } + + /** + * + * xRange. + * + * @return + */ + fun xRange(): Range { + return theXMinos.range() + } + + /** + * + * xmin. + * + * @return a double. + */ + fun xmin(): Double { + return theXMinos.min() + } + + /** + * + * xpar. + * + * @return a int. + */ + fun xpar(): Int { + return theParX + } + + /** + * + * yMinosError. + * + * @return a [hep.dataforge.MINUIT.MinosError] object. + */ + fun yMinosError(): MinosError { + return theYMinos + } + + /** + * + * yRange. + * + * @return + */ + fun yRange(): Range { + return theYMinos.range() + } + + /** + * + * ymin. + * + * @return a double. + */ + fun ymin(): Double { + return theYMinos.min() + } + + /** + * + * ypar. + * + * @return a int. + */ + fun ypar(): Int { + return theParY + } + + init { + theXMinos = xmnos + theYMinos = ymnos + theNFcn = nfcn + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/DavidonErrorUpdator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/DavidonErrorUpdator.kt new file mode 100644 index 0000000..9eb2443 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/DavidonErrorUpdator.kt @@ -0,0 +1,45 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.RealVector +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class DavidonErrorUpdator : MinimumErrorUpdator { + /** {@inheritDoc} */ + fun update(s0: MinimumState, p1: MinimumParameters, g1: FunctionGradient): MinimumError { + val V0: MnAlgebraicSymMatrix = s0.error().invHessian() + val dx: RealVector = MnUtils.sub(p1.vec(), s0.vec()) + val dg: RealVector = MnUtils.sub(g1.getGradient(), s0.gradient().getGradient()) + val delgam: Double = MnUtils.innerProduct(dx, dg) + val gvg: Double = MnUtils.similarity(dg, V0) + val vg: RealVector = MnUtils.mul(V0, dg) + var Vupd: MnAlgebraicSymMatrix = + MnUtils.sub(MnUtils.div(MnUtils.outerProduct(dx), delgam), MnUtils.div(MnUtils.outerProduct(vg), gvg)) + if (delgam > gvg) { + Vupd = MnUtils.add(Vupd, + MnUtils.mul(MnUtils.outerProduct(MnUtils.sub(MnUtils.div(dx, delgam), MnUtils.div(vg, gvg))), gvg)) + } + val sum_upd: Double = MnUtils.absoluteSumOfElements(Vupd) + Vupd = MnUtils.add(Vupd, V0) + val dcov: Double = 0.5 * (s0.error().dcovar() + sum_upd / MnUtils.absoluteSumOfElements(Vupd)) + return MinimumError(Vupd, dcov) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/FunctionGradient.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/FunctionGradient.kt new file mode 100644 index 0000000..a0866d9 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/FunctionGradient.kt @@ -0,0 +1,72 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector + +/** + * + * @version $Id$ + */ +class FunctionGradient { + private var theAnalytical = false + private var theG2ndDerivative: RealVector + private var theGStepSize: RealVector + private var theGradient: RealVector + private var theValid = false + + constructor(n: Int) { + theGradient = ArrayRealVector(n) + theG2ndDerivative = ArrayRealVector(n) + theGStepSize = ArrayRealVector(n) + } + + constructor(grd: RealVector) { + theGradient = grd + theG2ndDerivative = ArrayRealVector(grd.getDimension()) + theGStepSize = ArrayRealVector(grd.getDimension()) + theValid = true + theAnalytical = true + } + + constructor(grd: RealVector, g2: RealVector, gstep: RealVector) { + theGradient = grd + theG2ndDerivative = g2 + theGStepSize = gstep + theValid = true + theAnalytical = false + } + + fun getGradient(): RealVector { + return theGradient + } + + fun getGradientDerivative(): RealVector { + return theG2ndDerivative + } + + fun getStep(): RealVector { + return theGStepSize + } + + fun isAnalytical(): Boolean { + return theAnalytical + } + + fun isValid(): Boolean { + return theValid + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/FunctionMinimum.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/FunctionMinimum.kt new file mode 100644 index 0000000..56908f0 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/FunctionMinimum.kt @@ -0,0 +1,259 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.minuit.* + +/** + * Result of the minimization. + * + * + * The FunctionMinimum is the output of the minimizers and contains the + * minimization result. The methods + * + * * userState(), + * * userParameters() and + * * userCovariance() + * + * are provided. These can be used as new input to a new minimization after some + * manipulation. The parameters and/or the FunctionMinimum can be printed using + * the toString() method or the MnPrint class. + * + * @author Darksnake + */ +class FunctionMinimum { + private var theAboveMaxEdm = false + private var theErrorDef: Double + private var theReachedCallLimit = false + private var theSeed: MinimumSeed + private var theStates: MutableList + private var theUserState: MnUserParameterState + + internal constructor(seed: MinimumSeed, up: Double) { + theSeed = seed + theStates = ArrayList() + theStates.add(MinimumState(seed.parameters(), + seed.error(), + seed.gradient(), + seed.parameters().fval(), + seed.nfcn())) + theErrorDef = up + theUserState = MnUserParameterState() + } + + internal constructor(seed: MinimumSeed, states: MutableList, up: Double) { + theSeed = seed + theStates = states + theErrorDef = up + theUserState = MnUserParameterState() + } + + internal constructor(seed: MinimumSeed, states: MutableList, up: Double, x: MnReachedCallLimit?) { + theSeed = seed + theStates = states + theErrorDef = up + theReachedCallLimit = true + theUserState = MnUserParameterState() + } + + internal constructor(seed: MinimumSeed, states: MutableList, up: Double, x: MnAboveMaxEdm?) { + theSeed = seed + theStates = states + theErrorDef = up + theAboveMaxEdm = true + theReachedCallLimit = false + theUserState = MnUserParameterState() + } + + // why not + fun add(state: MinimumState) { + theStates.add(state) + } + + /** + * returns the expected vertical distance to the minimum (EDM) + * + * @return a double. + */ + fun edm(): Double { + return lastState().edm() + } + + fun error(): MinimumError { + return lastState().error() + } + + /** + * + * + * errorDef. + * + * @return a double. + */ + fun errorDef(): Double { + return theErrorDef + } + + /** + * Returns the function value at the minimum. + * + * @return a double. + */ + fun fval(): Double { + return lastState().fval() + } + + fun grad(): FunctionGradient { + return lastState().gradient() + } + + fun hasAccurateCovar(): Boolean { + return state().error().isAccurate() + } + + fun hasCovariance(): Boolean { + return state().error().isAvailable() + } + + fun hasMadePosDefCovar(): Boolean { + return state().error().isMadePosDef() + } + + fun hasPosDefCovar(): Boolean { + return state().error().isPosDef() + } + + fun hasReachedCallLimit(): Boolean { + return theReachedCallLimit + } + + fun hasValidCovariance(): Boolean { + return state().error().isValid() + } + + fun hasValidParameters(): Boolean { + return state().parameters().isValid() + } + + fun hesseFailed(): Boolean { + return state().error().hesseFailed() + } + + fun isAboveMaxEdm(): Boolean { + return theAboveMaxEdm + } + + /** + * In general, if this returns true, the minimizer did find a + * minimum without running into troubles. However, in some cases a minimum + * cannot be found, then the return value will be false. + * Reasons for the minimization to fail are + * + * * the number of allowed function calls has been exhausted + * * the minimizer could not improve the values of the parameters (and + * knowing that it has not converged yet) + * * a problem with the calculation of the covariance matrix + * + * Additional methods for the analysis of the state at the minimum are + * provided. + * + * @return a boolean. + */ + fun isValid(): Boolean { + return state().isValid() && !isAboveMaxEdm() && !hasReachedCallLimit() + } + + private fun lastState(): MinimumState { + return theStates[theStates.size - 1] + } + // forward interface of last state + /** + * returns the total number of function calls during the minimization. + * + * @return a int. + */ + fun nfcn(): Int { + return lastState().nfcn() + } + + fun parameters(): MinimumParameters { + return lastState().parameters() + } + + fun seed(): MinimumSeed { + return theSeed + } + + fun state(): MinimumState { + return lastState() + } + + fun states(): List { + return theStates + } + + /** + * {@inheritDoc} + * + * @return + */ + override fun toString(): String { + return MnPrint.toString(this) + } + + /** + * + * + * userCovariance. + * + * @return a [hep.dataforge.MINUIT.MnUserCovariance] object. + */ + fun userCovariance(): MnUserCovariance { + if (!theUserState.isValid()) { + theUserState = MnUserParameterState(state(), errorDef(), seed().trafo()) + } + return theUserState.covariance() + } + + /** + * + * + * userParameters. + * + * @return a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + fun userParameters(): MnUserParameters { + if (!theUserState.isValid()) { + theUserState = MnUserParameterState(state(), errorDef(), seed().trafo()) + } + return theUserState.parameters() + } + + /** + * user representation of state at minimum + * + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun userState(): MnUserParameterState { + if (!theUserState.isValid()) { + theUserState = MnUserParameterState(state(), errorDef(), seed().trafo()) + } + return theUserState + } + + internal class MnAboveMaxEdm + internal class MnReachedCallLimit +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/GradientCalculator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/GradientCalculator.kt new file mode 100644 index 0000000..379de1b --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/GradientCalculator.kt @@ -0,0 +1,41 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +interface GradientCalculator { + /** + * + * gradient. + * + * @param par a [hep.dataforge.MINUIT.MinimumParameters] object. + * @return a [hep.dataforge.MINUIT.FunctionGradient] object. + */ + fun gradient(par: MinimumParameters?): FunctionGradient + + /** + * + * gradient. + * + * @param par a [hep.dataforge.MINUIT.MinimumParameters] object. + * @param grad a [hep.dataforge.MINUIT.FunctionGradient] object. + * @return a [hep.dataforge.MINUIT.FunctionGradient] object. + */ + fun gradient(par: MinimumParameters?, grad: FunctionGradient?): FunctionGradient +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/HessianGradientCalculator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/HessianGradientCalculator.kt new file mode 100644 index 0000000..150d192 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/HessianGradientCalculator.kt @@ -0,0 +1,137 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class HessianGradientCalculator(fcn: MnFcn, par: MnUserTransformation, stra: MnStrategy) : GradientCalculator { + private val theFcn: MnFcn = fcn + private val theStrategy: MnStrategy + private val theTransformation: MnUserTransformation + fun deltaGradient(par: MinimumParameters, gradient: FunctionGradient): Pair { + require(par.isValid()) { "parameters are invalid" } + val x: RealVector = par.vec().copy() + val grd: RealVector = gradient.getGradient().copy() + val g2: RealVector = gradient.getGradientDerivative() + val gstep: RealVector = gradient.getStep() + val fcnmin: Double = par.fval() + // std::cout<<"fval: "< optstp) { + d = optstp + } + if (d < dmin) { + d = dmin + } + var chgold = 10000.0 + var dgmin = 0.0 + var grdold = 0.0 + var grdnew = 0.0 + for (j in 0 until ncycle()) { + x.setEntry(i, xtf + d) + val fs1: Double = theFcn.value(x) + x.setEntry(i, xtf - d) + val fs2: Double = theFcn.value(x) + x.setEntry(i, xtf) + // double sag = 0.5*(fs1+fs2-2.*fcnmin); + grdold = grd.getEntry(i) + grdnew = (fs1 - fs2) / (2.0 * d) + dgmin = precision().eps() * (abs(fs1) + abs(fs2)) / d + if (abs(grdnew) < precision().eps()) { + break + } + val change: Double = abs((grdold - grdnew) / grdnew) + if (change > chgold && j > 1) { + break + } + chgold = change + grd.setEntry(i, grdnew) + if (change < 0.05) { + break + } + if (abs(grdold - grdnew) < dgmin) { + break + } + if (d < dmin) { + break + } + d *= 0.2 + } + dgrd.setEntry(i, max(dgmin, abs(grdold - grdnew))) + } + return Pair(FunctionGradient(grd, g2, gstep), dgrd) + } + + fun fcn(): MnFcn { + return theFcn + } + + fun gradTolerance(): Double { + return strategy().gradientTolerance() + } + + /** {@inheritDoc} */ + fun gradient(par: MinimumParameters): FunctionGradient { + val gc = InitialGradientCalculator(theFcn, theTransformation, theStrategy) + val gra: FunctionGradient = gc.gradient(par) + return gradient(par, gra) + } + + /** {@inheritDoc} */ + fun gradient(par: MinimumParameters, gradient: FunctionGradient): FunctionGradient { + return deltaGradient(par, gradient).getFirst() + } + + fun ncycle(): Int { + return strategy().hessianGradientNCycles() + } + + fun precision(): MnMachinePrecision { + return theTransformation.precision() + } + + fun stepTolerance(): Double { + return strategy().gradientStepTolerance() + } + + fun strategy(): MnStrategy { + return theStrategy + } + + fun trafo(): MnUserTransformation { + return theTransformation + } + + init { + theTransformation = par + theStrategy = stra + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/InitialGradientCalculator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/InitialGradientCalculator.kt new file mode 100644 index 0000000..7945564 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/InitialGradientCalculator.kt @@ -0,0 +1,116 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector +import ru.inr.mass.minuit.* + +/** + * Calculating derivatives via finite differences + * @version $Id$ + */ +internal class InitialGradientCalculator(fcn: MnFcn, par: MnUserTransformation, stra: MnStrategy) { + private val theFcn: MnFcn = fcn + private val theStrategy: MnStrategy + private val theTransformation: MnUserTransformation + fun fcn(): MnFcn { + return theFcn + } + + fun gradTolerance(): Double { + return strategy().gradientTolerance() + } + + fun gradient(par: MinimumParameters): FunctionGradient { + require(par.isValid()) { "Parameters are invalid" } + val n: Int = trafo().variableParameters() + require(n == par.vec().getDimension()) { "Parameters have invalid size" } + val gr: RealVector = ArrayRealVector(n) + val gr2: RealVector = ArrayRealVector(n) + val gst: RealVector = ArrayRealVector(n) + + // initial starting values + for (i in 0 until n) { + val exOfIn: Int = trafo().extOfInt(i) + val `var`: Double = par.vec().getEntry(i) //parameter value + val werr: Double = trafo().parameter(exOfIn).error() //parameter error + val sav: Double = trafo().int2ext(i, `var`) //value after transformation + var sav2 = sav + werr //value after transfomation + error + if (trafo().parameter(exOfIn).hasLimits()) { + if (trafo().parameter(exOfIn).hasUpperLimit() + && sav2 > trafo().parameter(exOfIn).upperLimit() + ) { + sav2 = trafo().parameter(exOfIn).upperLimit() + } + } + var var2: Double = trafo().ext2int(exOfIn, sav2) + val vplu = var2 - `var` + sav2 = sav - werr + if (trafo().parameter(exOfIn).hasLimits()) { + if (trafo().parameter(exOfIn).hasLowerLimit() + && sav2 < trafo().parameter(exOfIn).lowerLimit() + ) { + sav2 = trafo().parameter(exOfIn).lowerLimit() + } + } + var2 = trafo().ext2int(exOfIn, sav2) + val vmin = var2 - `var` + val dirin: Double = 0.5 * (abs(vplu) + abs(vmin)) + val g2: Double = 2.0 * theFcn.errorDef() / (dirin * dirin) + val gsmin: Double = 8.0 * precision().eps2() * (abs(`var`) + precision().eps2()) + var gstep: Double = max(gsmin, 0.1 * dirin) + val grd = g2 * dirin + if (trafo().parameter(exOfIn).hasLimits()) { + if (gstep > 0.5) { + gstep = 0.5 + } + } + gr.setEntry(i, grd) + gr2.setEntry(i, g2) + gst.setEntry(i, gstep) + } + return FunctionGradient(gr, gr2, gst) + } + + fun gradient(par: MinimumParameters, gra: FunctionGradient?): FunctionGradient { + return gradient(par) + } + + fun ncycle(): Int { + return strategy().gradientNCycles() + } + + fun precision(): MnMachinePrecision { + return theTransformation.precision() + } + + fun stepTolerance(): Double { + return strategy().gradientStepTolerance() + } + + fun strategy(): MnStrategy { + return theStrategy + } + + fun trafo(): MnUserTransformation { + return theTransformation + } + + init { + theTransformation = par + theStrategy = stra + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumBuilder.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumBuilder.kt new file mode 100644 index 0000000..eadeeb9 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumBuilder.kt @@ -0,0 +1,43 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +interface MinimumBuilder { + /** + * + * minimum. + * + * @param fcn a [hep.dataforge.MINUIT.MnFcn] object. + * @param gc a [hep.dataforge.MINUIT.GradientCalculator] object. + * @param seed a [hep.dataforge.MINUIT.MinimumSeed] object. + * @param strategy a [hep.dataforge.MINUIT.MnStrategy] object. + * @param maxfcn a int. + * @param toler a double. + * @return a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + fun minimum( + fcn: MnFcn?, + gc: GradientCalculator?, + seed: MinimumSeed?, + strategy: MnStrategy?, + maxfcn: Int, + toler: Double + ): FunctionMinimum +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumError.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumError.kt new file mode 100644 index 0000000..514bfef --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumError.kt @@ -0,0 +1,155 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin + +/** + * MinimumError keeps the inverse 2nd derivative (inverse Hessian) used for + * calculating the parameter step size (-V*g) and for the covariance update + * (ErrorUpdator). The covariance matrix is equal to twice the inverse Hessian. + * + * @version $Id$ + */ +class MinimumError { + private var theAvailable = false + private var theDCovar: Double + private var theHesseFailed = false + private var theInvertFailed = false + private var theMadePosDef = false + private var theMatrix: MnAlgebraicSymMatrix + private var thePosDef = false + private var theValid = false + + constructor(n: Int) { + theMatrix = MnAlgebraicSymMatrix(n) + theDCovar = 1.0 + } + + constructor(mat: MnAlgebraicSymMatrix, dcov: Double) { + theMatrix = mat + theDCovar = dcov + theValid = true + thePosDef = true + theAvailable = true + } + + constructor(mat: MnAlgebraicSymMatrix, x: MnHesseFailed?) { + theMatrix = mat + theDCovar = 1.0 + theValid = false + thePosDef = false + theMadePosDef = false + theHesseFailed = true + theInvertFailed = false + theAvailable = true + } + + constructor(mat: MnAlgebraicSymMatrix, x: MnMadePosDef?) { + theMatrix = mat + theDCovar = 1.0 + theValid = false + thePosDef = false + theMadePosDef = true + theHesseFailed = false + theInvertFailed = false + theAvailable = true + } + + constructor(mat: MnAlgebraicSymMatrix, x: MnInvertFailed?) { + theMatrix = mat + theDCovar = 1.0 + theValid = false + thePosDef = true + theMadePosDef = false + theHesseFailed = false + theInvertFailed = true + theAvailable = true + } + + constructor(mat: MnAlgebraicSymMatrix, x: MnNotPosDef?) { + theMatrix = mat + theDCovar = 1.0 + theValid = false + thePosDef = false + theMadePosDef = false + theHesseFailed = false + theInvertFailed = false + theAvailable = true + } + + fun dcovar(): Double { + return theDCovar + } + + fun hesseFailed(): Boolean { + return theHesseFailed + } + + fun hessian(): MnAlgebraicSymMatrix { + return try { + val tmp: MnAlgebraicSymMatrix = theMatrix.copy() + tmp.invert() + tmp + } catch (x: SingularMatrixException) { + MINUITPlugin.logStatic("BasicMinimumError inversion fails; return diagonal matrix.") + val tmp = MnAlgebraicSymMatrix(theMatrix.nrow()) + var i = 0 + while (i < theMatrix.nrow()) { + tmp[i, i] = 1.0 / theMatrix[i, i] + i++ + } + tmp + } + } + + fun invHessian(): MnAlgebraicSymMatrix { + return theMatrix + } + + fun invertFailed(): Boolean { + return theInvertFailed + } + + fun isAccurate(): Boolean { + return theDCovar < 0.1 + } + + fun isAvailable(): Boolean { + return theAvailable + } + + fun isMadePosDef(): Boolean { + return theMadePosDef + } + + fun isPosDef(): Boolean { + return thePosDef + } + + fun isValid(): Boolean { + return theValid + } + + fun matrix(): MnAlgebraicSymMatrix { + return MnUtils.mul(theMatrix, 2) + } + + internal class MnHesseFailed + internal class MnInvertFailed + internal class MnMadePosDef + internal class MnNotPosDef +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumErrorUpdator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumErrorUpdator.kt new file mode 100644 index 0000000..6022aa5 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumErrorUpdator.kt @@ -0,0 +1,33 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal interface MinimumErrorUpdator { + /** + * + * update. + * + * @param state a [hep.dataforge.MINUIT.MinimumState] object. + * @param par a [hep.dataforge.MINUIT.MinimumParameters] object. + * @param grad a [hep.dataforge.MINUIT.FunctionGradient] object. + * @return a [hep.dataforge.MINUIT.MinimumError] object. + */ + fun update(state: MinimumState?, par: MinimumParameters?, grad: FunctionGradient?): MinimumError? +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumParameters.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumParameters.kt new file mode 100644 index 0000000..bed13ea --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumParameters.kt @@ -0,0 +1,70 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector + +/** + * + * @version $Id$ + */ +class MinimumParameters { + private var theFVal = 0.0 + private var theHasStep = false + private var theParameters: RealVector + private var theStepSize: RealVector + private var theValid = false + + constructor(n: Int) { + theParameters = ArrayRealVector(n) + theStepSize = ArrayRealVector(n) + } + + constructor(avec: RealVector, fval: Double) { + theParameters = avec + theStepSize = ArrayRealVector(avec.getDimension()) + theFVal = fval + theValid = true + } + + constructor(avec: RealVector, dirin: RealVector, fval: Double) { + theParameters = avec + theStepSize = dirin + theFVal = fval + theValid = true + theHasStep = true + } + + fun dirin(): RealVector { + return theStepSize + } + + fun fval(): Double { + return theFVal + } + + fun hasStepSize(): Boolean { + return theHasStep + } + + fun isValid(): Boolean { + return theValid + } + + fun vec(): RealVector { + return theParameters + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumSeed.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumSeed.kt new file mode 100644 index 0000000..aef672b --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumSeed.kt @@ -0,0 +1,64 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +class MinimumSeed(state: MinimumState, trafo: MnUserTransformation) { + private val theState: MinimumState = state + private val theTrafo: MnUserTransformation = trafo + private val theValid: Boolean = true + val edm: Double get() = state().edm() + + fun error(): MinimumError { + return state().error() + } + + fun fval(): Double { + return state().fval() + } + + fun gradient(): FunctionGradient { + return state().gradient() + } + + fun isValid(): Boolean { + return theValid + } + + fun nfcn(): Int { + return state().nfcn() + } + + fun parameters(): MinimumParameters { + return state().parameters() + } + + fun precision(): MnMachinePrecision { + return theTrafo.precision() + } + + fun state(): MinimumState { + return theState + } + + fun trafo(): MnUserTransformation { + return theTrafo + } + +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumSeedGenerator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumSeedGenerator.kt new file mode 100644 index 0000000..bd04c1a --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumSeedGenerator.kt @@ -0,0 +1,37 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * base class for seed generators (starting values); the seed generator prepares + * initial starting values from the input (MnUserParameterState) for the + * minimization; + * + * @version $Id$ + */ +interface MinimumSeedGenerator { + /** + * + * generate. + * + * @param fcn a [hep.dataforge.MINUIT.MnFcn] object. + * @param calc a [hep.dataforge.MINUIT.GradientCalculator] object. + * @param user a [hep.dataforge.MINUIT.MnUserParameterState] object. + * @param stra a [hep.dataforge.MINUIT.MnStrategy] object. + * @return a [hep.dataforge.MINUIT.MinimumSeed] object. + */ + fun generate(fcn: MnFcn?, calc: GradientCalculator?, user: MnUserParameterState?, stra: MnStrategy?): MinimumSeed +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumState.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumState.kt new file mode 100644 index 0000000..9f63e0e --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinimumState.kt @@ -0,0 +1,104 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.RealVector + +/** + * MinimumState keeps the information (position, gradient, 2nd deriv, etc) after + * one minimization step (usually in MinimumBuilder). + * + * @version $Id$ + */ +class MinimumState { + private var theEDM = 0.0 + private var theError: MinimumError + private var theGradient: FunctionGradient + private var theNFcn = 0 + private var theParameters: MinimumParameters + + constructor(n: Int) { + theParameters = MinimumParameters(n) + theError = MinimumError(n) + theGradient = FunctionGradient(n) + } + + constructor(states: MinimumParameters, err: MinimumError, grad: FunctionGradient, edm: Double, nfcn: Int) { + theParameters = states + theError = err + theGradient = grad + theEDM = edm + theNFcn = nfcn + } + + constructor(states: MinimumParameters, edm: Double, nfcn: Int) { + theParameters = states + theError = MinimumError(states.vec().getDimension()) + theGradient = FunctionGradient(states.vec().getDimension()) + theEDM = edm + theNFcn = nfcn + } + + fun edm(): Double { + return theEDM + } + + fun error(): MinimumError { + return theError + } + + fun fval(): Double { + return theParameters.fval() + } + + fun gradient(): FunctionGradient { + return theGradient + } + + fun hasCovariance(): Boolean { + return theError.isAvailable() + } + + fun hasParameters(): Boolean { + return theParameters.isValid() + } + + fun isValid(): Boolean { + return if (hasParameters() && hasCovariance()) { + parameters().isValid() && error().isValid() + } else if (hasParameters()) { + parameters().isValid() + } else { + false + } + } + + fun nfcn(): Int { + return theNFcn + } + + fun parameters(): MinimumParameters { + return theParameters + } + + fun size(): Int { + return theParameters.vec().getDimension() + } + + fun vec(): RealVector { + return theParameters.vec() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinosError.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinosError.kt new file mode 100644 index 0000000..c7cf105 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinosError.kt @@ -0,0 +1,219 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * MinosError class. + * + * @author Darksnake + * @version $Id$ + */ +class MinosError { + private var theLower: MnCross + private var theMinValue = 0.0 + private var theParameter = 0 + private var theUpper: MnCross + + internal constructor() { + theUpper = MnCross() + theLower = MnCross() + } + + internal constructor(par: Int, min: Double, low: MnCross, up: MnCross) { + theParameter = par + theMinValue = min + theUpper = up + theLower = low + } + + /** + * + * atLowerLimit. + * + * @return a boolean. + */ + fun atLowerLimit(): Boolean { + return theLower.atLimit() + } + + /** + * + * atLowerMaxFcn. + * + * @return a boolean. + */ + fun atLowerMaxFcn(): Boolean { + return theLower.atMaxFcn() + } + + /** + * + * atUpperLimit. + * + * @return a boolean. + */ + fun atUpperLimit(): Boolean { + return theUpper.atLimit() + } + + /** + * + * atUpperMaxFcn. + * + * @return a boolean. + */ + fun atUpperMaxFcn(): Boolean { + return theUpper.atMaxFcn() + } + + /** + * + * isValid. + * + * @return a boolean. + */ + fun isValid(): Boolean { + return theLower.isValid() && theUpper.isValid() + } + + /** + * + * lower. + * + * @return a double. + */ + fun lower(): Double { + return -1.0 * lowerState().error(parameter()) * (1.0 + theLower.value()) + } + + /** + * + * lowerNewMin. + * + * @return a boolean. + */ + fun lowerNewMin(): Boolean { + return theLower.newMinimum() + } + + /** + * + * lowerState. + * + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun lowerState(): MnUserParameterState { + return theLower.state() + } + + /** + * + * lowerValid. + * + * @return a boolean. + */ + fun lowerValid(): Boolean { + return theLower.isValid() + } + + /** + * + * min. + * + * @return a double. + */ + fun min(): Double { + return theMinValue + } + + /** + * + * nfcn. + * + * @return a int. + */ + fun nfcn(): Int { + return theUpper.nfcn() + theLower.nfcn() + } + + /** + * + * parameter. + * + * @return a int. + */ + fun parameter(): Int { + return theParameter + } + + /** + * + * range. + * + * @return + */ + fun range(): Range { + return Range(lower(), upper()) + } + + /** + * {@inheritDoc} + */ + override fun toString(): String { + return MnPrint.toString(this) + } + + /** + * + * upper. + * + * @return a double. + */ + fun upper(): Double { + return upperState().error(parameter()) * (1.0 + theUpper.value()) + } + + /** + * + * upperNewMin. + * + * @return a boolean. + */ + fun upperNewMin(): Boolean { + return theUpper.newMinimum() + } + + /** + * + * upperState. + * + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun upperState(): MnUserParameterState { + return theUpper.state() + } + + /** + * + * upperValid. + * + * @return a boolean. + */ + fun upperValid(): Boolean { + return theUpper.isValid() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinuitParameter.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinuitParameter.kt new file mode 100644 index 0000000..ff6834d --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MinuitParameter.kt @@ -0,0 +1,314 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +class MinuitParameter { + private var theConst = false + private var theError = 0.0 + private var theFix = false + private var theLoLimValid = false + private var theLoLimit = 0.0 + private var theName: String + private var theNum: Int + private var theUpLimValid = false + private var theUpLimit = 0.0 + private var theValue: Double + + /** + * constructor for constant parameter + * + * @param num a int. + * @param name a [String] object. + * @param val a double. + */ + constructor(num: Int, name: String, `val`: Double) { + theNum = num + theValue = `val` + theConst = true + theName = name + } + + /** + * constructor for standard parameter + * + * @param num a int. + * @param name a [String] object. + * @param val a double. + * @param err a double. + */ + constructor(num: Int, name: String, `val`: Double, err: Double) { + theNum = num + theValue = `val` + theError = err + theName = name + } + + /** + * constructor for limited parameter + * + * @param num a int. + * @param name a [String] object. + * @param val a double. + * @param err a double. + * @param min a double. + * @param max a double. + */ + constructor(num: Int, name: String, `val`: Double, err: Double, min: Double, max: Double) { + theNum = num + theValue = `val` + theError = err + theLoLimit = min + theUpLimit = max + theLoLimValid = true + theUpLimValid = true + require(min != max) { "min == max" } + if (min > max) { + theLoLimit = max + theUpLimit = min + } + theName = name + } + + private constructor(other: MinuitParameter) { + theNum = other.theNum + theName = other.theName + theValue = other.theValue + theError = other.theError + theConst = other.theConst + theFix = other.theFix + theLoLimit = other.theLoLimit + theUpLimit = other.theUpLimit + theLoLimValid = other.theLoLimValid + theUpLimValid = other.theUpLimValid + } + + /** + * + * copy. + * + * @return a [hep.dataforge.MINUIT.MinuitParameter] object. + */ + fun copy(): MinuitParameter { + return MinuitParameter(this) + } + + /** + * + * error. + * + * @return a double. + */ + fun error(): Double { + return theError + } + + /** + * + * fix. + */ + fun fix() { + theFix = true + } + + /** + * + * hasLimits. + * + * @return a boolean. + */ + fun hasLimits(): Boolean { + return theLoLimValid || theUpLimValid + } + + /** + * + * hasLowerLimit. + * + * @return a boolean. + */ + fun hasLowerLimit(): Boolean { + return theLoLimValid + } + + /** + * + * hasUpperLimit. + * + * @return a boolean. + */ + fun hasUpperLimit(): Boolean { + return theUpLimValid + } + //state of parameter (fixed/const/limited) + /** + * + * isConst. + * + * @return a boolean. + */ + fun isConst(): Boolean { + return theConst + } + + /** + * + * isFixed. + * + * @return a boolean. + */ + fun isFixed(): Boolean { + return theFix + } + + /** + * + * lowerLimit. + * + * @return a double. + */ + fun lowerLimit(): Double { + return theLoLimit + } + + /** + * + * name. + * + * @return a [String] object. + */ + fun name(): String { + return theName + } + //access methods + /** + * + * number. + * + * @return a int. + */ + fun number(): Int { + return theNum + } + + /** + * + * release. + */ + fun release() { + theFix = false + } + + /** + * + * removeLimits. + */ + fun removeLimits() { + theLoLimit = 0.0 + theUpLimit = 0.0 + theLoLimValid = false + theUpLimValid = false + } + + /** + * + * setError. + * + * @param err a double. + */ + fun setError(err: Double) { + theError = err + theConst = false + } + + /** + * + * setLimits. + * + * @param low a double. + * @param up a double. + */ + fun setLimits(low: Double, up: Double) { + require(low != up) { "min == max" } + theLoLimit = low + theUpLimit = up + theLoLimValid = true + theUpLimValid = true + if (low > up) { + theLoLimit = up + theUpLimit = low + } + } + + /** + * + * setLowerLimit. + * + * @param low a double. + */ + fun setLowerLimit(low: Double) { + theLoLimit = low + theUpLimit = 0.0 + theLoLimValid = true + theUpLimValid = false + } + + /** + * + * setUpperLimit. + * + * @param up a double. + */ + fun setUpperLimit(up: Double) { + theLoLimit = 0.0 + theUpLimit = up + theLoLimValid = false + theUpLimValid = true + } + //interaction + /** + * + * setValue. + * + * @param val a double. + */ + fun setValue(`val`: Double) { + theValue = `val` + } + + /** + * + * upperLimit. + * + * @return a double. + */ + fun upperLimit(): Double { + return theUpLimit + } + + /** + * + * value. + * + * @return a double. + */ + fun value(): Double { + return theValue + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnAlgebraicSymMatrix.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnAlgebraicSymMatrix.kt new file mode 100644 index 0000000..4b75858 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnAlgebraicSymMatrix.kt @@ -0,0 +1,458 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector + +/** + * + * @version $Id$ + */ +class MnAlgebraicSymMatrix(n: Int) { + private val theData: DoubleArray + private val theNRow: Int + private val theSize: Int + + /** + * + * copy. + * + * @return a [hep.dataforge.MINUIT.MnAlgebraicSymMatrix] object. + */ + fun copy(): MnAlgebraicSymMatrix { + val copy = MnAlgebraicSymMatrix(theNRow) + java.lang.System.arraycopy(theData, 0, copy.theData, 0, theSize) + return copy + } + + fun data(): DoubleArray { + return theData + } + + fun eigenvalues(): ArrayRealVector { + val nrow = theNRow + val tmp = DoubleArray((nrow + 1) * (nrow + 1)) + val work = DoubleArray(1 + 2 * nrow) + for (i in 0 until nrow) { + for (j in 0..i) { + tmp[1 + i + (1 + j) * nrow] = get(i, j) + tmp[(1 + i) * nrow + (1 + j)] = get(i, j) + } + } + val info = mneigen(tmp, nrow, nrow, work.size, work, 1e-6) + if (info != 0) { + throw EigenvaluesException() + } + val result = ArrayRealVector(nrow) + for (i in 0 until nrow) { + result.setEntry(i, work[1 + i]) + } + return result + } + + operator fun get(row: Int, col: Int): Double { + if (row >= theNRow || col >= theNRow) { + throw ArrayIndexOutOfBoundsException() + } + return theData[theIndex(row, col)] + } + + @Throws(SingularMatrixException::class) + fun invert() { + if (theSize == 1) { + val tmp = theData[0] + if (tmp <= 0.0) { + throw SingularMatrixException() + } + theData[0] = 1.0 / tmp + } else { + val nrow = theNRow + val s = DoubleArray(nrow) + val q = DoubleArray(nrow) + val pp = DoubleArray(nrow) + for (i in 0 until nrow) { + val si = theData[theIndex(i, i)] + if (si < 0.0) { + throw SingularMatrixException() + } + s[i] = 1.0 / sqrt(si) + } + for (i in 0 until nrow) { + for (j in i until nrow) { + theData[theIndex(i, j)] *= s[i] * s[j] + } + } + for (i in 0 until nrow) { + var k = i + if (theData[theIndex(k, k)] == 0.0) { + throw SingularMatrixException() + } + q[k] = 1.0 / theData[theIndex(k, k)] + pp[k] = 1.0 + theData[theIndex(k, k)] = 0.0 + val kp1 = k + 1 + if (k != 0) { + for (j in 0 until k) { + val index = theIndex(j, k) + pp[j] = theData[index] + q[j] = theData[index] * q[k] + theData[index] = 0.0 + } + } + if (k != nrow - 1) { + for (j in kp1 until nrow) { + val index = theIndex(k, j) + pp[j] = theData[index] + q[j] = -theData[index] * q[k] + theData[index] = 0.0 + } + } + for (j in 0 until nrow) { + k = j + while (k < nrow) { + theData[theIndex(j, k)] += pp[j] * q[k] + k++ + } + } + } + for (j in 0 until nrow) { + for (k in j until nrow) { + theData[theIndex(j, k)] *= s[j] * s[k] + } + } + } + } + + fun ncol(): Int { + return nrow() + } + + fun nrow(): Int { + return theNRow + } + + operator fun set(row: Int, col: Int, value: Double) { + if (row >= theNRow || col >= theNRow) { + throw ArrayIndexOutOfBoundsException() + } + theData[theIndex(row, col)] = value + } + + fun size(): Int { + return theSize + } + + private fun theIndex(row: Int, col: Int): Int { + return if (row > col) { + col + row * (row + 1) / 2 + } else { + row + col * (col + 1) / 2 + } + } + + /** {@inheritDoc} */ + override fun toString(): String { + return MnPrint.toString(this) + } /* mneig_ */ + + private inner class EigenvaluesException : RuntimeException() + companion object { + private fun mneigen(a: DoubleArray, ndima: Int, n: Int, mits: Int, work: DoubleArray, precis: Double): Int { + + /* System generated locals */ + var i__2: Int + var i__3: Int + + /* Local variables */ + var b: Double + var c__: Double + var f: Double + var h__: Double + var i__: Int + var j: Int + var k: Int + var l: Int + var m = 0 + var r__: Double + var s: Double + var i0: Int + var i1: Int + var j1: Int + var m1: Int + var hh: Double + var gl: Double + var pr: Double + var pt: Double + + /* PRECIS is the machine precision EPSMAC */ + /* Parameter adjustments */ + val a_dim1: Int = ndima + val a_offset: Int = 1 + a_dim1 * 1 + + /* Function Body */ + var ifault = 1 + i__ = n + var i__1: Int = n + i1 = 2 + while (i1 <= i__1) { + l = i__ - 2 + f = a[i__ + (i__ - 1) * a_dim1] + gl = 0.0 + if (l >= 1) { + i__2 = l + k = 1 + while (k <= i__2) { + + /* Computing 2nd power */ + val r__1 = a[i__ + k * a_dim1] + gl += r__1 * r__1 + ++k + } + } + /* Computing 2nd power */h__ = gl + f * f + if (gl <= 1e-35) { + work[i__] = 0.0 + work[n + i__] = f + } else { + ++l + gl = sqrt(h__) + if (f >= 0.0) { + gl = -gl + } + work[n + i__] = gl + h__ -= f * gl + a[i__ + (i__ - 1) * a_dim1] = f - gl + f = 0.0 + i__2 = l + j = 1 + while (j <= i__2) { + a[j + i__ * a_dim1] = a[i__ + j * a_dim1] / h__ + gl = 0.0 + i__3 = j + k = 1 + while (k <= i__3) { + gl += a[j + k * a_dim1] * a[i__ + k * a_dim1] + ++k + } + if (j < l) { + j1 = j + 1 + i__3 = l + k = j1 + while (k <= i__3) { + gl += a[k + j * a_dim1] * a[i__ + k * a_dim1] + ++k + } + } + work[n + j] = gl / h__ + f += gl * a[j + i__ * a_dim1] + ++j + } + hh = f / (h__ + h__) + i__2 = l + j = 1 + while (j <= i__2) { + f = a[i__ + j * a_dim1] + gl = work[n + j] - hh * f + work[n + j] = gl + i__3 = j + k = 1 + while (k <= i__3) { + a[j + k * a_dim1] = a[j + k * a_dim1] - f * work[n + k] - (gl + * a[i__ + k * a_dim1]) + ++k + } + ++j + } + work[i__] = h__ + } + --i__ + ++i1 + } + work[1] = 0.0 + work[n + 1] = 0.0 + i__1 = n + i__ = 1 + while (i__ <= i__1) { + l = i__ - 1 + if (work[i__] != 0.0 && l != 0) { + i__3 = l + j = 1 + while (j <= i__3) { + gl = 0.0 + i__2 = l + k = 1 + while (k <= i__2) { + gl += a[i__ + k * a_dim1] * a[k + j * a_dim1] + ++k + } + i__2 = l + k = 1 + while (k <= i__2) { + a[k + j * a_dim1] -= gl * a[k + i__ * a_dim1] + ++k + } + ++j + } + } + work[i__] = a[i__ + i__ * a_dim1] + a[i__ + i__ * a_dim1] = 1.0 + if (l != 0) { + i__2 = l + j = 1 + while (j <= i__2) { + a[i__ + j * a_dim1] = 0.0 + a[j + i__ * a_dim1] = 0.0 + ++j + } + } + ++i__ + } + val n1: Int = n - 1 + i__1 = n + i__ = 2 + while (i__ <= i__1) { + i0 = n + i__ - 1 + work[i0] = work[i0 + 1] + ++i__ + } + work[n + n] = 0.0 + b = 0.0 + f = 0.0 + i__1 = n + l = 1 + while (l <= i__1) { + j = 0 + h__ = precis * (abs(work[l]) + abs(work[n + l])) + if (b < h__) { + b = h__ + } + i__2 = n + m1 = l + while (m1 <= i__2) { + m = m1 + if (abs(work[n + m]) <= b) { + break + } + ++m1 + } + if (m != l) { + while (true) { + if (j == mits) { + return ifault + } + ++j + pt = (work[l + 1] - work[l]) / (work[n + l] * 2.0) + r__ = sqrt(pt * pt + 1.0) + pr = pt + r__ + if (pt < 0.0) { + pr = pt - r__ + } + h__ = work[l] - work[n + l] / pr + i__2 = n + i__ = l + while (i__ <= i__2) { + work[i__] -= h__ + ++i__ + } + f += h__ + pt = work[m] + c__ = 1.0 + s = 0.0 + m1 = m - 1 + i__ = m + i__2 = m1 + i1 = l + while (i1 <= i__2) { + j = i__ + --i__ + gl = c__ * work[n + i__] + h__ = c__ * pt + if (abs(pt) < abs(work[n + i__])) { + c__ = pt / work[n + i__] + r__ = sqrt(c__ * c__ + 1.0) + work[n + j] = s * work[n + i__] * r__ + s = 1.0 / r__ + c__ /= r__ + } else { + c__ = work[n + i__] / pt + r__ = sqrt(c__ * c__ + 1.0) + work[n + j] = s * pt * r__ + s = c__ / r__ + c__ = 1.0 / r__ + } + pt = c__ * work[i__] - s * gl + work[j] = h__ + s * (c__ * gl + s * work[i__]) + i__3 = n + k = 1 + while (k <= i__3) { + h__ = a[k + j * a_dim1] + a[k + j * a_dim1] = s * a[k + i__ * a_dim1] + c__ * h__ + a[k + i__ * a_dim1] = c__ * a[k + i__ * a_dim1] - s * h__ + ++k + } + ++i1 + } + work[n + l] = s * pt + work[l] = c__ * pt + if (abs(work[n + l]) <= b) { + break + } + } + } + work[l] += f + ++l + } + i__1 = n1 + i__ = 1 + while (i__ <= i__1) { + k = i__ + pt = work[i__] + i1 = i__ + 1 + i__3 = n + j = i1 + while (j <= i__3) { + if (work[j] < pt) { + k = j + pt = work[j] + } + ++j + } + if (k != i__) { + work[k] = work[i__] + work[i__] = pt + i__3 = n + j = 1 + while (j <= i__3) { + pt = a[j + i__ * a_dim1] + a[j + i__ * a_dim1] = a[j + k * a_dim1] + a[j + k * a_dim1] = pt + ++j + } + } + ++i__ + } + ifault = 0 + return ifault + } /* mneig_ */ + } + + init { + require(n >= 0) { "Invalid matrix size: $n" } + theSize = n * (n + 1) / 2 + theNRow = n + theData = DoubleArray(theSize) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnApplication.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnApplication.kt new file mode 100644 index 0000000..025eea4 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnApplication.kt @@ -0,0 +1,554 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* + +/** + * Base class for minimizers. + * + * @version $Id$ + * @author Darksnake + */ +abstract class MnApplication { + /* package protected */ + var checkAnalyticalDerivatives: Boolean + + /* package protected */ /* package protected */ + var theErrorDef = 1.0 /* package protected */ + var theFCN: MultiFunction? + + /* package protected */ /* package protected */ + var theNumCall /* package protected */ = 0 + var theState: MnUserParameterState + + /* package protected */ + var theStrategy: MnStrategy + + /* package protected */ + var useAnalyticalDerivatives: Boolean + + /* package protected */ + internal constructor(fcn: MultiFunction?, state: MnUserParameterState, stra: MnStrategy) { + theFCN = fcn + theState = state + theStrategy = stra + checkAnalyticalDerivatives = true + useAnalyticalDerivatives = true + } + + internal constructor(fcn: MultiFunction?, state: MnUserParameterState, stra: MnStrategy, nfcn: Int) { + theFCN = fcn + theState = state + theStrategy = stra + theNumCall = nfcn + checkAnalyticalDerivatives = true + useAnalyticalDerivatives = true + } + + /** + * + * MultiFunction. + * + * @return a [MultiFunction] object. + */ + fun MultiFunction(): MultiFunction? { + return theFCN + } + + /** + * add free parameter + * + * @param err a double. + * @param val a double. + * @param name a [String] object. + */ + fun add(name: String, `val`: Double, err: Double) { + theState.add(name, `val`, err) + } + + /** + * add limited parameter + * + * @param up a double. + * @param low a double. + * @param name a [String] object. + * @param val a double. + * @param err a double. + */ + fun add(name: String, `val`: Double, err: Double, low: Double, up: Double) { + theState.add(name, `val`, err, low, up) + } + + /** + * add const parameter + * + * @param name a [String] object. + * @param val a double. + */ + fun add(name: String, `val`: Double) { + theState.add(name, `val`) + } + + /** + * + * checkAnalyticalDerivatives. + * + * @return a boolean. + */ + fun checkAnalyticalDerivatives(): Boolean { + return checkAnalyticalDerivatives + } + + /** + * + * covariance. + * + * @return a [hep.dataforge.MINUIT.MnUserCovariance] object. + */ + fun covariance(): MnUserCovariance { + return theState.covariance() + } + + /** + * + * error. + * + * @param index a int. + * @return a double. + */ + fun error(index: Int): Double { + return theState.error(index) + } + + /** + * + * error. + * + * @param name a [String] object. + * @return a double. + */ + fun error(name: String?): Double { + return theState.error(name) + } + + /** + * + * errorDef. + * + * @return a double. + */ + fun errorDef(): Double { + return theErrorDef + } + + /** + * + * errors. + * + * @return an array of double. + */ + fun errors(): DoubleArray { + return theState.errors() + } + + fun ext2int(i: Int, value: Double): Double { + return theState.ext2int(i, value) + } + + fun extOfInt(i: Int): Int { + return theState.extOfInt(i) + } + //interaction via external number of parameter + /** + * + * fix. + * + * @param index a int. + */ + fun fix(index: Int) { + theState.fix(index) + } + //interaction via name of parameter + /** + * + * fix. + * + * @param name a [String] object. + */ + fun fix(name: String?) { + theState.fix(name) + } + + /** + * convert name into external number of parameter + * + * @param name a [String] object. + * @return a int. + */ + fun index(name: String?): Int { + return theState.index(name) + } + + // transformation internal <-> external + fun int2ext(i: Int, value: Double): Double { + return theState.int2ext(i, value) + } + + fun intOfExt(i: Int): Int { + return theState.intOfExt(i) + } + + /** + * + * minimize. + * + * @return a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + fun minimize(): FunctionMinimum { + return minimize(DEFAULT_MAXFCN) + } + + /** + * + * minimize. + * + * @param maxfcn a int. + * @return a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + fun minimize(maxfcn: Int): FunctionMinimum { + return minimize(maxfcn, DEFAULT_TOLER) + } + + /** + * Causes minimization of the FCN and returns the result in form of a + * FunctionMinimum. + * + * @param maxfcn specifies the (approximate) maximum number of function + * calls after which the calculation will be stopped even if it has not yet + * converged. + * @param toler specifies the required tolerance on the function value at + * the minimum. The default tolerance value is 0.1, and the minimization + * will stop when the estimated vertical distance to the minimum (EDM) is + * less than 0:001*tolerance*errorDef + * @return a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + fun minimize(maxfcn: Int, toler: Double): FunctionMinimum { + var maxfcn = maxfcn + check(theState.isValid()) { "Invalid state" } + val npar = variableParameters() + if (maxfcn == 0) { + maxfcn = 200 + 100 * npar + 5 * npar * npar + } + val min: FunctionMinimum = minimizer().minimize(theFCN, + theState, + theStrategy, + maxfcn, + toler, + theErrorDef, + useAnalyticalDerivatives, + checkAnalyticalDerivatives) + theNumCall += min.nfcn() + theState = min.userState() + return min + } + + abstract fun minimizer(): ModularFunctionMinimizer + + // facade: forward interface of MnUserParameters and MnUserTransformation + fun minuitParameters(): List { + return theState.minuitParameters() + } + + /** + * convert external number into name of parameter + * + * @param index a int. + * @return a [String] object. + */ + fun name(index: Int): String { + return theState.name(index) + } + + /** + * + * numOfCalls. + * + * @return a int. + */ + fun numOfCalls(): Int { + return theNumCall + } + + /** + * access to single parameter + * @param i + * @return + */ + fun parameter(i: Int): MinuitParameter { + return theState.parameter(i) + } + + /** + * + * parameters. + * + * @return a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + fun parameters(): MnUserParameters { + return theState.parameters() + } + + /** + * access to parameters and errors in column-wise representation + * + * @return an array of double. + */ + fun params(): DoubleArray { + return theState.params() + } + + /** + * + * precision. + * + * @return a [hep.dataforge.MINUIT.MnMachinePrecision] object. + */ + fun precision(): MnMachinePrecision { + return theState.precision() + } + + /** + * + * release. + * + * @param index a int. + */ + fun release(index: Int) { + theState.release(index) + } + + /** + * + * release. + * + * @param name a [String] object. + */ + fun release(name: String?) { + theState.release(name) + } + + /** + * + * removeLimits. + * + * @param index a int. + */ + fun removeLimits(index: Int) { + theState.removeLimits(index) + } + + /** + * + * removeLimits. + * + * @param name a [String] object. + */ + fun removeLimits(name: String?) { + theState.removeLimits(name) + } + + /** + * Minuit does a check of the user gradient at the beginning, if this is not + * wanted the set this to "false". + * + * @param check a boolean. + */ + fun setCheckAnalyticalDerivatives(check: Boolean) { + checkAnalyticalDerivatives = check + } + + /** + * + * setError. + * + * @param index a int. + * @param err a double. + */ + fun setError(index: Int, err: Double) { + theState.setError(index, err) + } + + /** + * + * setError. + * + * @param name a [String] object. + * @param err a double. + */ + fun setError(name: String?, err: Double) { + theState.setError(name, err) + } + + /** + * errorDef() is the error definition of the function. E.g. is 1 if function + * is Chi2 and 0.5 if function is -logLikelihood. If the user wants instead + * the 2-sigma errors, errorDef() = 4, as Chi2(x+n*sigma) = Chi2(x) + n*n. + * + * @param errorDef a double. + */ + fun setErrorDef(errorDef: Double) { + theErrorDef = errorDef + } + + /** + * + * setLimits. + * + * @param index a int. + * @param low a double. + * @param up a double. + */ + fun setLimits(index: Int, low: Double, up: Double) { + theState.setLimits(index, low, up) + } + + /** + * + * setLimits. + * + * @param name a [String] object. + * @param low a double. + * @param up a double. + */ + fun setLimits(name: String?, low: Double, up: Double) { + theState.setLimits(name, low, up) + } + + /** + * + * setPrecision. + * + * @param prec a double. + */ + fun setPrecision(prec: Double) { + theState.setPrecision(prec) + } + + /** + * By default if the function to be minimized implements MultiFunction then + * the analytical gradient provided by the function will be used. Set this + * to + * false to disable this behaviour and force numerical + * calculation of the gradient. + * + * @param use a boolean. + */ + fun setUseAnalyticalDerivatives(use: Boolean) { + useAnalyticalDerivatives = use + } + + /** + * + * setValue. + * + * @param index a int. + * @param val a double. + */ + fun setValue(index: Int, `val`: Double) { + theState.setValue(index, `val`) + } + + /** + * + * setValue. + * + * @param name a [String] object. + * @param val a double. + */ + fun setValue(name: String?, `val`: Double) { + theState.setValue(name, `val`) + } + + /** + * + * state. + * + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun state(): MnUserParameterState { + return theState + } + + /** + * + * strategy. + * + * @return a [hep.dataforge.MINUIT.MnStrategy] object. + */ + fun strategy(): MnStrategy { + return theStrategy + } + + /** + * + * useAnalyticalDerivaties. + * + * @return a boolean. + */ + fun useAnalyticalDerivaties(): Boolean { + return useAnalyticalDerivatives + } + + /** + * + * value. + * + * @param index a int. + * @return a double. + */ + fun value(index: Int): Double { + return theState.value(index) + } + + /** + * + * value. + * + * @param name a [String] object. + * @return a double. + */ + fun value(name: String?): Double { + return theState.value(name) + } + + /** + * + * variableParameters. + * + * @return a int. + */ + fun variableParameters(): Int { + return theState.variableParameters() + } + + companion object { + var DEFAULT_MAXFCN = 0 + var DEFAULT_STRATEGY = 1 + var DEFAULT_TOLER = 0.1 + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnContours.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnContours.kt new file mode 100644 index 0000000..1b700f4 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnContours.kt @@ -0,0 +1,283 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* + +/** + * API class for Contours error analysis (2-dim errors). Minimization has to be + * done before and minimum must be valid. Possibility to ask only for the points + * or the points and associated Minos errors. + * + * @version $Id$ + * @author Darksnake + */ +class MnContours(fcn: MultiFunction?, min: FunctionMinimum?, stra: MnStrategy?) { + private var theFCN: MultiFunction? = null + private var theMinimum: FunctionMinimum? = null + private var theStrategy: MnStrategy? = null + + /** + * construct from FCN + minimum + * + * @param fcn a [MultiFunction] object. + * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + constructor(fcn: MultiFunction?, min: FunctionMinimum?) : this(fcn, min, MnApplication.DEFAULT_STRATEGY) + + /** + * construct from FCN + minimum + strategy + * + * @param stra a int. + * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, min: FunctionMinimum?, stra: Int) : this(fcn, min, MnStrategy(stra)) + + /** + * + * contour. + * + * @param px a int. + * @param py a int. + * @return a [hep.dataforge.MINUIT.ContoursError] object. + */ + fun contour(px: Int, py: Int): ContoursError { + return contour(px, py, 1.0) + } + + /** + * + * contour. + * + * @param px a int. + * @param py a int. + * @param errDef a double. + * @return a [hep.dataforge.MINUIT.ContoursError] object. + */ + fun contour(px: Int, py: Int, errDef: Double): ContoursError { + return contour(px, py, errDef, 20) + } + + /** + * Causes a CONTOURS error analysis and returns the result in form of + * ContoursError. As a by-product ContoursError keeps the MinosError + * information of parameters parx and pary. The result ContoursError can be + * easily printed using MnPrint or toString(). + * + * @param npoints a int. + * @param px a int. + * @param py a int. + * @param errDef a double. + * @return a [hep.dataforge.MINUIT.ContoursError] object. + */ + fun contour(px: Int, py: Int, errDef: Double, npoints: Int): ContoursError { + var errDef = errDef + errDef *= theMinimum!!.errorDef() + assert(npoints > 3) + val maxcalls: Int = 100 * (npoints + 5) * (theMinimum!!.userState().variableParameters() + 1) + var nfcn = 0 + val result: MutableList = java.util.ArrayList(npoints) + val states: List = java.util.ArrayList() + val toler = 0.05 + + //get first four points + val minos = MnMinos(theFCN, theMinimum, theStrategy) + val valx: Double = theMinimum!!.userState().value(px) + val valy: Double = theMinimum!!.userState().value(py) + val mex: MinosError = minos.minos(px, errDef) + nfcn += mex.nfcn() + if (!mex.isValid()) { + MINUITPlugin.logStatic("MnContours is unable to find first two points.") + return ContoursError(px, py, result, mex, mex, nfcn) + } + val ex: Range = mex.range() + val mey: MinosError = minos.minos(py, errDef) + nfcn += mey.nfcn() + if (!mey.isValid()) { + MINUITPlugin.logStatic("MnContours is unable to find second two points.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + val ey: Range = mey.range() + val migrad = MnMigrad(theFCN, + theMinimum!!.userState().copy(), + MnStrategy(max(0, theStrategy!!.strategy() - 1))) + migrad.fix(px) + migrad.setValue(px, valx + ex.getSecond()) + val exy_up: FunctionMinimum = migrad.minimize() + nfcn += exy_up.nfcn() + if (!exy_up.isValid()) { + MINUITPlugin.logStatic("MnContours is unable to find upper y value for x parameter $px.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + migrad.setValue(px, valx + ex.getFirst()) + val exy_lo: FunctionMinimum = migrad.minimize() + nfcn += exy_lo.nfcn() + if (!exy_lo.isValid()) { + MINUITPlugin.logStatic("MnContours is unable to find lower y value for x parameter $px.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + val migrad1 = MnMigrad(theFCN, + theMinimum!!.userState().copy(), + MnStrategy(max(0, theStrategy!!.strategy() - 1))) + migrad1.fix(py) + migrad1.setValue(py, valy + ey.getSecond()) + val eyx_up: FunctionMinimum = migrad1.minimize() + nfcn += eyx_up.nfcn() + if (!eyx_up.isValid()) { + MINUITPlugin.logStatic("MnContours is unable to find upper x value for y parameter $py.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + migrad1.setValue(py, valy + ey.getFirst()) + val eyx_lo: FunctionMinimum = migrad1.minimize() + nfcn += eyx_lo.nfcn() + if (!eyx_lo.isValid()) { + MINUITPlugin.logStatic("MnContours is unable to find lower x value for y parameter $py.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + val scalx: Double = 1.0 / (ex.getSecond() - ex.getFirst()) + val scaly: Double = 1.0 / (ey.getSecond() - ey.getFirst()) + result.add(Range(valx + ex.getFirst(), exy_lo.userState().value(py))) + result.add(Range(eyx_lo.userState().value(px), valy + ey.getFirst())) + result.add(Range(valx + ex.getSecond(), exy_up.userState().value(py))) + result.add(Range(eyx_up.userState().value(px), valy + ey.getSecond())) + val upar: MnUserParameterState = theMinimum!!.userState().copy() + upar.fix(px) + upar.fix(py) + val par = intArrayOf(px, py) + val cross = MnFunctionCross(theFCN, upar, theMinimum!!.fval(), theStrategy, errDef) + for (i in 4 until npoints) { + var idist1: Range = result[result.size - 1] + var idist2: Range = result[0] + var pos2 = 0 + val distx: Double = idist1.getFirst() - idist2.getFirst() + val disty: Double = idist1.getSecond() - idist2.getSecond() + var bigdis = scalx * scalx * distx * distx + scaly * scaly * disty * disty + for (j in 0 until result.size - 1) { + val ipair: Range = result[j] + val distx2: Double = ipair.getFirst() - result[j + 1].getFirst() + val disty2: Double = ipair.getSecond() - result[j + 1].getSecond() + val dist = scalx * scalx * distx2 * distx2 + scaly * scaly * disty2 * disty2 + if (dist > bigdis) { + bigdis = dist + idist1 = ipair + idist2 = result[j + 1] + pos2 = j + 1 + } + } + val a1 = 0.5 + val a2 = 0.5 + var sca = 1.0 + while (true) { + if (nfcn > maxcalls) { + MINUITPlugin.logStatic("MnContours: maximum number of function calls exhausted.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + val xmidcr: Double = a1 * idist1.getFirst() + a2 * idist2.getFirst() + val ymidcr: Double = a1 * idist1.getSecond() + a2 * idist2.getSecond() + val xdir: Double = idist2.getSecond() - idist1.getSecond() + val ydir: Double = idist1.getFirst() - idist2.getFirst() + val scalfac: Double = + sca * max(abs(xdir * scalx), abs(ydir * scaly)) + val xdircr = xdir / scalfac + val ydircr = ydir / scalfac + val pmid = doubleArrayOf(xmidcr, ymidcr) + val pdir = doubleArrayOf(xdircr, ydircr) + val opt: MnCross = cross.cross(par, pmid, pdir, toler, maxcalls) + nfcn += opt.nfcn() + if (opt.isValid()) { + val aopt: Double = opt.value() + if (pos2 == 0) { + result.add(Range(xmidcr + aopt * xdircr, ymidcr + aopt * ydircr)) + } else { + result.add(pos2, Range(xmidcr + aopt * xdircr, ymidcr + aopt * ydircr)) + } + break + } + if (sca < 0.0) { + MINUITPlugin.logStatic("MnContours is unable to find point " + (i + 1) + " on contour.") + MINUITPlugin.logStatic("MnContours finds only $i points.") + return ContoursError(px, py, result, mex, mey, nfcn) + } + sca = -1.0 + } + } + return ContoursError(px, py, result, mex, mey, nfcn) + } + + /** + * + * points. + * + * @param px a int. + * @param py a int. + * @return a [List] object. + */ + fun points(px: Int, py: Int): List { + return points(px, py, 1.0) + } + + /** + * + * points. + * + * @param px a int. + * @param py a int. + * @param errDef a double. + * @return a [List] object. + */ + fun points(px: Int, py: Int, errDef: Double): List { + return points(px, py, errDef, 20) + } + + /** + * Calculates one function contour of FCN with respect to parameters parx + * and pary. The return value is a list of (x,y) points. FCN minimized + * always with respect to all other n - 2 variable parameters (if any). + * MINUITPlugin will try to find n points on the contour (default 20). To + * calculate more than one contour, the user needs to set the error + * definition in its FCN to the appropriate value for the desired confidence + * level and call this method for each contour. + * + * @param npoints a int. + * @param px a int. + * @param py a int. + * @param errDef a double. + * @return a [List] object. + */ + fun points(px: Int, py: Int, errDef: Double, npoints: Int): List { + val cont: ContoursError = contour(px, py, errDef, npoints) + return cont.points() + } + + fun strategy(): MnStrategy? { + return theStrategy + } + + /** + * construct from FCN + minimum + strategy + * + * @param stra a [hep.dataforge.MINUIT.MnStrategy] object. + * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object. + * @param fcn a [MultiFunction] object. + */ + init { + theFCN = fcn + theMinimum = min + theStrategy = stra + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnCovarianceSqueeze.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnCovarianceSqueeze.kt new file mode 100644 index 0000000..07b1f9f --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnCovarianceSqueeze.kt @@ -0,0 +1,113 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin + +/** + * + * @version $Id$ + */ +internal object MnCovarianceSqueeze { + fun squeeze(cov: MnUserCovariance, n: Int): MnUserCovariance { + assert(cov.nrow() > 0) + assert(n < cov.nrow()) + val hess = MnAlgebraicSymMatrix(cov.nrow()) + for (i in 0 until cov.nrow()) { + for (j in i until cov.nrow()) { + hess[i, j] = cov[i, j] + } + } + try { + hess.invert() + } catch (x: SingularMatrixException) { + MINUITPlugin.logStatic("MnUserCovariance inversion failed; return diagonal matrix;") + val result = MnUserCovariance(cov.nrow() - 1) + var i = 0 + var j = 0 + while (i < cov.nrow()) { + if (i == n) { + i++ + continue + } + result[j, j] = cov[i, i] + j++ + i++ + } + return result + } + val squeezed: MnAlgebraicSymMatrix = squeeze(hess, n) + try { + squeezed.invert() + } catch (x: SingularMatrixException) { + MINUITPlugin.logStatic("MnUserCovariance back-inversion failed; return diagonal matrix;") + val result = MnUserCovariance(squeezed.nrow()) + var i = 0 + while (i < squeezed.nrow()) { + result[i, i] = 1.0 / squeezed[i, i] + i++ + } + return result + } + return MnUserCovariance(squeezed.data(), squeezed.nrow()) + } + + fun squeeze(err: MinimumError, n: Int): MinimumError { + val hess: MnAlgebraicSymMatrix = err.hessian() + val squeezed: MnAlgebraicSymMatrix = squeeze(hess, n) + try { + squeezed.invert() + } catch (x: SingularMatrixException) { + MINUITPlugin.logStatic("MnCovarianceSqueeze: MinimumError inversion fails; return diagonal matrix.") + val tmp = MnAlgebraicSymMatrix(squeezed.nrow()) + var i = 0 + while (i < squeezed.nrow()) { + tmp[i, i] = 1.0 / squeezed[i, i] + i++ + } + return MinimumError(tmp, MnInvertFailed()) + } + return MinimumError(squeezed, err.dcovar()) + } + + fun squeeze(hess: MnAlgebraicSymMatrix, n: Int): MnAlgebraicSymMatrix { + assert(hess.nrow() > 0) + assert(n < hess.nrow()) + val hs = MnAlgebraicSymMatrix(hess.nrow() - 1) + var i = 0 + var j = 0 + while (i < hess.nrow()) { + if (i == n) { + i++ + continue + } + var k = i + var l = j + while (k < hess.nrow()) { + if (k == n) { + k++ + continue + } + hs[j, l] = hess[i, k] + l++ + k++ + } + j++ + i++ + } + return hs + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnCross.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnCross.kt new file mode 100644 index 0000000..f1487b1 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnCross.kt @@ -0,0 +1,99 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * MnCross class. + * + * @version $Id$ + * @author Darksnake + */ +class MnCross { + private var theLimset = false + private var theMaxFcn = false + private var theNFcn = 0 + private var theNewMin = false + private var theState: MnUserParameterState + private var theValid = false + private var theValue = 0.0 + + internal constructor() { + theState = MnUserParameterState() + } + + internal constructor(nfcn: Int) { + theState = MnUserParameterState() + theNFcn = nfcn + } + + internal constructor(value: Double, state: MnUserParameterState, nfcn: Int) { + theValue = value + theState = state + theNFcn = nfcn + theValid = true + } + + internal constructor(state: MnUserParameterState, nfcn: Int, x: CrossParLimit?) { + theState = state + theNFcn = nfcn + theLimset = true + } + + internal constructor(state: MnUserParameterState, nfcn: Int, x: CrossFcnLimit?) { + theState = state + theNFcn = nfcn + theMaxFcn = true + } + + internal constructor(state: MnUserParameterState, nfcn: Int, x: CrossNewMin?) { + theState = state + theNFcn = nfcn + theNewMin = true + } + + fun atLimit(): Boolean { + return theLimset + } + + fun atMaxFcn(): Boolean { + return theMaxFcn + } + + fun isValid(): Boolean { + return theValid + } + + fun newMinimum(): Boolean { + return theNewMin + } + + fun nfcn(): Int { + return theNFcn + } + + fun state(): MnUserParameterState { + return theState + } + + fun value(): Double { + return theValue + } + + internal class CrossFcnLimit + internal class CrossNewMin + internal class CrossParLimit +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnEigen.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnEigen.kt new file mode 100644 index 0000000..d7aade0 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnEigen.kt @@ -0,0 +1,50 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.RealVector + +/** + * Calculates and the eigenvalues of the user covariance matrix + * MnUserCovariance. + * + * @version $Id$ + * @author Darksnake + */ +object MnEigen { + /* Calculate eigenvalues of the covariance matrix. + * Will perform the calculation of the eigenvalues of the covariance matrix + * and return the result in the form of a double array. + * The eigenvalues are ordered from the smallest to the largest eigenvalue. + */ + /** + * + * eigenvalues. + * + * @param covar a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @return an array of double. + */ + fun eigenvalues(covar: MnUserCovariance): DoubleArray { + val cov = MnAlgebraicSymMatrix(covar.nrow()) + for (i in 0 until covar.nrow()) { + for (j in i until covar.nrow()) { + cov[i, j] = covar[i, j] + } + } + val eigen: RealVector = cov.eigenvalues() + return eigen.toArray() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnFcn.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnFcn.kt new file mode 100644 index 0000000..b11f710 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnFcn.kt @@ -0,0 +1,50 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction + +/** + * Функция, которая помнит количество вызовов себя и ErrorDef + * @version $Id$ + */ +class MnFcn(fcn: MultiFunction?, errorDef: Double) { + private val theErrorDef: Double + private val theFCN: MultiFunction? + protected var theNumCall: Int + fun errorDef(): Double { + return theErrorDef + } + + fun fcn(): MultiFunction? { + return theFCN + } + + fun numOfCalls(): Int { + return theNumCall + } + + fun value(v: RealVector): Double { + theNumCall++ + return theFCN.value(v.toArray()) + } + + init { + theFCN = fcn + theNumCall = 0 + theErrorDef = errorDef + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnFunctionCross.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnFunctionCross.kt new file mode 100644 index 0000000..a05590e --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnFunctionCross.kt @@ -0,0 +1,369 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* +import kotlin.math.* + +/** + * + * @version $Id$ + */ +internal class MnFunctionCross( + fcn: MultiFunction?, + state: MnUserParameterState, + fval: Double, + stra: MnStrategy?, + errorDef: Double +) { + private val theErrorDef: Double + private val theFCN: MultiFunction? + private val theFval: Double + private val theState: MnUserParameterState + private val theStrategy: MnStrategy? + fun cross(par: IntArray, pmid: DoubleArray, pdir: DoubleArray, tlr: Double, maxcalls: Int): MnCross { + val npar = par.size + var nfcn = 0 + val prec: MnMachinePrecision = theState.precision() + val tlf = tlr * theErrorDef + var tla = tlr + val maxitr = 15 + var ipt = 0 + val aminsv = theFval + val aim = aminsv + theErrorDef + var aopt = 0.0 + var limset = false + val alsb = DoubleArray(3) + val flsb = DoubleArray(3) + val up = theErrorDef + var aulim = 100.0 + for (i in par.indices) { + val kex = par[i] + if (theState.parameter(kex).hasLimits()) { + val zmid = pmid[i] + val zdir = pdir[i] + if (abs(zdir) < theState.precision().eps()) { + continue + } + if (zdir > 0.0 && theState.parameter(kex).hasUpperLimit()) { + val zlim: Double = theState.parameter(kex).upperLimit() + aulim = min(aulim, (zlim - zmid) / zdir) + } else if (zdir < 0.0 && theState.parameter(kex).hasLowerLimit()) { + val zlim: Double = theState.parameter(kex).lowerLimit() + aulim = min(aulim, (zlim - zmid) / zdir) + } + } + } + if (aulim < aopt + tla) { + limset = true + } + val migrad = MnMigrad(theFCN, theState, MnStrategy(max(0, theStrategy!!.strategy() - 1))) + for (i in 0 until npar) { + migrad.setValue(par[i], pmid[i]) + } + val min0: FunctionMinimum = migrad.minimize(maxcalls, tlr) + nfcn += min0.nfcn() + if (min0.hasReachedCallLimit()) { + return MnCross(min0.userState(), nfcn, MnCross.CrossFcnLimit()) + } + if (!min0.isValid()) { + return MnCross(nfcn) + } + if (limset && min0.fval() < aim) { + return MnCross(min0.userState(), nfcn, MnCross.CrossParLimit()) + } + ipt++ + alsb[0] = 0.0 + flsb[0] = min0.fval() + flsb[0] = max(flsb[0], aminsv + 0.1 * up) + aopt = sqrt(up / (flsb[0] - aminsv)) - 1.0 + if (abs(flsb[0] - aim) < tlf) { + return MnCross(aopt, min0.userState(), nfcn) + } + if (aopt > 1.0) { + aopt = 1.0 + } + if (aopt < -0.5) { + aopt = -0.5 + } + limset = false + if (aopt > aulim) { + aopt = aulim + limset = true + } + for (i in 0 until npar) { + migrad.setValue(par[i], pmid[i] + aopt * pdir[i]) + } + var min1: FunctionMinimum = migrad.minimize(maxcalls, tlr) + nfcn += min1.nfcn() + if (min1.hasReachedCallLimit()) { + return MnCross(min1.userState(), nfcn, MnCross.CrossFcnLimit()) + } + if (!min1.isValid()) { + return MnCross(nfcn) + } + if (limset && min1.fval() < aim) { + return MnCross(min1.userState(), nfcn, MnCross.CrossParLimit()) + } + ipt++ + alsb[1] = aopt + flsb[1] = min1.fval() + var dfda = (flsb[1] - flsb[0]) / (alsb[1] - alsb[0]) + var ecarmn = 0.0 + var ecarmx = 0.0 + var ibest = 0 + var iworst = 0 + var noless = 0 + var min2: FunctionMinimum? = null + L300@ while (true) { + if (dfda < 0.0) { + val maxlk = maxitr - ipt + for (it in 0 until maxlk) { + alsb[0] = alsb[1] + flsb[0] = flsb[1] + aopt = alsb[0] + 0.2 * it + limset = false + if (aopt > aulim) { + aopt = aulim + limset = true + } + for (i in 0 until npar) { + migrad.setValue(par[i], pmid[i] + aopt * pdir[i]) + } + min1 = migrad.minimize(maxcalls, tlr) + nfcn += min1.nfcn() + if (min1.hasReachedCallLimit()) { + return MnCross(min1.userState(), nfcn, MnCross.CrossFcnLimit()) + } + if (!min1.isValid()) { + return MnCross(nfcn) + } + if (limset && min1.fval() < aim) { + return MnCross(min1.userState(), nfcn, MnCross.CrossParLimit()) + } + ipt++ + alsb[1] = aopt + flsb[1] = min1.fval() + dfda = (flsb[1] - flsb[0]) / (alsb[1] - alsb[0]) + if (dfda > 0.0) { + break + } + } + if (ipt > maxitr) { + return MnCross(nfcn) + } + } + L460@ while (true) { + aopt = alsb[1] + (aim - flsb[1]) / dfda + val fdist: Double = + min(abs(aim - flsb[0]), abs(aim - flsb[1])) + val adist: Double = + min(abs(aopt - alsb[0]), abs(aopt - alsb[1])) + tla = tlr + if (abs(aopt) > 1.0) { + tla = tlr * abs(aopt) + } + if (adist < tla && fdist < tlf) { + return MnCross(aopt, min1.userState(), nfcn) + } + if (ipt > maxitr) { + return MnCross(nfcn) + } + val bmin: Double = min(alsb[0], alsb[1]) - 1.0 + if (aopt < bmin) { + aopt = bmin + } + val bmax: Double = max(alsb[0], alsb[1]) + 1.0 + if (aopt > bmax) { + aopt = bmax + } + limset = false + if (aopt > aulim) { + aopt = aulim + limset = true + } + for (i in 0 until npar) { + migrad.setValue(par[i], pmid[i] + aopt * pdir[i]) + } + min2 = migrad.minimize(maxcalls, tlr) + nfcn += min2.nfcn() + if (min2.hasReachedCallLimit()) { + return MnCross(min2.userState(), nfcn, CrossFcnLimit()) + } + if (!min2.isValid()) { + return MnCross(nfcn) + } + if (limset && min2.fval() < aim) { + return MnCross(min2.userState(), nfcn, MnCross.CrossParLimit()) + } + ipt++ + alsb[2] = aopt + flsb[2] = min2.fval() + ecarmn = abs(flsb[2] - aim) + ecarmx = 0.0 + ibest = 2 + iworst = 0 + noless = 0 + for (i in 0..2) { + val ecart: Double = abs(flsb[i] - aim) + if (ecart > ecarmx) { + ecarmx = ecart + iworst = i + } + if (ecart < ecarmn) { + ecarmn = ecart + ibest = i + } + if (flsb[i] < aim) { + noless++ + } + } + if (noless == 1 || noless == 2) { + break@L300 + } + if (noless == 0 && ibest != 2) { + return MnCross(nfcn) + } + if (noless == 3 && ibest != 2) { + alsb[1] = alsb[2] + flsb[1] = flsb[2] + continue@L300 + } + flsb[iworst] = flsb[2] + alsb[iworst] = alsb[2] + dfda = (flsb[1] - flsb[0]) / (alsb[1] - alsb[0]) + } + } + do { + val parbol: MnParabola = MnParabolaFactory.create(MnParabolaPoint(alsb[0], flsb[0]), + MnParabolaPoint(alsb[1], flsb[1]), + MnParabolaPoint( + alsb[2], flsb[2])) + val coeff1: Double = parbol.c() + val coeff2: Double = parbol.b() + val coeff3: Double = parbol.a() + val determ = coeff2 * coeff2 - 4.0 * coeff3 * (coeff1 - aim) + if (determ < prec.eps()) { + return MnCross(nfcn) + } + val rt: Double = sqrt(determ) + val x1 = (-coeff2 + rt) / (2.0 * coeff3) + val x2 = (-coeff2 - rt) / (2.0 * coeff3) + val s1 = coeff2 + 2.0 * x1 * coeff3 + val s2 = coeff2 + 2.0 * x2 * coeff3 + if (s1 * s2 > 0.0) { + MINUITPlugin.logStatic("MnFunctionCross problem 1") + } + aopt = x1 + var slope = s1 + if (s2 > 0.0) { + aopt = x2 + slope = s2 + } + tla = tlr + if (abs(aopt) > 1.0) { + tla = tlr * abs(aopt) + } + if (abs(aopt - alsb[ibest]) < tla && abs(flsb[ibest] - aim) < tlf) { + return MnCross(aopt, min2!!.userState(), nfcn) + } + var ileft = 3 + var iright = 3 + var iout = 3 + ibest = 0 + ecarmx = 0.0 + ecarmn = abs(aim - flsb[0]) + for (i in 0..2) { + val ecart: Double = abs(flsb[i] - aim) + if (ecart < ecarmn) { + ecarmn = ecart + ibest = i + } + if (ecart > ecarmx) { + ecarmx = ecart + } + if (flsb[i] > aim) { + if (iright == 3) { + iright = i + } else if (flsb[i] > flsb[iright]) { + iout = i + } else { + iout = iright + iright = i + } + } else if (ileft == 3) { + ileft = i + } else if (flsb[i] < flsb[ileft]) { + iout = i + } else { + iout = ileft + ileft = i + } + } + if (ecarmx > 10.0 * abs(flsb[iout] - aim)) { + aopt = 0.5 * (aopt + 0.5 * (alsb[iright] + alsb[ileft])) + } + var smalla = 0.1 * tla + if (slope * smalla > tlf) { + smalla = tlf / slope + } + val aleft = alsb[ileft] + smalla + val aright = alsb[iright] - smalla + if (aopt < aleft) { + aopt = aleft + } + if (aopt > aright) { + aopt = aright + } + if (aleft > aright) { + aopt = 0.5 * (aleft + aright) + } + limset = false + if (aopt > aulim) { + aopt = aulim + limset = true + } + for (i in 0 until npar) { + migrad.setValue(par[i], pmid[i] + aopt * pdir[i]) + } + min2 = migrad.minimize(maxcalls, tlr) + nfcn += min2.nfcn() + if (min2.hasReachedCallLimit()) { + return MnCross(min2.userState(), nfcn, CrossFcnLimit()) + } + if (!min2.isValid()) { + return MnCross(nfcn) + } + if (limset && min2.fval() < aim) { + return MnCross(min2.userState(), nfcn, CrossParLimit()) + } + ipt++ + alsb[iout] = aopt + flsb[iout] = min2.fval() + ibest = iout + } while (ipt < maxitr) + return MnCross(nfcn) + } + + init { + theFCN = fcn + theState = state + theFval = fval + theStrategy = stra + theErrorDef = errorDef + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnGlobalCorrelationCoeff.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnGlobalCorrelationCoeff.kt new file mode 100644 index 0000000..939dd7f --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnGlobalCorrelationCoeff.kt @@ -0,0 +1,79 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.SingularMatrixException + +/** + * + * MnGlobalCorrelationCoeff class. + * + * @version $Id$ + * @author Darksnake + */ +class MnGlobalCorrelationCoeff { + private var theGlobalCC: DoubleArray + private var theValid = false + + internal constructor() { + theGlobalCC = DoubleArray(0) + } + + internal constructor(cov: MnAlgebraicSymMatrix) { + try { + val inv: MnAlgebraicSymMatrix = cov.copy() + inv.invert() + theGlobalCC = DoubleArray(cov.nrow()) + for (i in 0 until cov.nrow()) { + val denom: Double = inv[i, i] * cov[i, i] + if (denom < 1.0 && denom > 0.0) { + theGlobalCC[i] = 0 + } else { + theGlobalCC[i] = sqrt(1.0 - 1.0 / denom) + } + } + theValid = true + } catch (x: SingularMatrixException) { + theValid = false + theGlobalCC = DoubleArray(0) + } + } + + /** + * + * globalCC. + * + * @return an array of double. + */ + fun globalCC(): DoubleArray { + return theGlobalCC + } + + /** + * + * isValid. + * + * @return a boolean. + */ + fun isValid(): Boolean { + return theValid + } + + /** {@inheritDoc} */ + override fun toString(): String { + return MnPrint.toString(this) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnHesse.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnHesse.kt new file mode 100644 index 0000000..3bb6c45 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnHesse.kt @@ -0,0 +1,371 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* + +/** + * With MnHesse the user can instructs MINUITPlugin to calculate, by finite + * differences, the Hessian or error matrix. That is, it calculates the full + * matrix of second derivatives of the function with respect to the currently + * variable parameters, and inverts it. + * + * @version $Id$ + * @author Darksnake + */ +class MnHesse { + private var theStrategy: MnStrategy + + /** + * default constructor with default strategy + */ + constructor() { + theStrategy = MnStrategy(1) + } + + /** + * constructor with user-defined strategy level + * + * @param stra a int. + */ + constructor(stra: Int) { + theStrategy = MnStrategy(stra) + } + + /** + * conctructor with specific strategy + * + * @param stra a [hep.dataforge.MINUIT.MnStrategy] object. + */ + constructor(stra: MnStrategy) { + theStrategy = stra + } + /// + /// low-level API + /// + /** + * + * calculate. + * + * @param fcn a [MultiFunction] object. + * @param par an array of double. + * @param err an array of double. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray): MnUserParameterState { + return calculate(fcn, par, err, 0) + } + + /** + * FCN + parameters + errors + * + * @param maxcalls a int. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + * @param err an array of double. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray, maxcalls: Int): MnUserParameterState { + return calculate(fcn, MnUserParameterState(par, err), maxcalls) + } + + /** + * + * calculate. + * + * @param fcn a [MultiFunction] object. + * @param par an array of double. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance): MnUserParameterState { + return calculate(fcn, par, cov, 0) + } + + /** + * FCN + parameters + MnUserCovariance + * + * @param maxcalls a int. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance, maxcalls: Int): MnUserParameterState { + return calculate(fcn, MnUserParameterState(par, cov), maxcalls) + } + /// + /// high-level API + /// + /** + * + * calculate. + * + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: MnUserParameters): MnUserParameterState { + return calculate(fcn, par, 0) + } + + /** + * FCN + MnUserParameters + * + * @param maxcalls a int. + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: MnUserParameters, maxcalls: Int): MnUserParameterState { + return calculate(fcn, MnUserParameterState(par), maxcalls) + } + + /** + * + * calculate. + * + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance?): MnUserParameterState { + return calculate(fcn, par, 0) + } + + /** + * FCN + MnUserParameters + MnUserCovariance + * + * @param maxcalls a int. + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate( + fcn: MultiFunction?, + par: MnUserParameters, + cov: MnUserCovariance, + maxcalls: Int + ): MnUserParameterState { + return calculate(fcn, MnUserParameterState(par, cov), maxcalls) + } + + /** + * FCN + MnUserParameterState + * + * @param maxcalls a int. + * @param fcn a [MultiFunction] object. + * @param state a [hep.dataforge.MINUIT.MnUserParameterState] object. + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun calculate(fcn: MultiFunction?, state: MnUserParameterState, maxcalls: Int): MnUserParameterState { + val errDef = 1.0 // FixMe! + val n: Int = state.variableParameters() + val mfcn = MnUserFcn(fcn, errDef, state.getTransformation()) + val x: RealVector = ArrayRealVector(n) + for (i in 0 until n) { + x.setEntry(i, state.intParameters()[i]) + } + val amin: Double = mfcn.value(x) + val gc = Numerical2PGradientCalculator(mfcn, state.getTransformation(), theStrategy) + val par = MinimumParameters(x, amin) + val gra: FunctionGradient = gc.gradient(par) + val tmp: MinimumState = calculate(mfcn, + MinimumState(par, MinimumError(MnAlgebraicSymMatrix(n), 1.0), gra, state.edm(), state.nfcn()), + state.getTransformation(), + maxcalls) + return MnUserParameterState(tmp, errDef, state.getTransformation()) + } + + /// + /// internal interface + /// + fun calculate(mfcn: MnFcn, st: MinimumState, trafo: MnUserTransformation, maxcalls: Int): MinimumState { + var maxcalls = maxcalls + val prec: MnMachinePrecision = trafo.precision() + // make sure starting at the right place + val amin: Double = mfcn.value(st.vec()) + val aimsag: Double = sqrt(prec.eps2()) * (abs(amin) + mfcn.errorDef()) + + // diagonal elements first + val n: Int = st.parameters().vec().getDimension() + if (maxcalls == 0) { + maxcalls = 200 + 100 * n + 5 * n * n + } + var vhmat = MnAlgebraicSymMatrix(n) + var g2: RealVector = st.gradient().getGradientDerivative().copy() + var gst: RealVector = st.gradient().getStep().copy() + var grd: RealVector = st.gradient().getGradient().copy() + var dirin: RealVector = st.gradient().getStep().copy() + val yy: RealVector = ArrayRealVector(n) + if (st.gradient().isAnalytical()) { + val igc = InitialGradientCalculator(mfcn, trafo, theStrategy) + val tmp: FunctionGradient = igc.gradient(st.parameters()) + gst = tmp.getStep().copy() + dirin = tmp.getStep().copy() + g2 = tmp.getGradientDerivative().copy() + } + return try { + val x: RealVector = st.parameters().vec().copy() + for (i in 0 until n) { + val xtf: Double = x.getEntry(i) + val dmin: Double = 8.0 * prec.eps2() * (abs(xtf) + prec.eps2()) + var d: Double = abs(gst.getEntry(i)) + if (d < dmin) { + d = dmin + } + for (icyc in 0 until ncycles()) { + var sag = 0.0 + var fs1 = 0.0 + var fs2 = 0.0 + var multpy = 0 + while (multpy < 5) { + x.setEntry(i, xtf + d) + fs1 = mfcn.value(x) + x.setEntry(i, xtf - d) + fs2 = mfcn.value(x) + x.setEntry(i, xtf) + sag = 0.5 * (fs1 + fs2 - 2.0 * amin) + if (sag > prec.eps2()) { + break + } + if (trafo.parameter(i).hasLimits()) { + if (d > 0.5) { + throw MnHesseFailedException("MnHesse: 2nd derivative zero for parameter") + } + d *= 10.0 + if (d > 0.5) { + d = 0.51 + } + multpy++ + continue + } + d *= 10.0 + multpy++ + } + if (multpy >= 5) { + throw MnHesseFailedException("MnHesse: 2nd derivative zero for parameter") + } + val g2bfor: Double = g2.getEntry(i) + g2.setEntry(i, 2.0 * sag / (d * d)) + grd.setEntry(i, (fs1 - fs2) / (2.0 * d)) + gst.setEntry(i, d) + dirin.setEntry(i, d) + yy.setEntry(i, fs1) + val dlast = d + d = sqrt(2.0 * aimsag / abs(g2.getEntry(i))) + if (trafo.parameter(i).hasLimits()) { + d = min(0.5, d) + } + if (d < dmin) { + d = dmin + } + + // see if converged + if (abs((d - dlast) / d) < tolerstp()) { + break + } + if (abs((g2.getEntry(i) - g2bfor) / g2.getEntry(i)) < tolerg2()) { + break + } + d = min(d, 10.0 * dlast) + d = max(d, 0.1 * dlast) + } + vhmat[i, i] = g2.getEntry(i) + if (mfcn.numOfCalls() - st.nfcn() > maxcalls) { + throw MnHesseFailedException("MnHesse: maximum number of allowed function calls exhausted.") + } + } + if (theStrategy.strategy() > 0) { + // refine first derivative + val hgc = HessianGradientCalculator(mfcn, trafo, theStrategy) + val gr: FunctionGradient = hgc.gradient(st.parameters(), FunctionGradient(grd, g2, gst)) + grd = gr.getGradient() + } + + //off-diagonal elements + for (i in 0 until n) { + x.setEntry(i, x.getEntry(i) + dirin.getEntry(i)) + for (j in i + 1 until n) { + x.setEntry(j, x.getEntry(j) + dirin.getEntry(j)) + val fs1: Double = mfcn.value(x) + val elem: Double = + (fs1 + amin - yy.getEntry(i) - yy.getEntry(j)) / (dirin.getEntry(i) * dirin.getEntry(j)) + vhmat[i, j] = elem + x.setEntry(j, x.getEntry(j) - dirin.getEntry(j)) + } + x.setEntry(i, x.getEntry(i) - dirin.getEntry(i)) + } + + //verify if matrix pos-def (still 2nd derivative) + val tmp: MinimumError = MnPosDef.test(MinimumError(vhmat, 1.0), prec) + vhmat = tmp.invHessian() + try { + vhmat.invert() + } catch (xx: SingularMatrixException) { + throw MnHesseFailedException("MnHesse: matrix inversion fails!") + } + val gr = FunctionGradient(grd, g2, gst) + if (tmp.isMadePosDef()) { + MINUITPlugin.logStatic("MnHesse: matrix is invalid!") + MINUITPlugin.logStatic("MnHesse: matrix is not pos. def.!") + MINUITPlugin.logStatic("MnHesse: matrix was forced pos. def.") + return MinimumState(st.parameters(), + MinimumError(vhmat, MnMadePosDef()), + gr, + st.edm(), + mfcn.numOfCalls()) + } + + //calculate edm + val err = MinimumError(vhmat, 0.0) + val edm: Double = VariableMetricEDMEstimator().estimate(gr, err) + MinimumState(st.parameters(), err, gr, edm, mfcn.numOfCalls()) + } catch (x: MnHesseFailedException) { + MINUITPlugin.logStatic(x.message) + MINUITPlugin.logStatic("MnHesse fails and will return diagonal matrix ") + var j = 0 + while (j < n) { + val tmp = if (g2.getEntry(j) < prec.eps2()) 1.0 else 1.0 / g2.getEntry(j) + vhmat[j, j] = if (tmp < prec.eps2()) 1.0 else tmp + j++ + } + MinimumState(st.parameters(), + MinimumError(vhmat, MnHesseFailed()), + st.gradient(), + st.edm(), + st.nfcn() + mfcn.numOfCalls()) + } + } + + /// forward interface of MnStrategy + fun ncycles(): Int { + return theStrategy.hessianNCycles() + } + + fun tolerg2(): Double { + return theStrategy.hessianG2Tolerance() + } + + fun tolerstp(): Double { + return theStrategy.hessianStepTolerance() + } + + private inner class MnHesseFailedException(message: String?) : java.lang.Exception(message) +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnLineSearch.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnLineSearch.kt new file mode 100644 index 0000000..7b1171d --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnLineSearch.kt @@ -0,0 +1,204 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.RealVector +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal object MnLineSearch { + fun search( + fcn: MnFcn, + st: MinimumParameters, + step: RealVector, + gdel: Double, + prec: MnMachinePrecision + ): MnParabolaPoint { + var overal = 1000.0 + var undral = -100.0 + val toler = 0.05 + var slamin = 0.0 + val slambg = 5.0 + val alpha = 2.0 + val maxiter = 12 + var niter = 0 + for (i in 0 until step.getDimension()) { + if (abs(step.getEntry(i)) < prec.eps()) { + continue + } + val ratio: Double = abs(st.vec().getEntry(i) / step.getEntry(i)) + if (abs(slamin) < prec.eps()) { + slamin = ratio + } + if (ratio < slamin) { + slamin = ratio + } + } + if (abs(slamin) < prec.eps()) { + slamin = prec.eps() + } + slamin *= prec.eps2() + val F0: Double = st.fval() + val F1: Double = fcn.value(MnUtils.add(st.vec(), step)) + var fvmin: Double = st.fval() + var xvmin = 0.0 + if (F1 < F0) { + fvmin = F1 + xvmin = 1.0 + } + var toler8 = toler + var slamax = slambg + var flast = F1 + var slam = 1.0 + var iterate = false + var p0 = MnParabolaPoint(0.0, F0) + var p1 = MnParabolaPoint(slam, flast) + var F2 = 0.0 + do { + // cut toler8 as function goes up + iterate = false + val pb: MnParabola = MnParabolaFactory.create(p0, gdel, p1) + var denom = 2.0 * (flast - F0 - gdel * slam) / (slam * slam) + if (abs(denom) < prec.eps()) { + denom = -0.1 * gdel + slam = 1.0 + } + if (abs(denom) > prec.eps()) { + slam = -gdel / denom + } + if (slam < 0.0) { + slam = slamax + } + if (slam > slamax) { + slam = slamax + } + if (slam < toler8) { + slam = toler8 + } + if (slam < slamin) { + return MnParabolaPoint(xvmin, fvmin) + } + if (abs(slam - 1.0) < toler8 && p1.y() < p0.y()) { + return MnParabolaPoint(xvmin, fvmin) + } + if (abs(slam - 1.0) < toler8) { + slam = 1.0 + toler8 + } + F2 = fcn.value(MnUtils.add(st.vec(), MnUtils.mul(step, slam))) + if (F2 < fvmin) { + fvmin = F2 + xvmin = slam + } + if (p0.y() - prec.eps() < fvmin && fvmin < p0.y() + prec.eps()) { + iterate = true + flast = F2 + toler8 = toler * slam + overal = slam - toler8 + slamax = overal + p1 = MnParabolaPoint(slam, flast) + niter++ + } + } while (iterate && niter < maxiter) + if (niter >= maxiter) { + // exhausted max number of iterations + return MnParabolaPoint(xvmin, fvmin) + } + var p2 = MnParabolaPoint(slam, F2) + do { + slamax = max(slamax, alpha * abs(xvmin)) + val pb: MnParabola = MnParabolaFactory.create(p0, p1, p2) + if (pb.a() < prec.eps2()) { + val slopem: Double = 2.0 * pb.a() * xvmin + pb.b() + slam = if (slopem < 0.0) { + xvmin + slamax + } else { + xvmin - slamax + } + } else { + slam = pb.min() + if (slam > xvmin + slamax) { + slam = xvmin + slamax + } + if (slam < xvmin - slamax) { + slam = xvmin - slamax + } + } + if (slam > 0.0) { + if (slam > overal) { + slam = overal + } + } else { + if (slam < undral) { + slam = undral + } + } + var F3 = 0.0 + do { + iterate = false + val toler9: Double = max(toler8, abs(toler8 * slam)) + // min. of parabola at one point + if (abs(p0.x() - slam) < toler9 || abs(p1.x() - slam) < toler9 || abs( + p2.x() - slam) < toler9 + ) { + return MnParabolaPoint(xvmin, fvmin) + } + F3 = fcn.value(MnUtils.add(st.vec(), MnUtils.mul(step, slam))) + // if latest point worse than all three previous, cut step + if (F3 > p0.y() && F3 > p1.y() && F3 > p2.y()) { + if (slam > xvmin) { + overal = min(overal, slam - toler8) + } + if (slam < xvmin) { + undral = max(undral, slam + toler8) + } + slam = 0.5 * (slam + xvmin) + iterate = true + niter++ + } + } while (iterate && niter < maxiter) + if (niter >= maxiter) { + // exhausted max number of iterations + return MnParabolaPoint(xvmin, fvmin) + } + + // find worst previous point out of three and replace + val p3 = MnParabolaPoint(slam, F3) + if (p0.y() > p1.y() && p0.y() > p2.y()) { + p0 = p3 + } else if (p1.y() > p0.y() && p1.y() > p2.y()) { + p1 = p3 + } else { + p2 = p3 + } + if (F3 < fvmin) { + fvmin = F3 + xvmin = slam + } else { + if (slam > xvmin) { + overal = min(overal, slam - toler8) + } + if (slam < xvmin) { + undral = max(undral, slam + toler8) + } + } + niter++ + } while (niter < maxiter) + return MnParabolaPoint(xvmin, fvmin) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMachinePrecision.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMachinePrecision.kt new file mode 100644 index 0000000..161ee0c --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMachinePrecision.kt @@ -0,0 +1,71 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * Determines the relative floating point arithmetic precision. The + * setPrecision() method can be used to override Minuit's own determination, + * when the user knows that the {FCN} function value is not calculated to the + * nominal machine accuracy. + * + * @version $Id$ + * @author Darksnake + */ +class MnMachinePrecision internal constructor() { + private var theEpsMa2 = 0.0 + private var theEpsMac = 0.0 + + /** + * eps returns the smallest possible number so that 1.+eps > 1. + * @return + */ + fun eps(): Double { + return theEpsMac + } + + /** + * eps2 returns 2*sqrt(eps) + * @return + */ + fun eps2(): Double { + return theEpsMa2 + } + + /** + * override Minuit's own determination + * + * @param prec a double. + */ + fun setPrecision(prec: Double) { + theEpsMac = prec + theEpsMa2 = 2.0 * sqrt(theEpsMac) + } + + init { + setPrecision(4.0E-7) + var epstry = 0.5 + val one = 1.0 + for (i in 0..99) { + epstry *= 0.5 + val epsp1 = one + epstry + val epsbak = epsp1 - one + if (epsbak < epstry) { + setPrecision(8.0 * epstry) + break + } + } + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMigrad.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMigrad.kt new file mode 100644 index 0000000..22616a1 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMigrad.kt @@ -0,0 +1,136 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction + +/** + * MnMigrad provides minimization of the function by the method of MIGRAD, the + * most efficient and complete single method, recommended for general functions, + * and the functionality for parameters interaction. It also retains the result + * from the last minimization in case the user may want to do subsequent + * minimization steps with parameter interactions in between the minimization + * requests. The minimization produces as a by-product the error matrix of the + * parameters, which is usually reliable unless warning messages are produced. + * + * @version $Id$ + * @author Darksnake + */ +class MnMigrad +/** + * construct from MultiFunction + MnUserParameterState + MnStrategy + * + * @param str a [hep.dataforge.MINUIT.MnStrategy] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameterState] object. + * @param fcn a [MultiFunction] object. + */ + (fcn: MultiFunction?, par: MnUserParameterState, str: MnStrategy) : MnApplication(fcn, par, str) { + private val theMinimizer: VariableMetricMinimizer = VariableMetricMinimizer() + + /** + * construct from MultiFunction + double[] for parameters and errors + * with default strategy + * + * @param err an array of double. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray) : this(fcn, par, err, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and errors + * + * @param stra a int. + * @param err an array of double. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray, stra: Int) : this(fcn, + MnUserParameterState(par, err), + MnStrategy(stra)) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance) : this(fcn, par, cov, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters with default + * strategy + * + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters) : this(fcn, par, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + * + * @param stra a int. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, stra: Int) : this(fcn, + MnUserParameterState(par), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance) : this(fcn, + par, + cov, + DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + override fun minimizer(): ModularFunctionMinimizer { + return theMinimizer + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMinimize.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMinimize.kt new file mode 100644 index 0000000..ea14a54 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMinimize.kt @@ -0,0 +1,133 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction + +/** + * Causes minimization of the function by the method of MIGRAD, as does the + * MnMigrad class, but switches to the SIMPLEX method if MIGRAD fails to + * converge. Constructor arguments, methods arguments and names of methods are + * the same as for MnMigrad or MnSimplex. + * + * @version $Id$ + * @author Darksnake + */ +class MnMinimize +/** + * construct from MultiFunction + MnUserParameterState + MnStrategy + * + * @param str a [hep.dataforge.MINUIT.MnStrategy] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameterState] object. + * @param fcn a [MultiFunction] object. + */ + (fcn: MultiFunction?, par: MnUserParameterState, str: MnStrategy) : MnApplication(fcn, par, str) { + private val theMinimizer: CombinedMinimizer = CombinedMinimizer() + + /** + * construct from MultiFunction + double[] for parameters and errors + * with default strategy + * + * @param err an array of double. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray) : this(fcn, par, err, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and errors + * + * @param stra a int. + * @param err an array of double. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray, stra: Int) : this(fcn, + MnUserParameterState(par, err), + MnStrategy(stra)) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance) : this(fcn, par, cov, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters with default + * strategy + * + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters) : this(fcn, par, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + * + * @param stra a int. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, stra: Int) : this(fcn, + MnUserParameterState(par), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance) : this(fcn, + par, + cov, + DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + override fun minimizer(): ModularFunctionMinimizer { + return theMinimizer + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMinos.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMinos.kt new file mode 100644 index 0000000..d49379b --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnMinos.kt @@ -0,0 +1,379 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* +import kotlin.jvm.JvmOverloads + +/** + * API class for Minos error analysis (asymmetric errors). Minimization has to + * be done before and minimum must be valid; possibility to ask only for one + * side of the Minos error; + * + * @version $Id$ + * @author Darksnake + */ +class MnMinos(fcn: MultiFunction?, min: FunctionMinimum?, stra: MnStrategy?) { + private var theFCN: MultiFunction? = null + private var theMinimum: FunctionMinimum? = null + private var theStrategy: MnStrategy? = null + + /** + * construct from FCN + minimum + * + * @param fcn a [MultiFunction] object. + * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object. + */ + constructor(fcn: MultiFunction?, min: FunctionMinimum?) : this(fcn, min, MnApplication.DEFAULT_STRATEGY) + + /** + * construct from FCN + minimum + strategy + * + * @param stra a int. + * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, min: FunctionMinimum?, stra: Int) : this(fcn, min, MnStrategy(stra)) + // public MnMinos(MultiFunction fcn, MnUserParameterState state, double errDef, MnStrategy stra) { + // theFCN = fcn; + // theStrategy = stra; + // + // MinimumState minState = null; + // + // MnUserTransformation transformation = state.getTransformation(); + // + // MinimumSeed seed = new MinimumSeed(minState, transformation); + // + // theMinimum = new FunctionMinimum(seed,errDef); + // } + /** + * + * loval. + * + * @param par a int. + * @return a [hep.dataforge.MINUIT.MnCross] object. + */ + fun loval(par: Int): MnCross { + return loval(par, 1.0) + } + + /** + * + * loval. + * + * @param par a int. + * @param errDef a double. + * @return a [hep.dataforge.MINUIT.MnCross] object. + */ + fun loval(par: Int, errDef: Double): MnCross { + return loval(par, errDef, MnApplication.DEFAULT_MAXFCN) + } + + /** + * + * loval. + * + * @param par a int. + * @param errDef a double. + * @param maxcalls a int. + * @return a [hep.dataforge.MINUIT.MnCross] object. + */ + fun loval(par: Int, errDef: Double, maxcalls: Int): MnCross { + var errDef = errDef + var maxcalls = maxcalls + errDef *= theMinimum!!.errorDef() + assert(theMinimum!!.isValid()) + assert(!theMinimum!!.userState().parameter(par).isFixed()) + assert(!theMinimum!!.userState().parameter(par).isConst()) + if (maxcalls == 0) { + val nvar: Int = theMinimum!!.userState().variableParameters() + maxcalls = 2 * (nvar + 1) * (200 + 100 * nvar + 5 * nvar * nvar) + } + val para = intArrayOf(par) + val upar: MnUserParameterState = theMinimum!!.userState().copy() + val err: Double = upar.error(par) + val `val`: Double = upar.value(par) - err + val xmid = doubleArrayOf(`val`) + val xdir = doubleArrayOf(-err) + val ind: Int = upar.intOfExt(par) + val m: MnAlgebraicSymMatrix = theMinimum!!.error().matrix() + val xunit: Double = sqrt(errDef / err) + for (i in 0 until m.nrow()) { + if (i == ind) { + continue + } + val xdev: Double = xunit * m[ind, i] + val ext: Int = upar.extOfInt(i) + upar.setValue(ext, upar.value(ext) - xdev) + } + upar.fix(par) + upar.setValue(par, `val`) + val toler = 0.1 + val cross = MnFunctionCross(theFCN, upar, theMinimum!!.fval(), theStrategy, errDef) + val aopt: MnCross = cross.cross(para, xmid, xdir, toler, maxcalls) + if (aopt.atLimit()) { + MINUITPlugin.logStatic("MnMinos parameter $par is at lower limit.") + } + if (aopt.atMaxFcn()) { + MINUITPlugin.logStatic("MnMinos maximum number of function calls exceeded for parameter $par") + } + if (aopt.newMinimum()) { + MINUITPlugin.logStatic("MnMinos new minimum found while looking for parameter $par") + } + if (!aopt.isValid()) { + MINUITPlugin.logStatic("MnMinos could not find lower value for parameter $par.") + } + return aopt + } + /** + * calculate one side (negative or positive error) of the parameter + * + * @param maxcalls a int. + * @param par a int. + * @param errDef a double. + * @return a double. + */ + /** + * + * lower. + * + * @param par a int. + * @param errDef a double. + * @return a double. + */ + /** + * + * lower. + * + * @param par a int. + * @return a double. + */ + @JvmOverloads + fun lower(par: Int, errDef: Double = 1.0, maxcalls: Int = MnApplication.DEFAULT_MAXFCN): Double { + val upar: MnUserParameterState = theMinimum!!.userState() + val err: Double = theMinimum!!.userState().error(par) + val aopt: MnCross = loval(par, errDef, maxcalls) + return if (aopt.isValid()) -1.0 * err * (1.0 + aopt.value()) else if (aopt.atLimit()) upar.parameter(par) + .lowerLimit() else upar.value(par) + } + + /** + * + * minos. + * + * @param par a int. + * @return a [hep.dataforge.MINUIT.MinosError] object. + */ + fun minos(par: Int): MinosError { + return minos(par, 1.0) + } + + /** + * + * minos. + * + * @param par a int. + * @param errDef a double. + * @return a [hep.dataforge.MINUIT.MinosError] object. + */ + fun minos(par: Int, errDef: Double): MinosError { + return minos(par, errDef, MnApplication.DEFAULT_MAXFCN) + } + + /** + * Causes a MINOS error analysis to be performed on the parameter whose + * number is specified. MINOS errors may be expensive to calculate, but are + * very reliable since they take account of non-linearities in the problem + * as well as parameter correlations, and are in general asymmetric. + * + * @param maxcalls Specifies the (approximate) maximum number of function + * calls per parameter requested, after which the calculation will be + * stopped for that parameter. + * @param errDef a double. + * @param par a int. + * @return a [hep.dataforge.MINUIT.MinosError] object. + */ + fun minos(par: Int, errDef: Double, maxcalls: Int): MinosError { + assert(theMinimum!!.isValid()) + assert(!theMinimum!!.userState().parameter(par).isFixed()) + assert(!theMinimum!!.userState().parameter(par).isConst()) + val up: MnCross = upval(par, errDef, maxcalls) + val lo: MnCross = loval(par, errDef, maxcalls) + return MinosError(par, theMinimum!!.userState().value(par), lo, up) + } + + /** + * + * range. + * + * @param par a int. + * @return + */ + fun range(par: Int): Range { + return range(par, 1.0) + } + + /** + * + * range. + * + * @param par a int. + * @param errDef a double. + * @return + */ + fun range(par: Int, errDef: Double): Range { + return range(par, errDef, MnApplication.DEFAULT_MAXFCN) + } + + /** + * Causes a MINOS error analysis for external parameter n. + * + * @param maxcalls a int. + * @param errDef a double. + * @return The lower and upper bounds of parameter + * @param par a int. + */ + fun range(par: Int, errDef: Double, maxcalls: Int): Range { + val mnerr: MinosError = minos(par, errDef, maxcalls) + return mnerr.range() + } + /** + * + * upper. + * + * @param par a int. + * @param errDef a double. + * @param maxcalls a int. + * @return a double. + */ + /** + * + * upper. + * + * @param par a int. + * @param errDef a double. + * @return a double. + */ + /** + * + * upper. + * + * @param par a int. + * @return a double. + */ + @JvmOverloads + fun upper(par: Int, errDef: Double = 1.0, maxcalls: Int = MnApplication.DEFAULT_MAXFCN): Double { + val upar: MnUserParameterState = theMinimum!!.userState() + val err: Double = theMinimum!!.userState().error(par) + val aopt: MnCross = upval(par, errDef, maxcalls) + return if (aopt.isValid()) err * (1.0 + aopt.value()) else if (aopt.atLimit()) upar.parameter(par) + .upperLimit() else upar.value(par) + } + + /** + * + * upval. + * + * @param par a int. + * @return a [hep.dataforge.MINUIT.MnCross] object. + */ + fun upval(par: Int): MnCross { + return upval(par, 1.0) + } + + /** + * + * upval. + * + * @param par a int. + * @param errDef a double. + * @return a [hep.dataforge.MINUIT.MnCross] object. + */ + fun upval(par: Int, errDef: Double): MnCross { + return upval(par, errDef, MnApplication.DEFAULT_MAXFCN) + } + + /** + * + * upval. + * + * @param par a int. + * @param errDef a double. + * @param maxcalls a int. + * @return a [hep.dataforge.MINUIT.MnCross] object. + */ + fun upval(par: Int, errDef: Double, maxcalls: Int): MnCross { + var errDef = errDef + var maxcalls = maxcalls + errDef *= theMinimum!!.errorDef() + assert(theMinimum!!.isValid()) + assert(!theMinimum!!.userState().parameter(par).isFixed()) + assert(!theMinimum!!.userState().parameter(par).isConst()) + if (maxcalls == 0) { + val nvar: Int = theMinimum!!.userState().variableParameters() + maxcalls = 2 * (nvar + 1) * (200 + 100 * nvar + 5 * nvar * nvar) + } + val para = intArrayOf(par) + val upar: MnUserParameterState = theMinimum!!.userState().copy() + val err: Double = upar.error(par) + val `val`: Double = upar.value(par) + err + val xmid = doubleArrayOf(`val`) + val xdir = doubleArrayOf(err) + val ind: Int = upar.intOfExt(par) + val m: MnAlgebraicSymMatrix = theMinimum!!.error().matrix() + val xunit: Double = sqrt(errDef / err) + for (i in 0 until m.nrow()) { + if (i == ind) { + continue + } + val xdev: Double = xunit * m[ind, i] + val ext: Int = upar.extOfInt(i) + upar.setValue(ext, upar.value(ext) + xdev) + } + upar.fix(par) + upar.setValue(par, `val`) + val toler = 0.1 + val cross = MnFunctionCross(theFCN, upar, theMinimum!!.fval(), theStrategy, errDef) + val aopt: MnCross = cross.cross(para, xmid, xdir, toler, maxcalls) + if (aopt.atLimit()) { + MINUITPlugin.logStatic("MnMinos parameter $par is at upper limit.") + } + if (aopt.atMaxFcn()) { + MINUITPlugin.logStatic("MnMinos maximum number of function calls exceeded for parameter $par") + } + if (aopt.newMinimum()) { + MINUITPlugin.logStatic("MnMinos new minimum found while looking for parameter $par") + } + if (!aopt.isValid()) { + MINUITPlugin.logStatic("MnMinos could not find upper value for parameter $par.") + } + return aopt + } + + /** + * construct from FCN + minimum + strategy + * + * @param stra a [hep.dataforge.MINUIT.MnStrategy] object. + * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object. + * @param fcn a [MultiFunction] object. + */ + init { + theFCN = fcn + theMinimum = min + theStrategy = stra + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabola.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabola.kt new file mode 100644 index 0000000..a0a56de --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabola.kt @@ -0,0 +1,55 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * parabola = a*xx + b*x + c + * + * @version $Id$ + */ +internal class MnParabola(private val theA: Double, private val theB: Double, private val theC: Double) { + fun a(): Double { + return theA + } + + fun b(): Double { + return theB + } + + fun c(): Double { + return theC + } + + fun min(): Double { + return -theB / (2.0 * theA) + } + + fun x_neg(y: Double): Double { + return -sqrt(y / theA + min() * min() - theC / theA) + min() + } + + fun x_pos(y: Double): Double { + return sqrt(y / theA + min() * min() - theC / theA) + min() + } + + fun y(x: Double): Double { + return theA * x * x + theB * x + theC + } + + fun ymin(): Double { + return -theB * theB / (4.0 * theA) + theC + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabolaFactory.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabolaFactory.kt new file mode 100644 index 0000000..f45d2b9 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabolaFactory.kt @@ -0,0 +1,58 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal object MnParabolaFactory { + fun create(p1: MnParabolaPoint, p2: MnParabolaPoint, p3: MnParabolaPoint): MnParabola { + var x1: Double = p1.x() + var x2: Double = p2.x() + var x3: Double = p3.x() + val dx12 = x1 - x2 + val dx13 = x1 - x3 + val dx23 = x2 - x3 + val xm = (x1 + x2 + x3) / 3.0 + x1 -= xm + x2 -= xm + x3 -= xm + val y1: Double = p1.y() + val y2: Double = p2.y() + val y3: Double = p3.y() + val a = y1 / (dx12 * dx13) - y2 / (dx12 * dx23) + y3 / (dx13 * dx23) + var b = -y1 * (x2 + x3) / (dx12 * dx13) + y2 * (x1 + x3) / (dx12 * dx23) - y3 * (x1 + x2) / (dx13 * dx23) + var c = y1 - a * x1 * x1 - b * x1 + c += xm * (xm * a - b) + b -= 2.0 * xm * a + return MnParabola(a, b, c) + } + + fun create(p1: MnParabolaPoint, dxdy1: Double, p2: MnParabolaPoint): MnParabola { + val x1: Double = p1.x() + val xx1 = x1 * x1 + val x2: Double = p2.x() + val xx2 = x2 * x2 + val y1: Double = p1.y() + val y12: Double = p1.y() - p2.y() + val det = xx1 - xx2 - 2.0 * x1 * (x1 - x2) + val a = -(y12 + (x2 - x1) * dxdy1) / det + val b = -(-2.0 * x1 * y12 + (xx1 - xx2) * dxdy1) / det + val c = y1 - a * xx1 - b * x1 + return MnParabola(a, b, c) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabolaPoint.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabolaPoint.kt new file mode 100644 index 0000000..858e010 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParabolaPoint.kt @@ -0,0 +1,30 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class MnParabolaPoint(private val theX: Double, private val theY: Double) { + fun x(): Double { + return theX + } + + fun y(): Double { + return theY + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParameterScan.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParameterScan.kt new file mode 100644 index 0000000..7791c20 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnParameterScan.kt @@ -0,0 +1,113 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction + +/** + * Scans the values of FCN as a function of one parameter and retains the best + * function and parameter values found + * + * @version $Id$ + */ +internal class MnParameterScan { + private var theAmin: Double + private var theFCN: MultiFunction? + private var theParameters: MnUserParameters + + constructor(fcn: MultiFunction, par: MnUserParameters) { + theFCN = fcn + theParameters = par + theAmin = fcn.value(par.params()) + } + + constructor(fcn: MultiFunction?, par: MnUserParameters, fval: Double) { + theFCN = fcn + theParameters = par + theAmin = fval + } + + fun fval(): Double { + return theAmin + } + + fun parameters(): MnUserParameters { + return theParameters + } + + fun scan(par: Int): List { + return scan(par, 41) + } + + fun scan(par: Int, maxsteps: Int): List { + return scan(par, maxsteps, 0.0, 0.0) + } + + /** + * returns pairs of (x,y) points, x=parameter value, y=function value of FCN + * @param high + * @return + */ + fun scan(par: Int, maxsteps: Int, low: Double, high: Double): List { + var maxsteps = maxsteps + var low = low + var high = high + if (maxsteps > 101) { + maxsteps = 101 + } + val result: MutableList = java.util.ArrayList(maxsteps + 1) + val params: DoubleArray = theParameters.params() + result.add(Range(params[par], theAmin)) + if (low > high) { + return result + } + if (maxsteps < 2) { + return result + } + if (low == 0.0 && high == 0.0) { + low = params[par] - 2.0 * theParameters.error(par) + high = params[par] + 2.0 * theParameters.error(par) + } + if (low == 0.0 && high == 0.0 && theParameters.parameter(par).hasLimits()) { + if (theParameters.parameter(par).hasLowerLimit()) { + low = theParameters.parameter(par).lowerLimit() + } + if (theParameters.parameter(par).hasUpperLimit()) { + high = theParameters.parameter(par).upperLimit() + } + } + if (theParameters.parameter(par).hasLimits()) { + if (theParameters.parameter(par).hasLowerLimit()) { + low = max(low, theParameters.parameter(par).lowerLimit()) + } + if (theParameters.parameter(par).hasUpperLimit()) { + high = min(high, theParameters.parameter(par).upperLimit()) + } + } + val x0 = low + val stp = (high - low) / (maxsteps - 1.0) + for (i in 0 until maxsteps) { + params[par] = x0 + i.toDouble() * stp + val fval: Double = theFCN.value(params) + if (fval < theAmin) { + theParameters.setValue(par, params[par]) + theAmin = fval + } + result.add(Range(params[par], fval)) + } + return result + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnPlot.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnPlot.kt new file mode 100644 index 0000000..656dd8d --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnPlot.kt @@ -0,0 +1,438 @@ +/* + * 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 ru.inr.mass.minuit + +import java.lang.StringBuffer +import kotlin.jvm.JvmOverloads + +/** + * MnPlot produces a text-screen graphical output of (x,y) points. E.g. from + * Scan or Contours. + * + * @version $Id$ + * @author Darksnake + */ +class MnPlot @JvmOverloads constructor(private val thePageWidth: Int = 80, private val thePageLength: Int = 30) { + private var bh = 0.0 + private var bl = 0.0 + private var bwid = 0.0 + private var nb = 0 + fun length(): Int { + return thePageLength + } + + private fun mnbins(a1: Double, a2: Double, naa: Int) { + + //*-*-*-*-*-*-*-*-*-*-*Compute reasonable histogram intervals*-*-*-*-*-*-*-*-* + //*-* ====================================== + //*-* Function TO DETERMINE REASONABLE HISTOGRAM INTERVALS + //*-* GIVEN ABSOLUTE UPPER AND LOWER BOUNDS A1 AND A2 + //*-* AND DESIRED MAXIMUM NUMBER OF BINS NAA + //*-* PROGRAM MAKES REASONABLE BINNING FROM BL TO BH OF WIDTH BWID + //*-* F. JAMES, AUGUST, 1974 , stolen for Minuit, 1988 + //*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* + + /* Local variables */ + var awid: Double + var ah: Double + var sigfig: Double + var sigrnd: Double + var alb: Double + var kwid: Int + var lwid: Int + var na = 0 + var log_: Int + val al: Double = if (a1 < a2) a1 else a2 + ah = if (a1 > a2) a1 else a2 + if (al == ah) { + ah = al + 1 + } + + //*-*- IF NAA .EQ. -1 , PROGRAM USES BWID INPUT FROM CALLING ROUTINE + var skip = naa == -1 && bwid > 0 + if (!skip) { + na = naa - 1 + if (na < 1) { + na = 1 + } + } + while (true) { + if (!skip) { + //*-*- GET NOMINAL BIN WIDTH IN EXPON FORM + awid = (ah - al) / na.toDouble() + log_ = log10(awid) + if (awid <= 1) { + --log_ + } + sigfig = awid * pow(10.0, -log_.toDouble()) + //*-*- ROUND MANTISSA UP TO 2, 2.5, 5, OR 10 + if (sigfig <= 2) { + sigrnd = 2.0 + } else if (sigfig <= 2.5) { + sigrnd = 2.5 + } else if (sigfig <= 5) { + sigrnd = 5.0 + } else { + sigrnd = 1.0 + ++log_ + } + bwid = sigrnd * pow(10.0, log_.toDouble()) + } + alb = al / bwid + lwid = alb.toInt() + if (alb < 0) { + --lwid + } + bl = bwid * lwid.toDouble() + alb = ah / bwid + 1 + kwid = alb.toInt() + if (alb < 0) { + --kwid + } + bh = bwid * kwid.toDouble() + nb = kwid - lwid + if (naa <= 5) { + if (naa == -1) { + return + } + //*-*- REQUEST FOR ONE BIN IS DIFFICULT CASE + if (naa > 1 || nb == 1) { + return + } + bwid *= 2.0 + nb = 1 + return + } + if (nb shl 1 != naa) { + return + } + ++na + skip = false + continue + } + } + + private fun mnplot(xpt: DoubleArray, ypt: DoubleArray, chpt: StringBuffer, nxypt: Int, npagwd: Int, npagln: Int) { + //*-*-*-*Plots points in array xypt onto one page with labelled axes*-*-*-*-* + //*-* =========================================================== + //*-* NXYPT is the number of points to be plotted + //*-* XPT(I) = x-coord. of ith point + //*-* YPT(I) = y-coord. of ith point + //*-* CHPT(I) = character to be plotted at this position + //*-* the input point arrays XPT, YPT, CHPT are destroyed. + //*-* + //*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* + + /* Local variables */ + var xmin: Double + var xmax: Double + var ymax: Double + var savx: Double + var savy: Double + var yprt: Double + var xbest: Double + var ybest: Double + val xvalus = DoubleArray(12) + val any: Double + val iten: Int + var j: Int + var k: Int + var maxnx: Int + var maxny: Int + var iquit: Int + var ni: Int + var linodd: Int + var ibk: Int + var isp1: Int + var ks: Int + var ix: Int + var overpr: Boolean + val cline = StringBuffer(npagwd) + for (ii in 0 until npagwd) { + cline.append(' ') + } + var chsav: Char + val chbest: Char + + /* Function Body */ + //*-* Computing MIN + maxnx = if (npagwd - 20 < 100) npagwd - 20 else 100 + if (maxnx < 10) { + maxnx = 10 + } + maxny = npagln + if (maxny < 10) { + maxny = 10 + } + if (nxypt <= 1) { + return + } + xbest = xpt[0] + ybest = ypt[0] + chbest = chpt.get(0) + //*-*- order the points by decreasing y + val km1: Int = nxypt - 1 + var i: Int = 1 + while (i <= km1) { + iquit = 0 + ni = nxypt - i + j = 1 + while (j <= ni) { + if (ypt[j - 1] > ypt[j]) { + ++j + continue + } + savx = xpt[j - 1] + xpt[j - 1] = xpt[j] + xpt[j] = savx + savy = ypt[j - 1] + ypt[j - 1] = ypt[j] + ypt[j] = savy + chsav = chpt.get(j - 1) + chpt.setCharAt(j - 1, chpt.get(j)) + chpt.setCharAt(j, chsav) + iquit = 1 + ++j + } + if (iquit == 0) { + break + } + ++i + } + //*-*- find extreme values + xmax = xpt[0] + xmin = xmax + i = 1 + while (i <= nxypt) { + if (xpt[i - 1] > xmax) { + xmax = xpt[i - 1] + } + if (xpt[i - 1] < xmin) { + xmin = xpt[i - 1] + } + ++i + } + val dxx: Double = (xmax - xmin) * .001 + xmax += dxx + xmin -= dxx + mnbins(xmin, xmax, maxnx) + xmin = bl + xmax = bh + var nx: Int = nb + val bwidx: Double = bwid + ymax = ypt[0] + var ymin: Double = ypt[nxypt - 1] + if (ymax == ymin) { + ymax = ymin + 1 + } + val dyy: Double = (ymax - ymin) * .001 + ymax += dyy + ymin -= dyy + mnbins(ymin, ymax, maxny) + ymin = bl + ymax = bh + var ny: Int = nb + val bwidy: Double = bwid + any = ny.toDouble() + //*-*- if first point is blank, it is an 'origin' + if (chbest != ' ') { + xbest = (xmax + xmin) * .5 + ybest = (ymax + ymin) * .5 + } + //*-*- find scale constants + val ax: Double = 1 / bwidx + val ay: Double = 1 / bwidy + val bx: Double = -ax * xmin + 2 + val by: Double = -ay * ymin - 2 + //*-*- convert points to grid positions + i = 1 + while (i <= nxypt) { + xpt[i - 1] = ax * xpt[i - 1] + bx + ypt[i - 1] = any - ay * ypt[i - 1] - by + ++i + } + val nxbest: Int = (ax * xbest + bx).toInt() + val nybest: Int = (any - ay * ybest - by).toInt() + //*-*- print the points + ny += 2 + nx += 2 + isp1 = 1 + linodd = 1 + overpr = false + i = 1 + while (i <= ny) { + ibk = 1 + while (ibk <= nx) { + cline.setCharAt(ibk - 1, ' ') + ++ibk + } + // cline.setCharAt(nx,'\0'); + // cline.setCharAt(nx+1,'\0'); + cline.setCharAt(0, '.') + cline.setCharAt(nx - 1, '.') + cline.setCharAt(nxbest - 1, '.') + if (i == 1 || i == nybest || i == ny) { + j = 1 + while (j <= nx) { + cline.setCharAt(j - 1, '.') + ++j + } + } + yprt = ymax - (i - 1.0) * bwidy + var isplset = false + if (isp1 <= nxypt) { + //*-*- find the points to be plotted on this line + k = isp1 + while (k <= nxypt) { + ks = ypt[k - 1].toInt() + if (ks > i) { + isp1 = k + isplset = true + break + } + ix = xpt[k - 1].toInt() + if (cline.get(ix - 1) != '.' && cline.get(ix - 1) != ' ') { + if (cline.get(ix - 1) == chpt.get(k - 1)) { + ++k + continue + } + overpr = true + //*-*- OVERPR is true if one or more positions contains more than + //*-*- one point + cline.setCharAt(ix - 1, '&') + ++k + continue + } + cline.setCharAt(ix - 1, chpt.get(k - 1)) + ++k + } + if (!isplset) { + isp1 = nxypt + 1 + } + } + if (linodd != 1 && i != ny) { + linodd = 1 + java.lang.System.out.printf(" %s", cline.substring(0, 60)) + } else { + java.lang.System.out.printf(" %14.7g ..%s", yprt, cline.substring(0, 60)) + linodd = 0 + } + println() + ++i + } + //*-*- print labels on x-axis every ten columns + ibk = 1 + while (ibk <= nx) { + cline.setCharAt(ibk - 1, ' ') + if (ibk % 10 == 1) { + cline.setCharAt(ibk - 1, '/') + } + ++ibk + } + java.lang.System.out.printf(" %s", cline) + java.lang.System.out.printf("\n") + ibk = 1 + while (ibk <= 12) { + xvalus[ibk - 1] = xmin + (ibk - 1.0) * 10 * bwidx + ++ibk + } + java.lang.System.out.printf(" ") + iten = (nx + 9) / 10 + ibk = 1 + while (ibk <= iten) { + java.lang.System.out.printf(" %9.4g", xvalus[ibk - 1]) + ++ibk + } + java.lang.System.out.printf("\n") + if (overpr) { + val chmess = " Overprint character is &" + java.lang.System.out.printf(" ONE COLUMN=%13.7g%s", bwidx, chmess) + } else { + val chmess = " " + java.lang.System.out.printf(" ONE COLUMN=%13.7g%s", bwidx, chmess) + } + println() + } + + /** + * + * plot. + * + * @param points a [List] object. + */ + fun plot(points: List) { + val x = DoubleArray(points.size) + val y = DoubleArray(points.size) + val chpt = StringBuffer(points.size) + for ((i, ipoint) in points.withIndex()) { + x[i] = ipoint.getFirst() + y[i] = ipoint.getSecond() + chpt.append('*') + } + mnplot(x, y, chpt, points.size, width(), length()) + } + + /** + * + * plot. + * + * @param xmin a double. + * @param ymin a double. + * @param points a [List] object. + */ + fun plot(xmin: Double, ymin: Double, points: List) { + val x = DoubleArray(points.size + 2) + x[0] = xmin + x[1] = xmin + val y = DoubleArray(points.size + 2) + y[0] = ymin + y[1] = ymin + val chpt = StringBuffer(points.size + 2) + chpt.append(' ') + chpt.append('X') + var i = 2 + for (ipoint in points) { + x[i] = ipoint.getFirst() + y[i] = ipoint.getSecond() + chpt.append('*') + i++ + } + mnplot(x, y, chpt, points.size + 2, width(), length()) + } + + fun width(): Int { + return thePageWidth + } + /** + * + * Constructor for MnPlot. + * + * @param thePageWidth a int. + * @param thePageLength a int. + */ + /** + * + * Constructor for MnPlot. + */ + init { + if (thePageWidth > 120) { + thePageWidth = 120 + } + if (thePageLength > 56) { + thePageLength = 56 + } + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnPosDef.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnPosDef.kt new file mode 100644 index 0000000..9023e35 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnPosDef.kt @@ -0,0 +1,89 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin + +/** + * + * @version $Id$ + */ +internal object MnPosDef { + fun test(st: MinimumState, prec: MnMachinePrecision): MinimumState { + val err: MinimumError = test(st.error(), prec) + return MinimumState(st.parameters(), err, st.gradient(), st.edm(), st.nfcn()) + } + + fun test(e: MinimumError, prec: MnMachinePrecision): MinimumError { + val err: MnAlgebraicSymMatrix = e.invHessian().copy() + if (err.size() === 1 && err[0, 0] < prec.eps()) { + err[0, 0] = 1.0 + return MinimumError(err, MnMadePosDef()) + } + if (err.size() === 1 && err[0, 0] > prec.eps()) { + return e + } + // std::cout<<"MnPosDef init matrix= "< 0.0) { + os.printf(" limited || %10g", ipar.value()) + if (abs(ipar.value() - ipar.lowerLimit()) < par.precision().eps2()) { + os.print("* ") + atLoLim = true + } + if (abs(ipar.value() - ipar.upperLimit()) < par.precision().eps2()) { + os.print("**") + atHiLim = true + } + os.printf(" || %10g\n", ipar.error()) + } else { + os.printf(" free || %10g || no\n", ipar.value()) + } + } else { + if (ipar.error() > 0.0) { + os.printf(" free || %10g || %10g\n", ipar.value(), ipar.error()) + } else { + os.printf(" free || %10g || no\n", ipar.value()) + } + } + } + os.println() + if (atLoLim) { + os.print("* parameter is at lower limit") + } + if (atHiLim) { + os.print("** parameter is at upper limit") + } + os.println() + } + + /** + * + * print. + * + * @param os a [PrintWriter] object. + * @param matrix a [hep.dataforge.MINUIT.MnUserCovariance] object. + */ + fun print(os: PrintWriter, matrix: MnUserCovariance) { + os.println() + os.println("MnUserCovariance: ") + run { + os.println() + val n: Int = matrix.nrow() + for (i in 0 until n) { + for (j in 0 until n) { + os.printf("%10g ", matrix[i, j]) + } + os.println() + } + } + os.println() + os.println("MnUserCovariance parameter correlations: ") + run { + os.println() + val n: Int = matrix.nrow() + for (i in 0 until n) { + val di: Double = matrix[i, i] + for (j in 0 until n) { + val dj: Double = matrix[j, j] + os.printf("%g ", matrix[i, j] / sqrt(abs(di * dj))) + } + os.println() + } + } + } + + /** + * + * print. + * + * @param os a [PrintWriter] object. + * @param coeff a [hep.dataforge.MINUIT.MnGlobalCorrelationCoeff] object. + */ + fun print(os: PrintWriter, coeff: MnGlobalCorrelationCoeff) { + os.println() + os.println("MnGlobalCorrelationCoeff: ") + run { + os.println() + for (i in 0 until coeff.globalCC().length) { + os.printf("%g\n", coeff.globalCC()[i]) + } + } + } + + /** + * + * print. + * + * @param os a [PrintWriter] object. + * @param state a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun print(os: PrintWriter, state: MnUserParameterState) { + os.println() + if (!state.isValid()) { + os.println() + os.println("WARNING: MnUserParameterState is not valid.") + os.println() + } + os.println("# of function calls: " + state.nfcn()) + os.println("function value: " + state.fval()) + os.println("expected distance to the minimum (edm): " + state.edm()) + os.println("external parameters: " + state.parameters()) + if (state.hasCovariance()) { + os.println("covariance matrix: " + state.covariance()) + } + if (state.hasGlobalCC()) { + os.println("global correlation coefficients : " + state.globalCC()) + } + if (!state.isValid()) { + os.println("WARNING: MnUserParameterState is not valid.") + } + os.println() + } + + /** + * + * print. + * + * @param os a [PrintWriter] object. + * @param me a [hep.dataforge.MINUIT.MinosError] object. + */ + fun print(os: PrintWriter, me: MinosError) { + os.println() + os.printf("Minos # of function calls: %d\n", me.nfcn()) + if (!me.isValid()) { + os.println("Minos error is not valid.") + } + if (!me.lowerValid()) { + os.println("lower Minos error is not valid.") + } + if (!me.upperValid()) { + os.println("upper Minos error is not valid.") + } + if (me.atLowerLimit()) { + os.println("Minos error is lower limit of parameter " + me.parameter()) + } + if (me.atUpperLimit()) { + os.println("Minos error is upper limit of parameter " + me.parameter()) + } + if (me.atLowerMaxFcn()) { + os.println("Minos number of function calls for lower error exhausted.") + } + if (me.atUpperMaxFcn()) { + os.println("Minos number of function calls for upper error exhausted.") + } + if (me.lowerNewMin()) { + os.println("Minos found a new minimum in negative direction.") + os.println(me.lowerState()) + } + if (me.upperNewMin()) { + os.println("Minos found a new minimum in positive direction.") + os.println(me.upperState()) + } + os.println("# ext. || name || value@min || negative || positive ") + os.printf("%4d||%10s||%10g||%10g||%10g\n", + me.parameter(), + me.lowerState().name(me.parameter()), + me.min(), + me.lower(), + me.upper()) + os.println() + } + + /** + * + * print. + * + * @param os a [PrintWriter] object. + * @param ce a [hep.dataforge.MINUIT.ContoursError] object. + */ + fun print(os: PrintWriter, ce: ContoursError) { + os.println() + os.println("Contours # of function calls: " + ce.nfcn()) + os.println("MinosError in x: ") + os.println(ce.xMinosError()) + os.println("MinosError in y: ") + os.println(ce.yMinosError()) + val plot = MnPlot() + plot.plot(ce.xmin(), ce.ymin(), ce.points()) + for ((i, ipoint) in ce.points().withIndex()) { + os.printf("%d %10g %10g\n", i, ipoint.getFirst(), ipoint.getSecond()) + } + os.println() + } + + fun toString(x: RealVector): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: MnAlgebraicSymMatrix?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(min: FunctionMinimum?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, min) } + return writer.toString() + } + + fun toString(x: MinimumState?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: MnUserParameters?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: MnUserCovariance?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: MnGlobalCorrelationCoeff?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: MnUserParameterState?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: MinosError?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } + + fun toString(x: ContoursError?): String { + val writer: java.io.StringWriter = java.io.StringWriter() + PrintWriter(writer).use { pw -> print(pw, x) } + return writer.toString() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnScan.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnScan.kt new file mode 100644 index 0000000..63e565b --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnScan.kt @@ -0,0 +1,181 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* + +/** + * MnScan scans the value of the user function by varying one parameter. It is + * sometimes useful for debugging the user function or finding a reasonable + * starting point. + * construct from MultiFunction + MnUserParameterState + MnStrategy + * + * @param str a [hep.dataforge.MINUIT.MnStrategy] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameterState] object. + * @param fcn a [MultiFunction] object. + * @version $Id$ + * @author Darksnake + */ +class MnScan(fcn: MultiFunction?, par: MnUserParameterState, str: MnStrategy) : MnApplication(fcn, par, str) { + private val theMinimizer: ScanMinimizer = ScanMinimizer() + + /** + * construct from MultiFunction + double[] for parameters and errors + * with default strategy + * + * @param err an array of double. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray) : this(fcn, par, err, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and errors + * + * @param stra a int. + * @param err an array of double. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray, stra: Int) : this(fcn, + MnUserParameterState(par, err), + MnStrategy(stra)) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance) : this(fcn, par, cov, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters with default + * strategy + * + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters) : this(fcn, par, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + * + * @param stra a int. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, stra: Int) : this(fcn, + MnUserParameterState(par), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance) : this(fcn, + par, + cov, + DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + override fun minimizer(): ModularFunctionMinimizer { + return theMinimizer + } + + /** + * + * scan. + * + * @param par a int. + * @return a [List] object. + */ + fun scan(par: Int): List { + return scan(par, 41) + } + + /** + * + * scan. + * + * @param par a int. + * @param maxsteps a int. + * @return a [List] object. + */ + fun scan(par: Int, maxsteps: Int): List { + return scan(par, maxsteps, 0.0, 0.0) + } + + /** + * Scans the value of the user function by varying parameter number par, + * leaving all other parameters fixed at the current value. If par is not + * specified, all variable parameters are scanned in sequence. The number of + * points npoints in the scan is 40 by default, and cannot exceed 100. The + * range of the scan is by default 2 standard deviations on each side of the + * current best value, but can be specified as from low to high. After each + * scan, if a new minimum is found, the best parameter values are retained + * as start values for future scans or minimizations. The curve resulting + * from each scan can be plotted on the output terminal using MnPlot in + * order to show the approximate behaviour of the function. + * + * @param high a double. + * @param par a int. + * @param maxsteps a int. + * @param low a double. + * @return a [List] object. + */ + fun scan(par: Int, maxsteps: Int, low: Double, high: Double): List { + val scan = MnParameterScan(theFCN, theState.parameters()) + var amin: Double = scan.fval() + val result: List = scan.scan(par, maxsteps, low, high) + if (scan.fval() < amin) { + theState.setValue(par, scan.parameters().value(par)) + amin = scan.fval() + } + return result + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnSeedGenerator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnSeedGenerator.kt new file mode 100644 index 0000000..9afd5f7 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnSeedGenerator.kt @@ -0,0 +1,108 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin +import ru.inr.mass.minuit.* +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class MnSeedGenerator : MinimumSeedGenerator { + /** {@inheritDoc} */ + fun generate(fcn: MnFcn, gc: GradientCalculator, st: MnUserParameterState, stra: MnStrategy): MinimumSeed { + val n: Int = st.variableParameters() + val prec: MnMachinePrecision = st.precision() + + // initial starting values + val x: RealVector = ArrayRealVector(n) + for (i in 0 until n) { + x.setEntry(i, st.intParameters()[i]) + } + val fcnmin: Double = fcn.value(x) + val pa = MinimumParameters(x, fcnmin) + val dgrad: FunctionGradient + if (gc is AnalyticalGradientCalculator) { + val igc = InitialGradientCalculator(fcn, st.getTransformation(), stra) + val tmp: FunctionGradient = igc.gradient(pa) + val grd: FunctionGradient = gc.gradient(pa) + dgrad = FunctionGradient(grd.getGradient(), tmp.getGradientDerivative(), tmp.getStep()) + if (gc.checkGradient()) { + val good = true + val hgc = HessianGradientCalculator(fcn, st.getTransformation(), MnStrategy(2)) + val hgrd: Pair = hgc.deltaGradient(pa, dgrad) + for (i in 0 until n) { + val provided: Double = grd.getGradient().getEntry(i) + val calculated: Double = hgrd.getFirst().getGradient().getEntry(i) + val delta: Double = hgrd.getSecond().getEntry(i) + if (abs(calculated - provided) > delta) { + MINUITPlugin.logStatic("" + + "gradient discrepancy of external parameter \"%d\" " + + "(internal parameter \"%d\") too large. Expected: \"%f\", provided: \"%f\"", + st.getTransformation().extOfInt(i), i, provided, calculated) + +// +// MINUITPlugin.logStatic("gradient discrepancy of external parameter " +// + st.getTransformation().extOfInt(i) +// + " (internal parameter " + i + ") too large."); +// good = false; + } + } + if (!good) { + MINUITPlugin.logStatic("Minuit does not accept user specified gradient.") + // assert(good); + } + } + } else { + dgrad = gc.gradient(pa) + } + val mat = MnAlgebraicSymMatrix(n) + var dcovar = 1.0 + if (st.hasCovariance()) { + for (i in 0 until n) { + for (j in i until n) { + mat[i, j] = st.intCovariance()[i, j] + } + } + dcovar = 0.0 + } else { + for (i in 0 until n) { + mat[i, i] = if (abs(dgrad.getGradientDerivative() + .getEntry(i)) > prec.eps2() + ) 1.0 / dgrad.getGradientDerivative().getEntry(i) else 1.0 + } + } + val err = MinimumError(mat, dcovar) + val edm: Double = VariableMetricEDMEstimator().estimate(dgrad, err) + var state = MinimumState(pa, err, dgrad, edm, fcn.numOfCalls()) + if (NegativeG2LineSearch.hasNegativeG2(dgrad, prec)) { + state = if (gc is AnalyticalGradientCalculator) { + val ngc = Numerical2PGradientCalculator(fcn, st.getTransformation(), stra) + NegativeG2LineSearch.search(fcn, state, ngc, prec) + } else { + NegativeG2LineSearch.search(fcn, state, gc, prec) + } + } + if (stra.strategy() === 2 && !st.hasCovariance()) { + //calculate full 2nd derivative + val tmp: MinimumState = MnHesse(stra).calculate(fcn, state, st.getTransformation(), 0) + return MinimumSeed(tmp, st.getTransformation()) + } + return MinimumSeed(state, st.getTransformation()) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnSimplex.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnSimplex.kt new file mode 100644 index 0000000..b00745f --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnSimplex.kt @@ -0,0 +1,138 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* + +/** + * SIMPLEX is a function minimization method using the simplex method of Nelder + * and Mead. MnSimplex provides minimization of the function by the method of + * SIMPLEX and the functionality for parameters interaction. It also retains the + * result from the last minimization in case the user may want to do subsequent + * minimization steps with parameter interactions in between the minimization + * requests. As SIMPLEX is a stepping method it does not produce a covariance + * matrix. + * + * @version $Id$ + * @author Darksnake + */ +class MnSimplex +/** + * construct from MultiFunction + MnUserParameterState + MnStrategy + * + * @param str a [hep.dataforge.MINUIT.MnStrategy] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameterState] object. + * @param fcn a [MultiFunction] object. + */ + (fcn: MultiFunction?, par: MnUserParameterState, str: MnStrategy) : MnApplication(fcn, par, str) { + private val theMinimizer: SimplexMinimizer = SimplexMinimizer() + + /** + * construct from MultiFunction + double[] for parameters and errors + * with default strategy + * + * @param err an array of double. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray) : this(fcn, par, err, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and errors + * + * @param stra a int. + * @param err an array of double. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, err: DoubleArray, stra: Int) : this(fcn, + MnUserParameterState(par, err), + MnStrategy(stra)) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par an array of double. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance) : this(fcn, par, cov, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + double[] for parameters and + * MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par an array of double. + */ + constructor(fcn: MultiFunction?, par: DoubleArray, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters with default + * strategy + * + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters) : this(fcn, par, DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + * + * @param stra a int. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, stra: Int) : this(fcn, + MnUserParameterState(par), + MnStrategy(stra)) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * with default strategy + * + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + * @param fcn a [MultiFunction] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance) : this(fcn, + par, + cov, + DEFAULT_STRATEGY) + + /** + * construct from MultiFunction + MnUserParameters + MnUserCovariance + * + * @param stra a int. + * @param cov a [hep.dataforge.MINUIT.MnUserCovariance] object. + * @param fcn a [MultiFunction] object. + * @param par a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + constructor(fcn: MultiFunction?, par: MnUserParameters, cov: MnUserCovariance, stra: Int) : this(fcn, + MnUserParameterState(par, cov), + MnStrategy(stra)) + + /** {@inheritDoc} */ + override fun minimizer(): ModularFunctionMinimizer { + return theMinimizer + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnStrategy.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnStrategy.kt new file mode 100644 index 0000000..31b8946 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnStrategy.kt @@ -0,0 +1,310 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * API class for defining three levels of strategies: low (0), medium (1), high + * (2). + * + * + * At many places in the analysis of the FCN (the user provided function), + * MINUIT must decide whether to be safe and waste a few function calls + * in order to know where it is, or to be fast and attempt to get the + * requested results with the fewest possible calls at a certain risk of not + * obtaining the precision desired by the user. In order to allow the user to + * infuence these decisions, the MnStrategy class allows the user to control + * different settings. MnStrategy can be instantiated with three different + * minimization quality levels for low (0), medium (1) and high (2) quality. + * Default settings for iteration cycles and tolerances are initialized then. + * + * + * The default setting is set for medium quality. Value 0 (low) indicates to + * MINUIT that it should economize function calls; it is intended for cases + * where there are many variable parameters and/or the function takes a long + * time to calculate and/or the user is not interested in very precise values + * for parameter errors. On the other hand, value 2 (high) indicates that MINUIT + * is allowed to waste function calls in order to be sure that all values are + * precise; it is it is intended for cases where the function is evaluated in a + * relatively short time and/or where the parameter errors must be calculated + * reliably. + * + * In addition all constants set in MnStrategy can be changed individually by + * the user, e.g. the number of iteration cycles in the numerical gradient. + * + * + * + * + * Acts on: Migrad (behavioural), Minos (lowers strategy by 1 for Minos-own + * minimization), Hesse (iterations), Numerical2PDerivative (iterations) + * + * @author Darksnake + * @version $Id$ + */ +class MnStrategy { + private var theGradNCyc = 0 + private var theGradTlr = 0.0 + private var theGradTlrStp = 0.0 + private var theHessGradNCyc = 0 + + //default strategy + private var theHessNCyc = 0 + private var theHessTlrG2 = 0.0 + private var theHessTlrStp = 0.0 + private var theStrategy = 0 + + /** + * Creates a MnStrategy object with the default strategy (medium) + */ + constructor() { + setMediumStrategy() + } + //user defined strategy (0, 1, >=2) + /** + * Creates a MnStrategy object with the user specified strategy. + * + * @param stra The use defined strategy, 0=low, 1 medium, 2=high. + */ + constructor(stra: Int) { + if (stra == 0) { + setLowStrategy() + } else if (stra == 1) { + setMediumStrategy() + } else { + setHighStrategy() + } + } + + /** + * + * gradientNCycles. + * + * @return a int. + */ + fun gradientNCycles(): Int { + return theGradNCyc + } + + /** + * + * gradientStepTolerance. + * + * @return a double. + */ + fun gradientStepTolerance(): Double { + return theGradTlrStp + } + + /** + * + * gradientTolerance. + * + * @return a double. + */ + fun gradientTolerance(): Double { + return theGradTlr + } + + /** + * + * hessianG2Tolerance. + * + * @return a double. + */ + fun hessianG2Tolerance(): Double { + return theHessTlrG2 + } + + /** + * + * hessianGradientNCycles. + * + * @return a int. + */ + fun hessianGradientNCycles(): Int { + return theHessGradNCyc + } + + /** + * + * hessianNCycles. + * + * @return a int. + */ + fun hessianNCycles(): Int { + return theHessNCyc + } + + /** + * + * hessianStepTolerance. + * + * @return a double. + */ + fun hessianStepTolerance(): Double { + return theHessTlrStp + } + + /** + * + * isHigh. + * + * @return a boolean. + */ + fun isHigh(): Boolean { + return theStrategy >= 2 + } + + /** + * + * isLow. + * + * @return a boolean. + */ + fun isLow(): Boolean { + return theStrategy <= 0 + } + + /** + * + * isMedium. + * + * @return a boolean. + */ + fun isMedium(): Boolean { + return theStrategy == 1 + } + + /** + * + * setGradientNCycles. + * + * @param n a int. + */ + fun setGradientNCycles(n: Int) { + theGradNCyc = n + } + + /** + * + * setGradientStepTolerance. + * + * @param stp a double. + */ + fun setGradientStepTolerance(stp: Double) { + theGradTlrStp = stp + } + + /** + * + * setGradientTolerance. + * + * @param toler a double. + */ + fun setGradientTolerance(toler: Double) { + theGradTlr = toler + } + + /** + * + * setHessianG2Tolerance. + * + * @param toler a double. + */ + fun setHessianG2Tolerance(toler: Double) { + theHessTlrG2 = toler + } + + /** + * + * setHessianGradientNCycles. + * + * @param n a int. + */ + fun setHessianGradientNCycles(n: Int) { + theHessGradNCyc = n + } + + /** + * + * setHessianNCycles. + * + * @param n a int. + */ + fun setHessianNCycles(n: Int) { + theHessNCyc = n + } + + /** + * + * setHessianStepTolerance. + * + * @param stp a double. + */ + fun setHessianStepTolerance(stp: Double) { + theHessTlrStp = stp + } + + fun setHighStrategy() { + theStrategy = 2 + setGradientNCycles(5) + setGradientStepTolerance(0.1) + setGradientTolerance(0.02) + setHessianNCycles(7) + setHessianStepTolerance(0.1) + setHessianG2Tolerance(0.02) + setHessianGradientNCycles(6) + } + + /** + * + * setLowStrategy. + */ + fun setLowStrategy() { + theStrategy = 0 + setGradientNCycles(2) + setGradientStepTolerance(0.5) + setGradientTolerance(0.1) + setHessianNCycles(3) + setHessianStepTolerance(0.5) + setHessianG2Tolerance(0.1) + setHessianGradientNCycles(1) + } + + /** + * + * setMediumStrategy. + */ + fun setMediumStrategy() { + theStrategy = 1 + setGradientNCycles(3) + setGradientStepTolerance(0.3) + setGradientTolerance(0.05) + setHessianNCycles(5) + setHessianStepTolerance(0.3) + setHessianG2Tolerance(0.05) + setHessianGradientNCycles(2) + } + + /** + * + * strategy. + * + * @return a int. + */ + fun strategy(): Int { + return theStrategy + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserCovariance.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserCovariance.kt new file mode 100644 index 0000000..297588f --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserCovariance.kt @@ -0,0 +1,147 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * MnUserCovariance is the external covariance matrix designed for the + * interaction of the user. The result of the minimization (internal covariance + * matrix) is converted into the user representable format. It can also be used + * as input prior to the minimization. The size of the covariance matrix is + * according to the number of variable parameters (free and limited). + * + * @version $Id$ + * @author Darksnake + */ +class MnUserCovariance { + private var theData: DoubleArray + private var theNRow: Int + + private constructor(other: MnUserCovariance) { + theData = other.theData.clone() + theNRow = other.theNRow + } + + internal constructor() { + theData = DoubleArray(0) + theNRow = 0 + } + + /* + * covariance matrix is stored in upper triangular packed storage format, + * e.g. the elements in the array are arranged like + * {a(0,0), a(0,1), a(1,1), a(0,2), a(1,2), a(2,2), ...}, + * the size is nrow*(nrow+1)/2. + */ + internal constructor(data: DoubleArray, nrow: Int) { + require(data.size == nrow * (nrow + 1) / 2) { "Inconsistent arguments" } + theData = data + theNRow = nrow + } + + /** + * + * Constructor for MnUserCovariance. + * + * @param nrow a int. + */ + constructor(nrow: Int) { + theData = DoubleArray(nrow * (nrow + 1) / 2) + theNRow = nrow + } + + /** + * + * copy. + * + * @return a [hep.dataforge.MINUIT.MnUserCovariance] object. + */ + fun copy(): MnUserCovariance { + return MnUserCovariance(this) + } + + fun data(): DoubleArray { + return theData + } + + /** + * + * get. + * + * @param row a int. + * @param col a int. + * @return a double. + */ + operator fun get(row: Int, col: Int): Double { + require(!(row >= theNRow || col >= theNRow)) + return if (row > col) { + theData[col + row * (row + 1) / 2] + } else { + theData[row + col * (col + 1) / 2] + } + } + + /** + * + * ncol. + * + * @return a int. + */ + fun ncol(): Int { + return theNRow + } + + /** + * + * nrow. + * + * @return a int. + */ + fun nrow(): Int { + return theNRow + } + + fun scale(f: Double) { + for (i in theData.indices) { + theData[i] *= f + } + } + + /** + * + * set. + * + * @param row a int. + * @param col a int. + * @param value a double. + */ + operator fun set(row: Int, col: Int, value: Double) { + require(!(row >= theNRow || col >= theNRow)) + if (row > col) { + theData[col + row * (row + 1) / 2] = value + } else { + theData[row + col * (col + 1) / 2] = value + } + } + + fun size(): Int { + return theData.size + } + + /** {@inheritDoc} */ + override fun toString(): String { + return MnPrint.toString(this) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserFcn.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserFcn.kt new file mode 100644 index 0000000..8198a41 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserFcn.kt @@ -0,0 +1,30 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction + +/** + * + * @version $Id$ + */ +internal class MnUserFcn(fcn: MultiFunction?, errDef: Double, trafo: MnUserTransformation) : MnFcn(fcn, errDef) { + private val theTransform: MnUserTransformation = trafo + override fun value(v: RealVector): Double { + return super.value(theTransform.transform(v)) + } + +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserParameterState.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserParameterState.kt new file mode 100644 index 0000000..e80dd60 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserParameterState.kt @@ -0,0 +1,756 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.minuit.* + +/** + * The class MnUserParameterState contains the MnUserParameters and the + * MnUserCovariance. It can be created on input by the user, or by MINUIT itself + * as user representable format of the result of the minimization. + * + * @version $Id$ + * @author Darksnake + */ +class MnUserParameterState { + private var theCovariance: MnUserCovariance + private var theCovarianceValid = false + private var theEDM = 0.0 + private var theFVal = 0.0 + private var theGCCValid = false + private var theGlobalCC: MnGlobalCorrelationCoeff? = null + private var theIntCovariance: MnUserCovariance + private var theIntParameters: MutableList + private var theNFcn = 0 + private var theParameters: MnUserParameters + private var theValid: Boolean + + internal constructor() { + theValid = false + theCovarianceValid = false + theParameters = MnUserParameters() + theCovariance = MnUserCovariance() + theIntParameters = java.util.ArrayList() + theIntCovariance = MnUserCovariance() + } + + private constructor(other: MnUserParameterState) { + theValid = other.theValid + theCovarianceValid = other.theCovarianceValid + theGCCValid = other.theGCCValid + theFVal = other.theFVal + theEDM = other.theEDM + theNFcn = other.theNFcn + theParameters = other.theParameters.copy() + theCovariance = other.theCovariance + theGlobalCC = other.theGlobalCC + theIntParameters = java.util.ArrayList(other.theIntParameters) + theIntCovariance = other.theIntCovariance.copy() + } + + /** + * construct from user parameters (before minimization) + * @param par + * @param err + */ + internal constructor(par: DoubleArray, err: DoubleArray) { + theValid = true + theParameters = MnUserParameters(par, err) + theCovariance = MnUserCovariance() + theGlobalCC = MnGlobalCorrelationCoeff() + theIntParameters = java.util.ArrayList(par.size) + for (i in par.indices) { + theIntParameters.add(par[i]) + } + theIntCovariance = MnUserCovariance() + } + + internal constructor(par: MnUserParameters) { + theValid = true + theParameters = par + theCovariance = MnUserCovariance() + theGlobalCC = MnGlobalCorrelationCoeff() + theIntParameters = java.util.ArrayList(par.variableParameters()) + theIntCovariance = MnUserCovariance() + val i = 0 + for (ipar in par.parameters()) { + if (ipar.isConst() || ipar.isFixed()) { + continue + } + if (ipar.hasLimits()) { + theIntParameters.add(ext2int(ipar.number(), ipar.value())) + } else { + theIntParameters.add(ipar.value()) + } + } + } + + /** + * construct from user parameters + covariance (before minimization) + * @param nrow + * @param cov + */ + internal constructor(par: DoubleArray, cov: DoubleArray, nrow: Int) { + theValid = true + theCovarianceValid = true + theCovariance = MnUserCovariance(cov, nrow) + theGlobalCC = MnGlobalCorrelationCoeff() + theIntParameters = java.util.ArrayList(par.size) + theIntCovariance = MnUserCovariance(cov, nrow) + val err = DoubleArray(par.size) + for (i in par.indices) { + assert(theCovariance[i, i] > 0.0) + err[i] = sqrt(theCovariance[i, i]) + theIntParameters.add(par[i]) + } + theParameters = MnUserParameters(par, err) + assert(theCovariance.nrow() === variableParameters()) + } + + internal constructor(par: DoubleArray, cov: MnUserCovariance) { + theValid = true + theCovarianceValid = true + theCovariance = cov + theGlobalCC = MnGlobalCorrelationCoeff() + theIntParameters = java.util.ArrayList(par.size) + theIntCovariance = cov.copy() + require(!(theCovariance.nrow() !== variableParameters())) { "Bad covariance size" } + val err = DoubleArray(par.size) + for (i in par.indices) { + require(theCovariance[i, i] > 0.0) { "Bad covariance" } + err[i] = sqrt(theCovariance[i, i]) + theIntParameters.add(par[i]) + } + theParameters = MnUserParameters(par, err) + } + + internal constructor(par: MnUserParameters, cov: MnUserCovariance) { + theValid = true + theCovarianceValid = true + theParameters = par + theCovariance = cov + theGlobalCC = MnGlobalCorrelationCoeff() + theIntParameters = java.util.ArrayList() + theIntCovariance = cov.copy() + theIntCovariance.scale(0.5) + val i = 0 + for (ipar in par.parameters()) { + if (ipar.isConst() || ipar.isFixed()) { + continue + } + if (ipar.hasLimits()) { + theIntParameters.add(ext2int(ipar.number(), ipar.value())) + } else { + theIntParameters.add(ipar.value()) + } + } + assert(theCovariance.nrow() === variableParameters()) + } + + /** + * construct from internal parameters (after minimization) + * @param trafo + * @param up + */ + internal constructor(st: MinimumState, up: Double, trafo: MnUserTransformation) { + theValid = st.isValid() + theCovarianceValid = false + theGCCValid = false + theFVal = st.fval() + theEDM = st.edm() + theNFcn = st.nfcn() + theParameters = MnUserParameters() + theCovariance = MnUserCovariance() + theGlobalCC = MnGlobalCorrelationCoeff() + theIntParameters = java.util.ArrayList() + theIntCovariance = MnUserCovariance() + for (ipar in trafo.parameters()) { + if (ipar.isConst()) { + add(ipar.name(), ipar.value()) + } else if (ipar.isFixed()) { + add(ipar.name(), ipar.value(), ipar.error()) + if (ipar.hasLimits()) { + if (ipar.hasLowerLimit() && ipar.hasUpperLimit()) { + setLimits(ipar.name(), ipar.lowerLimit(), ipar.upperLimit()) + } else if (ipar.hasLowerLimit() && !ipar.hasUpperLimit()) { + setLowerLimit(ipar.name(), ipar.lowerLimit()) + } else { + setUpperLimit(ipar.name(), ipar.upperLimit()) + } + } + fix(ipar.name()) + } else if (ipar.hasLimits()) { + val i: Int = trafo.intOfExt(ipar.number()) + val err: Double = if (st.hasCovariance()) sqrt(2.0 * up * st.error().invHessian()[i, i]) else st.parameters().dirin().getEntry(i) + add(ipar.name(), + trafo.int2ext(i, st.vec().getEntry(i)), + trafo.int2extError(i, st.vec().getEntry(i), err)) + if (ipar.hasLowerLimit() && ipar.hasUpperLimit()) { + setLimits(ipar.name(), ipar.lowerLimit(), ipar.upperLimit()) + } else if (ipar.hasLowerLimit() && !ipar.hasUpperLimit()) { + setLowerLimit(ipar.name(), ipar.lowerLimit()) + } else { + setUpperLimit(ipar.name(), ipar.upperLimit()) + } + } else { + val i: Int = trafo.intOfExt(ipar.number()) + val err: Double = if (st.hasCovariance()) sqrt(2.0 * up * st.error().invHessian()[i, i]) else st.parameters().dirin().getEntry(i) + add(ipar.name(), st.vec().getEntry(i), err) + } + } + theCovarianceValid = st.error().isValid() + if (theCovarianceValid) { + theCovariance = trafo.int2extCovariance(st.vec(), st.error().invHessian()) + theIntCovariance = MnUserCovariance(st.error().invHessian().data().clone(), st.error().invHessian().nrow()) + theCovariance.scale(2.0 * up) + theGlobalCC = MnGlobalCorrelationCoeff(st.error().invHessian()) + theGCCValid = true + assert(theCovariance.nrow() === variableParameters()) + } + } + + /** + * add free parameter name, value, error + * + * @param err a double. + * @param val a double. + * @param name a [String] object. + */ + fun add(name: String, `val`: Double, err: Double) { + theParameters.add(name, `val`, err) + theIntParameters.add(`val`) + theCovarianceValid = false + theGCCValid = false + theValid = true + } + + /** + * add limited parameter name, value, lower bound, upper bound + * + * @param name a [String] object. + * @param val a double. + * @param low a double. + * @param err a double. + * @param up a double. + */ + fun add(name: String, `val`: Double, err: Double, low: Double, up: Double) { + theParameters.add(name, `val`, err, low, up) + theCovarianceValid = false + theIntParameters.add(ext2int(index(name), `val`)) + theGCCValid = false + theValid = true + } + + /** + * add const parameter name, value + * + * @param name a [String] object. + * @param val a double. + */ + fun add(name: String, `val`: Double) { + theParameters.add(name, `val`) + theValid = true + } + + /** + * + * copy. + * + * @return a [hep.dataforge.MINUIT.MnUserParameterState] object. + */ + fun copy(): MnUserParameterState { + return MnUserParameterState(this) + } + + /** + * Covariance matrix in the external representation + * + * @return a [hep.dataforge.MINUIT.MnUserCovariance] object. + */ + fun covariance(): MnUserCovariance { + return theCovariance + } + + /** + * Returns the expected vertival distance to the minimum (EDM) + * + * @return a double. + */ + fun edm(): Double { + return theEDM + } + + /** + * + * error. + * + * @param index a int. + * @return a double. + */ + fun error(index: Int): Double { + return theParameters.error(index) + } + + /** + * + * error. + * + * @param name a [String] object. + * @return a double. + */ + fun error(name: String?): Double { + return error(index(name)) + } + + /** + * + * errors. + * + * @return an array of double. + */ + fun errors(): DoubleArray { + return theParameters.errors() + } + + fun ext2int(i: Int, `val`: Double): Double { + return theParameters.trafo().ext2int(i, `val`) + } + + /** + * + * extOfInt. + * + * @param internal a int. + * @return a int. + */ + fun extOfInt(internal: Int): Int { + return theParameters.trafo().extOfInt(internal) + } + /// interaction via external number of parameter + /** + * + * fix. + * + * @param e a int. + */ + fun fix(e: Int) { + val i = intOfExt(e) + if (theCovarianceValid) { + theCovariance = MnCovarianceSqueeze.squeeze(theCovariance, i) + theIntCovariance = MnCovarianceSqueeze.squeeze(theIntCovariance, i) + } + theIntParameters.removeAt(i) + theParameters.fix(e) + theGCCValid = false + } + /// interaction via name of parameter + /** + * + * fix. + * + * @param name a [String] object. + */ + fun fix(name: String?) { + fix(index(name)) + } + + /** + * returns the function value at the minimum + * + * @return a double. + */ + fun fval(): Double { + return theFVal + } + + /** + * transformation internal <-> external + * @return + */ + fun getTransformation(): MnUserTransformation { + return theParameters.trafo() + } + + fun globalCC(): MnGlobalCorrelationCoeff? { + return theGlobalCC + } + + /** + * Returns + * true if the the state has a valid covariance, + * false otherwise. + * + * @return a boolean. + */ + fun hasCovariance(): Boolean { + return theCovarianceValid + } + + /** + * + * hasGlobalCC. + * + * @return a boolean. + */ + fun hasGlobalCC(): Boolean { + return theGCCValid + } + + /** + * convert name into external number of parameter + * + * @param name a [String] object. + * @return a int. + */ + fun index(name: String?): Int { + return theParameters.index(name) + } + + // transformation internal <-> external + fun int2ext(i: Int, `val`: Double): Double { + return theParameters.trafo().int2ext(i, `val`) + } + + fun intCovariance(): MnUserCovariance { + return theIntCovariance + } + + fun intOfExt(ext: Int): Int { + return theParameters.trafo().intOfExt(ext) + } + + /** + * Minuit internal representation + * @return + */ + fun intParameters(): List { + return theIntParameters + } + + /** + * Returns + * true if the the state is valid, + * false if not + * + * @return a boolean. + */ + fun isValid(): Boolean { + return theValid + } + + // facade: forward interface of MnUserParameters and MnUserTransformation + fun minuitParameters(): List { + return theParameters.parameters() + } + + /** + * convert external number into name of parameter + * + * @param index a int. + * @return a [String] object. + */ + fun name(index: Int): String { + return theParameters.name(index) + } + + /** + * Returns the number of function calls during the minimization. + * + * @return a int. + */ + fun nfcn(): Int { + return theNFcn + } + + fun parameter(i: Int): MinuitParameter { + return theParameters.parameter(i) + } + + //user external representation + fun parameters(): MnUserParameters { + return theParameters + } + + /** + * access to parameters and errors in column-wise representation + * + * @return an array of double. + */ + fun params(): DoubleArray { + return theParameters.params() + } + + /** + * + * precision. + * + * @return a [hep.dataforge.MINUIT.MnMachinePrecision] object. + */ + fun precision(): MnMachinePrecision { + return theParameters.precision() + } + + /** + * + * release. + * + * @param e a int. + */ + fun release(e: Int) { + theParameters.release(e) + theCovarianceValid = false + theGCCValid = false + val i = intOfExt(e) + if (parameter(e).hasLimits()) { + theIntParameters.add(i, ext2int(e, parameter(e).value())) + } else { + theIntParameters.add(i, parameter(e).value()) + } + } + + /** + * + * release. + * + * @param name a [String] object. + */ + fun release(name: String?) { + release(index(name)) + } + + /** + * + * removeLimits. + * + * @param e a int. + */ + fun removeLimits(e: Int) { + theParameters.removeLimits(e) + theCovarianceValid = false + theGCCValid = false + if (!parameter(e).isFixed() && !parameter(e).isConst()) { + theIntParameters[intOfExt(e)] = value(e) + } + } + + /** + * + * removeLimits. + * + * @param name a [String] object. + */ + fun removeLimits(name: String?) { + removeLimits(index(name)) + } + + /** + * + * setError. + * + * @param e a int. + * @param err a double. + * @param err a double. + */ + fun setError(e: Int, err: Double) { + theParameters.setError(e, err) + } + + /** + * + * setError. + * + * @param name a [String] object. + * @param err a double. + */ + fun setError(name: String?, err: Double) { + setError(index(name), err) + } + + /** + * + * setLimits. + * + * @param e a int. + * @param low a double. + * @param up a double. + */ + fun setLimits(e: Int, low: Double, up: Double) { + theParameters.setLimits(e, low, up) + theCovarianceValid = false + theGCCValid = false + if (!parameter(e).isFixed() && !parameter(e).isConst()) { + val i = intOfExt(e) + if (low < theIntParameters[i] && theIntParameters[i] < up) { + theIntParameters[i] = ext2int(e, theIntParameters[i]) + } else { + theIntParameters[i] = ext2int(e, 0.5 * (low + up)) + } + } + } + + /** + * + * setLimits. + * + * @param name a [String] object. + * @param low a double. + * @param up a double. + */ + fun setLimits(name: String?, low: Double, up: Double) { + setLimits(index(name), low, up) + } + + /** + * + * setLowerLimit. + * + * @param e a int. + * @param low a double. + */ + fun setLowerLimit(e: Int, low: Double) { + theParameters.setLowerLimit(e, low) + theCovarianceValid = false + theGCCValid = false + if (!parameter(e).isFixed() && !parameter(e).isConst()) { + val i = intOfExt(e) + if (low < theIntParameters[i]) { + theIntParameters[i] = ext2int(e, theIntParameters[i]) + } else { + theIntParameters[i] = ext2int(e, low + 0.5 * abs(low + 1.0)) + } + } + } + + /** + * + * setLowerLimit. + * + * @param name a [String] object. + * @param low a double. + */ + fun setLowerLimit(name: String?, low: Double) { + setLowerLimit(index(name), low) + } + + /** + * + * setPrecision. + * + * @param eps a double. + */ + fun setPrecision(eps: Double) { + theParameters.setPrecision(eps) + } + + /** + * + * setUpperLimit. + * + * @param e a int. + * @param up a double. + */ + fun setUpperLimit(e: Int, up: Double) { + theParameters.setUpperLimit(e, up) + theCovarianceValid = false + theGCCValid = false + if (!parameter(e).isFixed() && !parameter(e).isConst()) { + val i = intOfExt(e) + if (theIntParameters[i] < up) { + theIntParameters[i] = ext2int(e, theIntParameters[i]) + } else { + theIntParameters[i] = ext2int(e, up - 0.5 * abs(up + 1.0)) + } + } + } + + /** + * + * setUpperLimit. + * + * @param name a [String] object. + * @param up a double. + */ + fun setUpperLimit(name: String?, up: Double) { + setUpperLimit(index(name), up) + } + + /** + * + * setValue. + * + * @param e a int. + * @param val a double. + */ + fun setValue(e: Int, `val`: Double) { + theParameters.setValue(e, `val`) + if (!parameter(e).isFixed() && !parameter(e).isConst()) { + val i = intOfExt(e) + if (parameter(e).hasLimits()) { + theIntParameters[i] = ext2int(e, `val`) + } else { + theIntParameters[i] = `val` + } + } + } + + /** + * + * setValue. + * + * @param name a [String] object. + * @param val a double. + */ + fun setValue(name: String?, `val`: Double) { + setValue(index(name), `val`) + } + + /** {@inheritDoc} */ + override fun toString(): String { + return MnPrint.toString(this) + } + + /** + * + * value. + * + * @param index a int. + * @return a double. + */ + fun value(index: Int): Double { + return theParameters.value(index) + } + + /** + * + * value. + * + * @param name a [String] object. + * @return a double. + */ + fun value(name: String?): Double { + return value(index(name)) + } + + /** + * + * variableParameters. + * + * @return a int. + */ + fun variableParameters(): Int { + return theParameters.variableParameters() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserParameters.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserParameters.kt new file mode 100644 index 0000000..9bac54b --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserParameters.kt @@ -0,0 +1,402 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * API class for the user interaction with the parameters. Serves as input to + * the minimizer as well as output from it; users can interact: fix/release + * parameters, set values and errors, etc.; parameters can be accessed via their + * parameter number or via their user-specified name. + * + * @version $Id$ + * @author Darksnake + */ +class MnUserParameters { + private var theTransformation: MnUserTransformation + + /** + * Creates a new instance of MnUserParameters + */ + constructor() { + theTransformation = MnUserTransformation() + } + + /** + * + * Constructor for MnUserParameters. + * + * @param par an array of double. + * @param err an array of double. + */ + constructor(par: DoubleArray, err: DoubleArray) { + theTransformation = MnUserTransformation(par, err) + } + + private constructor(other: MnUserParameters) { + theTransformation = other.theTransformation.copy() + } + + /** + * Add free parameter name, value, error + * + * + * When adding parameters, MINUIT assigns indices to each parameter which + * will be the same as in the double[] in the + * MultiFunction.valueOf(). That means the first parameter the user + * adds gets index 0, the second index 1, and so on. When calculating the + * function value inside FCN, MINUIT will call + * MultiFunction.valueOf() with the elements at their respective + * positions. + * + * @param err a double. + * @param val a double. + * @param name a [String] object. + */ + fun add(name: String, `val`: Double, err: Double) { + theTransformation.add(name, `val`, err) + } + + /** + * Add limited parameter name, value, lower bound, upper bound + * + * @param up a double. + * @param low a double. + * @param name a [String] object. + * @param val a double. + * @param err a double. + */ + fun add(name: String, `val`: Double, err: Double, low: Double, up: Double) { + theTransformation.add(name, `val`, err, low, up) + } + + /** + * Add const parameter name, value + * + * @param name a [String] object. + * @param val a double. + */ + fun add(name: String, `val`: Double) { + theTransformation.add(name, `val`) + } + + /** + * + * copy. + * + * @return a [hep.dataforge.MINUIT.MnUserParameters] object. + */ + fun copy(): MnUserParameters { + return MnUserParameters(this) + } + + /** + * + * error. + * + * @param index a int. + * @return a double. + */ + fun error(index: Int): Double { + return theTransformation.error(index) + } + + /** + * + * error. + * + * @param name a [String] object. + * @return a double. + */ + fun error(name: String?): Double { + return theTransformation.error(name) + } + + fun errors(): DoubleArray { + return theTransformation.errors() + } + /// interaction via external number of parameter + /** + * Fixes the specified parameter (so that the minimizer will no longer vary + * it) + * + * @param index a int. + */ + fun fix(index: Int) { + theTransformation.fix(index) + } + /// interaction via name of parameter + /** + * Fixes the specified parameter (so that the minimizer will no longer vary + * it) + * + * @param name a [String] object. + */ + fun fix(name: String?) { + theTransformation.fix(name) + } + + /** + * convert name into external number of parameter + * @param name + * @return + */ + fun index(name: String?): Int { + return theTransformation.index(name) + } + + /** + * convert external number into name of parameter + * @param index + * @return + */ + fun name(index: Int): String { + return theTransformation.name(index) + } + + /** + * access to single parameter + * @param index + * @return + */ + fun parameter(index: Int): MinuitParameter { + return theTransformation.parameter(index) + } + + /** + * access to parameters (row-wise) + * @return + */ + fun parameters(): List { + return theTransformation.parameters() + } + + /** + * access to parameters and errors in column-wise representation + * @return + */ + fun params(): DoubleArray { + return theTransformation.params() + } + + /** + * + * precision. + * + * @return a [hep.dataforge.MINUIT.MnMachinePrecision] object. + */ + fun precision(): MnMachinePrecision { + return theTransformation.precision() + } + + /** + * Releases the specified parameter (so that the minimizer can vary it) + * + * @param index a int. + */ + fun release(index: Int) { + theTransformation.release(index) + } + + /** + * Releases the specified parameter (so that the minimizer can vary it) + * + * @param name a [String] object. + */ + fun release(name: String?) { + theTransformation.release(name) + } + + /** + * + * removeLimits. + * + * @param index a int. + */ + fun removeLimits(index: Int) { + theTransformation.removeLimits(index) + } + + /** + * + * removeLimits. + * + * @param name a [String] object. + */ + fun removeLimits(name: String?) { + theTransformation.removeLimits(name) + } + + /** + * + * setError. + * + * @param index a int. + * @param err a double. + */ + fun setError(index: Int, err: Double) { + theTransformation.setError(index, err) + } + + /** + * + * setError. + * + * @param name a [String] object. + * @param err a double. + */ + fun setError(name: String?, err: Double) { + theTransformation.setError(name, err) + } + + /** + * Set the lower and upper bound on the specified variable. + * + * @param up a double. + * @param low a double. + * @param index a int. + */ + fun setLimits(index: Int, low: Double, up: Double) { + theTransformation.setLimits(index, low, up) + } + + /** + * Set the lower and upper bound on the specified variable. + * + * @param up a double. + * @param low a double. + * @param name a [String] object. + */ + fun setLimits(name: String?, low: Double, up: Double) { + theTransformation.setLimits(name, low, up) + } + + /** + * + * setLowerLimit. + * + * @param index a int. + * @param low a double. + */ + fun setLowerLimit(index: Int, low: Double) { + theTransformation.setLowerLimit(index, low) + } + + /** + * + * setLowerLimit. + * + * @param name a [String] object. + * @param low a double. + */ + fun setLowerLimit(name: String?, low: Double) { + theTransformation.setLowerLimit(name, low) + } + + /** + * + * setPrecision. + * + * @param eps a double. + */ + fun setPrecision(eps: Double) { + theTransformation.setPrecision(eps) + } + + /** + * + * setUpperLimit. + * + * @param index a int. + * @param up a double. + */ + fun setUpperLimit(index: Int, up: Double) { + theTransformation.setUpperLimit(index, up) + } + + /** + * + * setUpperLimit. + * + * @param name a [String] object. + * @param up a double. + */ + fun setUpperLimit(name: String?, up: Double) { + theTransformation.setUpperLimit(name, up) + } + + /** + * Set the value of parameter. The parameter in question may be variable, + * fixed, or constant, but must be defined. + * + * @param index a int. + * @param val a double. + */ + fun setValue(index: Int, `val`: Double) { + theTransformation.setValue(index, `val`) + } + + /** + * Set the value of parameter. The parameter in question may be variable, + * fixed, or constant, but must be defined. + * + * @param name a [String] object. + * @param val a double. + */ + fun setValue(name: String?, `val`: Double) { + theTransformation.setValue(name, `val`) + } + + /** {@inheritDoc} */ + override fun toString(): String { + return MnPrint.toString(this) + } + + fun trafo(): MnUserTransformation { + return theTransformation + } + + /** + * + * value. + * + * @param index a int. + * @return a double. + */ + fun value(index: Int): Double { + return theTransformation.value(index) + } + + /** + * + * value. + * + * @param name a [String] object. + * @return a double. + */ + fun value(name: String?): Double { + return theTransformation.value(name) + } + + /** + * + * variableParameters. + * + * @return a int. + */ + fun variableParameters(): Int { + return theTransformation.variableParameters() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserTransformation.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserTransformation.kt new file mode 100644 index 0000000..1066ac2 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUserTransformation.kt @@ -0,0 +1,390 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector + +/** + * knows how to andThen between user specified parameters (external) and + * internal parameters used for minimization + * + * Жуткий октопус, который занимается преобразованием внешних параметров во внутренние + * TODO по возможности отказаться от использования этого монстра + * @version $Id$ + */ +class MnUserTransformation { + private val nameMap: MutableMap = HashMap() + private var theCache: MutableList + private var theExtOfInt: MutableList + private var theParameters: MutableList + private var thePrecision: MnMachinePrecision + + constructor() { + thePrecision = MnMachinePrecision() + theParameters = java.util.ArrayList() + theExtOfInt = java.util.ArrayList() + theCache = java.util.ArrayList(0) + } + + private constructor(other: MnUserTransformation) { + thePrecision = other.thePrecision + theParameters = java.util.ArrayList(other.theParameters.size) + for (par in other.theParameters) { + theParameters.add(par.copy()) + } + theExtOfInt = java.util.ArrayList(other.theExtOfInt) + theCache = java.util.ArrayList(other.theCache) + } + + constructor(par: DoubleArray, err: DoubleArray) { + thePrecision = MnMachinePrecision() + theParameters = java.util.ArrayList(par.size) + theExtOfInt = java.util.ArrayList(par.size) + theCache = java.util.ArrayList(par.size) + for (i in par.indices) { + add("p$i", par[i], err[i]) + } + } + + /** + * add free parameter + * @param err + * @param val + */ + fun add(name: String, `val`: Double, err: Double) { + require(!nameMap.containsKey(name)) { "duplicate name: $name" } + nameMap[name] = theParameters.size + theExtOfInt.add(theParameters.size) + theCache.add(`val`) + theParameters.add(MinuitParameter(theParameters.size, name, `val`, err)) + } + + /** + * add limited parameter + * @param up + * @param low + */ + fun add(name: String, `val`: Double, err: Double, low: Double, up: Double) { + require(!nameMap.containsKey(name)) { "duplicate name: $name" } + nameMap[name] = theParameters.size + theExtOfInt.add(theParameters.size) + theCache.add(`val`) + theParameters.add(MinuitParameter(theParameters.size, name, `val`, err, low, up)) + } + + /** + * add parameter + * @param name + * @param val + */ + fun add(name: String, `val`: Double) { + require(!nameMap.containsKey(name)) { "duplicate name: $name" } + nameMap[name] = theParameters.size + theCache.add(`val`) + theParameters.add(MinuitParameter(theParameters.size, name, `val`)) + } + + /** + * + * copy. + * + * @return a [hep.dataforge.MINUIT.MnUserTransformation] object. + */ + fun copy(): MnUserTransformation { + return MnUserTransformation(this) + } + + fun dInt2Ext(i: Int, `val`: Double): Double { + var dd = 1.0 + val parm: MinuitParameter = theParameters[theExtOfInt[i]] + if (parm.hasLimits()) { + dd = if (parm.hasUpperLimit() && parm.hasLowerLimit()) { + theDoubleLimTrafo.dInt2Ext(`val`, + parm.upperLimit(), + parm.lowerLimit()) + } else if (parm.hasUpperLimit() && !parm.hasLowerLimit()) { + theUpperLimTrafo.dInt2Ext(`val`, parm.upperLimit()) + } else { + theLowerLimTrafo.dInt2Ext(`val`, parm.lowerLimit()) + } + } + return dd + } + + fun error(index: Int): Double { + return theParameters[index].error() + } + + fun error(name: String?): Double { + return error(index(name)) + } + + fun errors(): DoubleArray { + val result = DoubleArray(theParameters.size) + var i = 0 + for (parameter in theParameters) { + result[i++] = parameter.error() + } + return result + } + + fun ext2int(i: Int, `val`: Double): Double { + val parm: MinuitParameter = theParameters[i] + return if (parm.hasLimits()) { + if (parm.hasUpperLimit() && parm.hasLowerLimit()) { + theDoubleLimTrafo.ext2int(`val`, + parm.upperLimit(), + parm.lowerLimit(), + precision()) + } else if (parm.hasUpperLimit() && !parm.hasLowerLimit()) { + theUpperLimTrafo.ext2int(`val`, + parm.upperLimit(), + precision()) + } else { + theLowerLimTrafo.ext2int(`val`, + parm.lowerLimit(), + precision()) + } + } else `val` + } + + fun extOfInt(internal: Int): Int { + return theExtOfInt[internal] + } + + /** + * interaction via external number of parameter + * @param index + */ + fun fix(index: Int) { + val iind = intOfExt(index) + theExtOfInt.removeAt(iind) + theParameters[index].fix() + } + + /** + * interaction via name of parameter + * @param name + */ + fun fix(name: String?) { + fix(index(name)) + } + + /** + * convert name into external number of parameter + * @param name + * @return + */ + fun index(name: String?): Int { + return nameMap[name]!! + } + + fun int2ext(i: Int, `val`: Double): Double { + val parm: MinuitParameter = theParameters[theExtOfInt[i]] + return if (parm.hasLimits()) { + if (parm.hasUpperLimit() && parm.hasLowerLimit()) { + theDoubleLimTrafo.int2ext(`val`, + parm.upperLimit(), + parm.lowerLimit()) + } else if (parm.hasUpperLimit() && !parm.hasLowerLimit()) { + theUpperLimTrafo.int2ext(`val`, parm.upperLimit()) + } else { + theLowerLimTrafo.int2ext(`val`, parm.lowerLimit()) + } + } else `val` + } + + fun int2extCovariance(vec: RealVector, cov: MnAlgebraicSymMatrix): MnUserCovariance { + val result = MnUserCovariance(cov.nrow()) + for (i in 0 until vec.getDimension()) { + var dxdi = 1.0 + if (theParameters[theExtOfInt[i]].hasLimits()) { + dxdi = dInt2Ext(i, vec.getEntry(i)) + } + for (j in i until vec.getDimension()) { + var dxdj = 1.0 + if (theParameters[theExtOfInt[j]].hasLimits()) { + dxdj = dInt2Ext(j, vec.getEntry(j)) + } + result[i, j] = dxdi * cov[i, j] * dxdj + } + } + return result + } + + fun int2extError(i: Int, `val`: Double, err: Double): Double { + var dx = err + val parm: MinuitParameter = theParameters[theExtOfInt[i]] + if (parm.hasLimits()) { + val ui = int2ext(i, `val`) + var du1 = int2ext(i, `val` + dx) - ui + val du2 = int2ext(i, `val` - dx) - ui + if (parm.hasUpperLimit() && parm.hasLowerLimit()) { + if (dx > 1.0) { + du1 = parm.upperLimit() - parm.lowerLimit() + } + dx = 0.5 * (abs(du1) + abs(du2)) + } else { + dx = 0.5 * (abs(du1) + abs(du2)) + } + } + return dx + } + + fun intOfExt(ext: Int): Int { + for (iind in theExtOfInt.indices) { + if (ext == theExtOfInt[iind]) { + return iind + } + } + throw IllegalArgumentException("ext=$ext") + } + + /** + * convert external number into name of parameter + * @param index + * @return + */ + fun name(index: Int): String { + return theParameters[index].name() + } + + /** + * access to single parameter + * @param index + * @return + */ + fun parameter(index: Int): MinuitParameter { + return theParameters[index] + } + + fun parameters(): List { + return theParameters + } + + //access to parameters and errors in column-wise representation + fun params(): DoubleArray { + val result = DoubleArray(theParameters.size) + var i = 0 + for (parameter in theParameters) { + result[i++] = parameter.value() + } + return result + } + + fun precision(): MnMachinePrecision { + return thePrecision + } + + fun release(index: Int) { + require(!theExtOfInt.contains(index)) { "index=$index" } + theExtOfInt.add(index) + Collections.sort(theExtOfInt) + theParameters[index].release() + } + + fun release(name: String?) { + release(index(name)) + } + + fun removeLimits(index: Int) { + theParameters[index].removeLimits() + } + + fun removeLimits(name: String?) { + removeLimits(index(name)) + } + + fun setError(index: Int, err: Double) { + theParameters[index].setError(err) + } + + fun setError(name: String?, err: Double) { + setError(index(name), err) + } + + fun setLimits(index: Int, low: Double, up: Double) { + theParameters[index].setLimits(low, up) + } + + fun setLimits(name: String?, low: Double, up: Double) { + setLimits(index(name), low, up) + } + + fun setLowerLimit(index: Int, low: Double) { + theParameters[index].setLowerLimit(low) + } + + fun setLowerLimit(name: String?, low: Double) { + setLowerLimit(index(name), low) + } + + fun setPrecision(eps: Double) { + thePrecision.setPrecision(eps) + } + + fun setUpperLimit(index: Int, up: Double) { + theParameters[index].setUpperLimit(up) + } + + fun setUpperLimit(name: String?, up: Double) { + setUpperLimit(index(name), up) + } + + fun setValue(index: Int, `val`: Double) { + theParameters[index].setValue(`val`) + theCache[index] = `val` + } + + fun setValue(name: String?, `val`: Double) { + setValue(index(name), `val`) + } + + fun transform(pstates: RealVector): ArrayRealVector { + // FixMe: Worry about efficiency here + val result = ArrayRealVector(theCache.size) + for (i in 0 until result.getDimension()) { + result.setEntry(i, theCache[i]) + } + for (i in 0 until pstates.getDimension()) { + if (theParameters[theExtOfInt[i]].hasLimits()) { + result.setEntry(theExtOfInt[i], int2ext(i, pstates.getEntry(i))) + } else { + result.setEntry(theExtOfInt[i], pstates.getEntry(i)) + } + } + return result + } + + //forwarded interface + fun value(index: Int): Double { + return theParameters[index].value() + } + + fun value(name: String?): Double { + return value(index(name)) + } + + fun variableParameters(): Int { + return theExtOfInt.size + } + + companion object { + private val theDoubleLimTrafo: SinParameterTransformation = SinParameterTransformation() + private val theLowerLimTrafo: SqrtLowParameterTransformation = SqrtLowParameterTransformation() + private val theUpperLimTrafo: SqrtUpParameterTransformation = SqrtUpParameterTransformation() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUtils.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUtils.kt new file mode 100644 index 0000000..d9f3e1b --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/MnUtils.kt @@ -0,0 +1,147 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector + +/** + * Utilities for operating on vectors and matrices + * + * @version $Id$ + */ +internal object MnUtils { + fun absoluteSumOfElements(m: MnAlgebraicSymMatrix): Double { + val data: DoubleArray = m.data() + var result = 0.0 + for (i in data.indices) { + result += abs(data[i]) + } + return result + } + + fun add(v1: RealVector, v2: RealVector?): RealVector { + return v1.add(v2) + } + + fun add(m1: MnAlgebraicSymMatrix, m2: MnAlgebraicSymMatrix): MnAlgebraicSymMatrix { + require(!(m1.size() !== m2.size())) { "Incompatible matrices" } + val result: MnAlgebraicSymMatrix = m1.copy() + val a: DoubleArray = result.data() + val b: DoubleArray = m2.data() + for (i in a.indices) { + a[i] += b[i] + } + return result + } + + fun div(m: MnAlgebraicSymMatrix?, scale: Double): MnAlgebraicSymMatrix { + return mul(m, 1 / scale) + } + + fun div(m: RealVector?, scale: Double): RealVector { + return mul(m, 1 / scale) + } + + fun innerProduct(v1: RealVector, v2: RealVector): Double { + require(!(v1.getDimension() !== v2.getDimension())) { "Incompatible vectors" } + var total = 0.0 + for (i in 0 until v1.getDimension()) { + total += v1.getEntry(i) * v2.getEntry(i) + } + return total + } + + fun mul(v1: RealVector, scale: Double): RealVector { + return v1.mapMultiply(scale) + } + + fun mul(m1: MnAlgebraicSymMatrix, scale: Double): MnAlgebraicSymMatrix { + val result: MnAlgebraicSymMatrix = m1.copy() + val a: DoubleArray = result.data() + for (i in a.indices) { + a[i] *= scale + } + return result + } + + fun mul(m1: MnAlgebraicSymMatrix, v1: RealVector): ArrayRealVector { + require(!(m1.nrow() !== v1.getDimension())) { "Incompatible arguments" } + val result = ArrayRealVector(m1.nrow()) + for (i in 0 until result.getDimension()) { + var total = 0.0 + for (k in 0 until result.getDimension()) { + total += m1[i, k] * v1.getEntry(k) + } + result.setEntry(i, total) + } + return result + } + + fun mul(m1: MnAlgebraicSymMatrix, m2: MnAlgebraicSymMatrix): MnAlgebraicSymMatrix { + require(!(m1.size() !== m2.size())) { "Incompatible matrices" } + val n: Int = m1.nrow() + val result = MnAlgebraicSymMatrix(n) + for (i in 0 until n) { + for (j in 0..i) { + var total = 0.0 + for (k in 0 until n) { + total += m1[i, k] * m2[k, j] + } + result[i, j] = total + } + } + return result + } + + fun outerProduct(v2: RealVector): MnAlgebraicSymMatrix { + // Fixme: check this. I am assuming this is just an outer-product of vector + // with itself. + val n: Int = v2.getDimension() + val result = MnAlgebraicSymMatrix(n) + val data: DoubleArray = v2.toArray() + for (i in 0 until n) { + for (j in 0..i) { + result[i, j] = data[i] * data[j] + } + } + return result + } + + fun similarity(avec: RealVector, mat: MnAlgebraicSymMatrix): Double { + val n: Int = avec.getDimension() + val tmp: RealVector = mul(mat, avec) + var result = 0.0 + for (i in 0 until n) { + result += tmp.getEntry(i) * avec.getEntry(i) + } + return result + } + + fun sub(v1: RealVector, v2: RealVector?): RealVector { + return v1.subtract(v2) + } + + fun sub(m1: MnAlgebraicSymMatrix, m2: MnAlgebraicSymMatrix): MnAlgebraicSymMatrix { + require(!(m1.size() !== m2.size())) { "Incompatible matrices" } + val result: MnAlgebraicSymMatrix = m1.copy() + val a: DoubleArray = result.data() + val b: DoubleArray = m2.data() + for (i in a.indices) { + a[i] -= b[i] + } + return result + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ModularFunctionMinimizer.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ModularFunctionMinimizer.kt new file mode 100644 index 0000000..f234bcd --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ModularFunctionMinimizer.kt @@ -0,0 +1,72 @@ +/* + * 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 ru.inr.mass.minuit + +import ru.inr.mass.maths.MultiFunction +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +abstract class ModularFunctionMinimizer { + abstract fun builder(): MinimumBuilder + fun minimize( + fcn: MultiFunction?, + st: MnUserParameterState, + strategy: MnStrategy, + maxfcn: Int, + toler: Double, + errorDef: Double, + useAnalyticalGradient: Boolean, + checkGradient: Boolean + ): FunctionMinimum { + var maxfcn = maxfcn + val mfcn = MnUserFcn(fcn, errorDef, st.getTransformation()) + val gc: GradientCalculator + var providesAllDerivs = true + /* + * Проверяем в явном виде, что все аналитические производные присутствуют + * TODO сделать возможность того, что часть производных задается аналитически, а часть численно + */for (i in 0 until fcn.getDimension()) { + if (!fcn.providesDeriv(i)) providesAllDerivs = false + } + gc = if (providesAllDerivs && useAnalyticalGradient) { + AnalyticalGradientCalculator(fcn, st.getTransformation(), checkGradient) + } else { + Numerical2PGradientCalculator(mfcn, st.getTransformation(), strategy) + } + val npar: Int = st.variableParameters() + if (maxfcn == 0) { + maxfcn = 200 + 100 * npar + 5 * npar * npar + } + val mnseeds: MinimumSeed = seedGenerator().generate(mfcn, gc, st, strategy) + return minimize(mfcn, gc, mnseeds, strategy, maxfcn, toler) + } + + fun minimize( + mfcn: MnFcn, + gc: GradientCalculator?, + seed: MinimumSeed?, + strategy: MnStrategy?, + maxfcn: Int, + toler: Double + ): FunctionMinimum { + return builder().minimum(mfcn, gc, seed, strategy, maxfcn, toler * mfcn.errorDef()) + } + + abstract fun seedGenerator(): MinimumSeedGenerator +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/NegativeG2LineSearch.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/NegativeG2LineSearch.kt new file mode 100644 index 0000000..2e9ce58 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/NegativeG2LineSearch.kt @@ -0,0 +1,80 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector +import ru.inr.mass.minuit.* + +/** + * In case that one of the components of the second derivative g2 calculated by + * the numerical gradient calculator is negative, a 1dim line search in the + * direction of that component is done in order to find a better position where + * g2 is again positive. + * + * @version $Id$ + */ +internal object NegativeG2LineSearch { + fun hasNegativeG2(grad: FunctionGradient, prec: MnMachinePrecision): Boolean { + for (i in 0 until grad.getGradient().getDimension()) { + if (grad.getGradientDerivative().getEntry(i) < prec.eps2()) { + return true + } + } + return false + } + + fun search(fcn: MnFcn, st: MinimumState, gc: GradientCalculator, prec: MnMachinePrecision): MinimumState { + val negG2 = hasNegativeG2(st.gradient(), prec) + if (!negG2) { + return st + } + val n: Int = st.parameters().vec().getDimension() + var dgrad: FunctionGradient = st.gradient() + var pa: MinimumParameters = st.parameters() + var iterate = false + var iter = 0 + do { + iterate = false + for (i in 0 until n) { + if (dgrad.getGradientDerivative().getEntry(i) < prec.eps2()) { + // do line search if second derivative negative + var step: RealVector = ArrayRealVector(n) + step.setEntry(i, dgrad.getStep().getEntry(i) * dgrad.getGradient().getEntry(i)) + if (abs(dgrad.getGradient().getEntry(i)) > prec.eps2()) { + step.setEntry(i, + step.getEntry(i) * (-1.0 / abs(dgrad.getGradient().getEntry(i)))) + } + val gdel: Double = step.getEntry(i) * dgrad.getGradient().getEntry(i) + val pp: MnParabolaPoint = MnLineSearch.search(fcn, pa, step, gdel, prec) + step = MnUtils.mul(step, pp.x()) + pa = MinimumParameters(MnUtils.add(pa.vec(), step), pp.y()) + dgrad = gc.gradient(pa, dgrad) + iterate = true + break + } + } + } while (iter++ < 2 * n && iterate) + val mat = MnAlgebraicSymMatrix(n) + for (i in 0 until n) { + mat[i, i] = if (abs(dgrad.getGradientDerivative() + .getEntry(i)) > prec.eps2() + ) 1.0 / dgrad.getGradientDerivative().getEntry(i) else 1.0 + } + val err = MinimumError(mat, 1.0) + val edm: Double = VariableMetricEDMEstimator().estimate(dgrad, err) + return MinimumState(pa, err, dgrad, edm, fcn.numOfCalls()) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/Numerical2PGradientCalculator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/Numerical2PGradientCalculator.kt new file mode 100644 index 0000000..efa1d57 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/Numerical2PGradientCalculator.kt @@ -0,0 +1,122 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.RealVector +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class Numerical2PGradientCalculator(fcn: MnFcn, par: MnUserTransformation, stra: MnStrategy) : + GradientCalculator { + private val theFcn: MnFcn = fcn + private val theStrategy: MnStrategy + private val theTransformation: MnUserTransformation + fun fcn(): MnFcn { + return theFcn + } + + fun gradTolerance(): Double { + return strategy().gradientTolerance() + } + + /** {@inheritDoc} */ + fun gradient(par: MinimumParameters): FunctionGradient { + val gc = InitialGradientCalculator(theFcn, theTransformation, theStrategy) + val gra: FunctionGradient = gc.gradient(par) + return gradient(par, gra) + } + + /** {@inheritDoc} */ + fun gradient(par: MinimumParameters, gradient: FunctionGradient): FunctionGradient { + require(par.isValid()) { "Parameters are invalid" } + val x: RealVector = par.vec().copy() + val fcnmin: Double = par.fval() + val dfmin: Double = 8.0 * precision().eps2() * (abs(fcnmin) + theFcn.errorDef()) + val vrysml: Double = 8.0 * precision().eps() * precision().eps() + val n: Int = x.getDimension() + val grd: RealVector = gradient.getGradient().copy() + val g2: RealVector = gradient.getGradientDerivative().copy() + val gstep: RealVector = gradient.getStep().copy() + for (i in 0 until n) { + val xtf: Double = x.getEntry(i) + val epspri: Double = precision().eps2() + abs(grd.getEntry(i) * precision().eps2()) + var stepb4 = 0.0 + for (j in 0 until ncycle()) { + val optstp: Double = sqrt(dfmin / (abs(g2.getEntry(i)) + epspri)) + var step: Double = max(optstp, abs(0.1 * gstep.getEntry(i))) + if (trafo().parameter(trafo().extOfInt(i)).hasLimits()) { + if (step > 0.5) { + step = 0.5 + } + } + val stpmax: Double = 10.0 * abs(gstep.getEntry(i)) + if (step > stpmax) { + step = stpmax + } + val stpmin: Double = + max(vrysml, 8.0 * abs(precision().eps2() * x.getEntry(i))) + if (step < stpmin) { + step = stpmin + } + if (abs((step - stepb4) / step) < stepTolerance()) { + break + } + gstep.setEntry(i, step) + stepb4 = step + x.setEntry(i, xtf + step) + val fs1: Double = theFcn.value(x) + x.setEntry(i, xtf - step) + val fs2: Double = theFcn.value(x) + x.setEntry(i, xtf) + val grdb4: Double = grd.getEntry(i) + grd.setEntry(i, 0.5 * (fs1 - fs2) / step) + g2.setEntry(i, (fs1 + fs2 - 2.0 * fcnmin) / step / step) + if (abs(grdb4 - grd.getEntry(i)) / (abs(grd.getEntry(i)) + dfmin / step) < gradTolerance()) { + break + } + } + } + return FunctionGradient(grd, g2, gstep) + } + + fun ncycle(): Int { + return strategy().gradientNCycles() + } + + fun precision(): MnMachinePrecision { + return theTransformation.precision() + } + + fun stepTolerance(): Double { + return strategy().gradientStepTolerance() + } + + fun strategy(): MnStrategy { + return theStrategy + } + + fun trafo(): MnUserTransformation { + return theTransformation + } + + init { + theTransformation = par + theStrategy = stra + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/Range.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/Range.kt new file mode 100644 index 0000000..ebeedf7 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/Range.kt @@ -0,0 +1,32 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * A class representing a pair of double (x,y) or (lower,upper) + * + * @version $Id$ + * @author Darksnake + */ +class Range +/** + * + * Constructor for Range. + * + * @param k a double. + * @param v a double. + */ + (k: Double, v: Double) : Pair(k, v) \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ScanBuilder.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ScanBuilder.kt new file mode 100644 index 0000000..b7e7739 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ScanBuilder.kt @@ -0,0 +1,58 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector +import ru.inr.mass.minuit.* + +/** + * Performs a minimization using the simplex method of Nelder and Mead (ref. + * Comp. J. 7, 308 (1965)). + * + * @version $Id$ + */ +internal class ScanBuilder : MinimumBuilder { + /** {@inheritDoc} */ + fun minimum( + mfcn: MnFcn, + gc: GradientCalculator?, + seed: MinimumSeed, + stra: MnStrategy?, + maxfcn: Int, + toler: Double + ): FunctionMinimum { + val x: RealVector = seed.parameters().vec().copy() + val upst = MnUserParameterState(seed.state(), mfcn.errorDef(), seed.trafo()) + val scan = MnParameterScan(mfcn.fcn(), upst.parameters(), seed.fval()) + var amin: Double = scan.fval() + val n: Int = seed.trafo().variableParameters() + val dirin: RealVector = ArrayRealVector(n) + for (i in 0 until n) { + val ext: Int = seed.trafo().extOfInt(i) + scan.scan(ext) + if (scan.fval() < amin) { + amin = scan.fval() + x.setEntry(i, seed.trafo().ext2int(ext, scan.parameters().value(ext))) + } + dirin.setEntry(i, sqrt(2.0 * mfcn.errorDef() * seed.error().invHessian()[i, i])) + } + val mp = MinimumParameters(x, dirin, amin) + val st = MinimumState(mp, 0.0, mfcn.numOfCalls()) + val states: MutableList = java.util.ArrayList(1) + states.add(st) + return FunctionMinimum(seed, states, mfcn.errorDef()) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ScanMinimizer.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ScanMinimizer.kt new file mode 100644 index 0000000..e39a49c --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/ScanMinimizer.kt @@ -0,0 +1,36 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class ScanMinimizer : ModularFunctionMinimizer() { + private val theBuilder: ScanBuilder + private val theSeedGenerator: SimplexSeedGenerator = SimplexSeedGenerator() + override fun builder(): MinimumBuilder { + return theBuilder + } + + override fun seedGenerator(): MinimumSeedGenerator { + return theSeedGenerator + } + + init { + theBuilder = ScanBuilder() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexBuilder.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexBuilder.kt new file mode 100644 index 0000000..8627335 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexBuilder.kt @@ -0,0 +1,179 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class SimplexBuilder : MinimumBuilder { + /** {@inheritDoc} */ + fun minimum( + mfcn: MnFcn, + gc: GradientCalculator?, + seed: MinimumSeed, + strategy: MnStrategy?, + maxfcn: Int, + minedm: Double + ): FunctionMinimum { + val prec: MnMachinePrecision = seed.precision() + val x: RealVector = seed.parameters().vec().copy() + val step: RealVector = MnUtils.mul(seed.gradient().getStep(), 10.0) + val n: Int = x.getDimension() + val wg = 1.0 / n + val alpha = 1.0 + val beta = 0.5 + val gamma = 2.0 + val rhomin = 4.0 + val rhomax = 8.0 + val rho1 = 1.0 + alpha + val rho2 = 1.0 + alpha * gamma + val simpl: MutableList> = java.util.ArrayList>(n + 1) + simpl.add(Pair(seed.fval(), x.copy())) + var jl = 0 + var jh = 0 + var amin: Double = seed.fval() + var aming: Double = seed.fval() + for (i in 0 until n) { + val dmin: Double = 8.0 * prec.eps2() * (abs(x.getEntry(i)) + prec.eps2()) + if (step.getEntry(i) < dmin) { + step.setEntry(i, dmin) + } + x.setEntry(i, x.getEntry(i) + step.getEntry(i)) + val tmp: Double = mfcn.value(x) + if (tmp < amin) { + amin = tmp + jl = i + 1 + } + if (tmp > aming) { + aming = tmp + jh = i + 1 + } + simpl.add(Pair(tmp, x.copy())) + x.setEntry(i, x.getEntry(i) - step.getEntry(i)) + } + val simplex = SimplexParameters(simpl, jh, jl) + do { + amin = simplex[jl].getFirst() + jl = simplex.jl() + jh = simplex.jh() + var pbar: RealVector = ArrayRealVector(n) + for (i in 0 until n + 1) { + if (i == jh) { + continue + } + pbar = MnUtils.add(pbar, MnUtils.mul(simplex[i].getSecond(), wg)) + } + val pstar: RealVector = + MnUtils.sub(MnUtils.mul(pbar, 1.0 + alpha), MnUtils.mul(simplex[jh].getSecond(), alpha)) + val ystar: Double = mfcn.value(pstar) + if (ystar > amin) { + if (ystar < simplex[jh].getFirst()) { + simplex.update(ystar, pstar) + if (jh != simplex.jh()) { + continue + } + } + val pstst: RealVector = + MnUtils.add(MnUtils.mul(simplex[jh].getSecond(), beta), MnUtils.mul(pbar, 1.0 - beta)) + val ystst: Double = mfcn.value(pstst) + if (ystst > simplex[jh].getFirst()) { + break + } + simplex.update(ystst, pstst) + continue + } + var pstst: RealVector = MnUtils.add(MnUtils.mul(pstar, gamma), MnUtils.mul(pbar, 1.0 - gamma)) + var ystst: Double = mfcn.value(pstst) + val y1: Double = (ystar - simplex[jh].getFirst()) * rho2 + val y2: Double = (ystst - simplex[jh].getFirst()) * rho1 + var rho = 0.5 * (rho2 * y1 - rho1 * y2) / (y1 - y2) + if (rho < rhomin) { + if (ystst < simplex[jl].getFirst()) { + simplex.update(ystst, pstst) + } else { + simplex.update(ystar, pstar) + } + continue + } + if (rho > rhomax) { + rho = rhomax + } + val prho: RealVector = + MnUtils.add(MnUtils.mul(pbar, rho), MnUtils.mul(simplex[jh].getSecond(), 1.0 - rho)) + val yrho: Double = mfcn.value(prho) + if (yrho < simplex[jl].getFirst() && yrho < ystst) { + simplex.update(yrho, prho) + continue + } + if (ystst < simplex[jl].getFirst()) { + simplex.update(ystst, pstst) + continue + } + if (yrho > simplex[jl].getFirst()) { + if (ystst < simplex[jl].getFirst()) { + simplex.update(ystst, pstst) + } else { + simplex.update(ystar, pstar) + } + continue + } + if (ystar > simplex[jh].getFirst()) { + pstst = MnUtils.add(MnUtils.mul(simplex[jh].getSecond(), beta), MnUtils.mul(pbar, 1 - beta)) + ystst = mfcn.value(pstst) + if (ystst > simplex[jh].getFirst()) { + break + } + simplex.update(ystst, pstst) + } + } while (simplex.edm() > minedm && mfcn.numOfCalls() < maxfcn) + amin = simplex[jl].getFirst() + jl = simplex.jl() + jh = simplex.jh() + var pbar: RealVector = ArrayRealVector(n) + for (i in 0 until n + 1) { + if (i == jh) { + continue + } + pbar = MnUtils.add(pbar, MnUtils.mul(simplex[i].getSecond(), wg)) + } + var ybar: Double = mfcn.value(pbar) + if (ybar < amin) { + simplex.update(ybar, pbar) + } else { + pbar = simplex[jl].getSecond() + ybar = simplex[jl].getFirst() + } + var dirin: RealVector = simplex.dirin() + // scale to sigmas on parameters werr^2 = dirin^2 * (up/edm) + dirin = MnUtils.mul(dirin, sqrt(mfcn.errorDef() / simplex.edm())) + val st = MinimumState(MinimumParameters(pbar, dirin, ybar), simplex.edm(), mfcn.numOfCalls()) + val states: MutableList = java.util.ArrayList(1) + states.add(st) + if (mfcn.numOfCalls() > maxfcn) { + MINUITPlugin.logStatic("Simplex did not converge, #fcn calls exhausted.") + return FunctionMinimum(seed, states, mfcn.errorDef(), MnReachedCallLimit()) + } + if (simplex.edm() > minedm) { + MINUITPlugin.logStatic("Simplex did not converge, edm > minedm.") + return FunctionMinimum(seed, states, mfcn.errorDef(), MnAboveMaxEdm()) + } + return FunctionMinimum(seed, states, mfcn.errorDef()) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexMinimizer.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexMinimizer.kt new file mode 100644 index 0000000..f4bbcc3 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexMinimizer.kt @@ -0,0 +1,43 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class SimplexMinimizer : ModularFunctionMinimizer() { + private val theBuilder: SimplexBuilder + private val theSeedGenerator: SimplexSeedGenerator = SimplexSeedGenerator() + + /** {@inheritDoc} */ + override fun builder(): MinimumBuilder { + return theBuilder + } + + /** {@inheritDoc} */ + override fun seedGenerator(): MinimumSeedGenerator { + return theSeedGenerator + } + + /** + * + * Constructor for SimplexMinimizer. + */ + init { + theBuilder = SimplexBuilder() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexParameters.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexParameters.kt new file mode 100644 index 0000000..fef6e20 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexParameters.kt @@ -0,0 +1,85 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector + +/** + * + * @version $Id$ + */ +internal class SimplexParameters(simpl: MutableList>, jh: Int, jl: Int) { + private var theJHigh: Int + private var theJLow: Int + private val theSimplexParameters: MutableList> + fun dirin(): ArrayRealVector { + val dirin = ArrayRealVector(theSimplexParameters.size - 1) + for (i in 0 until theSimplexParameters.size - 1) { + var pbig: Double = theSimplexParameters[0].getSecond().getEntry(i) + var plit = pbig + for (theSimplexParameter in theSimplexParameters) { + if (theSimplexParameter.getSecond().getEntry(i) < plit) { + plit = theSimplexParameter.getSecond().getEntry(i) + } + if (theSimplexParameter.getSecond().getEntry(i) > pbig) { + pbig = theSimplexParameter.getSecond().getEntry(i) + } + } + dirin.setEntry(i, pbig - plit) + } + return dirin + } + + fun edm(): Double { + return theSimplexParameters[jh()].getFirst() - theSimplexParameters[jl()].getFirst() + } + + operator fun get(i: Int): Pair { + return theSimplexParameters[i] + } + + fun jh(): Int { + return theJHigh + } + + fun jl(): Int { + return theJLow + } + + fun simplex(): List> { + return theSimplexParameters + } + + fun update(y: Double, p: RealVector?) { + theSimplexParameters.set(jh(), Pair(y, p)) + if (y < theSimplexParameters[jl()].getFirst()) { + theJLow = jh() + } + var jh = 0 + for (i in 1 until theSimplexParameters.size) { + if (theSimplexParameters[i].getFirst() > theSimplexParameters[jh].getFirst()) { + jh = i + } + } + theJHigh = jh + } + + init { + theSimplexParameters = simpl + theJHigh = jh + theJLow = jl + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexSeedGenerator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexSeedGenerator.kt new file mode 100644 index 0000000..e5025ff --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SimplexSeedGenerator.kt @@ -0,0 +1,52 @@ +/* + * 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 ru.inr.mass.minuit + +import org.apache.commons.math3.linear.ArrayRealVector +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class SimplexSeedGenerator : MinimumSeedGenerator { + /** {@inheritDoc} */ + fun generate(fcn: MnFcn, gc: GradientCalculator?, st: MnUserParameterState, stra: MnStrategy): MinimumSeed { + val n: Int = st.variableParameters() + val prec: MnMachinePrecision = st.precision() + + // initial starting values + val x: RealVector = ArrayRealVector(n) + for (i in 0 until n) { + x.setEntry(i, st.intParameters()[i]) + } + val fcnmin: Double = fcn.value(x) + val pa = MinimumParameters(x, fcnmin) + val igc = InitialGradientCalculator(fcn, st.getTransformation(), stra) + val dgrad: FunctionGradient = igc.gradient(pa) + val mat = MnAlgebraicSymMatrix(n) + val dcovar = 1.0 + for (i in 0 until n) { + mat[i, i] = if (abs(dgrad.getGradientDerivative() + .getEntry(i)) > prec.eps2() + ) 1.0 / dgrad.getGradientDerivative().getEntry(i) else 1.0 + } + val err = MinimumError(mat, dcovar) + val edm: Double = VariableMetricEDMEstimator().estimate(dgrad, err) + val state = MinimumState(pa, err, dgrad, edm, fcn.numOfCalls()) + return MinimumSeed(state, st.getTransformation()) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SinParameterTransformation.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SinParameterTransformation.kt new file mode 100644 index 0000000..821adde --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SinParameterTransformation.kt @@ -0,0 +1,48 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class SinParameterTransformation { + fun dInt2Ext(value: Double, upper: Double, lower: Double): Double { + return 0.5 * abs((upper - lower) * cos(value)) + } + + fun ext2int(value: Double, upper: Double, lower: Double, prec: MnMachinePrecision): Double { + val piby2: Double = 2.0 * atan(1.0) + val distnn: Double = 8.0 * sqrt(prec.eps2()) + val vlimhi = piby2 - distnn + val vlimlo = -piby2 + distnn + val yy = 2.0 * (value - lower) / (upper - lower) - 1.0 + val yy2 = yy * yy + return if (yy2 > 1.0 - prec.eps2()) { + if (yy < 0.0) { + vlimlo + } else { + vlimhi + } + } else { + asin(yy) + } + } + + fun int2ext(value: Double, upper: Double, lower: Double): Double { + return lower + 0.5 * (upper - lower) * (sin(value) + 1.0) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SqrtLowParameterTransformation.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SqrtLowParameterTransformation.kt new file mode 100644 index 0000000..444b638 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SqrtLowParameterTransformation.kt @@ -0,0 +1,43 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class SqrtLowParameterTransformation { + // derivative of transformation from internal to external + fun dInt2Ext(value: Double, lower: Double): Double { + return value / sqrt(value * value + 1.0) + } + + // transformation from external to internal + fun ext2int(value: Double, lower: Double, prec: MnMachinePrecision): Double { + val yy = value - lower + 1.0 + val yy2 = yy * yy + return if (yy2 < 1.0 + prec.eps2()) { + 8 * sqrt(prec.eps2()) + } else { + sqrt(yy2 - 1) + } + } + + // transformation from internal to external + fun int2ext(value: Double, lower: Double): Double { + return lower - 1.0 + sqrt(value * value + 1.0) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SqrtUpParameterTransformation.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SqrtUpParameterTransformation.kt new file mode 100644 index 0000000..5774848 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/SqrtUpParameterTransformation.kt @@ -0,0 +1,43 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class SqrtUpParameterTransformation { + // derivative of transformation from internal to external + fun dInt2Ext(value: Double, upper: Double): Double { + return -value / sqrt(value * value + 1.0) + } + + // transformation from external to internal + fun ext2int(value: Double, upper: Double, prec: MnMachinePrecision): Double { + val yy = upper - value + 1.0 + val yy2 = yy * yy + return if (yy2 < 1.0 + prec.eps2()) { + 8 * sqrt(prec.eps2()) + } else { + sqrt(yy2 - 1) + } + } + + // transformation from internal to external + fun int2ext(value: Double, upper: Double): Double { + return upper + 1.0 - sqrt(value * value + 1.0) + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricBuilder.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricBuilder.kt new file mode 100644 index 0000000..b6bd530 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricBuilder.kt @@ -0,0 +1,138 @@ +/* + * 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 ru.inr.mass.minuit + +import hep.dataforge.stat.fit.MINUITPlugin +import ru.inr.mass.minuit.* +import ru.inr.mass.minuit.* + +/** + * + * @version $Id$ + */ +internal class VariableMetricBuilder : MinimumBuilder { + private val theErrorUpdator: DavidonErrorUpdator + private val theEstimator: VariableMetricEDMEstimator = VariableMetricEDMEstimator() + fun errorUpdator(): DavidonErrorUpdator { + return theErrorUpdator + } + + fun estimator(): VariableMetricEDMEstimator { + return theEstimator + } + + /** {@inheritDoc} */ + fun minimum( + fcn: MnFcn, + gc: GradientCalculator, + seed: MinimumSeed, + strategy: MnStrategy, + maxfcn: Int, + edmval: Double + ): FunctionMinimum { + val min: FunctionMinimum = minimum(fcn, gc, seed, maxfcn, edmval) + if (strategy.strategy() === 2 || strategy.strategy() === 1 && min.error().dcovar() > 0.05) { + val st: MinimumState = MnHesse(strategy).calculate(fcn, min.state(), min.seed().trafo(), 0) + min.add(st) + } + if (!min.isValid()) { + MINUITPlugin.logStatic("FunctionMinimum is invalid.") + } + return min + } + + fun minimum(fcn: MnFcn, gc: GradientCalculator, seed: MinimumSeed, maxfcn: Int, edmval: Double): FunctionMinimum { + var edmval = edmval + edmval *= 0.0001 + if (seed.parameters().vec().getDimension() === 0) { + return FunctionMinimum(seed, fcn.errorDef()) + } + val prec: MnMachinePrecision = seed.precision() + val result: MutableList = java.util.ArrayList(8) + var edm: Double = seed.state().edm() + if (edm < 0.0) { + MINUITPlugin.logStatic("VariableMetricBuilder: initial matrix not pos.def.") + if (seed.error().isPosDef()) { + throw RuntimeException("Something is wrong!") + } + return FunctionMinimum(seed, fcn.errorDef()) + } + result.add(seed.state()) + + // iterate until edm is small enough or max # of iterations reached + edm *= 1.0 + 3.0 * seed.error().dcovar() + var step: RealVector // = new ArrayRealVector(seed.gradient().getGradient().getDimension()); + do { + var s0: MinimumState = result[result.size - 1] + step = MnUtils.mul(MnUtils.mul(s0.error().invHessian(), s0.gradient().getGradient()), -1) + var gdel: Double = MnUtils.innerProduct(step, s0.gradient().getGradient()) + if (gdel > 0.0) { + MINUITPlugin.logStatic("VariableMetricBuilder: matrix not pos.def.") + MINUITPlugin.logStatic("gdel > 0: $gdel") + s0 = MnPosDef.test(s0, prec) + step = MnUtils.mul(MnUtils.mul(s0.error().invHessian(), s0.gradient().getGradient()), -1) + gdel = MnUtils.innerProduct(step, s0.gradient().getGradient()) + MINUITPlugin.logStatic("gdel: $gdel") + if (gdel > 0.0) { + result.add(s0) + return FunctionMinimum(seed, result, fcn.errorDef()) + } + } + val pp: MnParabolaPoint = MnLineSearch.search(fcn, s0.parameters(), step, gdel, prec) + if (abs(pp.y() - s0.fval()) < prec.eps()) { + MINUITPlugin.logStatic("VariableMetricBuilder: no improvement") + break //no improvement + } + val p = MinimumParameters(MnUtils.add(s0.vec(), MnUtils.mul(step, pp.x())), pp.y()) + val g: FunctionGradient = gc.gradient(p, s0.gradient()) + edm = estimator().estimate(g, s0.error()) + if (edm < 0.0) { + MINUITPlugin.logStatic("VariableMetricBuilder: matrix not pos.def.") + MINUITPlugin.logStatic("edm < 0") + s0 = MnPosDef.test(s0, prec) + edm = estimator().estimate(g, s0.error()) + if (edm < 0.0) { + result.add(s0) + return FunctionMinimum(seed, result, fcn.errorDef()) + } + } + val e: MinimumError = errorUpdator().update(s0, p, g) + result.add(MinimumState(p, e, g, edm, fcn.numOfCalls())) + // result[0] = MinimumState(p, e, g, edm, fcn.numOfCalls()); + edm *= 1.0 + 3.0 * e.dcovar() + } while (edm > edmval && fcn.numOfCalls() < maxfcn) + if (fcn.numOfCalls() >= maxfcn) { + MINUITPlugin.logStatic("VariableMetricBuilder: call limit exceeded.") + return FunctionMinimum(seed, result, fcn.errorDef(), MnReachedCallLimit()) + } + return if (edm > edmval) { + if (edm < abs(prec.eps2() * result[result.size - 1].fval())) { + MINUITPlugin.logStatic("VariableMetricBuilder: machine accuracy limits further improvement.") + FunctionMinimum(seed, result, fcn.errorDef()) + } else if (edm < 10.0 * edmval) { + FunctionMinimum(seed, result, fcn.errorDef()) + } else { + MINUITPlugin.logStatic("VariableMetricBuilder: finishes without convergence.") + MINUITPlugin.logStatic("VariableMetricBuilder: edm= $edm requested: $edmval") + FunctionMinimum(seed, result, fcn.errorDef(), MnAboveMaxEdm()) + } + } else FunctionMinimum(seed, result, fcn.errorDef()) + } + + init { + theErrorUpdator = DavidonErrorUpdator() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricEDMEstimator.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricEDMEstimator.kt new file mode 100644 index 0000000..8fca4e6 --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricEDMEstimator.kt @@ -0,0 +1,31 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @author tonyj + * @version $Id$ + */ +internal class VariableMetricEDMEstimator { + fun estimate(g: FunctionGradient, e: MinimumError): Double { + if (e.invHessian().size() === 1) { + return 0.5 * g.getGradient().getEntry(0) * g.getGradient().getEntry(0) * e.invHessian()[0, 0] + } + val rho: Double = MnUtils.similarity(g.getGradient(), e.invHessian()) + return 0.5 * rho + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricMinimizer.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricMinimizer.kt new file mode 100644 index 0000000..2a13a5f --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/VariableMetricMinimizer.kt @@ -0,0 +1,43 @@ +/* + * 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 ru.inr.mass.minuit + +/** + * + * @version $Id$ + */ +internal class VariableMetricMinimizer : ModularFunctionMinimizer() { + private val theMinBuilder: VariableMetricBuilder + private val theMinSeedGen: MnSeedGenerator = MnSeedGenerator() + + /** {@inheritDoc} */ + override fun builder(): MinimumBuilder { + return theMinBuilder + } + + /** {@inheritDoc} */ + override fun seedGenerator(): MinimumSeedGenerator { + return theMinSeedGen + } + + /** + * + * Constructor for VariableMetricMinimizer. + */ + init { + theMinBuilder = VariableMetricBuilder() + } +} \ No newline at end of file diff --git a/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/package-info.kt b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/package-info.kt new file mode 100644 index 0000000..22779da --- /dev/null +++ b/numass-model/src/commonMain/kotlin/ru/inr/mass/minuit/package-info.kt @@ -0,0 +1,17 @@ +/* + * 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 ru.inr.mass.minuit + diff --git a/numass-model/src/jvmMain/kotlin/ru/inr/mass/maths/functionCaching.kt b/numass-model/src/jvmMain/kotlin/ru/inr/mass/maths/functionCaching.kt new file mode 100644 index 0000000..505e341 --- /dev/null +++ b/numass-model/src/jvmMain/kotlin/ru/inr/mass/maths/functionCaching.kt @@ -0,0 +1,17 @@ +package ru.inr.mass.maths + +import org.apache.commons.math3.analysis.UnivariateFunction +import org.apache.commons.math3.analysis.interpolation.SplineInterpolator +import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction +import kotlin.math.abs + +public fun UnivariateFunction.cache( + range: ClosedFloatingPointRange, + numCachePoints: Int, +): PolynomialSplineFunction? { + val length = abs(range.endInclusive - range.start) + val grid: DoubleArray = DoubleArray(numCachePoints) { range.start + length / (numCachePoints - 1) * it } + val vals = DoubleArray(grid.size) { value(grid[it]) } + val interpolator = SplineInterpolator() + return interpolator.interpolate(grid, vals) +} \ No newline at end of file diff --git a/numass-workspace/src/main/kotlin/ru/inr/mass/workspace/amplitudeSpectrum.kt b/numass-workspace/src/main/kotlin/ru/inr/mass/workspace/amplitudeSpectrum.kt index 7714b60..e67e241 100644 --- a/numass-workspace/src/main/kotlin/ru/inr/mass/workspace/amplitudeSpectrum.kt +++ b/numass-workspace/src/main/kotlin/ru/inr/mass/workspace/amplitudeSpectrum.kt @@ -8,7 +8,7 @@ import space.kscience.dataforge.context.warn import space.kscience.kmath.histogram.UnivariateHistogram import space.kscience.kmath.histogram.center import space.kscience.kmath.histogram.put -import space.kscience.kmath.structures.RealBuffer +import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.asBuffer @@ -21,8 +21,8 @@ fun NumassPoint.spectrum(): UnivariateHistogram = UnivariateHistogram.uniform(1. } } -operator fun UnivariateHistogram.component1(): RealBuffer = bins.map { it.domain.center }.toDoubleArray().asBuffer() -operator fun UnivariateHistogram.component2(): RealBuffer = bins.map { it.value }.toDoubleArray().asBuffer() +operator fun UnivariateHistogram.component1(): DoubleBuffer = bins.map { it.domain.center }.toDoubleArray().asBuffer() +operator fun UnivariateHistogram.component2(): DoubleBuffer = bins.map { it.value }.toDoubleArray().asBuffer() fun Collection.spectrum(): UnivariateHistogram { if (distinctBy { it.voltage }.size != 1) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 4568435..d118ef4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,22 +1,19 @@ pluginManagement { repositories { maven("https://repo.kotlin.link") - gradlePluginPortal() mavenCentral() jcenter() - maven("https://dl.bintray.com/kotlin/kotlin-eap") - maven("https://dl.bintray.com/kotlin/kotlin-dev") + gradlePluginPortal() } - val toolsVersion = "0.9.2" - val kotlinVersion = "1.4.31" + val toolsVersion = "0.9.3" + val kotlinVersion = "1.4.32" plugins { id("ru.mipt.npm.gradle.project") version toolsVersion id("ru.mipt.npm.gradle.mpp") version toolsVersion id("ru.mipt.npm.gradle.jvm") version toolsVersion id("ru.mipt.npm.gradle.js") version toolsVersion - id("ru.mipt.npm.gradle.publish") version toolsVersion kotlin("jvm") version kotlinVersion kotlin("js") version kotlinVersion } @@ -35,3 +32,4 @@ pluginManagement { include("numass-data-model") include("numass-data-proto") include("numass-workspace") +include("numass-model")