From 257337f4fb2a7681ff769b202dd4f16ebb8e0725 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 25 Apr 2021 22:34:59 +0300 Subject: [PATCH 01/13] [WIP] Refactor optimization --- .../kscience/kmath/functions/integrate.kt | 4 +- .../kmath/functions/matrixIntegration.kt | 4 +- .../kmath/commons/integration/CMIntegrator.kt | 13 +- .../integration/GaussRuleIntegrator.kt | 4 +- .../commons/optimization/CMOptimization.kt | 145 ++-- .../kmath/commons/optimization/cmFit.kt | 20 +- .../commons/optimization/OptimizeTest.kt | 4 +- .../kmath/data/XYErrorColumnarData.kt | 33 + .../kscience/kmath/data/XYZColumnarData.kt | 2 +- .../expressions/DifferentiableExpression.kt | 2 +- .../kmath/expressions/SymbolIndexer.kt | 10 + .../space/kscience/kmath/linear/symmetric.kt | 34 + .../space/kscience/kmath/misc/Featured.kt | 37 + .../kmath/structures/BufferAccessor2D.kt | 2 +- .../kmath/structures/DoubleBufferField.kt | 10 +- .../space/kscience/kmath/real/RealVector.kt | 11 +- .../kmath/integration/GaussIntegrator.kt | 12 +- .../kscience/kmath/integration/Integrand.kt | 7 +- .../kscience/kmath/integration/Integrator.kt | 2 +- .../integration/MultivariateIntegrand.kt | 18 +- .../kmath/integration/UnivariateIntegrand.kt | 24 +- .../kmath/integration/GaussIntegralTest.kt | 4 +- .../optimization/FunctionOptimization.kt | 163 ++-- .../NoDerivFunctionOptimization.kt | 74 -- .../kmath/optimization/Optimization.kt | 49 -- .../kmath/optimization/OptimizationProblem.kt | 46 ++ .../kscience/kmath/optimization/XYFit.kt | 2 +- .../minuit/AnalyticalGradientCalculator.kt | 61 ++ .../optimization/minuit/CombinedMinimizer.kt | 32 + .../minuit/CombinedMinimumBuilder.kt | 57 ++ .../optimization/minuit/ContoursError.kt | 150 ++++ .../minuit/DavidonErrorUpdator.kt | 45 ++ .../optimization/minuit/FunctionGradient.kt | 72 ++ .../optimization/minuit/FunctionMinimum.kt | 259 ++++++ .../optimization/minuit/GradientCalculator.kt | 41 + .../minuit/HessianGradientCalculator.kt | 137 ++++ .../minuit/InitialGradientCalculator.kt | 116 +++ .../kmath/optimization/minuit/MINOSResult.kt | 70 ++ .../kmath/optimization/minuit/MINUITFitter.kt | 205 +++++ .../kmath/optimization/minuit/MINUITPlugin.kt | 86 ++ .../kmath/optimization/minuit/MINUITUtils.kt | 121 +++ .../optimization/minuit/MinimumBuilder.kt | 43 + .../kmath/optimization/minuit/MinimumError.kt | 155 ++++ .../minuit/MinimumErrorUpdator.kt | 33 + .../optimization/minuit/MinimumParameters.kt | 70 ++ .../kmath/optimization/minuit/MinimumSeed.kt | 64 ++ .../minuit/MinimumSeedGenerator.kt | 37 + .../kmath/optimization/minuit/MinimumState.kt | 104 +++ .../kmath/optimization/minuit/MinosError.kt | 219 +++++ .../optimization/minuit/MinuitParameter.kt | 314 ++++++++ .../minuit/MnAlgebraicSymMatrix.kt | 458 +++++++++++ .../optimization/minuit/MnApplication.kt | 554 +++++++++++++ .../kmath/optimization/minuit/MnContours.kt | 283 +++++++ .../minuit/MnCovarianceSqueeze.kt | 113 +++ .../kmath/optimization/minuit/MnCross.kt | 99 +++ .../kmath/optimization/minuit/MnEigen.kt | 50 ++ .../kmath/optimization/minuit/MnFcn.kt | 50 ++ .../optimization/minuit/MnFunctionCross.kt | 369 +++++++++ .../minuit/MnGlobalCorrelationCoeff.kt | 79 ++ .../kmath/optimization/minuit/MnHesse.kt | 371 +++++++++ .../kmath/optimization/minuit/MnLineSearch.kt | 204 +++++ .../optimization/minuit/MnMachinePrecision.kt | 71 ++ .../kmath/optimization/minuit/MnMigrad.kt | 136 ++++ .../kmath/optimization/minuit/MnMinimize.kt | 133 +++ .../kmath/optimization/minuit/MnMinos.kt | 379 +++++++++ .../kmath/optimization/minuit/MnParabola.kt | 55 ++ .../optimization/minuit/MnParabolaFactory.kt | 58 ++ .../optimization/minuit/MnParabolaPoint.kt | 30 + .../optimization/minuit/MnParameterScan.kt | 113 +++ .../kmath/optimization/minuit/MnPlot.kt | 438 ++++++++++ .../kmath/optimization/minuit/MnPosDef.kt | 89 +++ .../kmath/optimization/minuit/MnPrint.kt | 387 +++++++++ .../kmath/optimization/minuit/MnScan.kt | 181 +++++ .../optimization/minuit/MnSeedGenerator.kt | 107 +++ .../kmath/optimization/minuit/MnSimplex.kt | 138 ++++ .../kmath/optimization/minuit/MnStrategy.kt | 310 +++++++ .../optimization/minuit/MnUserCovariance.kt | 147 ++++ .../kmath/optimization/minuit/MnUserFcn.kt | 30 + .../minuit/MnUserParameterState.kt | 756 ++++++++++++++++++ .../optimization/minuit/MnUserParameters.kt | 402 ++++++++++ .../minuit/MnUserTransformation.kt | 390 +++++++++ .../kmath/optimization/minuit/MnUtils.kt | 147 ++++ .../minuit/ModularFunctionMinimizer.kt | 72 ++ .../minuit/NegativeG2LineSearch.kt | 80 ++ .../minuit/Numerical2PGradientCalculator.kt | 122 +++ .../kmath/optimization/minuit/Range.kt | 32 + .../kmath/optimization/minuit/ScanBuilder.kt | 58 ++ .../optimization/minuit/ScanMinimizer.kt | 36 + .../optimization/minuit/SimplexBuilder.kt | 179 +++++ .../optimization/minuit/SimplexMinimizer.kt | 43 + .../optimization/minuit/SimplexParameters.kt | 85 ++ .../minuit/SimplexSeedGenerator.kt | 52 ++ .../minuit/SinParameterTransformation.kt | 48 ++ .../minuit/SqrtLowParameterTransformation.kt | 43 + .../minuit/SqrtUpParameterTransformation.kt | 43 + .../minuit/VariableMetricBuilder.kt | 137 ++++ .../minuit/VariableMetricEDMEstimator.kt | 31 + .../minuit/VariableMetricMinimizer.kt | 43 + .../kmath/optimization/minuit/package-info.kt | 17 + .../kscience/kmath/optimization/qow/QowFit.kt | 380 +++++++++ 100 files changed, 11509 insertions(+), 346 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt create mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt create mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt delete mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/NoDerivFunctionOptimization.kt delete mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimization.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/AnalyticalGradientCalculator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimizer.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimumBuilder.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ContoursError.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/DavidonErrorUpdator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionGradient.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionMinimum.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/GradientCalculator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/HessianGradientCalculator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/InitialGradientCalculator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINOSResult.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITFitter.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITPlugin.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITUtils.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumBuilder.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumError.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumErrorUpdator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumParameters.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeed.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeedGenerator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumState.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinosError.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinuitParameter.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnAlgebraicSymMatrix.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnApplication.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnContours.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCovarianceSqueeze.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCross.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnEigen.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFcn.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFunctionCross.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnGlobalCorrelationCoeff.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnHesse.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnLineSearch.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMachinePrecision.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMigrad.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinimize.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinos.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabola.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaFactory.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaPoint.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParameterScan.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPlot.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPosDef.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPrint.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnScan.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSimplex.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnStrategy.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserCovariance.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserFcn.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameterState.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameters.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserTransformation.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUtils.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ModularFunctionMinimizer.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/NegativeG2LineSearch.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Numerical2PGradientCalculator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanBuilder.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanMinimizer.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexBuilder.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexMinimizer.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexParameters.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexSeedGenerator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SinParameterTransformation.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtLowParameterTransformation.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtUpParameterTransformation.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricEDMEstimator.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricMinimizer.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/package-info.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt index 6990e8c8f..7cdf7bef6 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/integrate.kt @@ -5,7 +5,7 @@ package space.kscience.kmath.functions -import space.kscience.kmath.integration.integrate +import space.kscience.kmath.integration.process import space.kscience.kmath.integration.value import space.kscience.kmath.operations.DoubleField import kotlin.math.pow @@ -15,7 +15,7 @@ fun main() { val function: UnivariateFunction = { x -> 3 * x.pow(2) + 2 * x + 1 } //get the result of the integration - val result = DoubleField.integrate(0.0..10.0, function = function) + val result = DoubleField.process(0.0..10.0, function = function) //the value is nullable because in some cases the integration could not succeed println(result.value) diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt index 8020df8f6..206ba3054 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt @@ -5,7 +5,7 @@ package space.kscience.kmath.functions -import space.kscience.kmath.integration.integrate +import space.kscience.kmath.integration.process import space.kscience.kmath.integration.value import space.kscience.kmath.nd.StructureND import space.kscience.kmath.nd.nd @@ -24,7 +24,7 @@ fun main(): Unit = DoubleField { val function: (Double) -> StructureND = { x: Double -> 3 * number(x).pow(2) + 2 * diagonal(x) + 1 } //get the result of the integration - val result = integrate(0.0..10.0, function = function) + val result = process(0.0..10.0, function = function) //the value is nullable because in some cases the integration could not succeed println(result.value) diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt index 92bf86128..9da35a7c0 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt @@ -24,7 +24,7 @@ public class CMIntegrator( public class MinIterations(public val value: Int) : IntegrandFeature public class MaxIterations(public val value: Int) : IntegrandFeature - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val integrator = integratorBuilder(integrand) val maxCalls = integrand.getFeature()?.maxCalls ?: defaultMaxCalls val remainingCalls = maxCalls - integrand.calls @@ -32,11 +32,12 @@ public class CMIntegrator( ?: error("Integration range is not provided") val res = integrator.integrate(remainingCalls, integrand.function, range.start, range.endInclusive) - return integrand + - IntegrandValue(res) + - IntegrandAbsoluteAccuracy(integrator.absoluteAccuracy) + - IntegrandRelativeAccuracy(integrator.relativeAccuracy) + - IntegrandCallsPerformed(integrator.evaluations + integrand.calls) + return integrand.with( + IntegrandValue(res), + IntegrandAbsoluteAccuracy(integrator.absoluteAccuracy), + IntegrandRelativeAccuracy(integrator.relativeAccuracy), + IntegrandCallsPerformed(integrator.evaluations + integrand.calls) + ) } diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt index 1c9915563..1361b1079 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/GaussRuleIntegrator.kt @@ -16,7 +16,7 @@ public class GaussRuleIntegrator( private var type: GaussRule = GaussRule.LEGANDRE, ) : UnivariateIntegrator { - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val range = integrand.getFeature()?.range ?: error("Integration range is not provided") val integrator: GaussIntegrator = getIntegrator(range) @@ -76,7 +76,7 @@ public class GaussRuleIntegrator( numPoints: Int = 100, type: GaussRule = GaussRule.LEGANDRE, function: (Double) -> Double, - ): Double = GaussRuleIntegrator(numPoints, type).integrate( + ): Double = GaussRuleIntegrator(numPoints, type).process( UnivariateIntegrand(function, IntegrationRange(range)) ).value!! } diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt index cfb8c39be..db9ba6f21 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt @@ -14,10 +14,7 @@ import org.apache.commons.math3.optim.nonlinear.scalar.gradient.NonLinearConjuga import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.AbstractSimplex import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.NelderMeadSimplex import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.expressions.SymbolIndexer -import space.kscience.kmath.expressions.derivative +import space.kscience.kmath.expressions.* import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.optimization.* @@ -26,94 +23,98 @@ import kotlin.reflect.KClass public operator fun PointValuePair.component1(): DoubleArray = point public operator fun PointValuePair.component2(): Double = value +public class CMOptimizerFactory(public val optimizerBuilder: () -> MultivariateOptimizer) : OptimizationFeature +//public class CMOptimizerData(public val ) + @OptIn(UnstableKMathAPI::class) -public class CMOptimization( - override val symbols: List, -) : FunctionOptimization, NoDerivFunctionOptimization, SymbolIndexer, OptimizationFeature { +public class CMOptimization : Optimizer> { - private val optimizationData: HashMap, OptimizationData> = HashMap() - private var optimizerBuilder: (() -> MultivariateOptimizer)? = null - public var convergenceChecker: ConvergenceChecker = SimpleValueChecker( - DEFAULT_RELATIVE_TOLERANCE, - DEFAULT_ABSOLUTE_TOLERANCE, - DEFAULT_MAX_ITER - ) + override suspend fun process( + problem: FunctionOptimization + ): FunctionOptimization = withSymbols(problem.parameters){ + val cmOptimizer: MultivariateOptimizer = + problem.getFeature()?.optimizerBuilder?.invoke() ?: SimplexOptimizer() - override var maximize: Boolean - get() = optimizationData[GoalType::class] == GoalType.MAXIMIZE - set(value) { - optimizationData[GoalType::class] = if (value) GoalType.MAXIMIZE else GoalType.MINIMIZE + val convergenceChecker: ConvergenceChecker = SimpleValueChecker( + DEFAULT_RELATIVE_TOLERANCE, + DEFAULT_ABSOLUTE_TOLERANCE, + DEFAULT_MAX_ITER + ) + + val optimizationData: HashMap, OptimizationData> = HashMap() + + fun addOptimizationData(data: OptimizationData) { + optimizationData[data::class] = data } - - public fun addOptimizationData(data: OptimizationData) { - optimizationData[data::class] = data - } - - init { addOptimizationData(MaxEval.unlimited()) - } + addOptimizationData(InitialGuess(problem.initialGuess.toDoubleArray())) - public fun exportOptimizationData(): List = optimizationData.values.toList() + fun exportOptimizationData(): List = optimizationData.values.toList() - public override fun initialGuess(map: Map): Unit { - addOptimizationData(InitialGuess(map.toDoubleArray())) - } - public override fun function(expression: Expression): Unit { - val objectiveFunction = ObjectiveFunction { - val args = it.toMap() - expression(args) + /** + * Register no-deriv function instead of differentiable function + */ + /** + * Register no-deriv function instead of differentiable function + */ + fun noDerivFunction(expression: Expression): Unit { + val objectiveFunction = ObjectiveFunction { + val args = problem.initialGuess + it.toMap() + expression(args) + } + addOptimizationData(objectiveFunction) } - addOptimizationData(objectiveFunction) - } - public override fun diffFunction(expression: DifferentiableExpression>) { - function(expression) - val gradientFunction = ObjectiveFunctionGradient { - val args = it.toMap() - DoubleArray(symbols.size) { index -> - expression.derivative(symbols[index])(args) + public override fun function(expression: DifferentiableExpression>) { + noDerivFunction(expression) + val gradientFunction = ObjectiveFunctionGradient { + val args = startingPoint + it.toMap() + DoubleArray(symbols.size) { index -> + expression.derivative(symbols[index])(args) + } + } + addOptimizationData(gradientFunction) + if (optimizerBuilder == null) { + optimizerBuilder = { + NonLinearConjugateGradientOptimizer( + NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, + convergenceChecker + ) + } } } - addOptimizationData(gradientFunction) - if (optimizerBuilder == null) { - optimizerBuilder = { - NonLinearConjugateGradientOptimizer( - NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, - convergenceChecker - ) + + public fun simplex(simplex: AbstractSimplex) { + addOptimizationData(simplex) + //Set optimization builder to simplex if it is not present + if (optimizerBuilder == null) { + optimizerBuilder = { SimplexOptimizer(convergenceChecker) } } } - } - public fun simplex(simplex: AbstractSimplex) { - addOptimizationData(simplex) - //Set optimization builder to simplex if it is not present - if (optimizerBuilder == null) { - optimizerBuilder = { SimplexOptimizer(convergenceChecker) } + public fun simplexSteps(steps: Map) { + simplex(NelderMeadSimplex(steps.toDoubleArray())) } - } - public fun simplexSteps(steps: Map) { - simplex(NelderMeadSimplex(steps.toDoubleArray())) - } + public fun goal(goalType: GoalType) { + addOptimizationData(goalType) + } - public fun goal(goalType: GoalType) { - addOptimizationData(goalType) - } + public fun optimizer(block: () -> MultivariateOptimizer) { + optimizerBuilder = block + } - public fun optimizer(block: () -> MultivariateOptimizer) { - optimizerBuilder = block - } + override fun update(result: OptimizationResult) { + initialGuess(result.point) + } - override fun update(result: OptimizationResult) { - initialGuess(result.point) - } - - override fun optimize(): OptimizationResult { - val optimizer = optimizerBuilder?.invoke() ?: error("Optimizer not defined") - val (point, value) = optimizer.optimize(*optimizationData.values.toTypedArray()) - return OptimizationResult(point.toMap(), value, setOf(this)) + override suspend fun optimize(): OptimizationResult { + val optimizer = optimizerBuilder?.invoke() ?: error("Optimizer not defined") + val (point, value) = optimizer.optimize(*optimizationData.values.toTypedArray()) + return OptimizationResult(point.toMap(), value) + } + return@withSymbols TODO() } public companion object : OptimizationProblemFactory { diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt index 13b5c73f4..12d924063 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt @@ -10,10 +10,7 @@ import space.kscience.kmath.commons.expressions.DerivativeStructureField import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.Expression import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.optimization.FunctionOptimization -import space.kscience.kmath.optimization.OptimizationResult -import space.kscience.kmath.optimization.noDerivOptimizeWith -import space.kscience.kmath.optimization.optimizeWith +import space.kscience.kmath.optimization.* import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.asBuffer @@ -46,20 +43,25 @@ public fun FunctionOptimization.Companion.chiSquared( /** * Optimize expression without derivatives */ -public fun Expression.optimize( +public suspend fun Expression.optimize( vararg symbols: Symbol, configuration: CMOptimization.() -> Unit, -): OptimizationResult = noDerivOptimizeWith(CMOptimization, symbols = symbols, configuration) +): OptimizationResult { + require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } + val problem = CMOptimization(symbols.toList(), configuration) + problem.noDerivFunction(this) + return problem.optimize() +} /** * Optimize differentiable expression */ -public fun DifferentiableExpression>.optimize( +public suspend fun DifferentiableExpression>.optimize( vararg symbols: Symbol, configuration: CMOptimization.() -> Unit, ): OptimizationResult = optimizeWith(CMOptimization, symbols = symbols, configuration) -public fun DifferentiableExpression>.minimize( +public suspend fun DifferentiableExpression>.minimize( vararg startPoint: Pair, configuration: CMOptimization.() -> Unit = {}, ): OptimizationResult { @@ -67,7 +69,7 @@ public fun DifferentiableExpression>.minimize( return optimize(*symbols){ maximize = false initialGuess(startPoint.toMap()) - diffFunction(this@minimize) + function(this@minimize) configuration() } } \ No newline at end of file diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt index 716cd1b0f..9b92eaac5 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt @@ -24,7 +24,7 @@ internal class OptimizeTest { } @Test - fun testGradientOptimization() { + fun testGradientOptimization() = runBlocking{ val result = normal.optimize(x, y) { initialGuess(x to 1.0, y to 1.0) //no need to select optimizer. Gradient optimizer is used by default because gradients are provided by function @@ -34,7 +34,7 @@ internal class OptimizeTest { } @Test - fun testSimplexOptimization() { + fun testSimplexOptimization() = runBlocking{ val result = normal.optimize(x, y) { initialGuess(x to 1.0, y to 1.0) simplexSteps(x to 2.0, y to 0.5) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt new file mode 100644 index 000000000..ea98e88b1 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.data + +import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.misc.Symbol.Companion.z +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.misc.symbol +import space.kscience.kmath.structures.Buffer + + +/** + * A [ColumnarData] with additional [Companion.yErr] column for an [Symbol.y] error + * Inherits [XYColumnarData]. + */ +@UnstableKMathAPI +public interface XYErrorColumnarData : XYColumnarData { + public val yErr: Buffer + + override fun get(symbol: Symbol): Buffer = when (symbol) { + Symbol.x -> x + Symbol.y -> y + Companion.yErr -> yErr + else -> error("A column for symbol $symbol not found") + } + + public companion object{ + public val yErr: Symbol by symbol + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt index d76a44e9e..2ae7233ec 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYZColumnarData.kt @@ -10,7 +10,7 @@ import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer /** - * A [XYColumnarData] with guaranteed [x], [y] and [z] columns designated by corresponding symbols. + * A [ColumnarData] with guaranteed [x], [y] and [z] columns designated by corresponding symbols. * Inherits [XYColumnarData]. */ @UnstableKMathAPI diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt index dbc1431b3..f2346f483 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt @@ -51,6 +51,6 @@ public abstract class FirstDerivativeExpression> : Differen /** * A factory that converts an expression in autodiff variables to a [DifferentiableExpression] */ -public fun interface AutoDiffProcessor, out R : Expression> { +public fun interface AutoDiffProcessor, out R : Expression> { public fun process(function: A.() -> I): DifferentiableExpression } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt index 738156975..ea72c5b9e 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/SymbolIndexer.kt @@ -10,6 +10,7 @@ import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.Structure2D import space.kscience.kmath.structures.BufferFactory +import space.kscience.kmath.structures.DoubleBuffer import kotlin.jvm.JvmInline /** @@ -46,6 +47,11 @@ public interface SymbolIndexer { return symbols.indices.associate { symbols[it] to get(it) } } + public fun Point.toMap(): Map { + require(size == symbols.size) { "The input array size for indexer should be ${symbols.size} but $size found" } + return symbols.indices.associate { symbols[it] to get(it) } + } + public operator fun Structure2D.get(rowSymbol: Symbol, columnSymbol: Symbol): T = get(indexOf(rowSymbol), indexOf(columnSymbol)) @@ -55,6 +61,10 @@ public interface SymbolIndexer { public fun Map.toPoint(bufferFactory: BufferFactory): Point = bufferFactory(symbols.size) { getValue(symbols[it]) } + public fun Map.toPoint(): DoubleBuffer = + DoubleBuffer(symbols.size) { getValue(symbols[it]) } + + public fun Map.toDoubleArray(): DoubleArray = DoubleArray(symbols.size) { getValue(symbols[it]) } } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt new file mode 100644 index 000000000..04d9a9897 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.linear + +import space.kscience.kmath.structures.BufferAccessor2D +import space.kscience.kmath.structures.MutableBuffer + +public object SymmetricMatrixFeature : MatrixFeature + +/** + * Naive implementation of a symmetric matrix builder, that adds a [SymmetricMatrixFeature] tag. The resulting matrix contains + * full `size^2` number of elements, but caches elements during calls to save [builder] calls. [builder] is always called in the + * upper triangle region meaning that `i <= j` + */ +public fun > LS.buildSymmetricMatrix( + size: Int, + builder: (i: Int, j: Int) -> T, +): Matrix = BufferAccessor2D(size, size, MutableBuffer.Companion::boxing).run { + val cache = factory(size * size) { null } + buildMatrix(size, size) { i, j -> + val cached = cache[i, j] + if (cached == null) { + val value = if (i <= j) builder(i, j) else builder(j, i) + cache[i, j] = value + cache[j, i] = value + value + } else { + cached + } + } + SymmetricMatrixFeature +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt new file mode 100644 index 000000000..157ff980b --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.misc + +import kotlin.reflect.KClass + +/** + * A entity that contains a set of features defined by their types + */ +public interface Featured { + public fun getFeature(type: KClass): T? +} + +/** + * A container for a set of features + */ +public class FeatureSet private constructor(public val features: Map, Any>) : Featured { + @Suppress("UNCHECKED_CAST") + override fun getFeature(type: KClass): T? = features[type] as? T + + public inline fun getFeature(): T? = getFeature(T::class) + + public fun with(feature: T, type: KClass = feature::class): FeatureSet = + FeatureSet(features + (type to feature)) + + public fun with(other: FeatureSet): FeatureSet = FeatureSet(features + other.features) + + public fun with(vararg otherFeatures: F): FeatureSet = + FeatureSet(features + otherFeatures.associateBy { it::class }) + + public companion object { + public fun of(vararg features: F): FeatureSet = FeatureSet(features.associateBy { it::class }) + } +} diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt index 352c75956..d29c54d46 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt @@ -13,7 +13,7 @@ import space.kscience.kmath.nd.as2D /** * A context that allows to operate on a [MutableBuffer] as on 2d array */ -internal class BufferAccessor2D( +internal class BufferAccessor2D( public val rowNum: Int, public val colNum: Int, val factory: MutableBufferFactory, diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt index 34b5e373b..e438995dd 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt @@ -5,8 +5,10 @@ package space.kscience.kmath.structures +import space.kscience.kmath.linear.Point import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.ExtendedFieldOperations +import space.kscience.kmath.operations.Norm import kotlin.math.* /** @@ -161,12 +163,16 @@ public object DoubleBufferFieldOperations : ExtendedFieldOperations, Double> { + override fun norm(arg: Point): Double = sqrt(arg.fold(0.0) { acc: Double, d: Double -> acc + d.pow(2) }) +} + /** * [ExtendedField] over [DoubleBuffer]. * * @property size the size of buffers to operate on. */ -public class DoubleBufferField(public val size: Int) : ExtendedField> { +public class DoubleBufferField(public val size: Int) : ExtendedField>, Norm, Double> { public override val zero: Buffer by lazy { DoubleBuffer(size) { 0.0 } } public override val one: Buffer by lazy { DoubleBuffer(size) { 1.0 } } @@ -274,4 +280,6 @@ public class DoubleBufferField(public val size: Int) : ExtendedField): Double = DoubleL2Norm.norm(arg) } diff --git a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealVector.kt b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealVector.kt index d3867ea89..dbf81b133 100644 --- a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealVector.kt +++ b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealVector.kt @@ -8,11 +8,8 @@ package space.kscience.kmath.real import space.kscience.kmath.linear.Point import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Norm -import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.* import space.kscience.kmath.structures.MutableBuffer.Companion.double -import space.kscience.kmath.structures.asBuffer -import space.kscience.kmath.structures.fold -import space.kscience.kmath.structures.indices import kotlin.math.pow import kotlin.math.sqrt @@ -105,8 +102,4 @@ public fun DoubleVector.sum(): Double { return res } -public object VectorL2Norm : Norm { - override fun norm(arg: DoubleVector): Double = sqrt(arg.fold(0.0) { acc: Double, d: Double -> acc + d.pow(2) }) -} - -public val DoubleVector.norm: Double get() = VectorL2Norm.norm(this) \ No newline at end of file +public val DoubleVector.norm: Double get() = DoubleL2Norm.norm(this) \ No newline at end of file diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt index ae82a40be..e3a9e5a4a 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt @@ -50,7 +50,7 @@ public class GaussIntegrator( } } - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand = with(algebra) { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand = with(algebra) { val f = integrand.function val (points, weights) = buildRule(integrand) var res = zero @@ -63,7 +63,7 @@ public class GaussIntegrator( c = t - res - y res = t } - return integrand + IntegrandValue(res) + IntegrandCallsPerformed(integrand.calls + points.size) + return integrand.with(IntegrandValue(res),IntegrandCallsPerformed(integrand.calls + points.size)) } public companion object { @@ -80,17 +80,17 @@ public class GaussIntegrator( * * [UnivariateIntegrandRanges] - Set of ranges and number of points per range. Defaults to given [IntegrationRange] and [IntegrandMaxCalls] */ @UnstableKMathAPI -public fun Field.integrate( +public fun Field.process( vararg features: IntegrandFeature, function: (Double) -> T, -): UnivariateIntegrand = GaussIntegrator(this).integrate(UnivariateIntegrand(function, *features)) +): UnivariateIntegrand = GaussIntegrator(this).process(UnivariateIntegrand(function, *features)) /** * Use [GaussIntegrator.Companion.integrate] to integrate the function in the current algebra with given [range] and [numPoints] */ @UnstableKMathAPI -public fun Field.integrate( +public fun Field.process( range: ClosedRange, order: Int = 10, intervals: Int = 10, @@ -104,7 +104,7 @@ public fun Field.integrate( val ranges = UnivariateIntegrandRanges( (0 until intervals).map { i -> (rangeSize * i)..(rangeSize * (i + 1)) to order } ) - return GaussIntegrator(this).integrate( + return GaussIntegrator(this).process( UnivariateIntegrand( function, IntegrationRange(range), diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt index 1ff8e422e..1f45e825b 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt @@ -5,12 +5,15 @@ package space.kscience.kmath.integration +import space.kscience.kmath.misc.FeatureSet +import space.kscience.kmath.misc.Featured import kotlin.reflect.KClass public interface IntegrandFeature -public interface Integrand { - public fun getFeature(type: KClass): T? +public interface Integrand: Featured{ + public val features: FeatureSet + override fun getFeature(type: KClass): T? = features.getFeature(type) } public inline fun Integrand.getFeature(): T? = getFeature(T::class) diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt index abe6ea5ff..1cf15b42f 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrator.kt @@ -12,5 +12,5 @@ public interface Integrator { /** * Runs one integration pass and return a new [Integrand] with a new set of features. */ - public fun integrate(integrand: I): I + public fun process(integrand: I): I } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt index 12d0ef0a6..b9c1589c0 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt @@ -6,27 +6,21 @@ package space.kscience.kmath.integration import space.kscience.kmath.linear.Point +import space.kscience.kmath.misc.FeatureSet import kotlin.reflect.KClass public class MultivariateIntegrand internal constructor( - private val features: Map, IntegrandFeature>, + override val features: FeatureSet, public val function: (Point) -> T, -) : Integrand { +) : Integrand - @Suppress("UNCHECKED_CAST") - override fun getFeature(type: KClass): T? = features[type] as? T - - public operator fun plus(pair: Pair, F>): MultivariateIntegrand = - MultivariateIntegrand(features + pair, function) - - public operator fun plus(feature: F): MultivariateIntegrand = - plus(feature::class to feature) -} +public fun MultivariateIntegrand.with(vararg newFeatures: IntegrandFeature): MultivariateIntegrand = + MultivariateIntegrand(features.with(*newFeatures), function) @Suppress("FunctionName") public fun MultivariateIntegrand( vararg features: IntegrandFeature, function: (Point) -> T, -): MultivariateIntegrand = MultivariateIntegrand(features.associateBy { it::class }, function) +): MultivariateIntegrand = MultivariateIntegrand(FeatureSet.of(*features), function) public val MultivariateIntegrand.value: T? get() = getFeature>()?.value diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt index bcd5005c4..3fc5b4599 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt @@ -5,30 +5,24 @@ package space.kscience.kmath.integration +import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.UnstableKMathAPI import kotlin.jvm.JvmInline -import kotlin.reflect.KClass + public class UnivariateIntegrand internal constructor( - private val features: Map, IntegrandFeature>, + override val features: FeatureSet, public val function: (Double) -> T, -) : Integrand { +) : Integrand - @Suppress("UNCHECKED_CAST") - override fun getFeature(type: KClass): T? = features[type] as? T - - public operator fun plus(pair: Pair, F>): UnivariateIntegrand = - UnivariateIntegrand(features + pair, function) - - public operator fun plus(feature: F): UnivariateIntegrand = - plus(feature::class to feature) -} +public fun UnivariateIntegrand.with(vararg newFeatures: IntegrandFeature): UnivariateIntegrand = + UnivariateIntegrand(features.with(*newFeatures), function) @Suppress("FunctionName") public fun UnivariateIntegrand( function: (Double) -> T, vararg features: IntegrandFeature, -): UnivariateIntegrand = UnivariateIntegrand(features.associateBy { it::class }, function) +): UnivariateIntegrand = UnivariateIntegrand(FeatureSet.of(*features), function) public typealias UnivariateIntegrator = Integrator> @@ -46,7 +40,7 @@ public fun UnivariateIntegrator.integrate( range: ClosedRange, vararg features: IntegrandFeature, function: (Double) -> Double, -): Double = integrate( +): Double = process( UnivariateIntegrand(function, IntegrationRange(range), *features) ).value ?: error("Unexpected: no value after integration.") @@ -65,7 +59,7 @@ public fun UnivariateIntegrator.integrate( featureBuilder() add(IntegrationRange(range)) } - return integrate( + return process( UnivariateIntegrand(function, *features.toTypedArray()) ).value ?: error("Unexpected: no value after integration.") } diff --git a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt index 195711452..d1e452454 100644 --- a/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt +++ b/kmath-functions/src/commonTest/kotlin/space/kscience/kmath/integration/GaussIntegralTest.kt @@ -16,7 +16,7 @@ import kotlin.test.assertEquals class GaussIntegralTest { @Test fun gaussSin() { - val res = DoubleField.integrate(0.0..2 * PI) { x -> + val res = DoubleField.process(0.0..2 * PI) { x -> sin(x) } assertEquals(0.0, res.value!!, 1e-2) @@ -24,7 +24,7 @@ class GaussIntegralTest { @Test fun gaussUniform() { - val res = DoubleField.integrate(0.0..100.0) { x -> + val res = DoubleField.process(0.0..100.0) { x -> if(x in 30.0..50.0){ 1.0 } else { diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index 38f3038c2..12ccea1d8 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -9,86 +9,97 @@ import space.kscience.kmath.expressions.AutoDiffProcessor import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.ExpressionAlgebra +import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.Symbol import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.indices -/** - * A likelihood function optimization problem with provided derivatives - */ -public interface FunctionOptimization : Optimization { - /** - * The optimization direction. If true search for function maximum, if false, search for the minimum - */ - public var maximize: Boolean - /** - * Define the initial guess for the optimization problem - */ - public fun initialGuess(map: Map) +public class FunctionOptimization( + override val features: FeatureSet, + public val expression: DifferentiableExpression>, + public val initialGuess: Map, + public val parameters: Collection, + public val maximize: Boolean, +) : OptimizationProblem - /** - * Set a differentiable expression as objective function as function and gradient provider - */ - public fun diffFunction(expression: DifferentiableExpression>) - - public companion object { - /** - * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation - */ - public fun chiSquared( - autoDiff: AutoDiffProcessor>, - x: Buffer, - y: Buffer, - yErr: Buffer, - model: A.(I) -> I, - ): DifferentiableExpression> where A : ExtendedField, A : ExpressionAlgebra { - require(x.size == y.size) { "X and y buffers should be of the same size" } - require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } - - return autoDiff.process { - var sum = zero - - x.indices.forEach { - val xValue = const(x[it]) - val yValue = const(y[it]) - val yErrValue = const(yErr[it]) - val modelValue = model(xValue) - sum += ((yValue - modelValue) / yErrValue).pow(2) - } - - sum - } - } - } -} - -/** - * Define a chi-squared-based objective function - */ -public fun FunctionOptimization.chiSquared( - autoDiff: AutoDiffProcessor>, - x: Buffer, - y: Buffer, - yErr: Buffer, - model: A.(I) -> I, -) where A : ExtendedField, A : ExpressionAlgebra { - val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) - diffFunction(chiSquared) - maximize = false -} - -/** - * Optimize differentiable expression using specific [OptimizationProblemFactory] - */ -public fun > DifferentiableExpression>.optimizeWith( - factory: OptimizationProblemFactory, - vararg symbols: Symbol, - configuration: F.() -> Unit, -): OptimizationResult { - require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = factory(symbols.toList(), configuration) - problem.diffFunction(this) - return problem.optimize() -} +// +///** +// * A likelihood function optimization problem with provided derivatives +// */ +//public interface FunctionOptimizationBuilder { +// /** +// * The optimization direction. If true search for function maximum, if false, search for the minimum +// */ +// public var maximize: Boolean +// +// /** +// * Define the initial guess for the optimization problem +// */ +// public fun initialGuess(map: Map) +// +// /** +// * Set a differentiable expression as objective function as function and gradient provider +// */ +// public fun function(expression: DifferentiableExpression>) +// +// public companion object { +// /** +// * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation +// */ +// public fun chiSquared( +// autoDiff: AutoDiffProcessor>, +// x: Buffer, +// y: Buffer, +// yErr: Buffer, +// model: A.(I) -> I, +// ): DifferentiableExpression> where A : ExtendedField, A : ExpressionAlgebra { +// require(x.size == y.size) { "X and y buffers should be of the same size" } +// require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } +// +// return autoDiff.process { +// var sum = zero +// +// x.indices.forEach { +// val xValue = const(x[it]) +// val yValue = const(y[it]) +// val yErrValue = const(yErr[it]) +// val modelValue = model(xValue) +// sum += ((yValue - modelValue) / yErrValue).pow(2) +// } +// +// sum +// } +// } +// } +//} +// +///** +// * Define a chi-squared-based objective function +// */ +//public fun FunctionOptimization.chiSquared( +// autoDiff: AutoDiffProcessor>, +// x: Buffer, +// y: Buffer, +// yErr: Buffer, +// model: A.(I) -> I, +//) where A : ExtendedField, A : ExpressionAlgebra { +// val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) +// function(chiSquared) +// maximize = false +//} +// +///** +// * Optimize differentiable expression using specific [OptimizationProblemFactory] +// */ +//public suspend fun > DifferentiableExpression>.optimizeWith( +// factory: OptimizationProblemFactory, +// vararg symbols: Symbol, +// configuration: F.() -> Unit, +//): OptimizationResult { +// require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } +// val problem = factory(symbols.toList(), configuration) +// problem.function(this) +// return problem.optimize() +//} diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/NoDerivFunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/NoDerivFunctionOptimization.kt deleted file mode 100644 index 67a21bf2a..000000000 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/NoDerivFunctionOptimization.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.optimization - -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.structures.Buffer -import space.kscience.kmath.structures.indices -import kotlin.math.pow - -/** - * A likelihood function optimization problem - */ -public interface NoDerivFunctionOptimization : Optimization { - /** - * The optimization direction. If true search for function maximum, if false, search for the minimum - */ - public var maximize: Boolean - - /** - * Define the initial guess for the optimization problem - */ - public fun initialGuess(map: Map) - - /** - * Set an objective function expression - */ - public fun function(expression: Expression) - - public companion object { - /** - * Generate a chi squared expression from given x-y-sigma model represented by an expression. Does not provide derivatives - */ - public fun chiSquared( - x: Buffer, - y: Buffer, - yErr: Buffer, - model: Expression, - xSymbol: Symbol = Symbol.x, - ): Expression { - require(x.size == y.size) { "X and y buffers should be of the same size" } - require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } - - return Expression { arguments -> - x.indices.sumOf { - val xValue = x[it] - val yValue = y[it] - val yErrValue = yErr[it] - val modifiedArgs = arguments + (xSymbol to xValue) - val modelValue = model(modifiedArgs) - ((yValue - modelValue) / yErrValue).pow(2) - } - } - } - } -} - - -/** - * Optimize expression without derivatives using specific [OptimizationProblemFactory] - */ -public fun > Expression.noDerivOptimizeWith( - factory: OptimizationProblemFactory, - vararg symbols: Symbol, - configuration: F.() -> Unit, -): OptimizationResult { - require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = factory(symbols.toList(), configuration) - problem.function(this) - return problem.optimize() -} diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimization.kt deleted file mode 100644 index 3b9868815..000000000 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimization.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.optimization - -import space.kscience.kmath.misc.Symbol - -public interface OptimizationFeature - -public class OptimizationResult( - public val point: Map, - public val value: T, - public val features: Set = emptySet(), -) { - override fun toString(): String { - return "OptimizationResult(point=$point, value=$value)" - } -} - -public operator fun OptimizationResult.plus( - feature: OptimizationFeature, -): OptimizationResult = OptimizationResult(point, value, features + feature) - -/** - * An optimization problem builder over [T] variables - */ -public interface Optimization { - - /** - * Update the problem from previous optimization run - */ - public fun update(result: OptimizationResult) - - /** - * Make an optimization run - */ - public fun optimize(): OptimizationResult -} - -public fun interface OptimizationProblemFactory> { - public fun build(symbols: List): P -} - -public operator fun > OptimizationProblemFactory.invoke( - symbols: List, - block: P.() -> Unit, -): P = build(symbols).apply(block) diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt new file mode 100644 index 000000000..9a5420be6 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.optimization + +import space.kscience.kmath.misc.FeatureSet +import space.kscience.kmath.misc.Featured +import kotlin.reflect.KClass + +public interface OptimizationFeature + +public interface OptimizationProblem : Featured { + public val features: FeatureSet + override fun getFeature(type: KClass): T? = features.getFeature(type) +} + +public inline fun OptimizationProblem.getFeature(): T? = getFeature(T::class) + +//public class OptimizationResult( +// public val point: Map, +// public val value: T, +// public val features: Set = emptySet(), +//) { +// override fun toString(): String { +// return "OptimizationResult(point=$point, value=$value)" +// } +//} +// +//public operator fun OptimizationResult.plus( +// feature: OptimizationFeature, +//): OptimizationResult = OptimizationResult(point, value, features + feature) +//public fun interface OptimizationProblemFactory> { +// public fun build(symbols: List): P +//} +// +//public operator fun > OptimizationProblemFactory.invoke( +// symbols: List, +// block: P.() -> Unit, +//): P = build(symbols).apply(block) + +public interface Optimizer

{ + public suspend fun process(problem: P): P +} + diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index f5cfa05e6..e4998c665 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -16,7 +16,7 @@ import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.Field @UnstableKMathAPI -public interface XYFit : Optimization { +public interface XYFit : OptimizationProblem { public val algebra: Field diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/AnalyticalGradientCalculator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/AnalyticalGradientCalculator.kt new file mode 100644 index 000000000..912fa22eb --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimizer.kt new file mode 100644 index 000000000..9363492ad --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimumBuilder.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimumBuilder.kt new file mode 100644 index 000000000..a2f0a644a --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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 space.kscience.kmath.optimization.minuit.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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ContoursError.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ContoursError.kt new file mode 100644 index 000000000..214d94c80 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/DavidonErrorUpdator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/DavidonErrorUpdator.kt new file mode 100644 index 000000000..9eb2443e4 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionGradient.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionGradient.kt new file mode 100644 index 000000000..a0866d916 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionMinimum.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionMinimum.kt new file mode 100644 index 000000000..56908f00d --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/GradientCalculator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/GradientCalculator.kt new file mode 100644 index 000000000..379de1b6d --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/HessianGradientCalculator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/HessianGradientCalculator.kt new file mode 100644 index 000000000..150d192f9 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/InitialGradientCalculator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/InitialGradientCalculator.kt new file mode 100644 index 000000000..794556414 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINOSResult.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINOSResult.kt new file mode 100644 index 000000000..c33994648 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/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 space.kscience.kmath.optimization.minuit + + +/** + * Контейнер для несимметричных оценок и доверительных интервалов + * + * @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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITFitter.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITFitter.kt new file mode 100644 index 000000000..a26321249 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITFitter.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +package space.kscience.kmath.optimization.minuit + +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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITPlugin.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITPlugin.kt new file mode 100644 index 000000000..7eaefd9d2 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITPlugin.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +package space.kscience.kmath.optimization.minuit + +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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITUtils.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITUtils.kt new file mode 100644 index 000000000..44c70cb42 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITUtils.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +package space.kscience.kmath.optimization.minuit + +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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumBuilder.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumBuilder.kt new file mode 100644 index 000000000..eadeeb91d --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumError.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumError.kt new file mode 100644 index 000000000..6993b9e6d --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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 space.kscience.kmath.optimization.minuit.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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumErrorUpdator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumErrorUpdator.kt new file mode 100644 index 000000000..6022aa5b7 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumParameters.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumParameters.kt new file mode 100644 index 000000000..bed13ea0b --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeed.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeed.kt new file mode 100644 index 000000000..aef672bb7 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeedGenerator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeedGenerator.kt new file mode 100644 index 000000000..bd04c1a45 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumState.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumState.kt new file mode 100644 index 000000000..9f63e0e1f --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinosError.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinosError.kt new file mode 100644 index 000000000..c7cf10523 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinuitParameter.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinuitParameter.kt new file mode 100644 index 000000000..ff6834df4 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnAlgebraicSymMatrix.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnAlgebraicSymMatrix.kt new file mode 100644 index 000000000..4b75858e1 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnApplication.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnApplication.kt new file mode 100644 index 000000000..025eea4ae --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnContours.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnContours.kt new file mode 100644 index 000000000..1b700f4e2 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCovarianceSqueeze.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCovarianceSqueeze.kt new file mode 100644 index 000000000..7614a93b0 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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 space.kscience.kmath.optimization.minuit.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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCross.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCross.kt new file mode 100644 index 000000000..f1487b106 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnEigen.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnEigen.kt new file mode 100644 index 000000000..d7aade0c9 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFcn.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFcn.kt new file mode 100644 index 000000000..b11f71035 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFunctionCross.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFunctionCross.kt new file mode 100644 index 000000000..a05590e53 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnGlobalCorrelationCoeff.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnGlobalCorrelationCoeff.kt new file mode 100644 index 000000000..939dd7fa0 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnHesse.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnHesse.kt new file mode 100644 index 000000000..3bb6c4551 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnLineSearch.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnLineSearch.kt new file mode 100644 index 000000000..7b1171d3c --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMachinePrecision.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMachinePrecision.kt new file mode 100644 index 000000000..161ee0c0a --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMigrad.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMigrad.kt new file mode 100644 index 000000000..22616a1a6 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinimize.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinimize.kt new file mode 100644 index 000000000..ea14a5453 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinos.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinos.kt new file mode 100644 index 000000000..d49379b3b --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabola.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabola.kt new file mode 100644 index 000000000..a0a56dedd --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaFactory.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaFactory.kt new file mode 100644 index 000000000..f45d2b9c9 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaPoint.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaPoint.kt new file mode 100644 index 000000000..858e010e6 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParameterScan.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParameterScan.kt new file mode 100644 index 000000000..7791c20e8 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPlot.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPlot.kt new file mode 100644 index 000000000..656dd8d35 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPosDef.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPosDef.kt new file mode 100644 index 000000000..f94e387d9 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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 space.kscience.kmath.optimization.minuit.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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnScan.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnScan.kt new file mode 100644 index 000000000..63e565b4f --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt new file mode 100644 index 000000000..cc3f9547e --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt @@ -0,0 +1,107 @@ +/* + * 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 space.kscience.kmath.optimization.minuit.MINUITPlugin +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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSimplex.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSimplex.kt new file mode 100644 index 000000000..b00745f26 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnStrategy.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnStrategy.kt new file mode 100644 index 000000000..31b894665 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserCovariance.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserCovariance.kt new file mode 100644 index 000000000..297588f8e --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserFcn.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserFcn.kt new file mode 100644 index 000000000..8198a41ab --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameterState.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameterState.kt new file mode 100644 index 000000000..e80dd60a1 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameters.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameters.kt new file mode 100644 index 000000000..9bac54b25 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserTransformation.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserTransformation.kt new file mode 100644 index 000000000..1066ac2da --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUtils.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUtils.kt new file mode 100644 index 000000000..d9f3e1bd5 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ModularFunctionMinimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ModularFunctionMinimizer.kt new file mode 100644 index 000000000..f234bcd48 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/NegativeG2LineSearch.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/NegativeG2LineSearch.kt new file mode 100644 index 000000000..2e9ce5813 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Numerical2PGradientCalculator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Numerical2PGradientCalculator.kt new file mode 100644 index 000000000..efa1d57af --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt new file mode 100644 index 000000000..ebeedf7e8 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanBuilder.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanBuilder.kt new file mode 100644 index 000000000..b7e773924 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanMinimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanMinimizer.kt new file mode 100644 index 000000000..e39a49c0d --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexBuilder.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexBuilder.kt new file mode 100644 index 000000000..8441a4177 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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 space.kscience.kmath.optimization.minuit.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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexMinimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexMinimizer.kt new file mode 100644 index 000000000..f4bbcc320 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexParameters.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexParameters.kt new file mode 100644 index 000000000..fef6e2010 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexSeedGenerator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexSeedGenerator.kt new file mode 100644 index 000000000..e5025ff3d --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SinParameterTransformation.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SinParameterTransformation.kt new file mode 100644 index 000000000..821addef7 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtLowParameterTransformation.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtLowParameterTransformation.kt new file mode 100644 index 000000000..444b63847 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtUpParameterTransformation.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtUpParameterTransformation.kt new file mode 100644 index 000000000..5774848bd --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.kt new file mode 100644 index 000000000..8362c5e7e --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.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 space.kscience.kmath.optimization.minuit.MINUITPlugin +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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricEDMEstimator.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricEDMEstimator.kt new file mode 100644 index 000000000..8fca4e6ee --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricMinimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricMinimizer.kt new file mode 100644 index 000000000..2a13a5fff --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/package-info.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/package-info.kt new file mode 100644 index 000000000..22779da86 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt new file mode 100644 index 000000000..02fd12dbc --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt @@ -0,0 +1,380 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.optimization.qow + +import space.kscience.kmath.data.ColumnarData +import space.kscience.kmath.data.XYErrorColumnarData +import space.kscience.kmath.expressions.DifferentiableExpression +import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.SymbolIndexer +import space.kscience.kmath.expressions.derivative +import space.kscience.kmath.linear.* +import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.Field +import space.kscience.kmath.optimization.OptimizationFeature +import space.kscience.kmath.optimization.OptimizationProblemFactory +import space.kscience.kmath.optimization.OptimizationResult +import space.kscience.kmath.optimization.XYFit +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.DoubleL2Norm +import kotlin.math.pow + + +private typealias ParamSet = Map + +public fun interface FitLogger { + public fun log(block: () -> String) +} + +@OptIn(UnstableKMathAPI::class) +public class QowFit( + override val symbols: List, + private val space: LinearSpace, + private val solver: LinearSolver, +) : XYFit, SymbolIndexer { + + private var logger: FitLogger? = null + + private var startingPoint: Map = TODO() + private var covariance: Matrix? = TODO() + private val prior: DifferentiableExpression>? = TODO() + private var data: XYErrorColumnarData = TODO() + private var model: DifferentiableExpression> = TODO() + + private val features = HashSet() + + override fun update(result: OptimizationResult) { + TODO("Not yet implemented") + } + + override val algebra: Field + get() = TODO("Not yet implemented") + + override fun data( + dataSet: ColumnarData, + xSymbol: Symbol, + ySymbol: Symbol, + xErrSymbol: Symbol?, + yErrSymbol: Symbol?, + ) { + TODO("Not yet implemented") + } + + override fun model(model: (Double) -> DifferentiableExpression) { + TODO("Not yet implemented") + } + + private var x: Symbol = Symbol.x + + /** + * The signed distance from the model to the [i]-th point of data. + */ + private fun distance(i: Int, parameters: Map): Double = + model(parameters + (x to data.x[i])) - data.y[i] + + + /** + * The derivative of [distance] + * TODO use expressions instead + */ + private fun distanceDerivative(symbol: Symbol, i: Int, parameters: Map): Double = + model.derivative(symbol)(parameters + (x to data.x[i])) + + /** + * The dispersion of [i]-th data point + */ + private fun getDispersion(i: Int, parameters: Map): Double = data.yErr[i].pow(2) + + private fun getCovariance(weight: QoWeight): Matrix = solver.inverse(getEqDerivValues(weight)) + + /** + * Теоретическая ковариация весовых функций. + * + * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2 + */ + private fun covarF(weight: QoWeight): Matrix = space.buildSymmetricMatrix(symbols.size) { k, l -> + (0 until data.size).sumOf { i -> weight.derivs[k, i] * weight.derivs[l, i] / weight.dispersion[i] } + } + + /** + * Экспериментальная ковариация весов. Формула (22) из + * http://arxiv.org/abs/physics/0604127 + * + * @param source + * @param set + * @param fitPars + * @param weight + * @return + */ + private fun covarFExp(weight: QoWeight, theta: Map): Matrix = space.run { + /* + * Важно! Если не делать предварителього вычисления этих производных, то + * количество вызывов функции будет dim^2 вместо dim Первый индекс - + * номер точки, второй - номер переменной, по которой берется производная + */ + val eqvalues = buildMatrix(data.size, symbols.size) { i, l -> + distance(i, theta) * weight.derivs[l, i] / weight.dispersion[i] + } + + buildMatrix(symbols.size, symbols.size) { k, l -> + (0 until data.size).sumOf { i -> eqvalues[i, l] * eqvalues[i, k] } + } + } + + /** + * производные уравнений для метода Ньютона + * + * @param source + * @param set + * @param fitPars + * @param weight + * @return + */ + private fun getEqDerivValues( + weight: QoWeight, theta: Map = weight.theta, + ): Matrix = space.run { + val fitDim = symbols.size + //Возвращает производную k-того Eq по l-тому параметру + val res = Array(fitDim) { DoubleArray(fitDim) } + val sderiv = buildMatrix(data.size, symbols.size) { i, l -> + distanceDerivative(symbols[l], i, theta) + } + + buildMatrix(symbols.size, symbols.size) { k, l -> + val base = (0 until data.size).sumOf { i -> + require(weight.dispersion[i] > 0) + sderiv[i, l] * weight.derivs[k, i] / weight.dispersion[i] + } + prior?.let { prior -> + //Check if this one is correct + val pi = prior(theta) + val deriv1 = prior.derivative(symbols[k])(theta) + val deriv2 = prior.derivative(symbols[l])(theta) + base + deriv1 * deriv2 / pi / pi + } ?: base + } + } + + + /** + * Значения уравнений метода квазиоптимальных весов + * + * @param source + * @param set + * @param fitPars + * @param weight + * @return + */ + private fun getEqValues(weight: QoWeight, theta: Map = weight.theta): Point { + val distances = DoubleBuffer(data.size) { i -> distance(i, theta) } + + return DoubleBuffer(symbols.size) { k -> + val base = (0 until data.size).sumOf { i -> distances[i] * weight.derivs[k, i] / weight.dispersion[i] } + //Поправка на априорную вероятность + prior?.let { prior -> + base - prior.derivative(symbols[k])(theta) / prior(theta) + } ?: base + } + } + + + /** + * The state of QOW fitter + * Created by Alexander Nozik on 17-Oct-16. + */ + private inner class QoWeight( + val theta: Map, + ) { + + init { + require(data.size > 0) { "The state does not contain data" } + } + + /** + * Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter + */ + val derivs: Matrix by lazy { + space.buildMatrix(data.size, symbols.size) { i, k -> + distanceDerivative(symbols[k], i, theta) + } + } + + /** + * Array of dispersions in each point + */ + val dispersion: Point by lazy { + DoubleBuffer(data.size) { i -> getDispersion(i, theta) } + } + + } + + private fun newtonianStep( + weight: QoWeight, + par: Map, + eqvalues: Point, + ): Map = space.run { + val start = par.toPoint() + val invJacob = solver.inverse(getEqDerivValues(weight, par)) + + val step = invJacob.dot(eqvalues) + return par + (start - step).toMap() + } + + private fun newtonianRun( + weight: QoWeight, + maxSteps: Int = 100, + tolerance: Double = 0.0, + fast: Boolean = false, + ): ParamSet { + + var dis: Double//норма невязки + // Для удобства работаем всегда с полным набором параметров + var par = startingPoint + + logger?.log { "Starting newtonian iteration from: \n\t$par" } + + var eqvalues = getEqValues(weight, par)//значения функций + + dis = DoubleL2Norm.norm(eqvalues)// невязка + logger?.log { "Starting discrepancy is $dis" } + var i = 0 + var flag = false + while (!flag) { + i++ + logger?.log { "Starting step number $i" } + + val currentSolution = if (fast) { + //Берет значения матрицы в той точке, где считается вес + newtonianStep(weight, weight.theta, eqvalues) + } else { + //Берет значения матрицы в точке par + newtonianStep(weight, par, eqvalues) + } + // здесь должен стоять учет границ параметров + logger?.log { "Parameter values after step are: \n\t$currentSolution" } + + eqvalues = getEqValues(weight, currentSolution) + val currentDis = DoubleL2Norm.norm(eqvalues)// невязка после шага + + logger?.log { "The discrepancy after step is: $currentDis." } + + if (currentDis >= dis && i > 1) { + //дополнительно проверяем, чтобы был сделан хотя бы один шаг + flag = true + logger?.log { "The discrepancy does not decrease. Stopping iteration." } + } else { + par = currentSolution + dis = currentDis + } + if (i >= maxSteps) { + flag = true + logger?.log { "Maximum number of iterations reached. Stopping iteration." } + } + if (dis <= tolerance) { + flag = true + logger?.log { "Tolerance threshold is reached. Stopping iteration." } + } + } + + return par + } + + +// +// override fun run(state: FitState, parentLog: History?, meta: Meta): FitResult { +// val log = Chronicle("QOW", parentLog) +// val action = meta.getString(FIT_STAGE_TYPE, TASK_RUN) +// log.report("QOW fit engine started task '{}'", action) +// return when (action) { +// TASK_SINGLE -> makeRun(state, log, meta) +// TASK_COVARIANCE -> generateErrors(state, log, meta) +// TASK_RUN -> { +// var res = makeRun(state, log, meta) +// res = makeRun(res.optState().get(), log, meta) +// generateErrors(res.optState().get(), log, meta) +// } +// else -> throw IllegalArgumentException("Unknown task") +// } +// } + +// private fun makeRun(state: FitState, log: History, meta: Meta): FitResult { +// /*Инициализация объектов, задание исходных значений*/ +// log.report("Starting fit using quasioptimal weights method.") +// +// val fitPars = getFitPars(state, meta) +// +// val curWeight = QoWeight(state, fitPars, state.parameters) +// +// // вычисляем вес в allPar. Потом можно будет попробовать ручное задание веса +// log.report("The starting weight is: \n\t{}", +// MathUtils.toString(curWeight.theta)) +// +// //Стартовая точка такая же как и параметр веса +// /*Фитирование*/ +// val res = newtonianRun(state, curWeight, log, meta) +// +// /*Генерация результата*/ +// +// return FitResult.build(state.edit().setPars(res).build(), *fitPars) +// } + + /** + * generateErrors. + */ + private fun generateErrors(): Matrix { + logger?.log { """ + Starting errors estimation using quasioptimal weights method. The starting weight is: + ${curWeight.theta} + """.trimIndent()} + val curWeight = QoWeight(startingPoint) + + val covar = getCovariance(curWeight) + + val decomposition = EigenDecomposition(covar.matrix) + var valid = true + for (lambda in decomposition.realEigenvalues) { + if (lambda <= 0) { + log.report("The covariance matrix is not positive defined. Error estimation is not valid") + valid = false + } + } + } + + + override suspend fun optimize(): OptimizationResult { + val curWeight = QoWeight(startingPoint) + logger?.log { + """ + Starting fit using quasioptimal weights method. The starting weight is: + ${curWeight.theta} + """.trimIndent() + } + val res = newtonianRun(curWeight) + } + + + companion object : OptimizationProblemFactory { + override fun build(symbols: List): QowFit { + TODO("Not yet implemented") + } + + + /** + * Constant `QOW_ENGINE_NAME="QOW"` + */ + const val QOW_ENGINE_NAME = "QOW" + + /** + * Constant `QOW_METHOD_FAST="fast"` + */ + const val QOW_METHOD_FAST = "fast" + + + } +} + From 7b6361e59d48d2ee12edbda6f99af91b1e38f0a5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 26 Apr 2021 15:02:19 +0300 Subject: [PATCH 02/13] [WIP] optimization refactor in process --- .../commons/optimization/CMOptimization.kt | 112 +++++---------- .../kmath/commons/optimization/cmFit.kt | 8 +- .../space/kscience/kmath/misc/Featured.kt | 4 +- .../space/kscience/kmath/misc/logging.kt | 14 ++ .../optimization/FunctionOptimization.kt | 130 +++++++----------- .../kmath/optimization/OptimizationProblem.kt | 6 + .../kscience/kmath/optimization/XYFit.kt | 34 ++++- .../kscience/kmath/optimization/qow/QowFit.kt | 4 - 8 files changed, 146 insertions(+), 166 deletions(-) create mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt index db9ba6f21..6bde14627 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt @@ -11,10 +11,8 @@ import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunction import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunctionGradient import org.apache.commons.math3.optim.nonlinear.scalar.gradient.NonLinearConjugateGradientOptimizer -import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.AbstractSimplex -import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.NelderMeadSimplex -import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer -import space.kscience.kmath.expressions.* +import space.kscience.kmath.expressions.derivative +import space.kscience.kmath.expressions.withSymbols import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.optimization.* @@ -24,107 +22,73 @@ public operator fun PointValuePair.component1(): DoubleArray = point public operator fun PointValuePair.component2(): Double = value public class CMOptimizerFactory(public val optimizerBuilder: () -> MultivariateOptimizer) : OptimizationFeature -//public class CMOptimizerData(public val ) +public class CMOptimizerData(public val data: List) : OptimizationFeature { + public constructor(vararg data: OptimizationData) : this(data.toList()) +} @OptIn(UnstableKMathAPI::class) public class CMOptimization : Optimizer> { override suspend fun process( - problem: FunctionOptimization - ): FunctionOptimization = withSymbols(problem.parameters){ - val cmOptimizer: MultivariateOptimizer = - problem.getFeature()?.optimizerBuilder?.invoke() ?: SimplexOptimizer() - + problem: FunctionOptimization, + ): FunctionOptimization = withSymbols(problem.parameters) { val convergenceChecker: ConvergenceChecker = SimpleValueChecker( DEFAULT_RELATIVE_TOLERANCE, DEFAULT_ABSOLUTE_TOLERANCE, DEFAULT_MAX_ITER ) + val cmOptimizer: MultivariateOptimizer = problem.getFeature()?.optimizerBuilder?.invoke() + ?: NonLinearConjugateGradientOptimizer( + NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, + convergenceChecker + ) + val optimizationData: HashMap, OptimizationData> = HashMap() fun addOptimizationData(data: OptimizationData) { optimizationData[data::class] = data } + addOptimizationData(MaxEval.unlimited()) addOptimizationData(InitialGuess(problem.initialGuess.toDoubleArray())) fun exportOptimizationData(): List = optimizationData.values.toList() - - /** - * Register no-deriv function instead of differentiable function - */ - /** - * Register no-deriv function instead of differentiable function - */ - fun noDerivFunction(expression: Expression): Unit { - val objectiveFunction = ObjectiveFunction { - val args = problem.initialGuess + it.toMap() - expression(args) - } - addOptimizationData(objectiveFunction) + val objectiveFunction = ObjectiveFunction { + val args = problem.initialGuess + it.toMap() + problem.expression(args) } + addOptimizationData(objectiveFunction) - public override fun function(expression: DifferentiableExpression>) { - noDerivFunction(expression) - val gradientFunction = ObjectiveFunctionGradient { - val args = startingPoint + it.toMap() - DoubleArray(symbols.size) { index -> - expression.derivative(symbols[index])(args) - } - } - addOptimizationData(gradientFunction) - if (optimizerBuilder == null) { - optimizerBuilder = { - NonLinearConjugateGradientOptimizer( - NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, - convergenceChecker - ) + val gradientFunction = ObjectiveFunctionGradient { + val args = problem.initialGuess + it.toMap() + DoubleArray(symbols.size) { index -> + problem.expression.derivative(symbols[index])(args) + } + } + addOptimizationData(gradientFunction) + + val logger = problem.getFeature() + + for (feature in problem.features) { + when (feature) { + is CMOptimizerData -> feature.data.forEach { addOptimizationData(it) } + is FunctionOptimizationTarget -> when(feature){ + FunctionOptimizationTarget.MAXIMIZE -> addOptimizationData(GoalType.MAXIMIZE) + FunctionOptimizationTarget.MINIMIZE -> addOptimizationData(GoalType.MINIMIZE) } + else -> logger?.log { "The feature $feature is unused in optimization" } } } - public fun simplex(simplex: AbstractSimplex) { - addOptimizationData(simplex) - //Set optimization builder to simplex if it is not present - if (optimizerBuilder == null) { - optimizerBuilder = { SimplexOptimizer(convergenceChecker) } - } - } - - public fun simplexSteps(steps: Map) { - simplex(NelderMeadSimplex(steps.toDoubleArray())) - } - - public fun goal(goalType: GoalType) { - addOptimizationData(goalType) - } - - public fun optimizer(block: () -> MultivariateOptimizer) { - optimizerBuilder = block - } - - override fun update(result: OptimizationResult) { - initialGuess(result.point) - } - - override suspend fun optimize(): OptimizationResult { - val optimizer = optimizerBuilder?.invoke() ?: error("Optimizer not defined") - val (point, value) = optimizer.optimize(*optimizationData.values.toTypedArray()) - return OptimizationResult(point.toMap(), value) - } - return@withSymbols TODO() + val (point, value) = cmOptimizer.optimize(*optimizationData.values.toTypedArray()) + return problem.withFeatures(FunctionOptimizationResult(point.toMap(), value)) } - public companion object : OptimizationProblemFactory { + public companion object { public const val DEFAULT_RELATIVE_TOLERANCE: Double = 1e-4 public const val DEFAULT_ABSOLUTE_TOLERANCE: Double = 1e-4 public const val DEFAULT_MAX_ITER: Int = 1000 - - override fun build(symbols: List): CMOptimization = CMOptimization(symbols) } } - -public fun CMOptimization.initialGuess(vararg pairs: Pair): Unit = initialGuess(pairs.toMap()) -public fun CMOptimization.simplexSteps(vararg pairs: Pair): Unit = simplexSteps(pairs.toMap()) diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt index 12d924063..9c0089b3d 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt @@ -17,22 +17,22 @@ import space.kscience.kmath.structures.asBuffer /** * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation */ -public fun FunctionOptimization.Companion.chiSquared( +public fun FunctionOptimization.Companion.chiSquaredExpression( x: Buffer, y: Buffer, yErr: Buffer, model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, -): DifferentiableExpression> = chiSquared(DerivativeStructureField, x, y, yErr, model) +): DifferentiableExpression> = chiSquaredExpression(DerivativeStructureField, x, y, yErr, model) /** * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation */ -public fun FunctionOptimization.Companion.chiSquared( +public fun FunctionOptimization.Companion.chiSquaredExpression( x: Iterable, y: Iterable, yErr: Iterable, model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, -): DifferentiableExpression> = chiSquared( +): DifferentiableExpression> = chiSquaredExpression( DerivativeStructureField, x.toList().asBuffer(), y.toList().asBuffer(), diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt index 157ff980b..a94efc788 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt @@ -17,7 +17,7 @@ public interface Featured { /** * A container for a set of features */ -public class FeatureSet private constructor(public val features: Map, Any>) : Featured { +public class FeatureSet private constructor(public val features: Map, F>) : Featured { @Suppress("UNCHECKED_CAST") override fun getFeature(type: KClass): T? = features[type] as? T @@ -31,6 +31,8 @@ public class FeatureSet private constructor(public val features: Map = FeatureSet(features + otherFeatures.associateBy { it::class }) + public operator fun iterator(): Iterator = features.values.iterator() + public companion object { public fun of(vararg features: F): FeatureSet = FeatureSet(features.associateBy { it::class }) } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt new file mode 100644 index 000000000..d13840841 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.misc + +public interface Loggable { + public fun log(tag: String = INFO, block: () -> String) + + public companion object { + public const val INFO: String = "INFO" + } +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index 12ccea1d8..db613e236 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -15,91 +15,57 @@ import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.indices +public class FunctionOptimizationResult(point: Map, public val value: T) : OptimizationResult(point) -public class FunctionOptimization( +public enum class FunctionOptimizationTarget : OptimizationFeature { + MAXIMIZE, + MINIMIZE +} + +public class FunctionOptimization( override val features: FeatureSet, public val expression: DifferentiableExpression>, public val initialGuess: Map, public val parameters: Collection, - public val maximize: Boolean, -) : OptimizationProblem +) : OptimizationProblem{ + public companion object{ + /** + * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation + */ + public fun chiSquaredExpression( + autoDiff: AutoDiffProcessor>, + x: Buffer, + y: Buffer, + yErr: Buffer, + model: A.(I) -> I, + ): DifferentiableExpression> where A : ExtendedField, A : ExpressionAlgebra { + require(x.size == y.size) { "X and y buffers should be of the same size" } + require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } + + return autoDiff.process { + var sum = zero + + x.indices.forEach { + val xValue = const(x[it]) + val yValue = const(y[it]) + val yErrValue = const(yErr[it]) + val modelValue = model(xValue) + sum += ((yValue - modelValue) / yErrValue).pow(2) + } + + sum + } + } + } +} + + +public fun FunctionOptimization.withFeatures( + vararg newFeature: OptimizationFeature, +): FunctionOptimization = FunctionOptimization( + features.with(*newFeature), + expression, + initialGuess, + parameters +) -// -///** -// * A likelihood function optimization problem with provided derivatives -// */ -//public interface FunctionOptimizationBuilder { -// /** -// * The optimization direction. If true search for function maximum, if false, search for the minimum -// */ -// public var maximize: Boolean -// -// /** -// * Define the initial guess for the optimization problem -// */ -// public fun initialGuess(map: Map) -// -// /** -// * Set a differentiable expression as objective function as function and gradient provider -// */ -// public fun function(expression: DifferentiableExpression>) -// -// public companion object { -// /** -// * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation -// */ -// public fun chiSquared( -// autoDiff: AutoDiffProcessor>, -// x: Buffer, -// y: Buffer, -// yErr: Buffer, -// model: A.(I) -> I, -// ): DifferentiableExpression> where A : ExtendedField, A : ExpressionAlgebra { -// require(x.size == y.size) { "X and y buffers should be of the same size" } -// require(y.size == yErr.size) { "Y and yErr buffer should of the same size" } -// -// return autoDiff.process { -// var sum = zero -// -// x.indices.forEach { -// val xValue = const(x[it]) -// val yValue = const(y[it]) -// val yErrValue = const(yErr[it]) -// val modelValue = model(xValue) -// sum += ((yValue - modelValue) / yErrValue).pow(2) -// } -// -// sum -// } -// } -// } -//} -// -///** -// * Define a chi-squared-based objective function -// */ -//public fun FunctionOptimization.chiSquared( -// autoDiff: AutoDiffProcessor>, -// x: Buffer, -// y: Buffer, -// yErr: Buffer, -// model: A.(I) -> I, -//) where A : ExtendedField, A : ExpressionAlgebra { -// val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) -// function(chiSquared) -// maximize = false -//} -// -///** -// * Optimize differentiable expression using specific [OptimizationProblemFactory] -// */ -//public suspend fun > DifferentiableExpression>.optimizeWith( -// factory: OptimizationProblemFactory, -// vararg symbols: Symbol, -// configuration: F.() -> Unit, -//): OptimizationResult { -// require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } -// val problem = factory(symbols.toList(), configuration) -// problem.function(this) -// return problem.optimize() -//} diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt index 9a5420be6..0d2e3cb83 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -7,6 +7,8 @@ package space.kscience.kmath.optimization import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.Featured +import space.kscience.kmath.misc.Loggable +import space.kscience.kmath.misc.Symbol import kotlin.reflect.KClass public interface OptimizationFeature @@ -18,6 +20,10 @@ public interface OptimizationProblem : Featured { public inline fun OptimizationProblem.getFeature(): T? = getFeature(T::class) +public open class OptimizationResult(public val point: Map) : OptimizationFeature + +public class OptimizationLog(private val loggable: Loggable) : Loggable by loggable, OptimizationFeature + //public class OptimizationResult( // public val point: Map, // public val value: T, diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index e4998c665..f1b6ef38d 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -14,9 +14,11 @@ import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.Field +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.indices @UnstableKMathAPI -public interface XYFit : OptimizationProblem { +public interface XYFit : OptimizationProblem { public val algebra: Field @@ -42,4 +44,34 @@ public interface XYFit : OptimizationProblem { ): Unit where A : ExtendedField, A : ExpressionAlgebra = model { arg -> autoDiff.process { modelFunction(const(arg)) } } +} + +// +///** +// * Define a chi-squared-based objective function +// */ +//public fun FunctionOptimization.chiSquared( +// autoDiff: AutoDiffProcessor>, +// x: Buffer, +// y: Buffer, +// yErr: Buffer, +// model: A.(I) -> I, +//) where A : ExtendedField, A : ExpressionAlgebra { +// val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) +// function(chiSquared) +// maximize = false +//} + +/** + * Optimize differentiable expression using specific [OptimizationProblemFactory] + */ +public suspend fun > DifferentiableExpression>.optimizeWith( + factory: OptimizationProblemFactory, + vararg symbols: Symbol, + configuration: F.() -> Unit, +): OptimizationResult { + require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } + val problem = factory(symbols.toList(), configuration) + problem.function(this) + return problem.optimize() } \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt index 02fd12dbc..d611adf50 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt @@ -27,10 +27,6 @@ import kotlin.math.pow private typealias ParamSet = Map -public fun interface FitLogger { - public fun log(block: () -> String) -} - @OptIn(UnstableKMathAPI::class) public class QowFit( override val symbols: List, From 88e94a7fd9cc64dbb4d892b21cedef237bfd0045 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 24 May 2021 17:02:12 +0300 Subject: [PATCH 03/13] [WIP] Optimization --- .../kmath/commons/fit/fitWithAutoDiff.kt | 1 - .../commons/optimization/CMOptimization.kt | 103 ++++++++++-------- .../kmath/commons/optimization/cmFit.kt | 2 +- .../kmath/data/XYErrorColumnarData.kt | 6 +- .../kmath/integration/GaussIntegrator.kt | 4 +- .../kscience/kmath/integration/Integrand.kt | 10 +- .../integration/MultivariateIntegrand.kt | 16 +-- .../kmath/integration/SimpsonIntegrator.kt | 4 +- .../kmath/integration/SplineIntegrator.kt | 4 +- .../kmath/integration/UnivariateIntegrand.kt | 23 ++-- .../optimization/FunctionOptimization.kt | 10 +- .../kmath/optimization/OptimizationProblem.kt | 43 +++----- .../kscience/kmath/optimization/XYFit.kt | 21 ++-- .../kmath/optimization/minuit/Range.kt | 32 ------ .../kscience/kmath/optimization/qow/QowFit.kt | 6 +- 15 files changed, 119 insertions(+), 166 deletions(-) delete mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt diff --git a/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt b/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt index 5e64235e3..5660c76e8 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt @@ -8,7 +8,6 @@ package space.kscience.kmath.commons.fit import kotlinx.html.br import kotlinx.html.h3 import space.kscience.kmath.commons.optimization.chiSquared -import space.kscience.kmath.commons.optimization.minimize import space.kscience.kmath.distributions.NormalDistribution import space.kscience.kmath.expressions.symbol import space.kscience.kmath.optimization.FunctionOptimization diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt index 6bde14627..282f1670d 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt @@ -13,7 +13,6 @@ import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunctionGradient import org.apache.commons.math3.optim.nonlinear.scalar.gradient.NonLinearConjugateGradientOptimizer import space.kscience.kmath.expressions.derivative import space.kscience.kmath.expressions.withSymbols -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.optimization.* import kotlin.reflect.KClass @@ -21,9 +20,15 @@ import kotlin.reflect.KClass public operator fun PointValuePair.component1(): DoubleArray = point public operator fun PointValuePair.component2(): Double = value -public class CMOptimizerFactory(public val optimizerBuilder: () -> MultivariateOptimizer) : OptimizationFeature +public class CMOptimizer(public val optimizerBuilder: () -> MultivariateOptimizer): OptimizationFeature{ + override fun toString(): String = "CMOptimizer($optimizerBuilder)" +} + public class CMOptimizerData(public val data: List) : OptimizationFeature { public constructor(vararg data: OptimizationData) : this(data.toList()) + + override fun toString(): String = "CMOptimizerData($data)" + } @OptIn(UnstableKMathAPI::class) @@ -31,59 +36,69 @@ public class CMOptimization : Optimizer> { override suspend fun process( problem: FunctionOptimization, - ): FunctionOptimization = withSymbols(problem.parameters) { - val convergenceChecker: ConvergenceChecker = SimpleValueChecker( - DEFAULT_RELATIVE_TOLERANCE, - DEFAULT_ABSOLUTE_TOLERANCE, - DEFAULT_MAX_ITER - ) + ): FunctionOptimization { + val startPoint = problem.getFeature>()?.point + ?: error("Starting point not defined in $problem") - val cmOptimizer: MultivariateOptimizer = problem.getFeature()?.optimizerBuilder?.invoke() - ?: NonLinearConjugateGradientOptimizer( - NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, - convergenceChecker + val parameters = problem.getFeature()?.symbols + ?: problem.getFeature>()?.point?.keys + ?:startPoint.keys + + + withSymbols(parameters) { + val convergenceChecker: ConvergenceChecker = SimpleValueChecker( + DEFAULT_RELATIVE_TOLERANCE, + DEFAULT_ABSOLUTE_TOLERANCE, + DEFAULT_MAX_ITER ) - val optimizationData: HashMap, OptimizationData> = HashMap() + val cmOptimizer: MultivariateOptimizer = problem.getFeature()?.optimizerBuilder?.invoke() + ?: NonLinearConjugateGradientOptimizer( + NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, + convergenceChecker + ) - fun addOptimizationData(data: OptimizationData) { - optimizationData[data::class] = data - } + val optimizationData: HashMap, OptimizationData> = HashMap() - addOptimizationData(MaxEval.unlimited()) - addOptimizationData(InitialGuess(problem.initialGuess.toDoubleArray())) - - fun exportOptimizationData(): List = optimizationData.values.toList() - - val objectiveFunction = ObjectiveFunction { - val args = problem.initialGuess + it.toMap() - problem.expression(args) - } - addOptimizationData(objectiveFunction) - - val gradientFunction = ObjectiveFunctionGradient { - val args = problem.initialGuess + it.toMap() - DoubleArray(symbols.size) { index -> - problem.expression.derivative(symbols[index])(args) + fun addOptimizationData(data: OptimizationData) { + optimizationData[data::class] = data } - } - addOptimizationData(gradientFunction) - val logger = problem.getFeature() + addOptimizationData(MaxEval.unlimited()) + addOptimizationData(InitialGuess(startPoint.toDoubleArray())) - for (feature in problem.features) { - when (feature) { - is CMOptimizerData -> feature.data.forEach { addOptimizationData(it) } - is FunctionOptimizationTarget -> when(feature){ - FunctionOptimizationTarget.MAXIMIZE -> addOptimizationData(GoalType.MAXIMIZE) - FunctionOptimizationTarget.MINIMIZE -> addOptimizationData(GoalType.MINIMIZE) + fun exportOptimizationData(): List = optimizationData.values.toList() + + val objectiveFunction = ObjectiveFunction { + val args = startPoint + it.toMap() + problem.expression(args) + } + addOptimizationData(objectiveFunction) + + val gradientFunction = ObjectiveFunctionGradient { + val args = startPoint + it.toMap() + DoubleArray(symbols.size) { index -> + problem.expression.derivative(symbols[index])(args) } - else -> logger?.log { "The feature $feature is unused in optimization" } } - } + addOptimizationData(gradientFunction) - val (point, value) = cmOptimizer.optimize(*optimizationData.values.toTypedArray()) - return problem.withFeatures(FunctionOptimizationResult(point.toMap(), value)) + val logger = problem.getFeature() + + for (feature in problem.features) { + when (feature) { + is CMOptimizerData -> feature.data.forEach { addOptimizationData(it) } + is FunctionOptimizationTarget -> when (feature) { + FunctionOptimizationTarget.MAXIMIZE -> addOptimizationData(GoalType.MAXIMIZE) + FunctionOptimizationTarget.MINIMIZE -> addOptimizationData(GoalType.MINIMIZE) + } + else -> logger?.log { "The feature $feature is unused in optimization" } + } + } + + val (point, value) = cmOptimizer.optimize(*optimizationData.values.toTypedArray()) + return problem.withFeatures(OptimizationResult(point.toMap()), OptimizationValue(value)) + } } public companion object { diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt index 9c0089b3d..f1095ef73 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt @@ -9,7 +9,7 @@ import org.apache.commons.math3.analysis.differentiation.DerivativeStructure import space.kscience.kmath.commons.expressions.DerivativeStructureField import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.optimization.* import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.asBuffer diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt index ea98e88b1..7199de888 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt @@ -5,10 +5,10 @@ package space.kscience.kmath.data -import space.kscience.kmath.misc.Symbol -import space.kscience.kmath.misc.Symbol.Companion.z +import space.kscience.kmath.data.XYErrorColumnarData.Companion +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.symbol import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.misc.symbol import space.kscience.kmath.structures.Buffer diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt index 283f97557..f794b075f 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt @@ -51,7 +51,7 @@ public class GaussIntegrator( } } - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand = with(algebra) { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand = with(algebra) { val f = integrand.function val (points, weights) = buildRule(integrand) var res = zero @@ -95,7 +95,7 @@ public fun GaussIntegrator.integrate( val ranges = UnivariateIntegrandRanges( (0 until intervals).map { i -> (range.start + rangeSize * i)..(range.start + rangeSize * (i + 1)) to order } ) - return integrate( + return process( UnivariateIntegrand( function, IntegrationRange(range), diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt index f9c26e88b..dcf711c3b 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt @@ -5,18 +5,20 @@ package space.kscience.kmath.integration +import space.kscience.kmath.misc.FeatureSet +import space.kscience.kmath.misc.Featured import kotlin.reflect.KClass public interface IntegrandFeature { override fun toString(): String } -public interface Integrand { - public val features: Set - public fun getFeature(type: KClass): T? +public interface Integrand : Featured { + public val features: FeatureSet + override fun getFeature(type: KClass): T? = features.getFeature(type) } -public inline fun Integrand.getFeature(): T? = getFeature(T::class) +public inline fun Integrand.getFeature(): T? = getFeature(T::class) public class IntegrandValue(public val value: T) : IntegrandFeature { override fun toString(): String = "Value($value)" diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt index 5ba411bf9..96b81aaa6 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/MultivariateIntegrand.kt @@ -6,29 +6,21 @@ package space.kscience.kmath.integration import space.kscience.kmath.linear.Point -import kotlin.reflect.KClass +import space.kscience.kmath.misc.FeatureSet public class MultivariateIntegrand internal constructor( - private val featureMap: Map, IntegrandFeature>, + override val features: FeatureSet, public val function: (Point) -> T, ) : Integrand { - override val features: Set get() = featureMap.values.toSet() - - @Suppress("UNCHECKED_CAST") - override fun getFeature(type: KClass): T? = featureMap[type] as? T - - public operator fun plus(pair: Pair, F>): MultivariateIntegrand = - MultivariateIntegrand(featureMap + pair, function) - public operator fun plus(feature: F): MultivariateIntegrand = - plus(feature::class to feature) + MultivariateIntegrand(features.with(feature), function) } @Suppress("FunctionName") public fun MultivariateIntegrand( vararg features: IntegrandFeature, function: (Point) -> T, -): MultivariateIntegrand = MultivariateIntegrand(features.associateBy { it::class }, function) +): MultivariateIntegrand = MultivariateIntegrand(FeatureSet.of(*features), function) public val MultivariateIntegrand.value: T? get() = getFeature>()?.value diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt index baa9d4af8..77f01101a 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SimpsonIntegrator.kt @@ -43,7 +43,7 @@ public class SimpsonIntegrator( return res } - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val ranges = integrand.getFeature() return if (ranges != null) { val res = algebra.sum(ranges.ranges.map { integrateRange(integrand, it.first, it.second) }) @@ -89,7 +89,7 @@ public object DoubleSimpsonIntegrator : UnivariateIntegrator { return res } - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val ranges = integrand.getFeature() return if (ranges != null) { val res = ranges.ranges.sumOf { integrateRange(integrand, it.first, it.second) } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt index 23d7bdd8d..54fa29e83 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/SplineIntegrator.kt @@ -53,7 +53,7 @@ public class SplineIntegrator>( public val algebra: Field, public val bufferFactory: MutableBufferFactory, ) : UnivariateIntegrator { - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand = algebra { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand = algebra { val range = integrand.getFeature()?.range ?: 0.0..1.0 val interpolator: PolynomialInterpolator = SplineInterpolator(algebra, bufferFactory) @@ -80,7 +80,7 @@ public class SplineIntegrator>( */ @UnstableKMathAPI public object DoubleSplineIntegrator : UnivariateIntegrator { - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val range = integrand.getFeature()?.range ?: 0.0..1.0 val interpolator: PolynomialInterpolator = SplineInterpolator(DoubleField, ::DoubleBuffer) diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt index e265f54e8..82fed30cd 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/UnivariateIntegrand.kt @@ -5,33 +5,24 @@ package space.kscience.kmath.integration +import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.DoubleBuffer -import kotlin.reflect.KClass public class UnivariateIntegrand internal constructor( - private val featureMap: Map, IntegrandFeature>, + override val features: FeatureSet, public val function: (Double) -> T, ) : Integrand { - - override val features: Set get() = featureMap.values.toSet() - - @Suppress("UNCHECKED_CAST") - override fun getFeature(type: KClass): T? = featureMap[type] as? T - - public operator fun plus(pair: Pair, F>): UnivariateIntegrand = - UnivariateIntegrand(featureMap + pair, function) - public operator fun plus(feature: F): UnivariateIntegrand = - plus(feature::class to feature) + UnivariateIntegrand(features.with(feature), function) } @Suppress("FunctionName") public fun UnivariateIntegrand( function: (Double) -> T, vararg features: IntegrandFeature, -): UnivariateIntegrand = UnivariateIntegrand(features.associateBy { it::class }, function) +): UnivariateIntegrand = UnivariateIntegrand(FeatureSet.of(*features), function) public typealias UnivariateIntegrator = Integrator> @@ -79,7 +70,7 @@ public val UnivariateIntegrand.value: T get() = valueOrNull ?: erro public fun UnivariateIntegrator.integrate( vararg features: IntegrandFeature, function: (Double) -> T, -): UnivariateIntegrand = integrate(UnivariateIntegrand(function, *features)) +): UnivariateIntegrand = process(UnivariateIntegrand(function, *features)) /** * A shortcut method to integrate a [function] in [range] with additional [features]. @@ -90,7 +81,7 @@ public fun UnivariateIntegrator.integrate( range: ClosedRange, vararg features: IntegrandFeature, function: (Double) -> T, -): UnivariateIntegrand = integrate(UnivariateIntegrand(function, IntegrationRange(range), *features)) +): UnivariateIntegrand = process(UnivariateIntegrand(function, IntegrationRange(range), *features)) /** * A shortcut method to integrate a [function] in [range] with additional [features]. @@ -107,5 +98,5 @@ public fun UnivariateIntegrator.integrate( featureBuilder() add(IntegrationRange(range)) } - return integrate(UnivariateIntegrand(function, *features.toTypedArray())) + return process(UnivariateIntegrand(function, *features.toTypedArray())) } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index db613e236..e5edb17e0 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -10,12 +10,13 @@ import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.ExpressionAlgebra import space.kscience.kmath.misc.FeatureSet -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.indices -public class FunctionOptimizationResult(point: Map, public val value: T) : OptimizationResult(point) +public class OptimizationValue(public val value: T) : OptimizationFeature{ + override fun toString(): String = "Value($value)" +} public enum class FunctionOptimizationTarget : OptimizationFeature { MAXIMIZE, @@ -25,9 +26,8 @@ public enum class FunctionOptimizationTarget : OptimizationFeature { public class FunctionOptimization( override val features: FeatureSet, public val expression: DifferentiableExpression>, - public val initialGuess: Map, - public val parameters: Collection, ) : OptimizationProblem{ + public companion object{ /** * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation @@ -65,7 +65,5 @@ public fun FunctionOptimization.withFeatures( ): FunctionOptimization = FunctionOptimization( features.with(*newFeature), expression, - initialGuess, - parameters ) diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt index 0d2e3cb83..53b6af144 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -5,13 +5,15 @@ package space.kscience.kmath.optimization +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.Featured import space.kscience.kmath.misc.Loggable -import space.kscience.kmath.misc.Symbol import kotlin.reflect.KClass -public interface OptimizationFeature +public interface OptimizationFeature { + override fun toString(): String +} public interface OptimizationProblem : Featured { public val features: FeatureSet @@ -20,31 +22,22 @@ public interface OptimizationProblem : Featured { public inline fun OptimizationProblem.getFeature(): T? = getFeature(T::class) -public open class OptimizationResult(public val point: Map) : OptimizationFeature +public open class OptimizationStartPoint(public val point: Map) : OptimizationFeature { + override fun toString(): String = "StartPoint($point)" +} -public class OptimizationLog(private val loggable: Loggable) : Loggable by loggable, OptimizationFeature +public open class OptimizationResult(public val point: Map) : OptimizationFeature { + override fun toString(): String = "Result($point)" +} + +public class OptimizationLog(private val loggable: Loggable) : Loggable by loggable, OptimizationFeature { + override fun toString(): String = "Log($loggable)" +} + +public class OptimizationParameters(public val symbols: List): OptimizationFeature{ + override fun toString(): String = "Parameters($symbols)" +} -//public class OptimizationResult( -// public val point: Map, -// public val value: T, -// public val features: Set = emptySet(), -//) { -// override fun toString(): String { -// return "OptimizationResult(point=$point, value=$value)" -// } -//} -// -//public operator fun OptimizationResult.plus( -// feature: OptimizationFeature, -//): OptimizationResult = OptimizationResult(point, value, features + feature) -//public fun interface OptimizationProblemFactory> { -// public fun build(symbols: List): P -//} -// -//public operator fun > OptimizationProblemFactory.invoke( -// symbols: List, -// block: P.() -> Unit, -//): P = build(symbols).apply(block) public interface Optimizer

{ public suspend fun process(problem: P): P diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index 637746a27..050f95a10 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -10,8 +10,6 @@ import space.kscience.kmath.expressions.* import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.Field -import space.kscience.kmath.structures.Buffer -import space.kscience.kmath.structures.indices @UnstableKMathAPI public interface XYFit : OptimizationProblem { @@ -59,15 +57,16 @@ public interface XYFit : OptimizationProblem { //} /** - * Optimize differentiable expression using specific [OptimizationProblemFactory] + * Optimize differentiable expression using specific [Optimizer] */ public suspend fun > DifferentiableExpression>.optimizeWith( - factory: OptimizationProblemFactory, - vararg symbols: Symbol, - configuration: F.() -> Unit, -): OptimizationResult { - require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = factory(symbols.toList(), configuration) - problem.function(this) - return problem.optimize() + optimizer: Optimizer, + startingPoint: Map, + vararg features: OptimizationFeature +): OptimizationProblem { +// require(startingPoint.isNotEmpty()) { "Must provide a list of symbols for optimization" } +// val problem = factory(symbols.toList(), configuration) +// problem.function(this) +// return problem.optimize() + val problem = FunctionOptimization() } \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt deleted file mode 100644 index ebeedf7e8..000000000 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Range.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt index d611adf50..2ae33d82f 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt @@ -7,12 +7,8 @@ package space.kscience.kmath.optimization.qow import space.kscience.kmath.data.ColumnarData import space.kscience.kmath.data.XYErrorColumnarData -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.expressions.SymbolIndexer -import space.kscience.kmath.expressions.derivative +import space.kscience.kmath.expressions.* import space.kscience.kmath.linear.* -import space.kscience.kmath.misc.Symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.Field From c240fa4a9efb55116571622fa57a5c3332a1a166 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 25 May 2021 16:42:23 +0300 Subject: [PATCH 04/13] [WIP] optimization refactoring --- .../kscience/kmath/data/XYColumnarData.kt | 21 +++++ .../kscience/kmath/expressions/Symbol.kt | 16 +++- .../optimization/FunctionOptimization.kt | 17 +++- .../kmath/optimization/OptimizationProblem.kt | 1 + .../kscience/kmath/optimization/XYFit.kt | 72 --------------- .../kmath/optimization/XYOptimization.kt | 92 +++++++++++++++++++ .../kscience/kmath/optimization/qow/QowFit.kt | 4 +- 7 files changed, 141 insertions(+), 82 deletions(-) delete mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt index 08bfd3ca3..2dc82c02e 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt @@ -45,6 +45,27 @@ public fun XYColumnarData(x: Buffer, y: Buffer): XYColum } } +/** + * Represent a [ColumnarData] as an [XYColumnarData]. The presence or respective columns is checked on creation. + */ +@UnstableKMathAPI +public fun ColumnarData.asXYData( + xSymbol: Symbol, + ySymbol: Symbol, +): XYColumnarData = object : XYColumnarData { + init { + requireNotNull(this@asXYData[xSymbol]){"The column with name $xSymbol is not present in $this"} + requireNotNull(this@asXYData[ySymbol]){"The column with name $ySymbol is not present in $this"} + } + override val size: Int get() = this@asXYData.size + override val x: Buffer get() = this@asXYData[xSymbol]!! + override val y: Buffer get() = this@asXYData[ySymbol]!! + override fun get(symbol: Symbol): Buffer? = when (symbol) { + Symbol.x -> x + Symbol.y -> y + else -> this@asXYData.get(symbol) + } +} /** * A zero-copy method to represent a [Structure2D] as a two-column x-y data. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt index 74dc7aedc..4cbdbf55b 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Symbol.kt @@ -19,9 +19,12 @@ public interface Symbol : MST { public val identity: String public companion object { - public val x: StringSymbol = StringSymbol("x") - public val y: StringSymbol = StringSymbol("y") - public val z: StringSymbol = StringSymbol("z") + public val x: Symbol = Symbol("x") + public val xError: Symbol = Symbol("x.error") + public val y: Symbol = Symbol("y") + public val yError: Symbol = Symbol("y.error") + public val z: Symbol = Symbol("z") + public val zError: Symbol = Symbol("z.error") } } @@ -29,10 +32,15 @@ public interface Symbol : MST { * A [Symbol] with a [String] identity */ @JvmInline -public value class StringSymbol(override val identity: String) : Symbol { +internal value class StringSymbol(override val identity: String) : Symbol { override fun toString(): String = identity } +/** + * Create s Symbols with a string identity + */ +public fun Symbol(identity: String): Symbol = StringSymbol(identity) + /** * A delegate to create a symbol with a string identity in this scope */ diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index e5edb17e0..2c70da308 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -5,10 +5,7 @@ package space.kscience.kmath.optimization -import space.kscience.kmath.expressions.AutoDiffProcessor -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.expressions.ExpressionAlgebra +import space.kscience.kmath.expressions.* import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.structures.Buffer @@ -67,3 +64,15 @@ public fun FunctionOptimization.withFeatures( expression, ) +/** + * Optimize differentiable expression using specific [optimizer] form given [startingPoint] + */ +public suspend fun DifferentiableExpression>.optimizeWith( + optimizer: Optimizer>, + startingPoint: Map, + vararg features: OptimizationFeature, +): FunctionOptimization { + val problem = FunctionOptimization(FeatureSet.of(OptimizationStartPoint(startingPoint), *features), this) + return optimizer.process(problem) +} + diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt index 53b6af144..2594fa182 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -35,6 +35,7 @@ public class OptimizationLog(private val loggable: Loggable) : Loggable by logga } public class OptimizationParameters(public val symbols: List): OptimizationFeature{ + public constructor(vararg symbols: Symbol) : this(listOf(*symbols)) override fun toString(): String = "Parameters($symbols)" } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt deleted file mode 100644 index 050f95a10..000000000 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.optimization - -import space.kscience.kmath.data.ColumnarData -import space.kscience.kmath.expressions.* -import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.ExtendedField -import space.kscience.kmath.operations.Field - -@UnstableKMathAPI -public interface XYFit : OptimizationProblem { - - public val algebra: Field - - /** - * Set X-Y data for this fit optionally including x and y errors - */ - public fun data( - dataSet: ColumnarData, - xSymbol: Symbol, - ySymbol: Symbol, - xErrSymbol: Symbol? = null, - yErrSymbol: Symbol? = null, - ) - - public fun model(model: (T) -> DifferentiableExpression) - - /** - * Set the differentiable model for this fit - */ - public fun model( - autoDiff: AutoDiffProcessor>, - modelFunction: A.(I) -> I, - ): Unit where A : ExtendedField, A : ExpressionAlgebra = model { arg -> - autoDiff.process { modelFunction(const(arg)) } - } -} - -// -///** -// * Define a chi-squared-based objective function -// */ -//public fun FunctionOptimization.chiSquared( -// autoDiff: AutoDiffProcessor>, -// x: Buffer, -// y: Buffer, -// yErr: Buffer, -// model: A.(I) -> I, -//) where A : ExtendedField, A : ExpressionAlgebra { -// val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) -// function(chiSquared) -// maximize = false -//} - -/** - * Optimize differentiable expression using specific [Optimizer] - */ -public suspend fun > DifferentiableExpression>.optimizeWith( - optimizer: Optimizer, - startingPoint: Map, - vararg features: OptimizationFeature -): OptimizationProblem { -// require(startingPoint.isNotEmpty()) { "Must provide a list of symbols for optimization" } -// val problem = factory(symbols.toList(), configuration) -// problem.function(this) -// return problem.optimize() - val problem = FunctionOptimization() -} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt new file mode 100644 index 000000000..dda95fcd7 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +@file:OptIn(UnstableKMathAPI::class) + +package space.kscience.kmath.optimization + +import space.kscience.kmath.data.XYColumnarData +import space.kscience.kmath.expressions.DifferentiableExpression +import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.misc.FeatureSet +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.Field +import space.kscience.kmath.operations.invoke + +public fun interface PointToCurveDistance { + public fun distance(problem: XYOptimization, index: Int): DifferentiableExpression> + + public companion object { + public fun byY( + algebra: Field, + ): PointToCurveDistance = PointToCurveDistance { problem, index -> + algebra { + val x = problem.data.x[index] + val y = problem.data.y[index] + + val model = problem.model(args + (Symbol.x to x)) + model - y + } + } + + +// val default = PointToCurveDistance{args, data, index -> +// +// } + } +} + + +public class XYOptimization( + override val features: FeatureSet, + public val data: XYColumnarData, + public val model: DifferentiableExpression>, +) : OptimizationProblem + +// +//@UnstableKMathAPI +//public interface XYFit : OptimizationProblem { +// +// public val algebra: Field +// +// /** +// * Set X-Y data for this fit optionally including x and y errors +// */ +// public fun data( +// dataSet: ColumnarData, +// xSymbol: Symbol, +// ySymbol: Symbol, +// xErrSymbol: Symbol? = null, +// yErrSymbol: Symbol? = null, +// ) +// +// public fun model(model: (T) -> DifferentiableExpression) +// +// /** +// * Set the differentiable model for this fit +// */ +// public fun model( +// autoDiff: AutoDiffProcessor>, +// modelFunction: A.(I) -> I, +// ): Unit where A : ExtendedField, A : ExpressionAlgebra = model { arg -> +// autoDiff.process { modelFunction(const(arg)) } +// } +//} + +// +///** +// * Define a chi-squared-based objective function +// */ +//public fun FunctionOptimization.chiSquared( +// autoDiff: AutoDiffProcessor>, +// x: Buffer, +// y: Buffer, +// yErr: Buffer, +// model: A.(I) -> I, +//) where A : ExtendedField, A : ExpressionAlgebra { +// val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) +// function(chiSquared) +// maximize = false +//} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt index 2ae33d82f..c78aef401 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt @@ -15,7 +15,7 @@ import space.kscience.kmath.operations.Field import space.kscience.kmath.optimization.OptimizationFeature import space.kscience.kmath.optimization.OptimizationProblemFactory import space.kscience.kmath.optimization.OptimizationResult -import space.kscience.kmath.optimization.XYFit +import space.kscience.kmath.optimization.XYOptimization import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.DoubleL2Norm import kotlin.math.pow @@ -28,7 +28,7 @@ public class QowFit( override val symbols: List, private val space: LinearSpace, private val solver: LinearSolver, -) : XYFit, SymbolIndexer { +) : XYOptimization, SymbolIndexer { private var logger: FitLogger? = null From c8621ee5b759cc364f94e1df3fbdf3262e6a7878 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 25 May 2021 21:11:57 +0300 Subject: [PATCH 05/13] [WIP] optimization refactoring --- .../commons/optimization/CMOptimization.kt | 2 +- .../optimization/FunctionOptimization.kt | 4 +- .../kmath/optimization/OptimizationProblem.kt | 4 -- .../kscience/kmath/optimization/Optimizer.kt | 10 +++ .../kmath/optimization/XYOptimization.kt | 62 +++++++++++++------ 5 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt index 549683d60..2faee1f5d 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt @@ -35,7 +35,7 @@ public class CMOptimizerData(public val data: List) : Optimiza @OptIn(UnstableKMathAPI::class) public class CMOptimization : Optimizer> { - override suspend fun process( + override suspend fun optimize( problem: FunctionOptimization, ): FunctionOptimization { val startPoint = problem.getFeature>()?.point diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index ee75bfbd6..62288242a 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -67,12 +67,12 @@ public fun FunctionOptimization.withFeatures( /** * Optimize differentiable expression using specific [optimizer] form given [startingPoint] */ -public suspend fun DifferentiableExpression>.optimizeWith( +public suspend fun DifferentiableExpression.optimizeWith( optimizer: Optimizer>, startingPoint: Map, vararg features: OptimizationFeature, ): FunctionOptimization { val problem = FunctionOptimization(FeatureSet.of(OptimizationStartPoint(startingPoint), *features), this) - return optimizer.process(problem) + return optimizer.optimize(problem) } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt index 2594fa182..739ce8ca5 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -40,7 +40,3 @@ public class OptimizationParameters(public val symbols: List): Optimizat } -public interface Optimizer

{ - public suspend fun process(problem: P): P -} - diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt new file mode 100644 index 000000000..ad1a16007 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.optimization + +public interface Optimizer

{ + public suspend fun optimize(problem: P): P +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt index dda95fcd7..7dc17f6d9 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt @@ -12,39 +12,63 @@ import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.operations.Field -import space.kscience.kmath.operations.invoke +import kotlin.math.pow -public fun interface PointToCurveDistance { - public fun distance(problem: XYOptimization, index: Int): DifferentiableExpression> +public interface PointToCurveDistance : OptimizationFeature { + public fun distance(problem: XYOptimization, index: Int): DifferentiableExpression public companion object { - public fun byY( - algebra: Field, - ): PointToCurveDistance = PointToCurveDistance { problem, index -> - algebra { + public val byY: PointToCurveDistance = object : PointToCurveDistance { + override fun distance(problem: XYOptimization, index: Int): DifferentiableExpression { + val x = problem.data.x[index] val y = problem.data.y[index] - - val model = problem.model(args + (Symbol.x to x)) - model - y + return object : DifferentiableExpression { + override fun derivativeOrNull(symbols: List): Expression? = + problem.model.derivativeOrNull(symbols) + + override fun invoke(arguments: Map): Double = + problem.model(arguments + (Symbol.x to x)) - y + } } + + override fun toString(): String = "PointToCurveDistanceByY" + } - -// val default = PointToCurveDistance{args, data, index -> -// -// } } } - -public class XYOptimization( +public class XYOptimization( override val features: FeatureSet, - public val data: XYColumnarData, - public val model: DifferentiableExpression>, + public val data: XYColumnarData, + public val model: DifferentiableExpression, ) : OptimizationProblem + +public suspend fun Optimizer>.maximumLogLikelihood(problem: XYOptimization): XYOptimization { + val distanceBuilder = problem.getFeature() ?: PointToCurveDistance.byY + val likelihood: DifferentiableExpression = object : DifferentiableExpression { + override fun derivativeOrNull(symbols: List): Expression? { + TODO("Not yet implemented") + } + + override fun invoke(arguments: Map): Double { + var res = 0.0 + for (index in 0 until problem.data.size) { + val d = distanceBuilder.distance(problem, index).invoke(arguments) + val sigma: Double = TODO() + res -= (d / sigma).pow(2) + } + return res + } + + } + val functionOptimization = FunctionOptimization(problem.features, likelihood) + val result = optimize(functionOptimization) + return XYOptimization(result.features, problem.data, problem.model) +} + // //@UnstableKMathAPI //public interface XYFit : OptimizationProblem { From 7f32348e7a1c1701288c35db3c0dd020baa6726b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 2 Jun 2021 21:10:05 +0300 Subject: [PATCH 06/13] Slight adjustment to tensor internals --- .../kmath/tensors/core/DoubleTensorAlgebra.kt | 60 ++++++++----- .../kmath/tensors/core/internal/linUtils.kt | 87 ++++++++++--------- 2 files changed, 84 insertions(+), 63 deletions(-) diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt index 1fd46bd57..f854beb29 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/DoubleTensorAlgebra.kt @@ -5,8 +5,10 @@ package space.kscience.kmath.tensors.core +import space.kscience.kmath.nd.MutableStructure2D import space.kscience.kmath.nd.as1D import space.kscience.kmath.nd.as2D +import space.kscience.kmath.structures.indices import space.kscience.kmath.tensors.api.AnalyticTensorAlgebra import space.kscience.kmath.tensors.api.LinearOpsTensorAlgebra import space.kscience.kmath.tensors.api.Tensor @@ -813,28 +815,32 @@ public open class DoubleTensorAlgebra : val sTensor = zeros(commonShape + intArrayOf(min(n, m))) val vTensor = zeros(commonShape + intArrayOf(min(n, m), m)) - tensor.matrixSequence() - .zip( - uTensor.matrixSequence() - .zip( - sTensor.vectorSequence() - .zip(vTensor.matrixSequence()) - ) - ).forEach { (matrix, USV) -> - val matrixSize = matrix.shape.reduce { acc, i -> acc * i } - val curMatrix = DoubleTensor( - matrix.shape, - matrix.mutableBuffer.array().slice(matrix.bufferStart until matrix.bufferStart + matrixSize) - .toDoubleArray() - ) - svdHelper(curMatrix, USV, m, n, epsilon) - } + val matrices = tensor.matrices + val uTensors = uTensor.matrices + val sTensorVectors = sTensor.vectors + val vTensors = vTensor.matrices + + for (index in matrices.indices) { + val matrix = matrices[index] + val usv = Triple( + uTensors[index], + sTensorVectors[index], + vTensors[index] + ) + val matrixSize = matrix.shape.reduce { acc, i -> acc * i } + val curMatrix = DoubleTensor( + matrix.shape, + matrix.mutableBuffer.array() + .slice(matrix.bufferStart until matrix.bufferStart + matrixSize) + .toDoubleArray() + ) + svdHelper(curMatrix, usv, m, n, epsilon) + } return Triple(uTensor.transpose(), sTensor, vTensor.transpose()) } - override fun Tensor.symEig(): Pair = - symEig(epsilon = 1e-15) + override fun Tensor.symEig(): Pair = symEig(epsilon = 1e-15) /** * Returns eigenvalues and eigenvectors of a real symmetric matrix input or a batch of real symmetric matrices, @@ -846,12 +852,26 @@ public open class DoubleTensorAlgebra : */ public fun Tensor.symEig(epsilon: Double): Pair { checkSymmetric(tensor, epsilon) + + fun MutableStructure2D.cleanSym(n: Int) { + for (i in 0 until n) { + for (j in 0 until n) { + if (i == j) { + this[i, j] = sign(this[i, j]) + } else { + this[i, j] = 0.0 + } + } + } + } + val (u, s, v) = tensor.svd(epsilon) val shp = s.shape + intArrayOf(1) val utv = u.transpose() dot v val n = s.shape.last() - for (matrix in utv.matrixSequence()) - cleanSymHelper(matrix.as2D(), n) + for (matrix in utv.matrixSequence()) { + matrix.as2D().cleanSym(n) + } val eig = (utv dot s.view(shp)).view(s.shape) return eig to v diff --git a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt index 7d3617547..0434bf96f 100644 --- a/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt +++ b/kmath-tensors/src/commonMain/kotlin/space/kscience/kmath/tensors/core/internal/linUtils.kt @@ -10,41 +10,54 @@ import space.kscience.kmath.nd.MutableStructure2D import space.kscience.kmath.nd.as1D import space.kscience.kmath.nd.as2D import space.kscience.kmath.operations.invoke -import space.kscience.kmath.tensors.core.* +import space.kscience.kmath.structures.VirtualBuffer +import space.kscience.kmath.structures.asSequence +import space.kscience.kmath.tensors.core.BufferedTensor +import space.kscience.kmath.tensors.core.DoubleTensor import space.kscience.kmath.tensors.core.DoubleTensorAlgebra -import space.kscience.kmath.tensors.core.DoubleTensorAlgebra.Companion.valueOrNull +import space.kscience.kmath.tensors.core.IntTensor import kotlin.math.abs import kotlin.math.min -import kotlin.math.sign import kotlin.math.sqrt +internal val BufferedTensor.vectors: VirtualBuffer> + get() { + val n = shape.size + val vectorOffset = shape[n - 1] + val vectorShape = intArrayOf(shape.last()) -internal fun BufferedTensor.vectorSequence(): Sequence> = sequence { - val n = shape.size - val vectorOffset = shape[n - 1] - val vectorShape = intArrayOf(shape.last()) - for (offset in 0 until numElements step vectorOffset) { - val vector = BufferedTensor(vectorShape, mutableBuffer, bufferStart + offset) - yield(vector) + return VirtualBuffer(numElements / vectorOffset) { index -> + val offset = index * vectorOffset + BufferedTensor(vectorShape, mutableBuffer, bufferStart + offset) + } } -} -internal fun BufferedTensor.matrixSequence(): Sequence> = sequence { - val n = shape.size - check(n >= 2) { "Expected tensor with 2 or more dimensions, got size $n" } - val matrixOffset = shape[n - 1] * shape[n - 2] - val matrixShape = intArrayOf(shape[n - 2], shape[n - 1]) - for (offset in 0 until numElements step matrixOffset) { - val matrix = BufferedTensor(matrixShape, mutableBuffer, bufferStart + offset) - yield(matrix) + +internal fun BufferedTensor.vectorSequence(): Sequence> = vectors.asSequence() + +/** + * A random access alternative to [matrixSequence] + */ +internal val BufferedTensor.matrices: VirtualBuffer> + get() { + val n = shape.size + check(n >= 2) { "Expected tensor with 2 or more dimensions, got size $n" } + val matrixOffset = shape[n - 1] * shape[n - 2] + val matrixShape = intArrayOf(shape[n - 2], shape[n - 1]) + + return VirtualBuffer(numElements / matrixOffset) { index -> + val offset = index * matrixOffset + BufferedTensor(matrixShape, mutableBuffer, bufferStart + offset) + } } -} + +internal fun BufferedTensor.matrixSequence(): Sequence> = matrices.asSequence() internal fun dotHelper( a: MutableStructure2D, b: MutableStructure2D, res: MutableStructure2D, - l: Int, m: Int, n: Int + l: Int, m: Int, n: Int, ) { for (i in 0 until l) { for (j in 0 until n) { @@ -60,7 +73,7 @@ internal fun dotHelper( internal fun luHelper( lu: MutableStructure2D, pivots: MutableStructure1D, - epsilon: Double + epsilon: Double, ): Boolean { val m = lu.rowNum @@ -122,7 +135,7 @@ internal fun BufferedTensor.setUpPivots(): IntTensor { internal fun DoubleTensorAlgebra.computeLU( tensor: DoubleTensor, - epsilon: Double + epsilon: Double, ): Pair? { checkSquareMatrix(tensor.shape) @@ -139,7 +152,7 @@ internal fun DoubleTensorAlgebra.computeLU( internal fun pivInit( p: MutableStructure2D, pivot: MutableStructure1D, - n: Int + n: Int, ) { for (i in 0 until n) { p[i, pivot[i]] = 1.0 @@ -150,7 +163,7 @@ internal fun luPivotHelper( l: MutableStructure2D, u: MutableStructure2D, lu: MutableStructure2D, - n: Int + n: Int, ) { for (i in 0 until n) { for (j in 0 until n) { @@ -170,7 +183,7 @@ internal fun luPivotHelper( internal fun choleskyHelper( a: MutableStructure2D, l: MutableStructure2D, - n: Int + n: Int, ) { for (i in 0 until n) { for (j in 0 until i) { @@ -200,7 +213,7 @@ internal fun luMatrixDet(lu: MutableStructure2D, pivots: MutableStructur internal fun luMatrixInv( lu: MutableStructure2D, pivots: MutableStructure1D, - invMatrix: MutableStructure2D + invMatrix: MutableStructure2D, ) { val m = lu.shape[0] @@ -227,7 +240,7 @@ internal fun luMatrixInv( internal fun DoubleTensorAlgebra.qrHelper( matrix: DoubleTensor, q: DoubleTensor, - r: MutableStructure2D + r: MutableStructure2D, ) { checkSquareMatrix(matrix.shape) val n = matrix.shape[0] @@ -280,12 +293,11 @@ internal fun DoubleTensorAlgebra.svd1d(a: DoubleTensor, epsilon: Double = 1e-10) internal fun DoubleTensorAlgebra.svdHelper( matrix: DoubleTensor, - USV: Pair, Pair, BufferedTensor>>, - m: Int, n: Int, epsilon: Double + USV: Triple, BufferedTensor, BufferedTensor>, + m: Int, n: Int, epsilon: Double, ) { val res = ArrayList>(0) - val (matrixU, SV) = USV - val (matrixS, matrixV) = SV + val (matrixU, matrixS, matrixV) = USV for (k in 0 until min(n, m)) { var a = matrix.copy() @@ -329,14 +341,3 @@ internal fun DoubleTensorAlgebra.svdHelper( matrixV.mutableBuffer.array()[matrixV.bufferStart + i] = vBuffer[i] } } - -internal fun cleanSymHelper(matrix: MutableStructure2D, n: Int) { - for (i in 0 until n) - for (j in 0 until n) { - if (i == j) { - matrix[i, j] = sign(matrix[i, j]) - } else { - matrix[i, j] = 0.0 - } - } -} From 95c0b2d3f097dfaec47ab449004befce7e349472 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 8 Jun 2021 14:27:45 +0300 Subject: [PATCH 07/13] [WIP] optimization with QOW --- .gitignore | 2 + CHANGELOG.md | 3 + .../kscience/kmath/benchmarks/DotBenchmark.kt | 6 +- .../benchmarks/MatrixInverseBenchmark.kt | 4 +- .../kmath/ejml/codegen/ejmlCodegen.kt | 2 +- .../kscience/kmath/ast/kotlingradSupport.kt | 4 +- .../kotlin/space/kscience/kmath/ast/parser.kt | 4 +- .../kmath/asm/internal/mapIntrinsics.kt | 3 +- .../DerivativeStructureExpression.kt | 10 +- .../integration/CMGaussRuleIntegrator.kt | 6 +- .../kmath/commons/integration/CMIntegrator.kt | 2 +- .../kscience/kmath/commons/linear/CMMatrix.kt | 14 +- .../kscience/kmath/commons/linear/CMSolver.kt | 10 + .../{CMOptimization.kt => CMOptimizer.kt} | 66 +- .../kmath/commons/optimization/cmFit.kt | 75 -- .../commons/optimization/OptimizeTest.kt | 36 +- .../space/kscience/kmath/data/ColumnarData.kt | 3 + .../expressions/DifferentiableExpression.kt | 9 +- .../kscience/kmath/linear/LinearSolver.kt | 19 - .../kscience/kmath/linear/LinearSpace.kt | 30 +- .../kscience/kmath/linear/LupDecomposition.kt | 65 +- .../kscience/kmath/linear/MatrixBuilder.kt | 31 +- .../kscience/kmath/linear/MatrixWrapper.kt | 27 +- .../kscience/kmath/linear/VirtualMatrix.kt | 3 + .../space/kscience/kmath/linear/symmetric.kt | 34 - .../space/kscience/kmath/misc/Featured.kt | 27 +- .../space/kscience/kmath/misc/annotations.kt | 4 +- .../space/kscience/kmath/nd/StructureND.kt | 8 +- .../kmath/structures/BufferAccessor2D.kt | 18 +- .../kmath/linear/DoubleLUSolverTest.kt | 12 +- .../space/kscience/kmath/linear/MatrixTest.kt | 8 +- .../kmath/structures/NumberNDFieldTest.kt | 2 +- .../kscience/kmath/dimensions/Wrappers.kt | 2 +- .../space/kscience/kmath/ejml/_generated.kt | 995 ------------------ .../kscience/kmath/ejml/EjmlMatrixTest.kt | 6 +- .../space/kscience/kmath/real/RealMatrix.kt | 36 +- .../kotlin/space/kscience/kmath/real/dot.kt | 2 +- .../kaceince/kmath/real/DoubleMatrixTest.kt | 4 +- .../kaceince/kmath/real/DoubleVectorTest.kt | 2 +- .../kscience/kmath/integration/Integrand.kt | 5 +- .../kmath/kotlingrad/KotlingradExpression.kt | 31 +- .../optimization/FunctionOptimization.kt | 10 +- .../kmath/optimization/OptimizationBuilder.kt | 93 ++ .../kmath/optimization/OptimizationProblem.kt | 40 +- .../kscience/kmath/optimization/Optimizer.kt | 2 +- .../kmath/optimization/QowOptimizer.kt | 247 +++++ .../kmath/optimization/XYOptimization.kt | 115 +- .../kmath/optimization/qow => tmp}/QowFit.kt | 0 .../minuit/AnalyticalGradientCalculator.kt | 0 .../minuit/CombinedMinimizer.kt | 0 .../minuit/CombinedMinimumBuilder.kt | 1 + .../minuit/ContoursError.kt | 0 .../minuit/DavidonErrorUpdator.kt | 0 .../minuit/FunctionGradient.kt | 0 .../minuit/FunctionMinimum.kt | 1 + .../minuit/GradientCalculator.kt | 0 .../minuit/HessianGradientCalculator.kt | 0 .../minuit/InitialGradientCalculator.kt | 0 .../minuit/MINOSResult.kt | 0 .../minuit/MINUITFitter.kt | 0 .../minuit/MINUITPlugin.kt | 0 .../minuit/MINUITUtils.kt | 0 .../minuit/MinimumBuilder.kt | 2 + .../minuit/MinimumError.kt | 0 .../minuit/MinimumErrorUpdator.kt | 0 .../minuit/MinimumParameters.kt | 0 .../minuit/MinimumSeed.kt | 4 +- .../minuit/MinimumSeedGenerator.kt | 2 + .../minuit/MinimumState.kt | 0 .../optimization => tmp}/minuit/MinosError.kt | 0 .../minuit/MinuitParameter.kt | 0 .../minuit/MnAlgebraicSymMatrix.kt | 0 .../minuit/MnApplication.kt | 0 .../optimization => tmp}/minuit/MnContours.kt | 0 .../minuit/MnCovarianceSqueeze.kt | 0 .../optimization => tmp}/minuit/MnCross.kt | 0 .../optimization => tmp}/minuit/MnEigen.kt | 0 .../optimization => tmp}/minuit/MnFcn.kt | 0 .../minuit/MnFunctionCross.kt | 0 .../minuit/MnGlobalCorrelationCoeff.kt | 0 .../optimization => tmp}/minuit/MnHesse.kt | 0 .../minuit/MnLineSearch.kt | 0 .../minuit/MnMachinePrecision.kt | 0 .../optimization => tmp}/minuit/MnMigrad.kt | 0 .../optimization => tmp}/minuit/MnMinimize.kt | 0 .../optimization => tmp}/minuit/MnMinos.kt | 0 .../optimization => tmp}/minuit/MnParabola.kt | 0 .../minuit/MnParabolaFactory.kt | 0 .../minuit/MnParabolaPoint.kt | 0 .../minuit/MnParameterScan.kt | 0 .../optimization => tmp}/minuit/MnPlot.kt | 0 .../optimization => tmp}/minuit/MnPosDef.kt | 0 .../optimization => tmp}/minuit/MnPrint.kt | 0 .../optimization => tmp}/minuit/MnScan.kt | 0 .../minuit/MnSeedGenerator.kt | 1 + .../optimization => tmp}/minuit/MnSimplex.kt | 0 .../optimization => tmp}/minuit/MnStrategy.kt | 0 .../minuit/MnUserCovariance.kt | 0 .../optimization => tmp}/minuit/MnUserFcn.kt | 0 .../minuit/MnUserParameterState.kt | 0 .../minuit/MnUserParameters.kt | 0 .../minuit/MnUserTransformation.kt | 0 .../optimization => tmp}/minuit/MnUtils.kt | 0 .../minuit/ModularFunctionMinimizer.kt | 1 + .../minuit/NegativeG2LineSearch.kt | 0 .../minuit/Numerical2PGradientCalculator.kt | 0 .../minuit/ScanBuilder.kt | 1 + .../minuit/ScanMinimizer.kt | 0 .../minuit/SimplexBuilder.kt | 1 + .../minuit/SimplexMinimizer.kt | 0 .../minuit/SimplexParameters.kt | 0 .../minuit/SimplexSeedGenerator.kt | 1 + .../minuit/SinParameterTransformation.kt | 0 .../minuit/SqrtLowParameterTransformation.kt | 0 .../minuit/SqrtUpParameterTransformation.kt | 0 .../minuit/VariableMetricBuilder.kt | 1 + .../minuit/VariableMetricEDMEstimator.kt | 0 .../minuit/VariableMetricMinimizer.kt | 0 .../minuit/package-info.kt | 0 119 files changed, 804 insertions(+), 1349 deletions(-) rename kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/{CMOptimization.kt => CMOptimizer.kt} (63%) delete mode 100644 kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt delete mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt delete mode 100644 kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt create mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization/qow => tmp}/QowFit.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/AnalyticalGradientCalculator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/CombinedMinimizer.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/CombinedMinimumBuilder.kt (97%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/ContoursError.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/DavidonErrorUpdator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/FunctionGradient.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/FunctionMinimum.kt (99%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/GradientCalculator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/HessianGradientCalculator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/InitialGradientCalculator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MINOSResult.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MINUITFitter.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MINUITPlugin.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MINUITUtils.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumBuilder.kt (95%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumError.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumErrorUpdator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumParameters.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumSeed.kt (95%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumSeedGenerator.kt (95%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinimumState.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinosError.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MinuitParameter.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnAlgebraicSymMatrix.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnApplication.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnContours.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnCovarianceSqueeze.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnCross.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnEigen.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnFcn.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnFunctionCross.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnGlobalCorrelationCoeff.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnHesse.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnLineSearch.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnMachinePrecision.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnMigrad.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnMinimize.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnMinos.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnParabola.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnParabolaFactory.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnParabolaPoint.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnParameterScan.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnPlot.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnPosDef.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnPrint.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnScan.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnSeedGenerator.kt (98%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnSimplex.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnStrategy.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnUserCovariance.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnUserFcn.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnUserParameterState.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnUserParameters.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnUserTransformation.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/MnUtils.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/ModularFunctionMinimizer.kt (97%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/NegativeG2LineSearch.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/Numerical2PGradientCalculator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/ScanBuilder.kt (97%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/ScanMinimizer.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SimplexBuilder.kt (99%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SimplexMinimizer.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SimplexParameters.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SimplexSeedGenerator.kt (97%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SinParameterTransformation.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SqrtLowParameterTransformation.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/SqrtUpParameterTransformation.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/VariableMetricBuilder.kt (98%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/VariableMetricEDMEstimator.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/VariableMetricMinimizer.kt (100%) rename kmath-stat/src/commonMain/{kotlin/space/kscience/kmath/optimization => tmp}/minuit/package-info.kt (100%) diff --git a/.gitignore b/.gitignore index d6c4af4e3..c5903368f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ out/ # Generated by javac -h and runtime *.class *.log + +/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 524d2a1de..e35153d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Jupyter Notebook integration module (kmath-jupyter) - `@PerformancePitfall` annotation to mark possibly slow API - BigInt operation performance improvement and fixes by @zhelenskiy (#328) +- Unified architecture for Integration and Optimization using features. ### Changed - Exponential operations merged with hyperbolic functions @@ -35,6 +36,8 @@ - Remove Any restriction on polynomials - Add `out` variance to type parameters of `StructureND` and its implementations where possible - Rename `DifferentiableMstExpression` to `KotlingradExpression` +- `FeatureSet` now accepts only `Feature`. It is possible to override keys and use interfaces. +- Use `Symbol` factory function instead of `StringSymbol` ### Deprecated diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt index 2c5a03a97..698f1a702 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/DotBenchmark.kt @@ -23,8 +23,8 @@ internal class DotBenchmark { const val dim = 1000 //creating invertible matrix - val matrix1 = LinearSpace.real.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } - val matrix2 = LinearSpace.real.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val matrix1 = LinearSpace.double.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } + val matrix2 = LinearSpace.double.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } val cmMatrix1 = CMLinearSpace { matrix1.toCM() } val cmMatrix2 = CMLinearSpace { matrix2.toCM() } @@ -63,7 +63,7 @@ internal class DotBenchmark { @Benchmark fun realDot(blackhole: Blackhole) { - LinearSpace.real { + LinearSpace.double { blackhole.consume(matrix1 dot matrix2) } } diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt index 7bb32af28..ff67ccc84 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/MatrixInverseBenchmark.kt @@ -25,7 +25,7 @@ internal class MatrixInverseBenchmark { private val random = Random(1224) private const val dim = 100 - private val space = LinearSpace.real + private val space = LinearSpace.double //creating invertible matrix private val u = space.buildMatrix(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 } @@ -35,7 +35,7 @@ internal class MatrixInverseBenchmark { @Benchmark fun kmathLupInversion(blackhole: Blackhole) { - blackhole.consume(LinearSpace.real.inverseWithLup(matrix)) + blackhole.consume(LinearSpace.double.inverseWithLup(matrix)) } @Benchmark diff --git a/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt b/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt index 5da7d0f67..da1f45c1f 100644 --- a/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt +++ b/buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt @@ -203,7 +203,7 @@ public object EjmlLinearSpace${ops} : EjmlLinearSpace<${type}, ${kmathAlgebra}, public override fun ${type}.times(v: Point<${type}>): Ejml${type}Vector<${ejmlMatrixType}> = v * this @UnstableKMathAPI - public override fun getFeature(structure: Matrix<${type}>, type: KClass): F? { + public override fun computeFeature(structure: Matrix<${type}>, type: KClass): F? { structure.getFeature(type)?.let { return it } val origin = structure.toEjml().origin diff --git a/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt b/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt index 420b23f9f..88fd312c7 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/ast/kotlingradSupport.kt @@ -9,7 +9,7 @@ import space.kscience.kmath.asm.compileToExpression import space.kscience.kmath.expressions.derivative import space.kscience.kmath.expressions.invoke import space.kscience.kmath.expressions.symbol -import space.kscience.kmath.kotlingrad.toDiffExpression +import space.kscience.kmath.kotlingrad.toKotlingradExpression import space.kscience.kmath.operations.DoubleField /** @@ -20,7 +20,7 @@ fun main() { val x by symbol val actualDerivative = "x^2-4*x-44".parseMath() - .toDiffExpression(DoubleField) + .toKotlingradExpression(DoubleField) .derivative(x) diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt index 5201fec38..bfba08bbd 100644 --- a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt +++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/parser.kt @@ -17,7 +17,7 @@ import com.github.h0tk3y.betterParse.lexer.regexToken import com.github.h0tk3y.betterParse.parser.ParseResult import com.github.h0tk3y.betterParse.parser.Parser import space.kscience.kmath.expressions.MST -import space.kscience.kmath.expressions.StringSymbol +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.operations.FieldOperations import space.kscience.kmath.operations.GroupOperations import space.kscience.kmath.operations.PowerOperations @@ -43,7 +43,7 @@ public object ArithmeticsEvaluator : Grammar() { private val ws: Token by regexToken("\\s+".toRegex(), ignore = true) private val number: Parser by num use { MST.Numeric(text.toDouble()) } - private val singular: Parser by id use { StringSymbol(text) } + private val singular: Parser by id use { Symbol(text) } private val unaryFunction: Parser by (id and -lpar and parser(ArithmeticsEvaluator::subSumChain) and -rpar) .map { (id, term) -> MST.Unary(id.text, term) } diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt index 8f4daecf9..948e7eab9 100644 --- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt +++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/internal/mapIntrinsics.kt @@ -7,7 +7,6 @@ package space.kscience.kmath.asm.internal -import space.kscience.kmath.expressions.StringSymbol import space.kscience.kmath.expressions.Symbol /** @@ -15,4 +14,4 @@ import space.kscience.kmath.expressions.Symbol * * @author Iaroslav Postovalov */ -internal fun Map.getOrFail(key: String): V = getValue(StringSymbol(key)) +internal fun Map.getOrFail(key: String): V = getValue(Symbol(key)) diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt index 361027968..a7ee9ff3f 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpression.kt @@ -103,12 +103,12 @@ public class DerivativeStructureField( public override operator fun DerivativeStructure.minus(b: Number): DerivativeStructure = subtract(b.toDouble()) public override operator fun Number.plus(b: DerivativeStructure): DerivativeStructure = b + this public override operator fun Number.minus(b: DerivativeStructure): DerivativeStructure = b - this +} - public companion object : - AutoDiffProcessor> { - public override fun process(function: DerivativeStructureField.() -> DerivativeStructure): DifferentiableExpression = - DerivativeStructureExpression(function) - } +public object DSProcessor : AutoDiffProcessor { + public override fun differentiate( + function: DerivativeStructureField.() -> DerivativeStructure, + ): DerivativeStructureExpression = DerivativeStructureExpression(function) } /** diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt index 4e174723d..e0a2f4931 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMGaussRuleIntegrator.kt @@ -16,7 +16,7 @@ public class CMGaussRuleIntegrator( private var type: GaussRule = GaussRule.LEGANDRE, ) : UnivariateIntegrator { - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val range = integrand.getFeature()?.range ?: error("Integration range is not provided") val integrator: GaussIntegrator = getIntegrator(range) @@ -76,8 +76,8 @@ public class CMGaussRuleIntegrator( numPoints: Int = 100, type: GaussRule = GaussRule.LEGANDRE, function: (Double) -> Double, - ): Double = CMGaussRuleIntegrator(numPoints, type).integrate( + ): Double = CMGaussRuleIntegrator(numPoints, type).process( UnivariateIntegrand(function, IntegrationRange(range)) - ).valueOrNull!! + ).value } } \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt index bcddccdc4..257429fa7 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/integration/CMIntegrator.kt @@ -18,7 +18,7 @@ public class CMIntegrator( public val integratorBuilder: (Integrand) -> org.apache.commons.math3.analysis.integration.UnivariateIntegrator, ) : UnivariateIntegrator { - override fun integrate(integrand: UnivariateIntegrand): UnivariateIntegrand { + override fun process(integrand: UnivariateIntegrand): UnivariateIntegrand { val integrator = integratorBuilder(integrand) val maxCalls = integrand.getFeature()?.maxCalls ?: defaultMaxCalls val remainingCalls = maxCalls - integrand.calls diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt index 11b097831..c6f1cd852 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMMatrix.kt @@ -95,7 +95,7 @@ public object CMLinearSpace : LinearSpace { v * this @UnstableKMathAPI - override fun getFeature(structure: Matrix, type: KClass): F? { + override fun computeFeature(structure: Matrix, type: KClass): F? { //Return the feature if it is intrinsic to the structure structure.getFeature(type)?.let { return it } @@ -109,22 +109,22 @@ public object CMLinearSpace : LinearSpace { LupDecompositionFeature { private val lup by lazy { LUDecomposition(origin) } override val determinant: Double by lazy { lup.determinant } - override val l: Matrix by lazy { CMMatrix(lup.l) + LFeature } - override val u: Matrix by lazy { CMMatrix(lup.u) + UFeature } + override val l: Matrix by lazy> { CMMatrix(lup.l).withFeature(LFeature) } + override val u: Matrix by lazy> { CMMatrix(lup.u).withFeature(UFeature) } override val p: Matrix by lazy { CMMatrix(lup.p) } } CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { - override val l: Matrix by lazy { + override val l: Matrix by lazy> { val cholesky = CholeskyDecomposition(origin) - CMMatrix(cholesky.l) + LFeature + CMMatrix(cholesky.l).withFeature(LFeature) } } QRDecompositionFeature::class -> object : QRDecompositionFeature { private val qr by lazy { QRDecomposition(origin) } - override val q: Matrix by lazy { CMMatrix(qr.q) + OrthogonalFeature } - override val r: Matrix by lazy { CMMatrix(qr.r) + UFeature } + override val q: Matrix by lazy> { CMMatrix(qr.q).withFeature(OrthogonalFeature) } + override val r: Matrix by lazy> { CMMatrix(qr.r).withFeature(UFeature) } } SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt index ee602ca06..9085974ea 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/linear/CMSolver.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.commons.linear import org.apache.commons.math3.linear.* +import space.kscience.kmath.linear.LinearSolver import space.kscience.kmath.linear.Matrix import space.kscience.kmath.linear.Point @@ -44,3 +45,12 @@ public fun CMLinearSpace.inverse( a: Matrix, decomposition: CMDecomposition = CMDecomposition.LUP, ): CMMatrix = solver(a, decomposition).inverse.wrap() + + +public fun CMLinearSpace.solver(decomposition: CMDecomposition): LinearSolver = object : LinearSolver { + override fun solve(a: Matrix, b: Matrix): Matrix = solve(a, b, decomposition) + + override fun solve(a: Matrix, b: Point): Point = solve(a, b, decomposition) + + override fun inverse(matrix: Matrix): Matrix = inverse(matrix, decomposition) +} \ No newline at end of file diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt similarity index 63% rename from kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt rename to kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt index 2faee1f5d..abf95daf6 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimization.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt @@ -2,7 +2,7 @@ * Copyright 2018-2021 KMath contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ - +@file:OptIn(UnstableKMathAPI::class) package space.kscience.kmath.commons.optimization import org.apache.commons.math3.optim.* @@ -11,6 +11,10 @@ import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunction import org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunctionGradient import org.apache.commons.math3.optim.nonlinear.scalar.gradient.NonLinearConjugateGradientOptimizer +import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.NelderMeadSimplex +import org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.SymbolIndexer import space.kscience.kmath.expressions.derivative import space.kscience.kmath.expressions.withSymbols import space.kscience.kmath.misc.UnstableKMathAPI @@ -21,29 +25,61 @@ import kotlin.reflect.KClass public operator fun PointValuePair.component1(): DoubleArray = point public operator fun PointValuePair.component2(): Double = value -public class CMOptimizer(public val optimizerBuilder: () -> MultivariateOptimizer): OptimizationFeature{ +public class CMOptimizerEngine(public val optimizerBuilder: () -> MultivariateOptimizer) : OptimizationFeature { override fun toString(): String = "CMOptimizer($optimizerBuilder)" } -public class CMOptimizerData(public val data: List) : OptimizationFeature { - public constructor(vararg data: OptimizationData) : this(data.toList()) +/** + * Specify a Commons-maths optimization engine + */ +public fun FunctionOptimizationBuilder.cmEngine(optimizerBuilder: () -> MultivariateOptimizer) { + addFeature(CMOptimizerEngine(optimizerBuilder)) +} + +public class CMOptimizerData(public val data: List OptimizationData>) : OptimizationFeature { + public constructor(vararg data: (SymbolIndexer.() -> OptimizationData)) : this(data.toList()) override fun toString(): String = "CMOptimizerData($data)" +} +/** + * Specify Commons-maths optimization data. + */ +public fun FunctionOptimizationBuilder.cmOptimizationData(data: SymbolIndexer.() -> OptimizationData) { + updateFeature { + val newData = (it?.data ?: emptyList()) + data + CMOptimizerData(newData) + } +} + +public fun FunctionOptimizationBuilder.simplexSteps(vararg steps: Pair) { + //TODO use convergence checker from features + cmEngine { SimplexOptimizer(CMOptimizer.defaultConvergenceChecker) } + cmOptimizationData { NelderMeadSimplex(mapOf(*steps).toDoubleArray()) } } @OptIn(UnstableKMathAPI::class) -public class CMOptimization : Optimizer> { +public object CMOptimizer : Optimizer> { + + public const val DEFAULT_RELATIVE_TOLERANCE: Double = 1e-4 + public const val DEFAULT_ABSOLUTE_TOLERANCE: Double = 1e-4 + public const val DEFAULT_MAX_ITER: Int = 1000 + + public val defaultConvergenceChecker: SimpleValueChecker = SimpleValueChecker( + DEFAULT_RELATIVE_TOLERANCE, + DEFAULT_ABSOLUTE_TOLERANCE, + DEFAULT_MAX_ITER + ) + override suspend fun optimize( problem: FunctionOptimization, ): FunctionOptimization { - val startPoint = problem.getFeature>()?.point - ?: error("Starting point not defined in $problem") + val startPoint = problem.startPoint val parameters = problem.getFeature()?.symbols ?: problem.getFeature>()?.point?.keys - ?:startPoint.keys + ?: startPoint.keys withSymbols(parameters) { @@ -53,7 +89,7 @@ public class CMOptimization : Optimizer> { DEFAULT_MAX_ITER ) - val cmOptimizer: MultivariateOptimizer = problem.getFeature()?.optimizerBuilder?.invoke() + val cmOptimizer: MultivariateOptimizer = problem.getFeature()?.optimizerBuilder?.invoke() ?: NonLinearConjugateGradientOptimizer( NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES, convergenceChecker @@ -68,7 +104,7 @@ public class CMOptimization : Optimizer> { addOptimizationData(MaxEval.unlimited()) addOptimizationData(InitialGuess(startPoint.toDoubleArray())) - fun exportOptimizationData(): List = optimizationData.values.toList() + //fun exportOptimizationData(): List = optimizationData.values.toList() val objectiveFunction = ObjectiveFunction { val args = startPoint + it.toMap() @@ -88,7 +124,9 @@ public class CMOptimization : Optimizer> { for (feature in problem.features) { when (feature) { - is CMOptimizerData -> feature.data.forEach { addOptimizationData(it) } + is CMOptimizerData -> feature.data.forEach { dataBuilder -> + addOptimizationData(dataBuilder()) + } is FunctionOptimizationTarget -> when (feature) { FunctionOptimizationTarget.MAXIMIZE -> addOptimizationData(GoalType.MAXIMIZE) FunctionOptimizationTarget.MINIMIZE -> addOptimizationData(GoalType.MINIMIZE) @@ -101,10 +139,4 @@ public class CMOptimization : Optimizer> { return problem.withFeatures(OptimizationResult(point.toMap()), OptimizationValue(value)) } } - - public companion object { - public const val DEFAULT_RELATIVE_TOLERANCE: Double = 1e-4 - public const val DEFAULT_ABSOLUTE_TOLERANCE: Double = 1e-4 - public const val DEFAULT_MAX_ITER: Int = 1000 - } } diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt deleted file mode 100644 index b1d7f5ca3..000000000 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/cmFit.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.commons.optimization - -import org.apache.commons.math3.analysis.differentiation.DerivativeStructure -import space.kscience.kmath.commons.expressions.DerivativeStructureField -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.expressions.Symbol -import space.kscience.kmath.optimization.* -import space.kscience.kmath.structures.Buffer -import space.kscience.kmath.structures.asBuffer - -/** - * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation - */ -public fun FunctionOptimization.Companion.chiSquaredExpression( - x: Buffer, - y: Buffer, - yErr: Buffer, - model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, -): DifferentiableExpression = chiSquaredExpression(DerivativeStructureField, x, y, yErr, model) - -/** - * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation - */ -public fun FunctionOptimization.Companion.chiSquaredExpression( - x: Iterable, - y: Iterable, - yErr: Iterable, - model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure, -): DifferentiableExpression = chiSquaredExpression( - DerivativeStructureField, - x.toList().asBuffer(), - y.toList().asBuffer(), - yErr.toList().asBuffer(), - model -) - -/** - * Optimize expression without derivatives - */ -public suspend fun Expression.optimize( - vararg symbols: Symbol, - configuration: CMOptimization.() -> Unit, -): OptimizationResult { - require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" } - val problem = CMOptimization(symbols.toList(), configuration) - problem.noDerivFunction(this) - return problem.optimize() -} - -/** - * Optimize differentiable expression - */ -public suspend fun DifferentiableExpression.optimize( - vararg symbols: Symbol, - configuration: CMOptimization.() -> Unit, -): OptimizationResult = optimizeWith(CMOptimization, symbols = symbols, configuration) - -public suspend fun DifferentiableExpression.minimize( - vararg startPoint: Pair, - configuration: CMOptimization.() -> Unit = {}, -): OptimizationResult { - val symbols = startPoint.map { it.first }.toTypedArray() - return optimize(*symbols){ - maximize = false - initialGuess(startPoint.toMap()) - function(this@minimize) - configuration() - } -} \ No newline at end of file diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt index b47d7da24..97761cfa2 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt @@ -6,43 +6,38 @@ package space.kscience.kmath.commons.optimization import kotlinx.coroutines.runBlocking +import space.kscience.kmath.commons.expressions.DSProcessor import space.kscience.kmath.commons.expressions.DerivativeStructureExpression import space.kscience.kmath.distributions.NormalDistribution +import space.kscience.kmath.expressions.Symbol.Companion.x +import space.kscience.kmath.expressions.Symbol.Companion.y import space.kscience.kmath.expressions.symbol -import space.kscience.kmath.optimization.FunctionOptimization +import space.kscience.kmath.optimization.* import space.kscience.kmath.stat.RandomGenerator import kotlin.math.pow import kotlin.test.Test internal class OptimizeTest { - val x by symbol - val y by symbol - val normal = DerivativeStructureExpression { - exp(-bindSymbol(x).pow(2) / 2) + exp(-bindSymbol(y) - .pow(2) / 2) + exp(-bindSymbol(x).pow(2) / 2) + exp(-bindSymbol(y).pow(2) / 2) } @Test - fun testGradientOptimization() = runBlocking{ - val result = normal.optimize(x, y) { - initialGuess(x to 1.0, y to 1.0) - //no need to select optimizer. Gradient optimizer is used by default because gradients are provided by function - } - println(result.point) - println(result.value) + fun testGradientOptimization() = runBlocking { + val result = normal.optimizeWith(CMOptimizer, x to 1.0, y to 1.0) + println(result.resultPoint) + println(result.resultValue) } @Test - fun testSimplexOptimization() = runBlocking{ - val result = normal.optimize(x, y) { - initialGuess(x to 1.0, y to 1.0) + fun testSimplexOptimization() = runBlocking { + val result = normal.optimizeWith(CMOptimizer, x to 1.0, y to 1.0) { simplexSteps(x to 2.0, y to 0.5) //this sets simplex optimizer } - println(result.point) - println(result.value) + println(result.resultPoint) + println(result.resultValue) } @Test @@ -62,6 +57,11 @@ internal class OptimizeTest { val yErr = List(x.size) { sigma } + val model = DSProcessor.differentiate { x1 -> + val cWithDefault = bindSymbolOrNull(c) ?: one + bindSymbol(a) * x1.pow(2) + bindSymbol(b) * x1 + cWithDefault + } + val chi2 = FunctionOptimization.chiSquared(x, y, yErr) { x1 -> val cWithDefault = bindSymbolOrNull(c) ?: one bindSymbol(a) * x1.pow(2) + bindSymbol(b) * x1 + cWithDefault diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt index 88c14d311..e06b774fd 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/ColumnarData.kt @@ -25,6 +25,9 @@ public interface ColumnarData { public operator fun get(symbol: Symbol): Buffer? } +@UnstableKMathAPI +public val ColumnarData<*>.indices: IntRange get() = 0 until size + /** * A zero-copy method to represent a [Structure2D] as a two-column x-y data. * There could more than two columns in the structure. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt index 011dc26e2..1782ff406 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/DifferentiableExpression.kt @@ -5,6 +5,8 @@ package space.kscience.kmath.expressions +import space.kscience.kmath.operations.Algebra + /** * Represents expression which structure can be differentiated. * @@ -63,7 +65,10 @@ public abstract class FirstDerivativeExpression : DifferentiableExpression /** * A factory that converts an expression in autodiff variables to a [DifferentiableExpression] + * @param T type of the constants for the expression + * @param I type of the actual expression state + * @param A type of expression algebra */ -public fun interface AutoDiffProcessor, out R : Expression> { - public fun process(function: A.() -> I): DifferentiableExpression +public fun interface AutoDiffProcessor> { + public fun differentiate(function: A.() -> I): DifferentiableExpression } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt index 9c3ffd819..288fabbaf 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSolver.kt @@ -5,8 +5,6 @@ package space.kscience.kmath.linear -import space.kscience.kmath.nd.as1D - /** * A group of methods to solve for *X* in equation *X = A -1 · B*, where *A* and *B* are matrices or * vectors. @@ -30,20 +28,3 @@ public interface LinearSolver { public fun inverse(matrix: Matrix): Matrix } -/** - * Convert matrix to vector if it is possible. - */ -public fun Matrix.asVector(): Point = - if (this.colNum == 1) - as1D() - else - error("Can't convert matrix with more than one column to vector") - -/** - * Creates an n × 1 [VirtualMatrix], where n is the size of the given buffer. - * - * @param T the type of elements contained in the buffer. - * @receiver a buffer. - * @return the new matrix. - */ -public fun Point.asMatrix(): VirtualMatrix = VirtualMatrix(size, 1) { i, _ -> get(i) } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt index ec073ac48..94083f70d 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LinearSpace.kt @@ -164,7 +164,7 @@ public interface LinearSpace> { public operator fun T.times(v: Point): Point = v * this /** - * Get a feature of the structure in this scope. Structure features take precedence other context features + * Compute a feature of the structure in this scope. Structure features take precedence other context features * * @param F the type of feature. * @param structure the structure. @@ -172,7 +172,7 @@ public interface LinearSpace> { * @return a feature object or `null` if it isn't present. */ @UnstableKMathAPI - public fun getFeature(structure: Matrix, type: KClass): F? = structure.getFeature(type) + public fun computeFeature(structure: Matrix, type: KClass): F? = structure.getFeature(type) public companion object { @@ -184,7 +184,7 @@ public interface LinearSpace> { bufferFactory: BufferFactory = Buffer.Companion::boxing, ): LinearSpace = BufferedLinearSpace(algebra, bufferFactory) - public val real: LinearSpace = buffered(DoubleField, ::DoubleBuffer) + public val double: LinearSpace = buffered(DoubleField, ::DoubleBuffer) /** * Automatic buffered matrix, unboxed if it is possible @@ -202,9 +202,27 @@ public interface LinearSpace> { * @return a feature object or `null` if it isn't present. */ @UnstableKMathAPI -public inline fun LinearSpace.getFeature(structure: Matrix): F? = - getFeature(structure, F::class) +public inline fun LinearSpace.computeFeature(structure: Matrix): F? = + computeFeature(structure, F::class) -public operator fun , R> LS.invoke(block: LS.() -> R): R = run(block) +public inline operator fun , R> LS.invoke(block: LS.() -> R): R = run(block) + +/** + * Convert matrix to vector if it is possible. + */ +public fun Matrix.asVector(): Point = + if (this.colNum == 1) + as1D() + else + error("Can't convert matrix with more than one column to vector") + +/** + * Creates an n × 1 [VirtualMatrix], where n is the size of the given buffer. + * + * @param T the type of elements contained in the buffer. + * @receiver a buffer. + * @return the new matrix. + */ +public fun Point.asMatrix(): VirtualMatrix = VirtualMatrix(size, 1) { i, _ -> get(i) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt index f3653d394..3b6208468 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/LupDecomposition.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.linear +import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.getFeature import space.kscience.kmath.operations.* @@ -34,7 +35,7 @@ public class LupDecomposition( j == i -> elementContext.one else -> elementContext.zero } - } + LFeature + }.withFeature(LFeature) /** @@ -44,7 +45,7 @@ public class LupDecomposition( */ override val u: Matrix = VirtualMatrix(lu.shape[0], lu.shape[1]) { i, j -> if (j >= i) lu[i, j] else elementContext.zero - } + UFeature + }.withFeature(UFeature) /** * Returns the P rows permutation matrix. @@ -82,7 +83,7 @@ public fun > LinearSpace>.lup( val m = matrix.colNum val pivot = IntArray(matrix.rowNum) - //TODO just waits for KEEP-176 + //TODO just waits for multi-receivers BufferAccessor2D(matrix.rowNum, matrix.colNum, factory).run { elementAlgebra { val lu = create(matrix) @@ -156,10 +157,13 @@ public inline fun > LinearSpace>.lup( noinline checkSingular: (T) -> Boolean, ): LupDecomposition = lup(MutableBuffer.Companion::auto, matrix, checkSingular) -public fun LinearSpace.lup(matrix: Matrix): LupDecomposition = - lup(::DoubleBuffer, matrix) { it < 1e-11 } +public fun LinearSpace.lup( + matrix: Matrix, + singularityThreshold: Double = 1e-11, +): LupDecomposition = + lup(::DoubleBuffer, matrix) { it < singularityThreshold } -public fun LupDecomposition.solveWithLup( +internal fun LupDecomposition.solve( factory: MutableBufferFactory, matrix: Matrix, ): Matrix { @@ -207,41 +211,24 @@ public fun LupDecomposition.solveWithLup( } } -public inline fun LupDecomposition.solveWithLup(matrix: Matrix): Matrix = - solveWithLup(MutableBuffer.Companion::auto, matrix) - /** - * Solves a system of linear equations *ax = b** using LUP decomposition. + * Produce a generic solver based on LUP decomposition */ +@PerformancePitfall() @OptIn(UnstableKMathAPI::class) -public inline fun > LinearSpace>.solveWithLup( - a: Matrix, - b: Matrix, - noinline bufferFactory: MutableBufferFactory = MutableBuffer.Companion::auto, - noinline checkSingular: (T) -> Boolean, -): Matrix { - // Use existing decomposition if it is provided by matrix - val decomposition = a.getFeature() ?: lup(bufferFactory, a, checkSingular) - return decomposition.solveWithLup(bufferFactory, b) +public fun , F : Field> LinearSpace.lupSolver( + bufferFactory: MutableBufferFactory, + singularityCheck: (T) -> Boolean, +): LinearSolver = object : LinearSolver { + override fun solve(a: Matrix, b: Matrix): Matrix { + // Use existing decomposition if it is provided by matrix + val decomposition = a.getFeature() ?: lup(bufferFactory, a, singularityCheck) + return decomposition.solve(bufferFactory, b) + } + + override fun inverse(matrix: Matrix): Matrix = solve(matrix, one(matrix.rowNum, matrix.colNum)) } -public inline fun > LinearSpace>.inverseWithLup( - matrix: Matrix, - noinline bufferFactory: MutableBufferFactory = MutableBuffer.Companion::auto, - noinline checkSingular: (T) -> Boolean, -): Matrix = solveWithLup(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular) - - -@OptIn(UnstableKMathAPI::class) -public fun LinearSpace.solveWithLup(a: Matrix, b: Matrix): Matrix { - // Use existing decomposition if it is provided by matrix - val bufferFactory: MutableBufferFactory = ::DoubleBuffer - val decomposition: LupDecomposition = a.getFeature() ?: lup(bufferFactory, a) { it < 1e-11 } - return decomposition.solveWithLup(bufferFactory, b) -} - -/** - * Inverses a square matrix using LUP decomposition. Non square matrix will throw a error. - */ -public fun LinearSpace.inverseWithLup(matrix: Matrix): Matrix = - solveWithLup(matrix, one(matrix.rowNum, matrix.colNum)) \ No newline at end of file +@PerformancePitfall +public fun LinearSpace.lupSolver(singularityThreshold: Double = 1e-11): LinearSolver = + lupSolver(::DoubleBuffer) { it < singularityThreshold } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt index 72d22233a..029612bc5 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixBuilder.kt @@ -7,6 +7,8 @@ package space.kscience.kmath.linear import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.Ring +import space.kscience.kmath.structures.BufferAccessor2D +import space.kscience.kmath.structures.MutableBuffer public class MatrixBuilder>( public val linearSpace: LinearSpace, @@ -45,4 +47,31 @@ public inline fun LinearSpace>.column( crossinline builder: (Int) -> T, ): Matrix = buildMatrix(size, 1) { i, _ -> builder(i) } -public fun LinearSpace>.column(vararg values: T): Matrix = column(values.size, values::get) \ No newline at end of file +public fun LinearSpace>.column(vararg values: T): Matrix = column(values.size, values::get) + +public object SymmetricMatrixFeature : MatrixFeature + +/** + * Naive implementation of a symmetric matrix builder, that adds a [SymmetricMatrixFeature] tag. The resulting matrix contains + * full `size^2` number of elements, but caches elements during calls to save [builder] calls. [builder] is always called in the + * upper triangle region meaning that `i <= j` + */ +public fun > MatrixBuilder.symmetric( + builder: (i: Int, j: Int) -> T, +): Matrix { + require(columns == rows) { "In order to build symmetric matrix, number of rows $rows should be equal to number of columns $columns" } + return with(BufferAccessor2D(rows, rows, MutableBuffer.Companion::boxing)) { + val cache = factory(rows * rows) { null } + linearSpace.buildMatrix(rows, rows) { i, j -> + val cached = cache[i, j] + if (cached == null) { + val value = if (i <= j) builder(i, j) else builder(j, i) + cache[i, j] = value + cache[j, i] = value + value + } else { + cached + } + }.withFeature(SymmetricMatrixFeature) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt index 16aadab3b..df62c6fc0 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/MatrixWrapper.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.linear +import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureFeature import space.kscience.kmath.nd.getFeature @@ -18,7 +19,7 @@ import kotlin.reflect.KClass */ public class MatrixWrapper internal constructor( public val origin: Matrix, - public val features: Set, + public val features: FeatureSet, ) : Matrix by origin { /** @@ -27,8 +28,7 @@ public class MatrixWrapper internal constructor( @UnstableKMathAPI @Suppress("UNCHECKED_CAST") public override fun getFeature(type: KClass): F? = - features.singleOrNull(type::isInstance) as? F - ?: origin.getFeature(type) + features.getFeature(type) ?: origin.getFeature(type) public override fun toString(): String = "MatrixWrapper(matrix=$origin, features=$features)" } @@ -44,20 +44,23 @@ public val Matrix.origin: Matrix /** * Add a single feature to a [Matrix] */ -public operator fun Matrix.plus(newFeature: MatrixFeature): MatrixWrapper = if (this is MatrixWrapper) { - MatrixWrapper(origin, features + newFeature) +public fun Matrix.withFeature(newFeature: MatrixFeature): MatrixWrapper = if (this is MatrixWrapper) { + MatrixWrapper(origin, features.with(newFeature)) } else { - MatrixWrapper(this, setOf(newFeature)) + MatrixWrapper(this, FeatureSet.of(newFeature)) } +@Deprecated("To be replaced by withFeature") +public operator fun Matrix.plus(newFeature: MatrixFeature): MatrixWrapper = withFeature(newFeature) + /** * Add a collection of features to a [Matrix] */ -public operator fun Matrix.plus(newFeatures: Collection): MatrixWrapper = +public fun Matrix.withFeatures(newFeatures: Iterable): MatrixWrapper = if (this is MatrixWrapper) { - MatrixWrapper(origin, features + newFeatures) + MatrixWrapper(origin, features.with(newFeatures)) } else { - MatrixWrapper(this, newFeatures.toSet()) + MatrixWrapper(this, FeatureSet.of(newFeatures)) } /** @@ -68,7 +71,7 @@ public fun LinearSpace>.one( columns: Int, ): Matrix = VirtualMatrix(rows, columns) { i, j -> if (i == j) elementAlgebra.one else elementAlgebra.zero -} + UnitFeature +}.withFeature(UnitFeature) /** @@ -79,7 +82,7 @@ public fun LinearSpace>.zero( columns: Int, ): Matrix = VirtualMatrix(rows, columns) { _, _ -> elementAlgebra.zero -} + ZeroFeature +}.withFeature(ZeroFeature) public class TransposedFeature(public val original: Matrix) : MatrixFeature @@ -90,4 +93,4 @@ public class TransposedFeature(public val original: Matrix) : Ma public fun Matrix.transpose(): Matrix = getFeature>()?.original ?: VirtualMatrix( colNum, rowNum, -) { i, j -> get(j, i) } + TransposedFeature(this) \ No newline at end of file +) { i, j -> get(j, i) }.withFeature(TransposedFeature(this)) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt index 3751bd33b..fb2b1e547 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/VirtualMatrix.kt @@ -20,3 +20,6 @@ public class VirtualMatrix( override operator fun get(i: Int, j: Int): T = generator(i, j) } + +public fun MatrixBuilder.virtual(generator: (i: Int, j: Int) -> T): VirtualMatrix = + VirtualMatrix(rows, columns, generator) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt deleted file mode 100644 index 04d9a9897..000000000 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/linear/symmetric.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package space.kscience.kmath.linear - -import space.kscience.kmath.structures.BufferAccessor2D -import space.kscience.kmath.structures.MutableBuffer - -public object SymmetricMatrixFeature : MatrixFeature - -/** - * Naive implementation of a symmetric matrix builder, that adds a [SymmetricMatrixFeature] tag. The resulting matrix contains - * full `size^2` number of elements, but caches elements during calls to save [builder] calls. [builder] is always called in the - * upper triangle region meaning that `i <= j` - */ -public fun > LS.buildSymmetricMatrix( - size: Int, - builder: (i: Int, j: Int) -> T, -): Matrix = BufferAccessor2D(size, size, MutableBuffer.Companion::boxing).run { - val cache = factory(size * size) { null } - buildMatrix(size, size) { i, j -> - val cached = cache[i, j] - if (cached == null) { - val value = if (i <= j) builder(i, j) else builder(j, i) - cache[i, j] = value - cache[j, i] = value - value - } else { - cached - } - } + SymmetricMatrixFeature -} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt index a94efc788..648b6376f 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt @@ -11,29 +11,44 @@ import kotlin.reflect.KClass * A entity that contains a set of features defined by their types */ public interface Featured { - public fun getFeature(type: KClass): T? + public fun getFeature(type: FeatureKey): T? +} + +public typealias FeatureKey = KClass + +public interface Feature> { + + /** + * A key used for extraction + */ + @Suppress("UNCHECKED_CAST") + public val key: FeatureKey get() = this::class as FeatureKey } /** * A container for a set of features */ -public class FeatureSet private constructor(public val features: Map, F>) : Featured { +public class FeatureSet> private constructor(public val features: Map, F>) : Featured { @Suppress("UNCHECKED_CAST") - override fun getFeature(type: KClass): T? = features[type] as? T + override fun getFeature(type: FeatureKey): T? = features[type] as? T public inline fun getFeature(): T? = getFeature(T::class) - public fun with(feature: T, type: KClass = feature::class): FeatureSet = + public fun with(feature: T, type: FeatureKey = feature.key): FeatureSet = FeatureSet(features + (type to feature)) public fun with(other: FeatureSet): FeatureSet = FeatureSet(features + other.features) public fun with(vararg otherFeatures: F): FeatureSet = - FeatureSet(features + otherFeatures.associateBy { it::class }) + FeatureSet(features + otherFeatures.associateBy { it.key }) + + public fun with(otherFeatures: Iterable): FeatureSet = + FeatureSet(features + otherFeatures.associateBy { it.key }) public operator fun iterator(): Iterator = features.values.iterator() public companion object { - public fun of(vararg features: F): FeatureSet = FeatureSet(features.associateBy { it::class }) + public fun > of(vararg features: F): FeatureSet = FeatureSet(features.associateBy { it.key }) + public fun > of(features: Iterable): FeatureSet = FeatureSet(features.associateBy { it.key }) } } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt index e521e6237..39b2779eb 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/annotations.kt @@ -12,7 +12,7 @@ package space.kscience.kmath.misc * in some way that may break some code. */ @MustBeDocumented -@Retention(value = AnnotationRetention.BINARY) +@Retention(value = AnnotationRetention.SOURCE) @RequiresOptIn("This API is unstable and could change in future", RequiresOptIn.Level.WARNING) public annotation class UnstableKMathAPI @@ -21,7 +21,7 @@ public annotation class UnstableKMathAPI * slow-down in some cases. Refer to the documentation and benchmark it to be sure. */ @MustBeDocumented -@Retention(value = AnnotationRetention.BINARY) +@Retention(value = AnnotationRetention.SOURCE) @RequiresOptIn( "Refer to the documentation to use this API in performance-critical code", RequiresOptIn.Level.WARNING diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt index 7fc91e321..4962dcb7d 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/nd/StructureND.kt @@ -5,6 +5,8 @@ package space.kscience.kmath.nd +import space.kscience.kmath.misc.Feature +import space.kscience.kmath.misc.Featured import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer @@ -13,7 +15,7 @@ import kotlin.jvm.JvmName import kotlin.native.concurrent.ThreadLocal import kotlin.reflect.KClass -public interface StructureFeature +public interface StructureFeature : Feature /** * Represents n-dimensional structure, i.e. multidimensional container of items of the same type and size. The number @@ -24,7 +26,7 @@ public interface StructureFeature * * @param T the type of items. */ -public interface StructureND { +public interface StructureND : Featured { /** * The shape of structure, i.e. non-empty sequence of non-negative integers that specify sizes of dimensions of * this structure. @@ -57,7 +59,7 @@ public interface StructureND { * If the feature is not present, null is returned. */ @UnstableKMathAPI - public fun getFeature(type: KClass): F? = null + override fun getFeature(type: KClass): F? = null public companion object { /** diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt index d29c54d46..a96253939 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/BufferAccessor2D.kt @@ -14,30 +14,30 @@ import space.kscience.kmath.nd.as2D * A context that allows to operate on a [MutableBuffer] as on 2d array */ internal class BufferAccessor2D( - public val rowNum: Int, - public val colNum: Int, + val rowNum: Int, + val colNum: Int, val factory: MutableBufferFactory, ) { - public operator fun Buffer.get(i: Int, j: Int): T = get(i * colNum + j) + operator fun Buffer.get(i: Int, j: Int): T = get(i * colNum + j) - public operator fun MutableBuffer.set(i: Int, j: Int, value: T) { + operator fun MutableBuffer.set(i: Int, j: Int, value: T) { set(i * colNum + j, value) } - public inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer = + inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer = factory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) } - public fun create(mat: Structure2D): MutableBuffer = create { i, j -> mat[i, j] } + fun create(mat: Structure2D): MutableBuffer = create { i, j -> mat[i, j] } //TODO optimize wrapper - public fun MutableBuffer.collect(): Structure2D = StructureND.buffered( + fun MutableBuffer.collect(): Structure2D = StructureND.buffered( DefaultStrides(intArrayOf(rowNum, colNum)), factory ) { (i, j) -> get(i, j) }.as2D() - public inner class Row(public val buffer: MutableBuffer, public val rowIndex: Int) : MutableBuffer { + inner class Row(val buffer: MutableBuffer, val rowIndex: Int) : MutableBuffer { override val size: Int get() = colNum override operator fun get(index: Int): T = buffer[rowIndex, index] @@ -54,5 +54,5 @@ internal class BufferAccessor2D( /** * Get row */ - public fun MutableBuffer.row(i: Int): Row = Row(this, i) + fun MutableBuffer.row(i: Int): Row = Row(this, i) } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt index 2d2a0952b..3be473e54 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/DoubleLUSolverTest.kt @@ -22,14 +22,14 @@ class DoubleLUSolverTest { @Test fun testInvertOne() { - val matrix = LinearSpace.real.one(2, 2) - val inverted = LinearSpace.real.inverseWithLup(matrix) + val matrix = LinearSpace.double.one(2, 2) + val inverted = LinearSpace.double.lupSolver().inverse(matrix) assertMatrixEquals(matrix, inverted) } @Test fun testDecomposition() { - LinearSpace.real.run { + LinearSpace.double.run { val matrix = matrix(2, 2)( 3.0, 1.0, 2.0, 3.0 @@ -46,14 +46,14 @@ class DoubleLUSolverTest { @Test fun testInvert() { - val matrix = LinearSpace.real.matrix(2, 2)( + val matrix = LinearSpace.double.matrix(2, 2)( 3.0, 1.0, 1.0, 3.0 ) - val inverted = LinearSpace.real.inverseWithLup(matrix) + val inverted = LinearSpace.double.lupSolver().inverse(matrix) - val expected = LinearSpace.real.matrix(2, 2)( + val expected = LinearSpace.double.matrix(2, 2)( 0.375, -0.125, -0.125, 0.375 ) diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt index 170f9caf4..feae07c1e 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/linear/MatrixTest.kt @@ -19,14 +19,14 @@ import kotlin.test.assertTrue class MatrixTest { @Test fun testTranspose() { - val matrix = LinearSpace.real.one(3, 3) + val matrix = LinearSpace.double.one(3, 3) val transposed = matrix.transpose() assertTrue { StructureND.contentEquals(matrix, transposed) } } @Test fun testBuilder() { - val matrix = LinearSpace.real.matrix(2, 3)( + val matrix = LinearSpace.double.matrix(2, 3)( 1.0, 0.0, 0.0, 0.0, 1.0, 2.0 ) @@ -48,7 +48,7 @@ class MatrixTest { infix fun Matrix.pow(power: Int): Matrix { var res = this repeat(power - 1) { - res = LinearSpace.real.run { res dot this@pow } + res = LinearSpace.double.run { res dot this@pow } } return res } @@ -61,7 +61,7 @@ class MatrixTest { val firstMatrix = StructureND.auto(2, 3) { (i, j) -> (i + j).toDouble() }.as2D() val secondMatrix = StructureND.auto(3, 2) { (i, j) -> (i + j).toDouble() }.as2D() - LinearSpace.real.run { + LinearSpace.double.run { // val firstMatrix = produce(2, 3) { i, j -> (i + j).toDouble() } // val secondMatrix = produce(3, 2) { i, j -> (i + j).toDouble() } val result = firstMatrix dot secondMatrix diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt index fb51553f7..8a03115b5 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/structures/NumberNDFieldTest.kt @@ -40,7 +40,7 @@ class NumberNDFieldTest { @Test fun testGeneration() { - val array = LinearSpace.real.buildMatrix(3, 3) { i, j -> + val array = LinearSpace.double.buildMatrix(3, 3) { i, j -> (i * 10 + j).toDouble() } diff --git a/kmath-dimensions/src/commonMain/kotlin/space/kscience/kmath/dimensions/Wrappers.kt b/kmath-dimensions/src/commonMain/kotlin/space/kscience/kmath/dimensions/Wrappers.kt index 2ebcc454d..91ab6a5d6 100644 --- a/kmath-dimensions/src/commonMain/kotlin/space/kscience/kmath/dimensions/Wrappers.kt +++ b/kmath-dimensions/src/commonMain/kotlin/space/kscience/kmath/dimensions/Wrappers.kt @@ -151,7 +151,7 @@ public value class DMatrixContext>(public val context: context.run { (this@transpose as Matrix).transpose() }.coerce() public companion object { - public val real: DMatrixContext = DMatrixContext(LinearSpace.real) + public val real: DMatrixContext = DMatrixContext(LinearSpace.double) } } diff --git a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt b/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt deleted file mode 100644 index 139c55697..000000000 --- a/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt +++ /dev/null @@ -1,995 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -/* This file is generated with buildSrc/src/main/kotlin/space/kscience/kmath/ejml/codegen/ejmlCodegen.kt */ - -package space.kscience.kmath.ejml - -import org.ejml.data.* -import org.ejml.dense.row.CommonOps_DDRM -import org.ejml.dense.row.CommonOps_FDRM -import org.ejml.dense.row.factory.DecompositionFactory_DDRM -import org.ejml.dense.row.factory.DecompositionFactory_FDRM -import org.ejml.sparse.FillReducing -import org.ejml.sparse.csc.CommonOps_DSCC -import org.ejml.sparse.csc.CommonOps_FSCC -import org.ejml.sparse.csc.factory.DecompositionFactory_DSCC -import org.ejml.sparse.csc.factory.DecompositionFactory_FSCC -import org.ejml.sparse.csc.factory.LinearSolverFactory_DSCC -import org.ejml.sparse.csc.factory.LinearSolverFactory_FSCC -import space.kscience.kmath.linear.* -import space.kscience.kmath.linear.Matrix -import space.kscience.kmath.misc.UnstableKMathAPI -import space.kscience.kmath.nd.StructureFeature -import space.kscience.kmath.operations.DoubleField -import space.kscience.kmath.operations.FloatField -import space.kscience.kmath.operations.invoke -import space.kscience.kmath.structures.DoubleBuffer -import space.kscience.kmath.structures.FloatBuffer -import kotlin.reflect.KClass -import kotlin.reflect.cast - -/** - * [EjmlVector] specialization for [Double]. - */ -public class EjmlDoubleVector(public override val origin: M) : EjmlVector(origin) { - init { - require(origin.numRows == 1) { "The origin matrix must have only one row to form a vector" } - } - - public override operator fun get(index: Int): Double = origin[0, index] -} - -/** - * [EjmlVector] specialization for [Float]. - */ -public class EjmlFloatVector(public override val origin: M) : EjmlVector(origin) { - init { - require(origin.numRows == 1) { "The origin matrix must have only one row to form a vector" } - } - - public override operator fun get(index: Int): Float = origin[0, index] -} - -/** - * [EjmlMatrix] specialization for [Double]. - */ -public class EjmlDoubleMatrix(public override val origin: M) : EjmlMatrix(origin) { - public override operator fun get(i: Int, j: Int): Double = origin[i, j] -} - -/** - * [EjmlMatrix] specialization for [Float]. - */ -public class EjmlFloatMatrix(public override val origin: M) : EjmlMatrix(origin) { - public override operator fun get(i: Int, j: Int): Float = origin[i, j] -} - -/** - * [EjmlLinearSpace] implementation based on [CommonOps_DDRM], [DecompositionFactory_DDRM] operations and - * [DMatrixRMaj] matrices. - */ -public object EjmlLinearSpaceDDRM : EjmlLinearSpace() { - /** - * The [DoubleField] reference. - */ - public override val elementAlgebra: DoubleField get() = DoubleField - - @Suppress("UNCHECKED_CAST") - public override fun Matrix.toEjml(): EjmlDoubleMatrix = when { - this is EjmlDoubleMatrix<*> && origin is DMatrixRMaj -> this as EjmlDoubleMatrix - else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } - } - - @Suppress("UNCHECKED_CAST") - public override fun Point.toEjml(): EjmlDoubleVector = when { - this is EjmlDoubleVector<*> && origin is DMatrixRMaj -> this as EjmlDoubleVector - else -> EjmlDoubleVector(DMatrixRMaj(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } - }) - } - - public override fun buildMatrix( - rows: Int, - columns: Int, - initializer: DoubleField.(i: Int, j: Int) -> Double, - ): EjmlDoubleMatrix = DMatrixRMaj(rows, columns).also { - (0 until rows).forEach { row -> - (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } - } - }.wrapMatrix() - - public override fun buildVector( - size: Int, - initializer: DoubleField.(Int) -> Double, - ): EjmlDoubleVector = EjmlDoubleVector(DMatrixRMaj(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } - }) - - private fun T.wrapMatrix() = EjmlDoubleMatrix(this) - private fun T.wrapVector() = EjmlDoubleVector(this) - - public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } - - public override fun Matrix.dot(other: Matrix): EjmlDoubleMatrix { - val out = DMatrixRMaj(1, 1) - CommonOps_DDRM.mult(toEjml().origin, other.toEjml().origin, out) - return out.wrapMatrix() - } - - public override fun Matrix.dot(vector: Point): EjmlDoubleVector { - val out = DMatrixRMaj(1, 1) - CommonOps_DDRM.mult(toEjml().origin, vector.toEjml().origin, out) - return out.wrapVector() - } - - public override operator fun Matrix.minus(other: Matrix): EjmlDoubleMatrix { - val out = DMatrixRMaj(1, 1) - - CommonOps_DDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - ) - - return out.wrapMatrix() - } - - public override operator fun Matrix.times(value: Double): EjmlDoubleMatrix { - val res = DMatrixRMaj(1, 1) - CommonOps_DDRM.scale(value, toEjml().origin, res) - return res.wrapMatrix() - } - - public override fun Point.unaryMinus(): EjmlDoubleVector { - val res = DMatrixRMaj(1, 1) - CommonOps_DDRM.changeSign(toEjml().origin, res) - return res.wrapVector() - } - - public override fun Matrix.plus(other: Matrix): EjmlDoubleMatrix { - val out = DMatrixRMaj(1, 1) - - CommonOps_DDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - ) - - return out.wrapMatrix() - } - - public override fun Point.plus(other: Point): EjmlDoubleVector { - val out = DMatrixRMaj(1, 1) - - CommonOps_DDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - ) - - return out.wrapVector() - } - - public override fun Point.minus(other: Point): EjmlDoubleVector { - val out = DMatrixRMaj(1, 1) - - CommonOps_DDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - ) - - return out.wrapVector() - } - - public override fun Double.times(m: Matrix): EjmlDoubleMatrix = m * this - - public override fun Point.times(value: Double): EjmlDoubleVector { - val res = DMatrixRMaj(1, 1) - CommonOps_DDRM.scale(value, toEjml().origin, res) - return res.wrapVector() - } - - public override fun Double.times(v: Point): EjmlDoubleVector = v * this - - @UnstableKMathAPI - public override fun getFeature(structure: Matrix, type: KClass): F? { - structure.getFeature(type)?.let { return it } - val origin = structure.toEjml().origin - - return when (type) { - InverseMatrixFeature::class -> object : InverseMatrixFeature { - override val inverse: Matrix by lazy { - val res = origin.copy() - CommonOps_DDRM.invert(res) - res.wrapMatrix() - } - } - - DeterminantFeature::class -> object : DeterminantFeature { - override val determinant: Double by lazy { CommonOps_DDRM.det(origin) } - } - - SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { - private val svd by lazy { - DecompositionFactory_DDRM.svd(origin.numRows, origin.numCols, true, true, false) - .apply { decompose(origin.copy()) } - } - - override val u: Matrix by lazy { svd.getU(null, false).wrapMatrix() } - override val s: Matrix by lazy { svd.getW(null).wrapMatrix() } - override val v: Matrix by lazy { svd.getV(null, false).wrapMatrix() } - override val singularValues: Point by lazy { DoubleBuffer(svd.singularValues) } - } - - QRDecompositionFeature::class -> object : QRDecompositionFeature { - private val qr by lazy { - DecompositionFactory_DDRM.qr().apply { decompose(origin.copy()) } - } - - override val q: Matrix by lazy { - qr.getQ(null, false).wrapMatrix() + OrthogonalFeature - } - - override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } - } - - CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { - override val l: Matrix by lazy { - val cholesky = - DecompositionFactory_DDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) } - - cholesky.getT(null).wrapMatrix() + LFeature - } - } - - LupDecompositionFeature::class -> object : LupDecompositionFeature { - private val lup by lazy { - DecompositionFactory_DDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) } - } - - override val l: Matrix by lazy { - lup.getLower(null).wrapMatrix() + LFeature - } - - override val u: Matrix by lazy { - lup.getUpper(null).wrapMatrix() + UFeature - } - - override val p: Matrix by lazy { lup.getRowPivot(null).wrapMatrix() } - } - - else -> null - }?.let(type::cast) - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p matrix. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Matrix): EjmlDoubleMatrix { - val res = DMatrixRMaj(1, 1) - CommonOps_DDRM.solve(DMatrixRMaj(a.toEjml().origin), DMatrixRMaj(b.toEjml().origin), res) - return res.wrapMatrix() - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p vector. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Point): EjmlDoubleVector { - val res = DMatrixRMaj(1, 1) - CommonOps_DDRM.solve(DMatrixRMaj(a.toEjml().origin), DMatrixRMaj(b.toEjml().origin), res) - return EjmlDoubleVector(res) - } -} - -/** - * [EjmlLinearSpace] implementation based on [CommonOps_FDRM], [DecompositionFactory_FDRM] operations and - * [FMatrixRMaj] matrices. - */ -public object EjmlLinearSpaceFDRM : EjmlLinearSpace() { - /** - * The [FloatField] reference. - */ - public override val elementAlgebra: FloatField get() = FloatField - - @Suppress("UNCHECKED_CAST") - public override fun Matrix.toEjml(): EjmlFloatMatrix = when { - this is EjmlFloatMatrix<*> && origin is FMatrixRMaj -> this as EjmlFloatMatrix - else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } - } - - @Suppress("UNCHECKED_CAST") - public override fun Point.toEjml(): EjmlFloatVector = when { - this is EjmlFloatVector<*> && origin is FMatrixRMaj -> this as EjmlFloatVector - else -> EjmlFloatVector(FMatrixRMaj(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } - }) - } - - public override fun buildMatrix( - rows: Int, - columns: Int, - initializer: FloatField.(i: Int, j: Int) -> Float, - ): EjmlFloatMatrix = FMatrixRMaj(rows, columns).also { - (0 until rows).forEach { row -> - (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } - } - }.wrapMatrix() - - public override fun buildVector( - size: Int, - initializer: FloatField.(Int) -> Float, - ): EjmlFloatVector = EjmlFloatVector(FMatrixRMaj(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } - }) - - private fun T.wrapMatrix() = EjmlFloatMatrix(this) - private fun T.wrapVector() = EjmlFloatVector(this) - - public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } - - public override fun Matrix.dot(other: Matrix): EjmlFloatMatrix { - val out = FMatrixRMaj(1, 1) - CommonOps_FDRM.mult(toEjml().origin, other.toEjml().origin, out) - return out.wrapMatrix() - } - - public override fun Matrix.dot(vector: Point): EjmlFloatVector { - val out = FMatrixRMaj(1, 1) - CommonOps_FDRM.mult(toEjml().origin, vector.toEjml().origin, out) - return out.wrapVector() - } - - public override operator fun Matrix.minus(other: Matrix): EjmlFloatMatrix { - val out = FMatrixRMaj(1, 1) - - CommonOps_FDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - ) - - return out.wrapMatrix() - } - - public override operator fun Matrix.times(value: Float): EjmlFloatMatrix { - val res = FMatrixRMaj(1, 1) - CommonOps_FDRM.scale(value, toEjml().origin, res) - return res.wrapMatrix() - } - - public override fun Point.unaryMinus(): EjmlFloatVector { - val res = FMatrixRMaj(1, 1) - CommonOps_FDRM.changeSign(toEjml().origin, res) - return res.wrapVector() - } - - public override fun Matrix.plus(other: Matrix): EjmlFloatMatrix { - val out = FMatrixRMaj(1, 1) - - CommonOps_FDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - ) - - return out.wrapMatrix() - } - - public override fun Point.plus(other: Point): EjmlFloatVector { - val out = FMatrixRMaj(1, 1) - - CommonOps_FDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - ) - - return out.wrapVector() - } - - public override fun Point.minus(other: Point): EjmlFloatVector { - val out = FMatrixRMaj(1, 1) - - CommonOps_FDRM.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - ) - - return out.wrapVector() - } - - public override fun Float.times(m: Matrix): EjmlFloatMatrix = m * this - - public override fun Point.times(value: Float): EjmlFloatVector { - val res = FMatrixRMaj(1, 1) - CommonOps_FDRM.scale(value, toEjml().origin, res) - return res.wrapVector() - } - - public override fun Float.times(v: Point): EjmlFloatVector = v * this - - @UnstableKMathAPI - public override fun getFeature(structure: Matrix, type: KClass): F? { - structure.getFeature(type)?.let { return it } - val origin = structure.toEjml().origin - - return when (type) { - InverseMatrixFeature::class -> object : InverseMatrixFeature { - override val inverse: Matrix by lazy { - val res = origin.copy() - CommonOps_FDRM.invert(res) - res.wrapMatrix() - } - } - - DeterminantFeature::class -> object : DeterminantFeature { - override val determinant: Float by lazy { CommonOps_FDRM.det(origin) } - } - - SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature { - private val svd by lazy { - DecompositionFactory_FDRM.svd(origin.numRows, origin.numCols, true, true, false) - .apply { decompose(origin.copy()) } - } - - override val u: Matrix by lazy { svd.getU(null, false).wrapMatrix() } - override val s: Matrix by lazy { svd.getW(null).wrapMatrix() } - override val v: Matrix by lazy { svd.getV(null, false).wrapMatrix() } - override val singularValues: Point by lazy { FloatBuffer(svd.singularValues) } - } - - QRDecompositionFeature::class -> object : QRDecompositionFeature { - private val qr by lazy { - DecompositionFactory_FDRM.qr().apply { decompose(origin.copy()) } - } - - override val q: Matrix by lazy { - qr.getQ(null, false).wrapMatrix() + OrthogonalFeature - } - - override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } - } - - CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { - override val l: Matrix by lazy { - val cholesky = - DecompositionFactory_FDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) } - - cholesky.getT(null).wrapMatrix() + LFeature - } - } - - LupDecompositionFeature::class -> object : LupDecompositionFeature { - private val lup by lazy { - DecompositionFactory_FDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) } - } - - override val l: Matrix by lazy { - lup.getLower(null).wrapMatrix() + LFeature - } - - override val u: Matrix by lazy { - lup.getUpper(null).wrapMatrix() + UFeature - } - - override val p: Matrix by lazy { lup.getRowPivot(null).wrapMatrix() } - } - - else -> null - }?.let(type::cast) - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p matrix. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Matrix): EjmlFloatMatrix { - val res = FMatrixRMaj(1, 1) - CommonOps_FDRM.solve(FMatrixRMaj(a.toEjml().origin), FMatrixRMaj(b.toEjml().origin), res) - return res.wrapMatrix() - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p vector. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Point): EjmlFloatVector { - val res = FMatrixRMaj(1, 1) - CommonOps_FDRM.solve(FMatrixRMaj(a.toEjml().origin), FMatrixRMaj(b.toEjml().origin), res) - return EjmlFloatVector(res) - } -} - -/** - * [EjmlLinearSpace] implementation based on [CommonOps_DSCC], [DecompositionFactory_DSCC] operations and - * [DMatrixSparseCSC] matrices. - */ -public object EjmlLinearSpaceDSCC : EjmlLinearSpace() { - /** - * The [DoubleField] reference. - */ - public override val elementAlgebra: DoubleField get() = DoubleField - - @Suppress("UNCHECKED_CAST") - public override fun Matrix.toEjml(): EjmlDoubleMatrix = when { - this is EjmlDoubleMatrix<*> && origin is DMatrixSparseCSC -> this as EjmlDoubleMatrix - else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } - } - - @Suppress("UNCHECKED_CAST") - public override fun Point.toEjml(): EjmlDoubleVector = when { - this is EjmlDoubleVector<*> && origin is DMatrixSparseCSC -> this as EjmlDoubleVector - else -> EjmlDoubleVector(DMatrixSparseCSC(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } - }) - } - - public override fun buildMatrix( - rows: Int, - columns: Int, - initializer: DoubleField.(i: Int, j: Int) -> Double, - ): EjmlDoubleMatrix = DMatrixSparseCSC(rows, columns).also { - (0 until rows).forEach { row -> - (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } - } - }.wrapMatrix() - - public override fun buildVector( - size: Int, - initializer: DoubleField.(Int) -> Double, - ): EjmlDoubleVector = EjmlDoubleVector(DMatrixSparseCSC(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } - }) - - private fun T.wrapMatrix() = EjmlDoubleMatrix(this) - private fun T.wrapVector() = EjmlDoubleVector(this) - - public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } - - public override fun Matrix.dot(other: Matrix): EjmlDoubleMatrix { - val out = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.mult(toEjml().origin, other.toEjml().origin, out) - return out.wrapMatrix() - } - - public override fun Matrix.dot(vector: Point): EjmlDoubleVector { - val out = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.mult(toEjml().origin, vector.toEjml().origin, out) - return out.wrapVector() - } - - public override operator fun Matrix.minus(other: Matrix): EjmlDoubleMatrix { - val out = DMatrixSparseCSC(1, 1) - - CommonOps_DSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapMatrix() - } - - public override operator fun Matrix.times(value: Double): EjmlDoubleMatrix { - val res = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.scale(value, toEjml().origin, res) - return res.wrapMatrix() - } - - public override fun Point.unaryMinus(): EjmlDoubleVector { - val res = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.changeSign(toEjml().origin, res) - return res.wrapVector() - } - - public override fun Matrix.plus(other: Matrix): EjmlDoubleMatrix { - val out = DMatrixSparseCSC(1, 1) - - CommonOps_DSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapMatrix() - } - - public override fun Point.plus(other: Point): EjmlDoubleVector { - val out = DMatrixSparseCSC(1, 1) - - CommonOps_DSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapVector() - } - - public override fun Point.minus(other: Point): EjmlDoubleVector { - val out = DMatrixSparseCSC(1, 1) - - CommonOps_DSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapVector() - } - - public override fun Double.times(m: Matrix): EjmlDoubleMatrix = m * this - - public override fun Point.times(value: Double): EjmlDoubleVector { - val res = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.scale(value, toEjml().origin, res) - return res.wrapVector() - } - - public override fun Double.times(v: Point): EjmlDoubleVector = v * this - - @UnstableKMathAPI - public override fun getFeature(structure: Matrix, type: KClass): F? { - structure.getFeature(type)?.let { return it } - val origin = structure.toEjml().origin - - return when (type) { - QRDecompositionFeature::class -> object : QRDecompositionFeature { - private val qr by lazy { - DecompositionFactory_DSCC.qr(FillReducing.NONE).apply { decompose(origin.copy()) } - } - - override val q: Matrix by lazy { - qr.getQ(null, false).wrapMatrix() + OrthogonalFeature - } - - override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } - } - - CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { - override val l: Matrix by lazy { - val cholesky = - DecompositionFactory_DSCC.cholesky().apply { decompose(origin.copy()) } - - (cholesky.getT(null) as DMatrix).wrapMatrix() + LFeature - } - } - - LUDecompositionFeature::class, DeterminantFeature::class, InverseMatrixFeature::class -> object : - LUDecompositionFeature, DeterminantFeature, InverseMatrixFeature { - private val lu by lazy { - DecompositionFactory_DSCC.lu(FillReducing.NONE).apply { decompose(origin.copy()) } - } - - override val l: Matrix by lazy { - lu.getLower(null).wrapMatrix() + LFeature - } - - override val u: Matrix by lazy { - lu.getUpper(null).wrapMatrix() + UFeature - } - - override val inverse: Matrix by lazy { - var a = origin - val inverse = DMatrixRMaj(1, 1) - val solver = LinearSolverFactory_DSCC.lu(FillReducing.NONE) - if (solver.modifiesA()) a = a.copy() - val i = CommonOps_DDRM.identity(a.numRows) - solver.solve(i, inverse) - inverse.wrapMatrix() - } - - override val determinant: Double by lazy { elementAlgebra.number(lu.computeDeterminant().real) } - } - - else -> null - }?.let(type::cast) - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p matrix. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Matrix): EjmlDoubleMatrix { - val res = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.solve(DMatrixSparseCSC(a.toEjml().origin), DMatrixSparseCSC(b.toEjml().origin), res) - return res.wrapMatrix() - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p vector. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Point): EjmlDoubleVector { - val res = DMatrixSparseCSC(1, 1) - CommonOps_DSCC.solve(DMatrixSparseCSC(a.toEjml().origin), DMatrixSparseCSC(b.toEjml().origin), res) - return EjmlDoubleVector(res) - } -} - -/** - * [EjmlLinearSpace] implementation based on [CommonOps_FSCC], [DecompositionFactory_FSCC] operations and - * [FMatrixSparseCSC] matrices. - */ -public object EjmlLinearSpaceFSCC : EjmlLinearSpace() { - /** - * The [FloatField] reference. - */ - public override val elementAlgebra: FloatField get() = FloatField - - @Suppress("UNCHECKED_CAST") - public override fun Matrix.toEjml(): EjmlFloatMatrix = when { - this is EjmlFloatMatrix<*> && origin is FMatrixSparseCSC -> this as EjmlFloatMatrix - else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) } - } - - @Suppress("UNCHECKED_CAST") - public override fun Point.toEjml(): EjmlFloatVector = when { - this is EjmlFloatVector<*> && origin is FMatrixSparseCSC -> this as EjmlFloatVector - else -> EjmlFloatVector(FMatrixSparseCSC(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = get(row) } - }) - } - - public override fun buildMatrix( - rows: Int, - columns: Int, - initializer: FloatField.(i: Int, j: Int) -> Float, - ): EjmlFloatMatrix = FMatrixSparseCSC(rows, columns).also { - (0 until rows).forEach { row -> - (0 until columns).forEach { col -> it[row, col] = elementAlgebra.initializer(row, col) } - } - }.wrapMatrix() - - public override fun buildVector( - size: Int, - initializer: FloatField.(Int) -> Float, - ): EjmlFloatVector = EjmlFloatVector(FMatrixSparseCSC(size, 1).also { - (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) } - }) - - private fun T.wrapMatrix() = EjmlFloatMatrix(this) - private fun T.wrapVector() = EjmlFloatVector(this) - - public override fun Matrix.unaryMinus(): Matrix = this * elementAlgebra { -one } - - public override fun Matrix.dot(other: Matrix): EjmlFloatMatrix { - val out = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.mult(toEjml().origin, other.toEjml().origin, out) - return out.wrapMatrix() - } - - public override fun Matrix.dot(vector: Point): EjmlFloatVector { - val out = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.mult(toEjml().origin, vector.toEjml().origin, out) - return out.wrapVector() - } - - public override operator fun Matrix.minus(other: Matrix): EjmlFloatMatrix { - val out = FMatrixSparseCSC(1, 1) - - CommonOps_FSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapMatrix() - } - - public override operator fun Matrix.times(value: Float): EjmlFloatMatrix { - val res = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.scale(value, toEjml().origin, res) - return res.wrapMatrix() - } - - public override fun Point.unaryMinus(): EjmlFloatVector { - val res = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.changeSign(toEjml().origin, res) - return res.wrapVector() - } - - public override fun Matrix.plus(other: Matrix): EjmlFloatMatrix { - val out = FMatrixSparseCSC(1, 1) - - CommonOps_FSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapMatrix() - } - - public override fun Point.plus(other: Point): EjmlFloatVector { - val out = FMatrixSparseCSC(1, 1) - - CommonOps_FSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra.one, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapVector() - } - - public override fun Point.minus(other: Point): EjmlFloatVector { - val out = FMatrixSparseCSC(1, 1) - - CommonOps_FSCC.add( - elementAlgebra.one, - toEjml().origin, - elementAlgebra { -one }, - other.toEjml().origin, - out, - null, - null, - ) - - return out.wrapVector() - } - - public override fun Float.times(m: Matrix): EjmlFloatMatrix = m * this - - public override fun Point.times(value: Float): EjmlFloatVector { - val res = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.scale(value, toEjml().origin, res) - return res.wrapVector() - } - - public override fun Float.times(v: Point): EjmlFloatVector = v * this - - @UnstableKMathAPI - public override fun getFeature(structure: Matrix, type: KClass): F? { - structure.getFeature(type)?.let { return it } - val origin = structure.toEjml().origin - - return when (type) { - QRDecompositionFeature::class -> object : QRDecompositionFeature { - private val qr by lazy { - DecompositionFactory_FSCC.qr(FillReducing.NONE).apply { decompose(origin.copy()) } - } - - override val q: Matrix by lazy { - qr.getQ(null, false).wrapMatrix() + OrthogonalFeature - } - - override val r: Matrix by lazy { qr.getR(null, false).wrapMatrix() + UFeature } - } - - CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature { - override val l: Matrix by lazy { - val cholesky = - DecompositionFactory_FSCC.cholesky().apply { decompose(origin.copy()) } - - (cholesky.getT(null) as FMatrix).wrapMatrix() + LFeature - } - } - - LUDecompositionFeature::class, DeterminantFeature::class, InverseMatrixFeature::class -> object : - LUDecompositionFeature, DeterminantFeature, InverseMatrixFeature { - private val lu by lazy { - DecompositionFactory_FSCC.lu(FillReducing.NONE).apply { decompose(origin.copy()) } - } - - override val l: Matrix by lazy { - lu.getLower(null).wrapMatrix() + LFeature - } - - override val u: Matrix by lazy { - lu.getUpper(null).wrapMatrix() + UFeature - } - - override val inverse: Matrix by lazy { - var a = origin - val inverse = FMatrixRMaj(1, 1) - val solver = LinearSolverFactory_FSCC.lu(FillReducing.NONE) - if (solver.modifiesA()) a = a.copy() - val i = CommonOps_FDRM.identity(a.numRows) - solver.solve(i, inverse) - inverse.wrapMatrix() - } - - override val determinant: Float by lazy { elementAlgebra.number(lu.computeDeterminant().real) } - } - - else -> null - }?.let(type::cast) - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p matrix. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Matrix): EjmlFloatMatrix { - val res = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.solve(FMatrixSparseCSC(a.toEjml().origin), FMatrixSparseCSC(b.toEjml().origin), res) - return res.wrapMatrix() - } - - /** - * Solves for *x* in the following equation: *x = [a] -1 · [b]*. - * - * @param a the base matrix. - * @param b n by p vector. - * @return the solution for *x* that is n by p. - */ - public fun solve(a: Matrix, b: Point): EjmlFloatVector { - val res = FMatrixSparseCSC(1, 1) - CommonOps_FSCC.solve(FMatrixSparseCSC(a.toEjml().origin), FMatrixSparseCSC(b.toEjml().origin), res) - return EjmlFloatVector(res) - } -} - diff --git a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt b/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt index 50675bdac..6055ae1d8 100644 --- a/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt +++ b/kmath-ejml/src/test/kotlin/space/kscience/kmath/ejml/EjmlMatrixTest.kt @@ -11,7 +11,7 @@ import org.ejml.dense.row.RandomMatrices_DDRM import org.ejml.dense.row.factory.DecompositionFactory_DDRM import space.kscience.kmath.linear.DeterminantFeature import space.kscience.kmath.linear.LupDecompositionFeature -import space.kscience.kmath.linear.getFeature +import space.kscience.kmath.linear.computeFeature import space.kscience.kmath.misc.PerformancePitfall import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.nd.StructureND @@ -59,9 +59,9 @@ internal class EjmlMatrixTest { fun features() { val m = randomMatrix val w = EjmlDoubleMatrix(m) - val det: DeterminantFeature = EjmlLinearSpaceDDRM.getFeature(w) ?: fail() + val det: DeterminantFeature = EjmlLinearSpaceDDRM.computeFeature(w) ?: fail() assertEquals(CommonOps_DDRM.det(m), det.determinant) - val lup: LupDecompositionFeature = EjmlLinearSpaceDDRM.getFeature(w) ?: fail() + val lup: LupDecompositionFeature = EjmlLinearSpaceDDRM.computeFeature(w) ?: fail() val ludecompositionF64 = DecompositionFactory_DDRM.lu(m.numRows, m.numCols) .also { it.decompose(m.copy()) } diff --git a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt index 8023236ea..38e5b4beb 100644 --- a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt +++ b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/RealMatrix.kt @@ -32,18 +32,18 @@ import kotlin.math.pow public typealias RealMatrix = Matrix public fun realMatrix(rowNum: Int, colNum: Int, initializer: DoubleField.(i: Int, j: Int) -> Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum, initializer) + LinearSpace.double.buildMatrix(rowNum, colNum, initializer) @OptIn(UnstableKMathAPI::class) public fun realMatrix(rowNum: Int, colNum: Int): MatrixBuilder = - LinearSpace.real.matrix(rowNum, colNum) + LinearSpace.double.matrix(rowNum, colNum) public fun Array.toMatrix(): RealMatrix { - return LinearSpace.real.buildMatrix(size, this[0].size) { row, col -> this@toMatrix[row][col] } + return LinearSpace.double.buildMatrix(size, this[0].size) { row, col -> this@toMatrix[row][col] } } public fun Sequence.toMatrix(): RealMatrix = toList().let { - LinearSpace.real.buildMatrix(it.size, it[0].size) { row, col -> it[row][col] } + LinearSpace.double.buildMatrix(it.size, it[0].size) { row, col -> it[row][col] } } public fun RealMatrix.repeatStackVertical(n: Int): RealMatrix = @@ -56,37 +56,37 @@ public fun RealMatrix.repeatStackVertical(n: Int): RealMatrix = */ public operator fun RealMatrix.times(double: Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> + LinearSpace.double.buildMatrix(rowNum, colNum) { row, col -> get(row, col) * double } public operator fun RealMatrix.plus(double: Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> + LinearSpace.double.buildMatrix(rowNum, colNum) { row, col -> get(row, col) + double } public operator fun RealMatrix.minus(double: Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> + LinearSpace.double.buildMatrix(rowNum, colNum) { row, col -> get(row, col) - double } public operator fun RealMatrix.div(double: Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> + LinearSpace.double.buildMatrix(rowNum, colNum) { row, col -> get(row, col) / double } public operator fun Double.times(matrix: RealMatrix): RealMatrix = - LinearSpace.real.buildMatrix(matrix.rowNum, matrix.colNum) { row, col -> + LinearSpace.double.buildMatrix(matrix.rowNum, matrix.colNum) { row, col -> this@times * matrix[row, col] } public operator fun Double.plus(matrix: RealMatrix): RealMatrix = - LinearSpace.real.buildMatrix(matrix.rowNum, matrix.colNum) { row, col -> + LinearSpace.double.buildMatrix(matrix.rowNum, matrix.colNum) { row, col -> this@plus + matrix[row, col] } public operator fun Double.minus(matrix: RealMatrix): RealMatrix = - LinearSpace.real.buildMatrix(matrix.rowNum, matrix.colNum) { row, col -> + LinearSpace.double.buildMatrix(matrix.rowNum, matrix.colNum) { row, col -> this@minus - matrix[row, col] } @@ -101,20 +101,20 @@ public operator fun Double.minus(matrix: RealMatrix): RealMatrix = @UnstableKMathAPI public operator fun RealMatrix.times(other: RealMatrix): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> this@times[row, col] * other[row, col] } + LinearSpace.double.buildMatrix(rowNum, colNum) { row, col -> this@times[row, col] * other[row, col] } public operator fun RealMatrix.plus(other: RealMatrix): RealMatrix = - LinearSpace.real.run { this@plus + other } + LinearSpace.double.run { this@plus + other } public operator fun RealMatrix.minus(other: RealMatrix): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { row, col -> this@minus[row, col] - other[row, col] } + LinearSpace.double.buildMatrix(rowNum, colNum) { row, col -> this@minus[row, col] - other[row, col] } /* * Operations on columns */ public inline fun RealMatrix.appendColumn(crossinline mapper: (Buffer) -> Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum + 1) { row, col -> + LinearSpace.double.buildMatrix(rowNum, colNum + 1) { row, col -> if (col < colNum) get(row, col) else @@ -122,7 +122,7 @@ public inline fun RealMatrix.appendColumn(crossinline mapper: (Buffer) - } public fun RealMatrix.extractColumns(columnRange: IntRange): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, columnRange.count()) { row, col -> + LinearSpace.double.buildMatrix(rowNum, columnRange.count()) { row, col -> this@extractColumns[row, columnRange.first + col] } @@ -155,14 +155,14 @@ public fun RealMatrix.max(): Double? = elements().map { (_, value) -> value }.ma public fun RealMatrix.average(): Double = elements().map { (_, value) -> value }.average() public inline fun RealMatrix.map(crossinline transform: (Double) -> Double): RealMatrix = - LinearSpace.real.buildMatrix(rowNum, colNum) { i, j -> + LinearSpace.double.buildMatrix(rowNum, colNum) { i, j -> transform(get(i, j)) } /** * Inverse a square real matrix using LUP decomposition */ -public fun RealMatrix.inverseWithLup(): RealMatrix = LinearSpace.real.inverseWithLup(this) +public fun RealMatrix.inverseWithLup(): RealMatrix = LinearSpace.double.lupSolver().inverse(this) //extended operations diff --git a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt index b79e5030c..395c94838 100644 --- a/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt +++ b/kmath-for-real/src/commonMain/kotlin/space/kscience/kmath/real/dot.kt @@ -12,6 +12,6 @@ import space.kscience.kmath.linear.Matrix /** * Optimized dot product for real matrices */ -public infix fun Matrix.dot(other: Matrix): Matrix = LinearSpace.real.run { +public infix fun Matrix.dot(other: Matrix): Matrix = LinearSpace.double.run { this@dot dot other } \ No newline at end of file diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt index b3e129c2e..5a80ee677 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleMatrixTest.kt @@ -65,7 +65,7 @@ internal class DoubleMatrixTest { 4.0, 6.0, 2.0 ) val matrix2 = (matrix1 * 2.5 + 1.0 - 2.0) / 2.0 - val expectedResult = LinearSpace.real.matrix(2, 3)( + val expectedResult = LinearSpace.double.matrix(2, 3)( 0.75, -0.5, 3.25, 4.5, 7.0, 2.0 ) @@ -160,7 +160,7 @@ internal class DoubleMatrixTest { @Test fun testAllElementOperations() { - val matrix1 = LinearSpace.real.matrix(2, 4)( + val matrix1 = LinearSpace.double.matrix(2, 4)( -1.0, 0.0, 3.0, 15.0, 4.0, -6.0, 7.0, -11.0 ) diff --git a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleVectorTest.kt b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleVectorTest.kt index 9de54381c..ec7b536ba 100644 --- a/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleVectorTest.kt +++ b/kmath-for-real/src/commonTest/kotlin/kaceince/kmath/real/DoubleVectorTest.kt @@ -35,7 +35,7 @@ internal class DoubleVectorTest { val vector2 = DoubleBuffer(5) { 5 - it.toDouble() } val matrix1 = vector1.asMatrix() val matrix2 = vector2.asMatrix().transpose() - val product = LinearSpace.real.run { matrix1 dot matrix2 } + val product = LinearSpace.double.run { matrix1 dot matrix2 } assertEquals(5.0, product[1, 0]) assertEquals(6.0, product[2, 2]) } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt index dcf711c3b..f90159c81 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/Integrand.kt @@ -5,11 +5,12 @@ package space.kscience.kmath.integration +import space.kscience.kmath.misc.Feature import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.Featured import kotlin.reflect.KClass -public interface IntegrandFeature { +public interface IntegrandFeature : Feature { override fun toString(): String } @@ -18,7 +19,7 @@ public interface Integrand : Featured { override fun getFeature(type: KClass): T? = features.getFeature(type) } -public inline fun Integrand.getFeature(): T? = getFeature(T::class) +public inline fun Integrand.getFeature(): T? = getFeature(T::class) public class IntegrandValue(public val value: T) : IntegrandFeature { override fun toString(): String = "Value($value)" diff --git a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt index 4294462c0..a59c301cf 100644 --- a/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt +++ b/kmath-kotlingrad/src/main/kotlin/space/kscience/kmath/kotlingrad/KotlingradExpression.kt @@ -27,19 +27,30 @@ public class KotlingradExpression>( ) : SpecialDifferentiableExpression> { public override fun invoke(arguments: Map): T = mst.interpret(algebra, arguments) - public override fun derivativeOrNull(symbols: List): KotlingradExpression = - KotlingradExpression( - algebra, - symbols.map(Symbol::identity) - .map(MstNumericAlgebra::bindSymbol) - .map>>(Symbol::toSVar) - .fold(mst.toSFun(), SFun>::d) - .toMst(), - ) + public override fun derivativeOrNull( + symbols: List, + ): KotlingradExpression = KotlingradExpression( + algebra, + symbols.map(Symbol::identity) + .map(MstNumericAlgebra::bindSymbol) + .map>>(Symbol::toSVar) + .fold(mst.toSFun(), SFun>::d) + .toMst(), + ) +} + +/** + * A diff processor using [MST] to Kotlingrad converter + */ +public class KotlingradProcessor>( + public val algebra: A, +) : AutoDiffProcessor { + override fun differentiate(function: MstExtendedField.() -> MST): DifferentiableExpression = + MstExtendedField.function().toKotlingradExpression(algebra) } /** * Wraps this [MST] into [KotlingradExpression]. */ -public fun > MST.toDiffExpression(algebra: A): KotlingradExpression = +public fun > MST.toKotlingradExpression(algebra: A): KotlingradExpression = KotlingradExpression(algebra, this) diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index 62288242a..4d4f99b71 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -23,7 +23,7 @@ public enum class FunctionOptimizationTarget : OptimizationFeature { public class FunctionOptimization( override val features: FeatureSet, public val expression: DifferentiableExpression, -) : OptimizationProblem{ +) : OptimizationProblem{ public companion object{ /** @@ -56,7 +56,6 @@ public class FunctionOptimization( } } - public fun FunctionOptimization.withFeatures( vararg newFeature: OptimizationFeature, ): FunctionOptimization = FunctionOptimization( @@ -68,7 +67,7 @@ public fun FunctionOptimization.withFeatures( * Optimize differentiable expression using specific [optimizer] form given [startingPoint] */ public suspend fun DifferentiableExpression.optimizeWith( - optimizer: Optimizer>, + optimizer: Optimizer>, startingPoint: Map, vararg features: OptimizationFeature, ): FunctionOptimization { @@ -76,3 +75,8 @@ public suspend fun DifferentiableExpression.optimizeWith( return optimizer.optimize(problem) } +public val FunctionOptimization.resultValueOrNull:T? + get() = getFeature>()?.point?.let { expression(it) } + +public val FunctionOptimization.resultValue: T + get() = resultValueOrNull ?: error("Result is not present in $this") \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt new file mode 100644 index 000000000..7d52ae26e --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.optimization + +import space.kscience.kmath.data.XYColumnarData +import space.kscience.kmath.expressions.DifferentiableExpression +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.misc.FeatureSet + +public abstract class OptimizationBuilder> { + public val features: ArrayList = ArrayList() + + public fun addFeature(feature: OptimizationFeature) { + features.add(feature) + } + + public inline fun updateFeature(update: (T?) -> T) { + val existing = features.find { it.key == T::class } as? T + val new = update(existing) + if (existing != null) { + features.remove(existing) + } + addFeature(new) + } + + public abstract fun build(): R +} + +public fun OptimizationBuilder.startAt(startingPoint: Map) { + addFeature(OptimizationStartPoint(startingPoint)) +} + +public class FunctionOptimizationBuilder( + private val expression: DifferentiableExpression, +) : OptimizationBuilder>() { + override fun build(): FunctionOptimization = FunctionOptimization(FeatureSet.of(features), expression) +} + +public fun FunctionOptimization( + expression: DifferentiableExpression, + builder: FunctionOptimizationBuilder.() -> Unit, +): FunctionOptimization = FunctionOptimizationBuilder(expression).apply(builder).build() + +public suspend fun DifferentiableExpression.optimizeWith( + optimizer: Optimizer>, + startingPoint: Map, + builder: FunctionOptimizationBuilder.() -> Unit = {}, +): FunctionOptimization { + val problem = FunctionOptimization(this) { + startAt(startingPoint) + builder() + } + return optimizer.optimize(problem) +} + +public suspend fun DifferentiableExpression.optimizeWith( + optimizer: Optimizer>, + vararg startingPoint: Pair, + builder: FunctionOptimizationBuilder.() -> Unit = {}, +): FunctionOptimization { + val problem = FunctionOptimization(this) { + startAt(mapOf(*startingPoint)) + builder() + } + return optimizer.optimize(problem) +} + + +public class XYOptimizationBuilder( + public val data: XYColumnarData, + public val model: DifferentiableExpression, +) : OptimizationBuilder() { + + public var pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY + public var pointWeight: PointWeight = PointWeight.byYSigma + + override fun build(): XYOptimization = XYOptimization( + FeatureSet.of(features), + data, + model, + pointToCurveDistance, + pointWeight + ) +} + +public fun XYOptimization( + data: XYColumnarData, + model: DifferentiableExpression, + builder: XYOptimizationBuilder.() -> Unit, +): XYOptimization = XYOptimizationBuilder(data, model).apply(builder).build() \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt index 739ce8ca5..b42be4035 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt @@ -5,37 +5,61 @@ package space.kscience.kmath.optimization +import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.Symbol -import space.kscience.kmath.misc.FeatureSet -import space.kscience.kmath.misc.Featured -import space.kscience.kmath.misc.Loggable +import space.kscience.kmath.linear.Matrix +import space.kscience.kmath.misc.* import kotlin.reflect.KClass -public interface OptimizationFeature { +public interface OptimizationFeature : Feature { + // enforce toString override override fun toString(): String } -public interface OptimizationProblem : Featured { +public interface OptimizationProblem : Featured { public val features: FeatureSet - override fun getFeature(type: KClass): T? = features.getFeature(type) + override fun getFeature(type: KClass): F? = features.getFeature(type) } -public inline fun OptimizationProblem.getFeature(): T? = getFeature(T::class) +public inline fun OptimizationProblem<*>.getFeature(): F? = getFeature(F::class) public open class OptimizationStartPoint(public val point: Map) : OptimizationFeature { override fun toString(): String = "StartPoint($point)" } + +public interface OptimizationPrior : OptimizationFeature, DifferentiableExpression { + override val key: FeatureKey get() = OptimizationPrior::class +} + +public class OptimizationCovariance(public val covariance: Matrix) : OptimizationFeature { + override fun toString(): String = "Covariance($covariance)" +} + +/** + * Get the starting point for optimization. Throws error if not defined. + */ +public val OptimizationProblem.startPoint: Map + get() = getFeature>()?.point + ?: error("Starting point not defined in $this") + public open class OptimizationResult(public val point: Map) : OptimizationFeature { override fun toString(): String = "Result($point)" } +public val OptimizationProblem.resultPointOrNull: Map? + get() = getFeature>()?.point + +public val OptimizationProblem.resultPoint: Map + get() = resultPointOrNull ?: error("Result is not present in $this") + public class OptimizationLog(private val loggable: Loggable) : Loggable by loggable, OptimizationFeature { override fun toString(): String = "Log($loggable)" } -public class OptimizationParameters(public val symbols: List): OptimizationFeature{ +public class OptimizationParameters(public val symbols: List) : OptimizationFeature { public constructor(vararg symbols: Symbol) : this(listOf(*symbols)) + override fun toString(): String = "Parameters($symbols)" } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt index ad1a16007..78385a99b 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt @@ -5,6 +5,6 @@ package space.kscience.kmath.optimization -public interface Optimizer

{ +public interface Optimizer> { public suspend fun optimize(problem: P): P } \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt new file mode 100644 index 000000000..acfc7e445 --- /dev/null +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt @@ -0,0 +1,247 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.optimization + +import space.kscience.kmath.expressions.DifferentiableExpression +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.SymbolIndexer +import space.kscience.kmath.expressions.derivative +import space.kscience.kmath.linear.* +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.structures.DoubleBuffer +import space.kscience.kmath.structures.DoubleL2Norm + + +/** + * An optimizer based onf Fyodor Tkachev's quasi-optimal weights method. + * See [the article](http://arxiv.org/abs/physics/0604127). + */ +@UnstableKMathAPI +public class QowOptimizer : Optimizer { + + private val linearSpace: LinearSpace = LinearSpace.double + private val solver: LinearSolver = linearSpace.lupSolver() + + @OptIn(UnstableKMathAPI::class) + private inner class QoWeight( + val problem: XYOptimization, + val parameters: Map, + ) : Map by parameters, SymbolIndexer { + override val symbols: List = parameters.keys.toList() + + val data get() = problem.data + + /** + * Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter + */ + val derivs: Matrix by lazy { + linearSpace.buildMatrix(problem.data.size, symbols.size) { i, k -> + problem.distance(i).derivative(symbols[k])(parameters) + } + } + + /** + * Array of dispersions in each point + */ + val dispersion: Point by lazy { + DoubleBuffer(problem.data.size) { i -> + problem.weight(i).invoke(parameters) + } + } + + val prior: DifferentiableExpression? get() = problem.getFeature>() + } + + /** + * The signed distance from the model to the [i]-th point of data. + */ + private fun QoWeight.distance(i: Int, parameters: Map): Double = problem.distance(i)(parameters) + + + /** + * The derivative of [distance] + */ + private fun QoWeight.distanceDerivative(symbol: Symbol, i: Int, parameters: Map): Double = + problem.distance(i).derivative(symbol)(parameters) + + /** + * Теоретическая ковариация весовых функций. + * + * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2 + */ + private fun QoWeight.covarF(): Matrix = + linearSpace.matrix(size, size).symmetric { k, l -> + (0 until data.size).sumOf { i -> derivs[k, i] * derivs[l, i] / dispersion[i] } + } + + /** + * Экспериментальная ковариация весов. Формула (22) из + * http://arxiv.org/abs/physics/0604127 + */ + private fun QoWeight.covarFExp(theta: Map): Matrix = + with(linearSpace) { + /* + * Важно! Если не делать предварителього вычисления этих производных, то + * количество вызывов функции будет dim^2 вместо dim Первый индекс - + * номер точки, второй - номер переменной, по которой берется производная + */ + val eqvalues = linearSpace.buildMatrix(data.size, size) { i, l -> + distance(i, theta) * derivs[l, i] / dispersion[i] + } + + buildMatrix(size, size) { k, l -> + (0 until data.size).sumOf { i -> eqvalues[i, l] * eqvalues[i, k] } + } + } + + /** + * Equation derivatives for Newton run + */ + private fun QoWeight.getEqDerivValues( + theta: Map = parameters, + ): Matrix = with(linearSpace) { + val fitDim = size + //Возвращает производную k-того Eq по l-тому параметру + //val res = Array(fitDim) { DoubleArray(fitDim) } + val sderiv = buildMatrix(data.size, size) { i, l -> + distanceDerivative(symbols[l], i, theta) + } + + buildMatrix(size, size) { k, l -> + val base = (0 until data.size).sumOf { i -> + require(dispersion[i] > 0) + sderiv[i, l] * derivs[k, i] / dispersion[i] + } + prior?.let { prior -> + //Check if this one is correct + val pi = prior(theta) + val deriv1 = prior.derivative(symbols[k])(theta) + val deriv2 = prior.derivative(symbols[l])(theta) + base + deriv1 * deriv2 / pi / pi + } ?: base + } + } + + + /** + * Значения уравнений метода квазиоптимальных весов + */ + private fun QoWeight.getEqValues(theta: Map = this): Point { + val distances = DoubleBuffer(data.size) { i -> distance(i, theta) } + + return DoubleBuffer(size) { k -> + val base = (0 until data.size).sumOf { i -> distances[i] * derivs[k, i] / dispersion[i] } + //Поправка на априорную вероятность + prior?.let { prior -> + base - prior.derivative(symbols[k])(theta) / prior(theta) + } ?: base + } + } + + + private fun QoWeight.newtonianStep( + theta: Map, + eqvalues: Point, + ): QoWeight = linearSpace { + with(this@newtonianStep) { + val start = theta.toPoint() + val invJacob = solver.inverse(this@newtonianStep.getEqDerivValues(theta)) + + val step = invJacob.dot(eqvalues) + return QoWeight(problem, theta + (start - step).toMap()) + } + } + + private fun QoWeight.newtonianRun( + maxSteps: Int = 100, + tolerance: Double = 0.0, + fast: Boolean = false, + ): QoWeight { + + val logger = problem.getFeature() + + var dis: Double//норма невязки + // Для удобства работаем всегда с полным набором параметров + var par = problem.startPoint + + logger?.log { "Starting newtonian iteration from: \n\t$par" } + + var eqvalues = getEqValues(par)//значения функций + + dis = DoubleL2Norm.norm(eqvalues)// невязка + logger?.log { "Starting discrepancy is $dis" } + var i = 0 + var flag = false + while (!flag) { + i++ + logger?.log { "Starting step number $i" } + + val currentSolution = if (fast) { + //Берет значения матрицы в той точке, где считается вес + newtonianStep(this, eqvalues) + } else { + //Берет значения матрицы в точке par + newtonianStep(par, eqvalues) + } + // здесь должен стоять учет границ параметров + logger?.log { "Parameter values after step are: \n\t$currentSolution" } + + eqvalues = getEqValues(currentSolution) + val currentDis = DoubleL2Norm.norm(eqvalues)// невязка после шага + + logger?.log { "The discrepancy after step is: $currentDis." } + + if (currentDis >= dis && i > 1) { + //дополнительно проверяем, чтобы был сделан хотя бы один шаг + flag = true + logger?.log { "The discrepancy does not decrease. Stopping iteration." } + } else { + par = currentSolution + dis = currentDis + } + if (i >= maxSteps) { + flag = true + logger?.log { "Maximum number of iterations reached. Stopping iteration." } + } + if (dis <= tolerance) { + flag = true + logger?.log { "Tolerance threshold is reached. Stopping iteration." } + } + } + + return QoWeight(problem, par) + } + + private fun QoWeight.covariance(): Matrix { + val logger = problem.getFeature() + + logger?.log { + """ + Starting errors estimation using quasioptimal weights method. The starting weight is: + ${problem.startPoint} + """.trimIndent() + } + + val covar = solver.inverse(getEqDerivValues()) + //TODO fix eigenvalues check +// val decomposition = EigenDecomposition(covar.matrix) +// var valid = true +// for (lambda in decomposition.realEigenvalues) { +// if (lambda <= 0) { +// logger?.log { "The covariance matrix is not positive defined. Error estimation is not valid" } +// valid = false +// } +// } + return covar + } + + override suspend fun optimize(problem: XYOptimization): XYOptimization { + val initialWeight = QoWeight(problem, problem.startPoint) + val res = initialWeight.newtonianRun() + return res.problem.withFeature(OptimizationResult(res.parameters)) + } +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt index 7dc17f6d9..68c0c77eb 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt +++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt @@ -7,13 +7,21 @@ package space.kscience.kmath.optimization import space.kscience.kmath.data.XYColumnarData +import space.kscience.kmath.data.indices import space.kscience.kmath.expressions.DifferentiableExpression import space.kscience.kmath.expressions.Expression import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.derivative import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.UnstableKMathAPI +import kotlin.math.PI +import kotlin.math.ln import kotlin.math.pow +import kotlin.math.sqrt +/** + * Specify the way to compute distance from point to the curve as DifferentiableExpression + */ public interface PointToCurveDistance : OptimizationFeature { public fun distance(problem: XYOptimization, index: Int): DifferentiableExpression @@ -33,42 +41,107 @@ public interface PointToCurveDistance : OptimizationFeature { } override fun toString(): String = "PointToCurveDistanceByY" - } - } } +/** + * Compute a wight of the point. The more the weight, the more impact this point will have on the fit. + * By default uses Dispersion^-1 + */ +public interface PointWeight : OptimizationFeature { + public fun weight(problem: XYOptimization, index: Int): DifferentiableExpression + + public companion object { + public fun bySigma(sigmaSymbol: Symbol): PointWeight = object : PointWeight { + override fun weight(problem: XYOptimization, index: Int): DifferentiableExpression = + object : DifferentiableExpression { + override fun invoke(arguments: Map): Double { + return problem.data[sigmaSymbol]?.get(index)?.pow(-2) ?: 1.0 + } + + override fun derivativeOrNull(symbols: List): Expression = Expression { 0.0 } + } + + override fun toString(): String = "PointWeightBySigma($sigmaSymbol)" + + } + + public val byYSigma: PointWeight = bySigma(Symbol.yError) + } +} + +/** + * An optimization for XY data. + */ public class XYOptimization( override val features: FeatureSet, public val data: XYColumnarData, public val model: DifferentiableExpression, -) : OptimizationProblem + internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, + internal val pointWeight: PointWeight = PointWeight.byYSigma, +) : OptimizationProblem { + public fun distance(index: Int): DifferentiableExpression = pointToCurveDistance.distance(this, index) + public fun weight(index: Int): DifferentiableExpression = pointWeight.weight(this, index) +} -public suspend fun Optimizer>.maximumLogLikelihood(problem: XYOptimization): XYOptimization { - val distanceBuilder = problem.getFeature() ?: PointToCurveDistance.byY - val likelihood: DifferentiableExpression = object : DifferentiableExpression { - override fun derivativeOrNull(symbols: List): Expression? { - TODO("Not yet implemented") +public fun XYOptimization.withFeature(vararg features: OptimizationFeature): XYOptimization { + return XYOptimization(this.features.with(*features), data, model, pointToCurveDistance, pointWeight) +} + +private val oneOver2Pi = 1.0 / sqrt(2 * PI) + +internal fun XYOptimization.likelihood(): DifferentiableExpression = object : DifferentiableExpression { + override fun derivativeOrNull(symbols: List): Expression = Expression { arguments -> + data.indices.sumOf { index -> + + val d = distance(index)(arguments) + val weight = weight(index)(arguments) + val weightDerivative = weight(index)(arguments) + + // -1 / (sqrt(2 PI) * sigma) + 2 (x-mu)/ 2 sigma^2 * d mu/ d theta - (x-mu)^2 / 2 * d w/ d theta + return@sumOf -oneOver2Pi * sqrt(weight) + //offset derivative + d * model.derivative(symbols)(arguments) * weight - //model derivative + d.pow(2) * weightDerivative / 2 //weight derivative } - - override fun invoke(arguments: Map): Double { - var res = 0.0 - for (index in 0 until problem.data.size) { - val d = distanceBuilder.distance(problem, index).invoke(arguments) - val sigma: Double = TODO() - res -= (d / sigma).pow(2) - } - return res - } - } - val functionOptimization = FunctionOptimization(problem.features, likelihood) - val result = optimize(functionOptimization) + + override fun invoke(arguments: Map): Double { + return data.indices.sumOf { index -> + val d = distance(index)(arguments) + val weight = weight(index)(arguments) + //1/sqrt(2 PI sigma^2) - (x-mu)^2/ (2 * sigma^2) + oneOver2Pi * ln(weight) - d.pow(2) * weight + } / 2 + } + +} + +/** + * Optimize given XY (least squares) [problem] using this function [Optimizer]. + * The problem is treated as maximum likelihood problem and is done via maximizing logarithmic likelihood, respecting + * possible weight dependency on the model and parameters. + */ +public suspend fun Optimizer>.maximumLogLikelihood(problem: XYOptimization): XYOptimization { + val functionOptimization = FunctionOptimization(problem.features, problem.likelihood()) + val result = optimize(functionOptimization.withFeatures(FunctionOptimizationTarget.MAXIMIZE)) return XYOptimization(result.features, problem.data, problem.model) } +public suspend fun Optimizer>.maximumLogLikelihood( + data: XYColumnarData, + model: DifferentiableExpression, + builder: XYOptimizationBuilder.() -> Unit, +): XYOptimization = maximumLogLikelihood(XYOptimization(data, model, builder)) + +//public suspend fun XYColumnarData.fitWith( +// optimizer: XYOptimization, +// problemBuilder: XYOptimizationBuilder.() -> Unit = {}, +// +//) + + // //@UnstableKMathAPI //public interface XYFit : OptimizationProblem { diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt b/kmath-stat/src/commonMain/tmp/QowFit.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/qow/QowFit.kt rename to kmath-stat/src/commonMain/tmp/QowFit.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/AnalyticalGradientCalculator.kt b/kmath-stat/src/commonMain/tmp/minuit/AnalyticalGradientCalculator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/AnalyticalGradientCalculator.kt rename to kmath-stat/src/commonMain/tmp/minuit/AnalyticalGradientCalculator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimizer.kt b/kmath-stat/src/commonMain/tmp/minuit/CombinedMinimizer.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimizer.kt rename to kmath-stat/src/commonMain/tmp/minuit/CombinedMinimizer.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimumBuilder.kt b/kmath-stat/src/commonMain/tmp/minuit/CombinedMinimumBuilder.kt similarity index 97% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimumBuilder.kt rename to kmath-stat/src/commonMain/tmp/minuit/CombinedMinimumBuilder.kt index a2f0a644a..8c5452575 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/CombinedMinimumBuilder.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/CombinedMinimumBuilder.kt @@ -16,6 +16,7 @@ package ru.inr.mass.minuit import space.kscience.kmath.optimization.minuit.MINUITPlugin +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ContoursError.kt b/kmath-stat/src/commonMain/tmp/minuit/ContoursError.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ContoursError.kt rename to kmath-stat/src/commonMain/tmp/minuit/ContoursError.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/DavidonErrorUpdator.kt b/kmath-stat/src/commonMain/tmp/minuit/DavidonErrorUpdator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/DavidonErrorUpdator.kt rename to kmath-stat/src/commonMain/tmp/minuit/DavidonErrorUpdator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionGradient.kt b/kmath-stat/src/commonMain/tmp/minuit/FunctionGradient.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionGradient.kt rename to kmath-stat/src/commonMain/tmp/minuit/FunctionGradient.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionMinimum.kt b/kmath-stat/src/commonMain/tmp/minuit/FunctionMinimum.kt similarity index 99% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionMinimum.kt rename to kmath-stat/src/commonMain/tmp/minuit/FunctionMinimum.kt index 56908f00d..e43523291 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/FunctionMinimum.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/FunctionMinimum.kt @@ -16,6 +16,7 @@ package ru.inr.mass.minuit import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * Result of the minimization. diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/GradientCalculator.kt b/kmath-stat/src/commonMain/tmp/minuit/GradientCalculator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/GradientCalculator.kt rename to kmath-stat/src/commonMain/tmp/minuit/GradientCalculator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/HessianGradientCalculator.kt b/kmath-stat/src/commonMain/tmp/minuit/HessianGradientCalculator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/HessianGradientCalculator.kt rename to kmath-stat/src/commonMain/tmp/minuit/HessianGradientCalculator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/InitialGradientCalculator.kt b/kmath-stat/src/commonMain/tmp/minuit/InitialGradientCalculator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/InitialGradientCalculator.kt rename to kmath-stat/src/commonMain/tmp/minuit/InitialGradientCalculator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINOSResult.kt b/kmath-stat/src/commonMain/tmp/minuit/MINOSResult.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINOSResult.kt rename to kmath-stat/src/commonMain/tmp/minuit/MINOSResult.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITFitter.kt b/kmath-stat/src/commonMain/tmp/minuit/MINUITFitter.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITFitter.kt rename to kmath-stat/src/commonMain/tmp/minuit/MINUITFitter.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITPlugin.kt b/kmath-stat/src/commonMain/tmp/minuit/MINUITPlugin.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITPlugin.kt rename to kmath-stat/src/commonMain/tmp/minuit/MINUITPlugin.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITUtils.kt b/kmath-stat/src/commonMain/tmp/minuit/MINUITUtils.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MINUITUtils.kt rename to kmath-stat/src/commonMain/tmp/minuit/MINUITUtils.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumBuilder.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumBuilder.kt similarity index 95% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumBuilder.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumBuilder.kt index eadeeb91d..7d918c339 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumBuilder.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/MinimumBuilder.kt @@ -15,6 +15,8 @@ */ package ru.inr.mass.minuit +import space.kscience.kmath.optimization.minuit.MinimumSeed + /** * * @version $Id$ diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumError.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumError.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumError.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumError.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumErrorUpdator.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumErrorUpdator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumErrorUpdator.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumErrorUpdator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumParameters.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumParameters.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumParameters.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumParameters.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeed.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumSeed.kt similarity index 95% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeed.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumSeed.kt index aef672bb7..53a78da75 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeed.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/MinimumSeed.kt @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.inr.mass.minuit +package space.kscience.kmath.optimization.minuit + +import ru.inr.mass.minuit.* /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeedGenerator.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumSeedGenerator.kt similarity index 95% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeedGenerator.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumSeedGenerator.kt index bd04c1a45..e152559b5 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumSeedGenerator.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/MinimumSeedGenerator.kt @@ -15,6 +15,8 @@ */ package ru.inr.mass.minuit +import space.kscience.kmath.optimization.minuit.MinimumSeed + /** * base class for seed generators (starting values); the seed generator prepares * initial starting values from the input (MnUserParameterState) for the diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumState.kt b/kmath-stat/src/commonMain/tmp/minuit/MinimumState.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinimumState.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinimumState.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinosError.kt b/kmath-stat/src/commonMain/tmp/minuit/MinosError.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinosError.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinosError.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinuitParameter.kt b/kmath-stat/src/commonMain/tmp/minuit/MinuitParameter.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MinuitParameter.kt rename to kmath-stat/src/commonMain/tmp/minuit/MinuitParameter.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnAlgebraicSymMatrix.kt b/kmath-stat/src/commonMain/tmp/minuit/MnAlgebraicSymMatrix.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnAlgebraicSymMatrix.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnAlgebraicSymMatrix.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnApplication.kt b/kmath-stat/src/commonMain/tmp/minuit/MnApplication.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnApplication.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnApplication.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnContours.kt b/kmath-stat/src/commonMain/tmp/minuit/MnContours.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnContours.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnContours.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCovarianceSqueeze.kt b/kmath-stat/src/commonMain/tmp/minuit/MnCovarianceSqueeze.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCovarianceSqueeze.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnCovarianceSqueeze.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCross.kt b/kmath-stat/src/commonMain/tmp/minuit/MnCross.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnCross.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnCross.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnEigen.kt b/kmath-stat/src/commonMain/tmp/minuit/MnEigen.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnEigen.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnEigen.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFcn.kt b/kmath-stat/src/commonMain/tmp/minuit/MnFcn.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFcn.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnFcn.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFunctionCross.kt b/kmath-stat/src/commonMain/tmp/minuit/MnFunctionCross.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnFunctionCross.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnFunctionCross.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnGlobalCorrelationCoeff.kt b/kmath-stat/src/commonMain/tmp/minuit/MnGlobalCorrelationCoeff.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnGlobalCorrelationCoeff.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnGlobalCorrelationCoeff.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnHesse.kt b/kmath-stat/src/commonMain/tmp/minuit/MnHesse.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnHesse.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnHesse.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnLineSearch.kt b/kmath-stat/src/commonMain/tmp/minuit/MnLineSearch.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnLineSearch.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnLineSearch.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMachinePrecision.kt b/kmath-stat/src/commonMain/tmp/minuit/MnMachinePrecision.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMachinePrecision.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnMachinePrecision.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMigrad.kt b/kmath-stat/src/commonMain/tmp/minuit/MnMigrad.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMigrad.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnMigrad.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinimize.kt b/kmath-stat/src/commonMain/tmp/minuit/MnMinimize.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinimize.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnMinimize.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinos.kt b/kmath-stat/src/commonMain/tmp/minuit/MnMinos.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnMinos.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnMinos.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabola.kt b/kmath-stat/src/commonMain/tmp/minuit/MnParabola.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabola.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnParabola.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaFactory.kt b/kmath-stat/src/commonMain/tmp/minuit/MnParabolaFactory.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaFactory.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnParabolaFactory.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaPoint.kt b/kmath-stat/src/commonMain/tmp/minuit/MnParabolaPoint.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParabolaPoint.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnParabolaPoint.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParameterScan.kt b/kmath-stat/src/commonMain/tmp/minuit/MnParameterScan.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnParameterScan.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnParameterScan.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPlot.kt b/kmath-stat/src/commonMain/tmp/minuit/MnPlot.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPlot.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnPlot.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPosDef.kt b/kmath-stat/src/commonMain/tmp/minuit/MnPosDef.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPosDef.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnPosDef.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPrint.kt b/kmath-stat/src/commonMain/tmp/minuit/MnPrint.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPrint.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnPrint.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnScan.kt b/kmath-stat/src/commonMain/tmp/minuit/MnScan.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnScan.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnScan.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt b/kmath-stat/src/commonMain/tmp/minuit/MnSeedGenerator.kt similarity index 98% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnSeedGenerator.kt index cc3f9547e..a42edf4f1 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSeedGenerator.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/MnSeedGenerator.kt @@ -17,6 +17,7 @@ package ru.inr.mass.minuit import space.kscience.kmath.optimization.minuit.MINUITPlugin import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSimplex.kt b/kmath-stat/src/commonMain/tmp/minuit/MnSimplex.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnSimplex.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnSimplex.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnStrategy.kt b/kmath-stat/src/commonMain/tmp/minuit/MnStrategy.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnStrategy.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnStrategy.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserCovariance.kt b/kmath-stat/src/commonMain/tmp/minuit/MnUserCovariance.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserCovariance.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnUserCovariance.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserFcn.kt b/kmath-stat/src/commonMain/tmp/minuit/MnUserFcn.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserFcn.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnUserFcn.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameterState.kt b/kmath-stat/src/commonMain/tmp/minuit/MnUserParameterState.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameterState.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnUserParameterState.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameters.kt b/kmath-stat/src/commonMain/tmp/minuit/MnUserParameters.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserParameters.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnUserParameters.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserTransformation.kt b/kmath-stat/src/commonMain/tmp/minuit/MnUserTransformation.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUserTransformation.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnUserTransformation.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUtils.kt b/kmath-stat/src/commonMain/tmp/minuit/MnUtils.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnUtils.kt rename to kmath-stat/src/commonMain/tmp/minuit/MnUtils.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ModularFunctionMinimizer.kt b/kmath-stat/src/commonMain/tmp/minuit/ModularFunctionMinimizer.kt similarity index 97% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ModularFunctionMinimizer.kt rename to kmath-stat/src/commonMain/tmp/minuit/ModularFunctionMinimizer.kt index f234bcd48..84130d24f 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ModularFunctionMinimizer.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/ModularFunctionMinimizer.kt @@ -17,6 +17,7 @@ package ru.inr.mass.minuit import ru.inr.mass.maths.MultiFunction import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/NegativeG2LineSearch.kt b/kmath-stat/src/commonMain/tmp/minuit/NegativeG2LineSearch.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/NegativeG2LineSearch.kt rename to kmath-stat/src/commonMain/tmp/minuit/NegativeG2LineSearch.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Numerical2PGradientCalculator.kt b/kmath-stat/src/commonMain/tmp/minuit/Numerical2PGradientCalculator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/Numerical2PGradientCalculator.kt rename to kmath-stat/src/commonMain/tmp/minuit/Numerical2PGradientCalculator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanBuilder.kt b/kmath-stat/src/commonMain/tmp/minuit/ScanBuilder.kt similarity index 97% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanBuilder.kt rename to kmath-stat/src/commonMain/tmp/minuit/ScanBuilder.kt index b7e773924..57f910a26 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanBuilder.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/ScanBuilder.kt @@ -17,6 +17,7 @@ package ru.inr.mass.minuit import org.apache.commons.math3.linear.ArrayRealVector import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * Performs a minimization using the simplex method of Nelder and Mead (ref. diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanMinimizer.kt b/kmath-stat/src/commonMain/tmp/minuit/ScanMinimizer.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/ScanMinimizer.kt rename to kmath-stat/src/commonMain/tmp/minuit/ScanMinimizer.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexBuilder.kt b/kmath-stat/src/commonMain/tmp/minuit/SimplexBuilder.kt similarity index 99% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexBuilder.kt rename to kmath-stat/src/commonMain/tmp/minuit/SimplexBuilder.kt index 8441a4177..0b10155ff 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexBuilder.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/SimplexBuilder.kt @@ -17,6 +17,7 @@ package ru.inr.mass.minuit import space.kscience.kmath.optimization.minuit.MINUITPlugin import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexMinimizer.kt b/kmath-stat/src/commonMain/tmp/minuit/SimplexMinimizer.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexMinimizer.kt rename to kmath-stat/src/commonMain/tmp/minuit/SimplexMinimizer.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexParameters.kt b/kmath-stat/src/commonMain/tmp/minuit/SimplexParameters.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexParameters.kt rename to kmath-stat/src/commonMain/tmp/minuit/SimplexParameters.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexSeedGenerator.kt b/kmath-stat/src/commonMain/tmp/minuit/SimplexSeedGenerator.kt similarity index 97% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexSeedGenerator.kt rename to kmath-stat/src/commonMain/tmp/minuit/SimplexSeedGenerator.kt index e5025ff3d..577545fc3 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SimplexSeedGenerator.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/SimplexSeedGenerator.kt @@ -17,6 +17,7 @@ package ru.inr.mass.minuit import org.apache.commons.math3.linear.ArrayRealVector import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SinParameterTransformation.kt b/kmath-stat/src/commonMain/tmp/minuit/SinParameterTransformation.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SinParameterTransformation.kt rename to kmath-stat/src/commonMain/tmp/minuit/SinParameterTransformation.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtLowParameterTransformation.kt b/kmath-stat/src/commonMain/tmp/minuit/SqrtLowParameterTransformation.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtLowParameterTransformation.kt rename to kmath-stat/src/commonMain/tmp/minuit/SqrtLowParameterTransformation.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtUpParameterTransformation.kt b/kmath-stat/src/commonMain/tmp/minuit/SqrtUpParameterTransformation.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/SqrtUpParameterTransformation.kt rename to kmath-stat/src/commonMain/tmp/minuit/SqrtUpParameterTransformation.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.kt b/kmath-stat/src/commonMain/tmp/minuit/VariableMetricBuilder.kt similarity index 98% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.kt rename to kmath-stat/src/commonMain/tmp/minuit/VariableMetricBuilder.kt index 8362c5e7e..edc6783b6 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricBuilder.kt +++ b/kmath-stat/src/commonMain/tmp/minuit/VariableMetricBuilder.kt @@ -17,6 +17,7 @@ package ru.inr.mass.minuit import space.kscience.kmath.optimization.minuit.MINUITPlugin import ru.inr.mass.minuit.* +import space.kscience.kmath.optimization.minuit.MinimumSeed /** * diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricEDMEstimator.kt b/kmath-stat/src/commonMain/tmp/minuit/VariableMetricEDMEstimator.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricEDMEstimator.kt rename to kmath-stat/src/commonMain/tmp/minuit/VariableMetricEDMEstimator.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricMinimizer.kt b/kmath-stat/src/commonMain/tmp/minuit/VariableMetricMinimizer.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/VariableMetricMinimizer.kt rename to kmath-stat/src/commonMain/tmp/minuit/VariableMetricMinimizer.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/package-info.kt b/kmath-stat/src/commonMain/tmp/minuit/package-info.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/package-info.kt rename to kmath-stat/src/commonMain/tmp/minuit/package-info.kt From 3ba12f49993d03e9af6bd59b97186e72e78799f6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 9 Jul 2021 14:11:26 +0300 Subject: [PATCH 08/13] Generic Buffer Algebra --- CHANGELOG.md | 1 + .../kscience/kmath/structures/buffers.kt | 22 +++ .../kmath/operations/BufferAlgebra.kt | 146 ++++++++++++++++++ .../kmath/structures/DoubleBufferField.kt | 4 + 4 files changed, 173 insertions(+) create mode 100644 examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt create mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BufferAlgebra.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index e35153d81..8c4f5b547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Use `Symbol` factory function instead of `StringSymbol` ### Deprecated +- Specialized `DoubleBufferAlgebra` ### Removed - Nearest in Domain. To be implemented in geometry package. diff --git a/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt b/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt new file mode 100644 index 000000000..eabb16701 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.structures + +import space.kscience.kmath.operations.DoubleField +import space.kscience.kmath.operations.bufferAlgebra +import space.kscience.kmath.operations.produce + +inline fun MutableBuffer.Companion.same( + n: Int, + value: R +): MutableBuffer = auto(n) { value } + + +fun main() { + with(DoubleField.bufferAlgebra(5)) { + println(number(2.0) + produce(1, 2, 3, 4, 5)) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BufferAlgebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BufferAlgebra.kt new file mode 100644 index 000000000..9f4d741fd --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BufferAlgebra.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.operations + +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.BufferFactory +import space.kscience.kmath.structures.DoubleBuffer + +/** + * An algebra over [Buffer] + */ +@UnstableKMathAPI +public interface BufferAlgebra> : Algebra> { + public val bufferFactory: BufferFactory + public val elementAlgebra: A + + //TODO move to multi-receiver inline extension + public fun Buffer.map(block: (T) -> T): Buffer = bufferFactory(size) { block(get(it)) } + + public fun Buffer.zip(other: Buffer, block: (left: T, right: T) -> T): Buffer { + require(size == other.size) { "Incompatible buffer sizes. left: $size, right: ${other.size}" } + return bufferFactory(size) { block(this[it], other[it]) } + } + + override fun unaryOperationFunction(operation: String): (arg: Buffer) -> Buffer { + val operationFunction = elementAlgebra.unaryOperationFunction(operation) + return { arg -> bufferFactory(arg.size) { operationFunction(arg[it]) } } + } + + override fun binaryOperationFunction(operation: String): (left: Buffer, right: Buffer) -> Buffer { + val operationFunction = elementAlgebra.binaryOperationFunction(operation) + return { left, right -> + bufferFactory(left.size) { operationFunction(left[it], right[it]) } + } + } +} + +@UnstableKMathAPI +public fun > BufferAlgebra.sin(arg: Buffer): Buffer = + arg.map(elementAlgebra::sin) + +@UnstableKMathAPI +public fun > BufferAlgebra.cos(arg: Buffer): Buffer = + arg.map(elementAlgebra::cos) + +@UnstableKMathAPI +public fun > BufferAlgebra.tan(arg: Buffer): Buffer = + arg.map(elementAlgebra::tan) + +@UnstableKMathAPI +public fun > BufferAlgebra.asin(arg: Buffer): Buffer = + arg.map(elementAlgebra::asin) + +@UnstableKMathAPI +public fun > BufferAlgebra.acos(arg: Buffer): Buffer = + arg.map(elementAlgebra::acos) + +@UnstableKMathAPI +public fun > BufferAlgebra.atan(arg: Buffer): Buffer = + arg.map(elementAlgebra::atan) + +@UnstableKMathAPI +public fun > BufferAlgebra.exp(arg: Buffer): Buffer = + arg.map(elementAlgebra::exp) + +@UnstableKMathAPI +public fun > BufferAlgebra.ln(arg: Buffer): Buffer = + arg.map(elementAlgebra::ln) + +@UnstableKMathAPI +public fun > BufferAlgebra.sinh(arg: Buffer): Buffer = + arg.map(elementAlgebra::sinh) + +@UnstableKMathAPI +public fun > BufferAlgebra.cosh(arg: Buffer): Buffer = + arg.map(elementAlgebra::cosh) + +@UnstableKMathAPI +public fun > BufferAlgebra.tanh(arg: Buffer): Buffer = + arg.map(elementAlgebra::tanh) + +@UnstableKMathAPI +public fun > BufferAlgebra.asinh(arg: Buffer): Buffer = + arg.map(elementAlgebra::asinh) + +@UnstableKMathAPI +public fun > BufferAlgebra.acosh(arg: Buffer): Buffer = + arg.map(elementAlgebra::acosh) + +@UnstableKMathAPI +public fun > BufferAlgebra.atanh(arg: Buffer): Buffer = + arg.map(elementAlgebra::atanh) + +@UnstableKMathAPI +public fun > BufferAlgebra.pow(arg: Buffer, pow: Number): Buffer = + with(elementAlgebra) { arg.map { power(it, pow) } } + + +@UnstableKMathAPI +public class BufferField>( + override val bufferFactory: BufferFactory, + override val elementAlgebra: A, + public val size: Int +) : BufferAlgebra, Field> { + + public fun produce(vararg elements: T): Buffer { + require(elements.size == size) { "Expected $size elements but found ${elements.size}" } + return bufferFactory(size) { elements[it] } + } + + override val zero: Buffer = bufferFactory(size) { elementAlgebra.zero } + override val one: Buffer = bufferFactory(size) { elementAlgebra.one } + + + override fun add(a: Buffer, b: Buffer): Buffer = a.zip(b, elementAlgebra::add) + override fun multiply(a: Buffer, b: Buffer): Buffer = a.zip(b, elementAlgebra::multiply) + override fun divide(a: Buffer, b: Buffer): Buffer = a.zip(b, elementAlgebra::divide) + + override fun scale(a: Buffer, value: Double): Buffer = with(elementAlgebra) { a.map { scale(it, value) } } + override fun Buffer.unaryMinus(): Buffer = with(elementAlgebra) { map { -it } } + + override fun unaryOperationFunction(operation: String): (arg: Buffer) -> Buffer { + return super.unaryOperationFunction(operation) + } + + override fun binaryOperationFunction(operation: String): (left: Buffer, right: Buffer) -> Buffer { + return super.binaryOperationFunction(operation) + } +} + +//Double buffer specialization + +@UnstableKMathAPI +public fun BufferField.produce(vararg elements: Number): Buffer { + require(elements.size == size) { "Expected $size elements but found ${elements.size}" } + return bufferFactory(size) { elements[it].toDouble() } +} + +@UnstableKMathAPI +public fun DoubleField.bufferAlgebra(size: Int): BufferField = + BufferField(::DoubleBuffer, DoubleField, size) + diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt index e438995dd..c65a0a48a 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/structures/DoubleBufferField.kt @@ -6,6 +6,8 @@ package space.kscience.kmath.structures import space.kscience.kmath.linear.Point +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.operations.ExtendedFieldOperations import space.kscience.kmath.operations.Norm @@ -14,6 +16,7 @@ import kotlin.math.* /** * [ExtendedFieldOperations] over [DoubleBuffer]. */ +@Deprecated("To be replaced by generic BufferAlgebra") public object DoubleBufferFieldOperations : ExtendedFieldOperations> { override fun Buffer.unaryMinus(): DoubleBuffer = if (this is DoubleBuffer) { DoubleBuffer(size) { -array[it] } @@ -172,6 +175,7 @@ public object DoubleL2Norm : Norm, Double> { * * @property size the size of buffers to operate on. */ +@Deprecated("To be replaced by generic BufferAlgebra") public class DoubleBufferField(public val size: Int) : ExtendedField>, Norm, Double> { public override val zero: Buffer by lazy { DoubleBuffer(size) { 0.0 } } public override val one: Buffer by lazy { DoubleBuffer(size) { 1.0 } } From 9b8da4cdcc07648dbd337a2aa254e1338325a966 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 12 Aug 2021 20:28:45 +0300 Subject: [PATCH 09/13] Fix JS bug with null cast --- .../kotlin/space/kscience/kmath/misc/Featured.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt index 648b6376f..be1c8380c 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt @@ -16,13 +16,14 @@ public interface Featured { public typealias FeatureKey = KClass -public interface Feature> { +public interface Feature> { /** * A key used for extraction */ @Suppress("UNCHECKED_CAST") - public val key: FeatureKey get() = this::class as FeatureKey + public val key: FeatureKey + get() = this::class as FeatureKey } /** @@ -30,7 +31,7 @@ public interface Feature> { */ public class FeatureSet> private constructor(public val features: Map, F>) : Featured { @Suppress("UNCHECKED_CAST") - override fun getFeature(type: FeatureKey): T? = features[type] as? T + override fun getFeature(type: FeatureKey): T? = features[type]?.let { it as T } public inline fun getFeature(): T? = getFeature(T::class) @@ -49,6 +50,7 @@ public class FeatureSet> private constructor(public val features: public companion object { public fun > of(vararg features: F): FeatureSet = FeatureSet(features.associateBy { it.key }) - public fun > of(features: Iterable): FeatureSet = FeatureSet(features.associateBy { it.key }) + public fun > of(features: Iterable): FeatureSet = + FeatureSet(features.associateBy { it.key }) } } From aaa298616d2668f80062079ede547f4228246b5e Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 16 Aug 2021 09:55:03 +0300 Subject: [PATCH 10/13] QOW is working more or less --- examples/build.gradle.kts | 1 + .../fitWithAutoDiff.kt => fit/chiSquared.kt} | 13 +- .../kotlin/space/kscience/kmath/fit/qowFit.kt | 105 ++++++++++ .../kmath/functions/matrixIntegration.kt | 2 +- kmath-commons/build.gradle.kts | 1 + .../kmath/commons/optimization/CMOptimizer.kt | 7 +- .../DerivativeStructureExpressionTest.kt | 4 +- .../commons/optimization/OptimizeTest.kt | 3 +- .../kscience/kmath/data/XYColumnarData.kt | 20 +- .../kmath/data/XYErrorColumnarData.kt | 25 ++- .../kscience/kmath/expressions/Expression.kt | 7 +- .../kmath/expressions/specialExpressions.kt | 17 +- .../space/kscience/kmath/misc/Featured.kt | 7 +- .../space/kscience/kmath/misc/logging.kt | 14 +- .../kmath/expressions/ExpressionFieldTest.kt | 6 +- .../kmath/integration/GaussIntegrator.kt | 3 +- .../kmath/interpolation/Interpolator.kt | 6 +- kmath-optimization/build.gradle.kts | 20 ++ .../optimization/FunctionOptimization.kt | 33 ++- .../kmath/optimization/OptimizationBuilder.kt | 8 +- .../kmath/optimization/OptimizationProblem.kt | 0 .../kscience/kmath/optimization/Optimizer.kt | 0 .../kmath/optimization/QowOptimizer.kt | 72 +++---- .../kscience/kmath/optimization/XYFit.kt | 125 ++++++++++++ .../kmath/optimization/logLikelihood.kt | 66 ++++++ kmath-stat/build.gradle.kts | 3 +- .../kmath/optimization/XYOptimization.kt | 189 ------------------ settings.gradle.kts | 1 + 28 files changed, 477 insertions(+), 281 deletions(-) rename examples/src/main/kotlin/space/kscience/kmath/{commons/fit/fitWithAutoDiff.kt => fit/chiSquared.kt} (91%) create mode 100644 examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt create mode 100644 kmath-optimization/build.gradle.kts rename {kmath-stat => kmath-optimization}/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt (65%) rename {kmath-stat => kmath-optimization}/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt (94%) rename {kmath-stat => kmath-optimization}/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt (100%) rename {kmath-stat => kmath-optimization}/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt (100%) rename {kmath-stat => kmath-optimization}/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt (77%) create mode 100644 kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt create mode 100644 kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt delete mode 100644 kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 4cc6fecc0..d06005321 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(project(":kmath-coroutines")) implementation(project(":kmath-commons")) implementation(project(":kmath-complex")) + implementation(project(":kmath-optimization")) implementation(project(":kmath-stat")) implementation(project(":kmath-viktor")) implementation(project(":kmath-dimensions")) diff --git a/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt b/examples/src/main/kotlin/space/kscience/kmath/fit/chiSquared.kt similarity index 91% rename from examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt rename to examples/src/main/kotlin/space/kscience/kmath/fit/chiSquared.kt index 8d95ebb4a..c1070de52 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/commons/fit/fitWithAutoDiff.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/fit/chiSquared.kt @@ -3,19 +3,17 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package space.kscience.kmath.commons.fit +package space.kscience.kmath.fit import kotlinx.html.br import kotlinx.html.h3 import space.kscience.kmath.commons.expressions.DSProcessor import space.kscience.kmath.commons.optimization.CMOptimizer import space.kscience.kmath.distributions.NormalDistribution +import space.kscience.kmath.expressions.binding import space.kscience.kmath.expressions.chiSquaredExpression import space.kscience.kmath.expressions.symbol -import space.kscience.kmath.optimization.FunctionOptimizationTarget -import space.kscience.kmath.optimization.optimizeWith -import space.kscience.kmath.optimization.resultPoint -import space.kscience.kmath.optimization.resultValue +import space.kscience.kmath.optimization.* import space.kscience.kmath.real.DoubleVector import space.kscience.kmath.real.map import space.kscience.kmath.real.step @@ -25,6 +23,7 @@ import space.kscience.kmath.structures.toList import space.kscience.plotly.* import space.kscience.plotly.models.ScatterMode import space.kscience.plotly.models.TraceValues +import kotlin.math.abs import kotlin.math.pow import kotlin.math.sqrt @@ -45,7 +44,7 @@ operator fun TraceValues.invoke(vector: DoubleVector) { */ suspend fun main() { //A generator for a normally distributed values - val generator = NormalDistribution(2.0, 7.0) + val generator = NormalDistribution(0.0, 1.0) //A chain/flow of random values with the given seed val chain = generator.sample(RandomGenerator.default(112667)) @@ -56,7 +55,7 @@ suspend fun main() { //Perform an operation on each x value (much more effective, than numpy) - val y = x.map { + val y = x.map { it -> val value = it.pow(2) + it + 1 value + chain.next() * sqrt(value) } diff --git a/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt b/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt new file mode 100644 index 000000000..944f80697 --- /dev/null +++ b/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.fit + +import kotlinx.html.br +import kotlinx.html.h3 +import space.kscience.kmath.commons.expressions.DSProcessor +import space.kscience.kmath.data.XYErrorColumnarData +import space.kscience.kmath.distributions.NormalDistribution +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.binding +import space.kscience.kmath.expressions.symbol +import space.kscience.kmath.optimization.QowOptimizer +import space.kscience.kmath.optimization.fitWith +import space.kscience.kmath.optimization.resultPoint +import space.kscience.kmath.real.map +import space.kscience.kmath.real.step +import space.kscience.kmath.stat.RandomGenerator +import space.kscience.kmath.structures.asIterable +import space.kscience.kmath.structures.toList +import space.kscience.plotly.* +import space.kscience.plotly.models.ScatterMode +import kotlin.math.abs +import kotlin.math.pow +import kotlin.math.sqrt + +// Forward declaration of symbols that will be used in expressions. +private val a by symbol +private val b by symbol +private val c by symbol + + +/** + * Least squares fie with auto-differentiation. Uses `kmath-commons` and `kmath-for-real` modules. + */ +suspend fun main() { + //A generator for a normally distributed values + val generator = NormalDistribution(0.0, 1.0) + + //A chain/flow of random values with the given seed + val chain = generator.sample(RandomGenerator.default(112667)) + + + //Create a uniformly distributed x values like numpy.arrange + val x = 1.0..100.0 step 1.0 + + + //Perform an operation on each x value (much more effective, than numpy) + val y = x.map { it -> + val value = it.pow(2) + it + 100 + value + chain.next() * sqrt(value) + } + // this will also work, but less effective: + // val y = x.pow(2)+ x + 1 + chain.nextDouble() + + // create same errors for all xs + val yErr = y.map { sqrt(abs(it)) } + require(yErr.asIterable().all { it > 0 }) { "All errors must be strictly positive" } + + val result = XYErrorColumnarData.of(x, y, yErr).fitWith( + QowOptimizer, + DSProcessor, + mapOf(a to 1.0, b to 1.2, c to 99.0) + ) { arg -> + //bind variables to autodiff context + val a by binding + val b by binding + //Include default value for c if it is not provided as a parameter + val c = bindSymbolOrNull(c) ?: one + a * arg.pow(2) + b * arg + c + } + + //display a page with plot and numerical results + val page = Plotly.page { + plot { + scatter { + mode = ScatterMode.markers + x(x) + y(y) + error_y { + array = yErr.toList() + } + name = "data" + } + scatter { + mode = ScatterMode.lines + x(x) + y(x.map { result.model(result.resultPoint + (Symbol.x to it)) }) + name = "fit" + } + } + br() + h3 { + +"Fit result: ${result.resultPoint}" + } +// h3 { +// +"Chi2/dof = ${result.resultValue / (x.size - 3)}" +// } + } + + page.makeFile() +} diff --git a/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt b/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt index 2619d3d74..e5ba29d73 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/functions/matrixIntegration.kt @@ -22,7 +22,7 @@ fun main(): Unit = DoubleField { } //Define a function in a nd space - val function: (Double) -> StructureND = { x: Double -> 3 * number(x).pow(2) + 2 * diagonal(x) + 1 } + val function: (Double) -> StructureND = { x: Double -> 3 * x.pow(2) + 2 * diagonal(x) + 1 } //get the result of the integration val result = gaussIntegrator.integrate(0.0..10.0, function = function) diff --git a/kmath-commons/build.gradle.kts b/kmath-commons/build.gradle.kts index a208c956c..96c17a215 100644 --- a/kmath-commons/build.gradle.kts +++ b/kmath-commons/build.gradle.kts @@ -9,6 +9,7 @@ dependencies { api(project(":kmath-core")) api(project(":kmath-complex")) api(project(":kmath-coroutines")) + api(project(":kmath-optimization")) api(project(":kmath-stat")) api(project(":kmath-functions")) api("org.apache.commons:commons-math3:3.6.1") diff --git a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt index abf95daf6..11eb6fba8 100644 --- a/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt +++ b/kmath-commons/src/main/kotlin/space/kscience/kmath/commons/optimization/CMOptimizer.kt @@ -18,6 +18,7 @@ import space.kscience.kmath.expressions.SymbolIndexer import space.kscience.kmath.expressions.derivative import space.kscience.kmath.expressions.withSymbols import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.misc.log import space.kscience.kmath.optimization.* import kotlin.collections.set import kotlin.reflect.KClass @@ -108,15 +109,17 @@ public object CMOptimizer : Optimizer> { val objectiveFunction = ObjectiveFunction { val args = startPoint + it.toMap() - problem.expression(args) + val res = problem.expression(args) + res } addOptimizationData(objectiveFunction) val gradientFunction = ObjectiveFunctionGradient { val args = startPoint + it.toMap() - DoubleArray(symbols.size) { index -> + val res = DoubleArray(symbols.size) { index -> problem.expression.derivative(symbols[index])(args) } + res } addOptimizationData(gradientFunction) diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt index 3c57f5467..56252ab34 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/expressions/DerivativeStructureExpressionTest.kt @@ -42,8 +42,8 @@ internal class AutoDiffTest { @Test fun autoDifTest() { val f = DerivativeStructureExpression { - val x by binding() - val y by binding() + val x by binding + val y by binding x.pow(2) + 2 * x * y + y.pow(2) + 1 } diff --git a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt index 170fceceb..facbf3a9a 100644 --- a/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt +++ b/kmath-commons/src/test/kotlin/space/kscience/kmath/commons/optimization/OptimizeTest.kt @@ -15,6 +15,7 @@ import space.kscience.kmath.expressions.chiSquaredExpression import space.kscience.kmath.expressions.symbol import space.kscience.kmath.optimization.* import space.kscience.kmath.stat.RandomGenerator +import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.asBuffer import space.kscience.kmath.structures.map import kotlin.math.pow @@ -58,7 +59,7 @@ internal class OptimizeTest { it.pow(2) + it + 1 + chain.next() } - val yErr = List(x.size) { sigma }.asBuffer() + val yErr = DoubleBuffer(x.size) { sigma } val chi2 = DSProcessor.chiSquaredExpression( x, y, yErr diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt index bd0e13b9d..2fce772cc 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYColumnarData.kt @@ -32,19 +32,21 @@ public interface XYColumnarData : ColumnarData { Symbol.y -> y else -> null } -} -@Suppress("FunctionName") -@UnstableKMathAPI -public fun XYColumnarData(x: Buffer, y: Buffer): XYColumnarData { - require(x.size == y.size) { "Buffer size mismatch. x buffer size is ${x.size}, y buffer size is ${y.size}" } - return object : XYColumnarData { - override val size: Int = x.size - override val x: Buffer = x - override val y: Buffer = y + public companion object{ + @UnstableKMathAPI + public fun of(x: Buffer, y: Buffer): XYColumnarData { + require(x.size == y.size) { "Buffer size mismatch. x buffer size is ${x.size}, y buffer size is ${y.size}" } + return object : XYColumnarData { + override val size: Int = x.size + override val x: Buffer = x + override val y: Buffer = y + } + } } } + /** * Represent a [ColumnarData] as an [XYColumnarData]. The presence or respective columns is checked on creation. */ diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt index 7199de888..8ddd6406f 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/data/XYErrorColumnarData.kt @@ -5,15 +5,13 @@ package space.kscience.kmath.data -import space.kscience.kmath.data.XYErrorColumnarData.Companion import space.kscience.kmath.expressions.Symbol -import space.kscience.kmath.expressions.symbol import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.structures.Buffer /** - * A [ColumnarData] with additional [Companion.yErr] column for an [Symbol.y] error + * A [ColumnarData] with additional [Symbol.yError] column for an [Symbol.y] error * Inherits [XYColumnarData]. */ @UnstableKMathAPI @@ -23,11 +21,24 @@ public interface XYErrorColumnarData : XYColumnarData = when (symbol) { Symbol.x -> x Symbol.y -> y - Companion.yErr -> yErr + Symbol.yError -> yErr else -> error("A column for symbol $symbol not found") } - public companion object{ - public val yErr: Symbol by symbol + public companion object { + public fun of( + x: Buffer, y: Buffer, yErr: Buffer + ): XYErrorColumnarData { + require(x.size == y.size) { "Buffer size mismatch. x buffer size is ${x.size}, y buffer size is ${y.size}" } + require(y.size == yErr.size) { "Buffer size mismatch. y buffer size is ${x.size}, yErr buffer size is ${y.size}" } + + return object : XYErrorColumnarData { + override val size: Int = x.size + override val x: Buffer = x + override val y: Buffer = y + override val yErr: Buffer = yErr + } + } } -} \ No newline at end of file +} + diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt index 5105c2bec..e94cb98eb 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/Expression.kt @@ -68,6 +68,7 @@ public interface ExpressionAlgebra : Algebra { /** * Bind a symbol by name inside the [ExpressionAlgebra] */ -public fun ExpressionAlgebra.binding(): ReadOnlyProperty = ReadOnlyProperty { _, property -> - bindSymbol(property.name) ?: error("A variable with name ${property.name} does not exist") -} +public val ExpressionAlgebra.binding: ReadOnlyProperty + get() = ReadOnlyProperty { _, property -> + bindSymbol(property.name) ?: error("A variable with name ${property.name} does not exist") + } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/specialExpressions.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/specialExpressions.kt index ede4a779c..6b17dfca5 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/specialExpressions.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/expressions/specialExpressions.kt @@ -7,13 +7,18 @@ package space.kscience.kmath.expressions import space.kscience.kmath.operations.ExtendedField import space.kscience.kmath.structures.Buffer +import space.kscience.kmath.structures.asIterable import space.kscience.kmath.structures.indices +import kotlin.jvm.JvmName /** * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic * differentiation. + * + * **WARNING** All elements of [yErr] must be positive. */ -public fun AutoDiffProcessor.chiSquaredExpression( +@JvmName("genericChiSquaredExpression") +public fun , I : Any, A> AutoDiffProcessor.chiSquaredExpression( x: Buffer, y: Buffer, yErr: Buffer, @@ -35,4 +40,14 @@ public fun AutoDiffProcessor.chiSquaredExpression sum } +} + +public fun AutoDiffProcessor.chiSquaredExpression( + x: Buffer, + y: Buffer, + yErr: Buffer, + model: A.(I) -> I, +): DifferentiableExpression where A : ExtendedField, A : ExpressionAlgebra { + require(yErr.asIterable().all { it > 0.0 }) { "All errors must be strictly positive" } + return chiSquaredExpression(x, y, yErr, model) } \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt index be1c8380c..29b7caec6 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/Featured.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.misc +import kotlin.jvm.JvmInline import kotlin.reflect.KClass /** @@ -29,7 +30,8 @@ public interface Feature> { /** * A container for a set of features */ -public class FeatureSet> private constructor(public val features: Map, F>) : Featured { +@JvmInline +public value class FeatureSet> private constructor(public val features: Map, F>) : Featured { @Suppress("UNCHECKED_CAST") override fun getFeature(type: FeatureKey): T? = features[type]?.let { it as T } @@ -48,6 +50,9 @@ public class FeatureSet> private constructor(public val features: public operator fun iterator(): Iterator = features.values.iterator() + override fun toString(): String = features.values.joinToString(prefix = "[ ", postfix = " ]") + + public companion object { public fun > of(vararg features: F): FeatureSet = FeatureSet(features.associateBy { it.key }) public fun > of(features: Iterable): FeatureSet = diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt index d13840841..9dfc564c3 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/misc/logging.kt @@ -5,10 +5,18 @@ package space.kscience.kmath.misc -public interface Loggable { - public fun log(tag: String = INFO, block: () -> String) +import space.kscience.kmath.misc.Loggable.Companion.INFO + +public fun interface Loggable { + public fun log(tag: String, block: () -> String) public companion object { public const val INFO: String = "INFO" + + public val console: Loggable = Loggable { tag, block -> + println("[$tag] ${block()}") + } } -} \ No newline at end of file +} + +public fun Loggable.log(block: () -> String): Unit = log(INFO, block) \ No newline at end of file diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt index 4d1b00b3d..80c5943cf 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/expressions/ExpressionFieldTest.kt @@ -16,7 +16,7 @@ class ExpressionFieldTest { @Test fun testExpression() { val expression = with(FunctionalExpressionField(DoubleField)) { - val x by binding() + val x by binding x * x + 2 * x + one } @@ -27,7 +27,7 @@ class ExpressionFieldTest { @Test fun separateContext() { fun FunctionalExpressionField.expression(): Expression { - val x by binding() + val x by binding return x * x + 2 * x + one } @@ -38,7 +38,7 @@ class ExpressionFieldTest { @Test fun valueExpression() { val expressionBuilder: FunctionalExpressionField.() -> Expression = { - val x by binding() + val x by binding x * x + 2 * x + one } diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt index 9b938394a..9785d7744 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/integration/GaussIntegrator.kt @@ -73,7 +73,8 @@ public class GaussIntegrator( } /** - * Create a Gauss-Legendre integrator for this field. + * Create a Gauss integrator for this field. By default, uses Legendre rule to compute points and weights. + * Custom rules could be provided by [GaussIntegratorRuleFactory] feature. * @see [GaussIntegrator] */ public val Field.gaussIntegrator: GaussIntegrator get() = GaussIntegrator(this) diff --git a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt index f4a0abd5d..b13adefa5 100644 --- a/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt +++ b/kmath-functions/src/commonMain/kotlin/space/kscience/kmath/interpolation/Interpolator.kt @@ -42,20 +42,20 @@ public fun > PolynomialInterpolator.interpolatePolynomials( x: Buffer, y: Buffer, ): PiecewisePolynomial { - val pointSet = XYColumnarData(x, y) + val pointSet = XYColumnarData.of(x, y) return interpolatePolynomials(pointSet) } public fun > PolynomialInterpolator.interpolatePolynomials( data: Map, ): PiecewisePolynomial { - val pointSet = XYColumnarData(data.keys.toList().asBuffer(), data.values.toList().asBuffer()) + val pointSet = XYColumnarData.of(data.keys.toList().asBuffer(), data.values.toList().asBuffer()) return interpolatePolynomials(pointSet) } public fun > PolynomialInterpolator.interpolatePolynomials( data: List>, ): PiecewisePolynomial { - val pointSet = XYColumnarData(data.map { it.first }.asBuffer(), data.map { it.second }.asBuffer()) + val pointSet = XYColumnarData.of(data.map { it.first }.asBuffer(), data.map { it.second }.asBuffer()) return interpolatePolynomials(pointSet) } diff --git a/kmath-optimization/build.gradle.kts b/kmath-optimization/build.gradle.kts new file mode 100644 index 000000000..ca013b2f4 --- /dev/null +++ b/kmath-optimization/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("ru.mipt.npm.gradle.mpp") + id("ru.mipt.npm.gradle.native") +} + +kscience { + useAtomic() +} + +kotlin.sourceSets { + commonMain { + dependencies { + api(project(":kmath-coroutines")) + } + } +} + +readme { + maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL +} \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt similarity index 65% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt rename to kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt index 1af6c4bda..02602b068 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/FunctionOptimization.kt @@ -5,13 +5,11 @@ package space.kscience.kmath.optimization -import space.kscience.kmath.expressions.* +import space.kscience.kmath.expressions.DifferentiableExpression +import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.misc.FeatureSet -import space.kscience.kmath.operations.ExtendedField -import space.kscience.kmath.structures.Buffer -import space.kscience.kmath.structures.indices -public class OptimizationValue(public val value: T) : OptimizationFeature{ +public class OptimizationValue(public val value: T) : OptimizationFeature { override fun toString(): String = "Value($value)" } @@ -23,9 +21,28 @@ public enum class FunctionOptimizationTarget : OptimizationFeature { public class FunctionOptimization( override val features: FeatureSet, public val expression: DifferentiableExpression, -) : OptimizationProblem{ +) : OptimizationProblem { - public companion object + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as FunctionOptimization<*> + + if (features != other.features) return false + if (expression != other.expression) return false + + return true + } + + override fun hashCode(): Int { + var result = features.hashCode() + result = 31 * result + expression.hashCode() + return result + } + + override fun toString(): String = "FunctionOptimization(features=$features)" } public fun FunctionOptimization.withFeatures( @@ -47,7 +64,7 @@ public suspend fun DifferentiableExpression.optimizeWith( return optimizer.optimize(problem) } -public val FunctionOptimization.resultValueOrNull:T? +public val FunctionOptimization.resultValueOrNull: T? get() = getFeature>()?.point?.let { expression(it) } public val FunctionOptimization.resultValue: T diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt similarity index 94% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt rename to kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt index 7d52ae26e..10a25c029 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt @@ -72,15 +72,15 @@ public suspend fun DifferentiableExpression.optimizeWith( public class XYOptimizationBuilder( public val data: XYColumnarData, public val model: DifferentiableExpression, -) : OptimizationBuilder() { +) : OptimizationBuilder() { public var pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY public var pointWeight: PointWeight = PointWeight.byYSigma - override fun build(): XYOptimization = XYOptimization( - FeatureSet.of(features), + override fun build(): XYFit = XYFit( data, model, + FeatureSet.of(features), pointToCurveDistance, pointWeight ) @@ -90,4 +90,4 @@ public fun XYOptimization( data: XYColumnarData, model: DifferentiableExpression, builder: XYOptimizationBuilder.() -> Unit, -): XYOptimization = XYOptimizationBuilder(data, model).apply(builder).build() \ No newline at end of file +): XYFit = XYOptimizationBuilder(data, model).apply(builder).build() \ No newline at end of file diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt rename to kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationProblem.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt similarity index 100% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt rename to kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/Optimizer.kt diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt similarity index 77% rename from kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt rename to kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt index 365c58952..f0969d2f6 100644 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt @@ -11,6 +11,7 @@ import space.kscience.kmath.expressions.SymbolIndexer import space.kscience.kmath.expressions.derivative import space.kscience.kmath.linear.* import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.misc.log import space.kscience.kmath.operations.DoubleField import space.kscience.kmath.structures.DoubleBuffer import space.kscience.kmath.structures.DoubleL2Norm @@ -21,14 +22,14 @@ import space.kscience.kmath.structures.DoubleL2Norm * See [the article](http://arxiv.org/abs/physics/0604127). */ @UnstableKMathAPI -public class QowOptimizer : Optimizer { +public object QowOptimizer : Optimizer { private val linearSpace: LinearSpace = LinearSpace.double private val solver: LinearSolver = linearSpace.lupSolver() @OptIn(UnstableKMathAPI::class) - private inner class QoWeight( - val problem: XYOptimization, + private class QoWeight( + val problem: XYFit, val parameters: Map, ) : Map by parameters, SymbolIndexer { override val symbols: List = parameters.keys.toList() @@ -39,8 +40,8 @@ public class QowOptimizer : Optimizer { * Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter */ val derivs: Matrix by lazy { - linearSpace.buildMatrix(problem.data.size, symbols.size) { i, k -> - problem.distance(i).derivative(symbols[k])(parameters) + linearSpace.buildMatrix(problem.data.size, symbols.size) { d, s -> + problem.distance(d).derivative(symbols[s])(parameters) } } @@ -48,25 +49,27 @@ public class QowOptimizer : Optimizer { * Array of dispersions in each point */ val dispersion: Point by lazy { - DoubleBuffer(problem.data.size) { i -> - problem.weight(i).invoke(parameters) + DoubleBuffer(problem.data.size) { d -> + problem.weight(d).invoke(parameters) } } val prior: DifferentiableExpression? get() = problem.getFeature>() + + override fun toString(): String = parameters.toString() } /** - * The signed distance from the model to the [i]-th point of data. + * The signed distance from the model to the [d]-th point of data. */ - private fun QoWeight.distance(i: Int, parameters: Map): Double = problem.distance(i)(parameters) + private fun QoWeight.distance(d: Int, parameters: Map): Double = problem.distance(d)(parameters) /** * The derivative of [distance] */ - private fun QoWeight.distanceDerivative(symbol: Symbol, i: Int, parameters: Map): Double = - problem.distance(i).derivative(symbol)(parameters) + private fun QoWeight.distanceDerivative(symbol: Symbol, d: Int, parameters: Map): Double = + problem.distance(d).derivative(symbol)(parameters) /** * Теоретическая ковариация весовых функций. @@ -74,8 +77,8 @@ public class QowOptimizer : Optimizer { * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2 */ private fun QoWeight.covarF(): Matrix = - linearSpace.matrix(size, size).symmetric { k, l -> - (0 until data.size).sumOf { i -> derivs[k, i] * derivs[l, i] / dispersion[i] } + linearSpace.matrix(size, size).symmetric { s1, s2 -> + (0 until data.size).sumOf { d -> derivs[d, s1] * derivs[d, s2] / dispersion[d] } } /** @@ -89,12 +92,12 @@ public class QowOptimizer : Optimizer { * количество вызывов функции будет dim^2 вместо dim Первый индекс - * номер точки, второй - номер переменной, по которой берется производная */ - val eqvalues = linearSpace.buildMatrix(data.size, size) { i, l -> - distance(i, theta) * derivs[l, i] / dispersion[i] + val eqvalues = linearSpace.buildMatrix(data.size, size) { d, s -> + distance(d, theta) * derivs[d, s] / dispersion[d] } - buildMatrix(size, size) { k, l -> - (0 until data.size).sumOf { i -> eqvalues[i, l] * eqvalues[i, k] } + buildMatrix(size, size) { s1, s2 -> + (0 until data.size).sumOf { d -> eqvalues[d, s2] * eqvalues[d, s1] } } } @@ -106,20 +109,20 @@ public class QowOptimizer : Optimizer { ): Matrix = with(linearSpace) { //Возвращает производную k-того Eq по l-тому параметру //val res = Array(fitDim) { DoubleArray(fitDim) } - val sderiv = buildMatrix(data.size, size) { i, l -> - distanceDerivative(symbols[l], i, theta) + val sderiv = buildMatrix(data.size, size) { d, s -> + distanceDerivative(symbols[s], d, theta) } - buildMatrix(size, size) { k, l -> - val base = (0 until data.size).sumOf { i -> - require(dispersion[i] > 0) - sderiv[i, l] * derivs[k, i] / dispersion[i] + buildMatrix(size, size) { s1, s2 -> + val base = (0 until data.size).sumOf { d -> + require(dispersion[d] > 0) + sderiv[d, s2] * derivs[d, s1] / dispersion[d] } prior?.let { prior -> //Check if this one is correct val pi = prior(theta) - val deriv1 = prior.derivative(symbols[k])(theta) - val deriv2 = prior.derivative(symbols[l])(theta) + val deriv1 = prior.derivative(symbols[s1])(theta) + val deriv2 = prior.derivative(symbols[s2])(theta) base + deriv1 * deriv2 / pi / pi } ?: base } @@ -130,13 +133,13 @@ public class QowOptimizer : Optimizer { * Значения уравнений метода квазиоптимальных весов */ private fun QoWeight.getEqValues(theta: Map = this): Point { - val distances = DoubleBuffer(data.size) { i -> distance(i, theta) } + val distances = DoubleBuffer(data.size) { d -> distance(d, theta) } - return DoubleBuffer(size) { k -> - val base = (0 until data.size).sumOf { i -> distances[i] * derivs[k, i] / dispersion[i] } + return DoubleBuffer(size) { s -> + val base = (0 until data.size).sumOf { d -> distances[d] * derivs[d, s] / dispersion[d] } //Поправка на априорную вероятность prior?.let { prior -> - base - prior.derivative(symbols[k])(theta) / prior(theta) + base - prior.derivative(symbols[s])(theta) / prior(theta) } ?: base } } @@ -163,15 +166,15 @@ public class QowOptimizer : Optimizer { val logger = problem.getFeature() - var dis: Double//норма невязки - // Для удобства работаем всегда с полным набором параметров + var dis: Double //discrepancy value + // Working with the full set of parameters var par = problem.startPoint logger?.log { "Starting newtonian iteration from: \n\t$par" } - var eqvalues = getEqValues(par)//значения функций + var eqvalues = getEqValues(par) //Values of the weight functions - dis = DoubleL2Norm.norm(eqvalues)// невязка + dis = DoubleL2Norm.norm(eqvalues) // discrepancy logger?.log { "Starting discrepancy is $dis" } var i = 0 var flag = false @@ -238,7 +241,8 @@ public class QowOptimizer : Optimizer { return covar } - override suspend fun optimize(problem: XYOptimization): XYOptimization { + override suspend fun optimize(problem: XYFit): XYFit { + val qowSteps = 2 val initialWeight = QoWeight(problem, problem.startPoint) val res = initialWeight.newtonianRun() return res.problem.withFeature(OptimizationResult(res.parameters)) diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt new file mode 100644 index 000000000..cbd765923 --- /dev/null +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +@file:OptIn(UnstableKMathAPI::class) + +package space.kscience.kmath.optimization + +import space.kscience.kmath.data.XYColumnarData +import space.kscience.kmath.expressions.* +import space.kscience.kmath.misc.FeatureSet +import space.kscience.kmath.misc.Loggable +import space.kscience.kmath.misc.UnstableKMathAPI +import space.kscience.kmath.operations.ExtendedField +import space.kscience.kmath.operations.bindSymbol +import kotlin.math.pow + +/** + * Specify the way to compute distance from point to the curve as DifferentiableExpression + */ +public interface PointToCurveDistance : OptimizationFeature { + public fun distance(problem: XYFit, index: Int): DifferentiableExpression + + public companion object { + public val byY: PointToCurveDistance = object : PointToCurveDistance { + override fun distance(problem: XYFit, index: Int): DifferentiableExpression { + val x = problem.data.x[index] + val y = problem.data.y[index] + + return object : DifferentiableExpression { + override fun derivativeOrNull( + symbols: List + ): Expression? = problem.model.derivativeOrNull(symbols)?.let { derivExpression -> + Expression { arguments -> + derivExpression.invoke(arguments + (Symbol.x to x)) + } + } + + override fun invoke(arguments: Map): Double = + problem.model(arguments + (Symbol.x to x)) - y + } + } + + override fun toString(): String = "PointToCurveDistanceByY" + } + } +} + +/** + * Compute a wight of the point. The more the weight, the more impact this point will have on the fit. + * By default, uses Dispersion^-1 + */ +public interface PointWeight : OptimizationFeature { + public fun weight(problem: XYFit, index: Int): DifferentiableExpression + + public companion object { + public fun bySigma(sigmaSymbol: Symbol): PointWeight = object : PointWeight { + override fun weight(problem: XYFit, index: Int): DifferentiableExpression = + object : DifferentiableExpression { + override fun invoke(arguments: Map): Double { + return problem.data[sigmaSymbol]?.get(index)?.pow(-2) ?: 1.0 + } + + override fun derivativeOrNull(symbols: List): Expression = Expression { 0.0 } + } + + override fun toString(): String = "PointWeightBySigma($sigmaSymbol)" + + } + + public val byYSigma: PointWeight = bySigma(Symbol.yError) + } +} + +/** + * A fit problem for X-Y-Yerr data. Also known as "least-squares" problem. + */ +public class XYFit( + public val data: XYColumnarData, + public val model: DifferentiableExpression, + override val features: FeatureSet, + internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, + internal val pointWeight: PointWeight = PointWeight.byYSigma, +) : OptimizationProblem { + public fun distance(index: Int): DifferentiableExpression = pointToCurveDistance.distance(this, index) + + public fun weight(index: Int): DifferentiableExpression = pointWeight.weight(this, index) +} + +public fun XYFit.withFeature(vararg features: OptimizationFeature): XYFit { + return XYFit(data, model, this.features.with(*features), pointToCurveDistance, pointWeight) +} + +/** + * Fit given dta with + */ +public suspend fun XYColumnarData.fitWith( + optimizer: Optimizer, + processor: AutoDiffProcessor, + startingPoint: Map, + vararg features: OptimizationFeature = emptyArray(), + xSymbol: Symbol = Symbol.x, + pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, + pointWeight: PointWeight = PointWeight.byYSigma, + model: A.(I) -> I +): XYFit where A : ExtendedField, A : ExpressionAlgebra { + val modelExpression = processor.differentiate { + val x = bindSymbol(xSymbol) + model(x) + } + + var actualFeatures = FeatureSet.of(*features, OptimizationStartPoint(startingPoint)) + + if (actualFeatures.getFeature() == null) { + actualFeatures = actualFeatures.with(OptimizationLog(Loggable.console)) + } + val problem = XYFit( + this, + modelExpression, + actualFeatures, + pointToCurveDistance, + pointWeight + ) + return optimizer.optimize(problem) +} \ No newline at end of file diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt new file mode 100644 index 000000000..6701887a2 --- /dev/null +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2018-2021 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.optimization + +import space.kscience.kmath.data.XYColumnarData +import space.kscience.kmath.data.indices +import space.kscience.kmath.expressions.DifferentiableExpression +import space.kscience.kmath.expressions.Expression +import space.kscience.kmath.expressions.Symbol +import space.kscience.kmath.expressions.derivative +import space.kscience.kmath.misc.UnstableKMathAPI +import kotlin.math.PI +import kotlin.math.ln +import kotlin.math.pow +import kotlin.math.sqrt + + +private val oneOver2Pi = 1.0 / sqrt(2 * PI) + +@UnstableKMathAPI +internal fun XYFit.logLikelihood(): DifferentiableExpression = object : DifferentiableExpression { + override fun derivativeOrNull(symbols: List): Expression = Expression { arguments -> + data.indices.sumOf { index -> + val d = distance(index)(arguments) + val weight = weight(index)(arguments) + val weightDerivative = weight(index)(arguments) + + // -1 / (sqrt(2 PI) * sigma) + 2 (x-mu)/ 2 sigma^2 * d mu/ d theta - (x-mu)^2 / 2 * d w/ d theta + return@sumOf -oneOver2Pi * sqrt(weight) + //offset derivative + d * model.derivative(symbols)(arguments) * weight - //model derivative + d.pow(2) * weightDerivative / 2 //weight derivative + } + } + + override fun invoke(arguments: Map): Double { + return data.indices.sumOf { index -> + val d = distance(index)(arguments) + val weight = weight(index)(arguments) + //1/sqrt(2 PI sigma^2) - (x-mu)^2/ (2 * sigma^2) + oneOver2Pi * ln(weight) - d.pow(2) * weight + } / 2 + } + +} + +/** + * Optimize given XY (least squares) [problem] using this function [Optimizer]. + * The problem is treated as maximum likelihood problem and is done via maximizing logarithmic likelihood, respecting + * possible weight dependency on the model and parameters. + */ +@UnstableKMathAPI +public suspend fun Optimizer>.maximumLogLikelihood(problem: XYFit): XYFit { + val functionOptimization = FunctionOptimization(problem.features, problem.logLikelihood()) + val result = optimize(functionOptimization.withFeatures(FunctionOptimizationTarget.MAXIMIZE)) + return XYFit(problem.data, problem.model, result.features) +} + +@UnstableKMathAPI +public suspend fun Optimizer>.maximumLogLikelihood( + data: XYColumnarData, + model: DifferentiableExpression, + builder: XYOptimizationBuilder.() -> Unit, +): XYFit = maximumLogLikelihood(XYOptimization(data, model, builder)) diff --git a/kmath-stat/build.gradle.kts b/kmath-stat/build.gradle.kts index e3e396b6f..41a1666f8 100644 --- a/kmath-stat/build.gradle.kts +++ b/kmath-stat/build.gradle.kts @@ -1,6 +1,5 @@ plugins { - kotlin("multiplatform") - id("ru.mipt.npm.gradle.common") + id("ru.mipt.npm.gradle.mpp") id("ru.mipt.npm.gradle.native") } diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt deleted file mode 100644 index 68c0c77eb..000000000 --- a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/XYOptimization.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ -@file:OptIn(UnstableKMathAPI::class) - -package space.kscience.kmath.optimization - -import space.kscience.kmath.data.XYColumnarData -import space.kscience.kmath.data.indices -import space.kscience.kmath.expressions.DifferentiableExpression -import space.kscience.kmath.expressions.Expression -import space.kscience.kmath.expressions.Symbol -import space.kscience.kmath.expressions.derivative -import space.kscience.kmath.misc.FeatureSet -import space.kscience.kmath.misc.UnstableKMathAPI -import kotlin.math.PI -import kotlin.math.ln -import kotlin.math.pow -import kotlin.math.sqrt - -/** - * Specify the way to compute distance from point to the curve as DifferentiableExpression - */ -public interface PointToCurveDistance : OptimizationFeature { - public fun distance(problem: XYOptimization, index: Int): DifferentiableExpression - - public companion object { - public val byY: PointToCurveDistance = object : PointToCurveDistance { - override fun distance(problem: XYOptimization, index: Int): DifferentiableExpression { - - val x = problem.data.x[index] - val y = problem.data.y[index] - return object : DifferentiableExpression { - override fun derivativeOrNull(symbols: List): Expression? = - problem.model.derivativeOrNull(symbols) - - override fun invoke(arguments: Map): Double = - problem.model(arguments + (Symbol.x to x)) - y - } - } - - override fun toString(): String = "PointToCurveDistanceByY" - } - } -} - -/** - * Compute a wight of the point. The more the weight, the more impact this point will have on the fit. - * By default uses Dispersion^-1 - */ -public interface PointWeight : OptimizationFeature { - public fun weight(problem: XYOptimization, index: Int): DifferentiableExpression - - public companion object { - public fun bySigma(sigmaSymbol: Symbol): PointWeight = object : PointWeight { - override fun weight(problem: XYOptimization, index: Int): DifferentiableExpression = - object : DifferentiableExpression { - override fun invoke(arguments: Map): Double { - return problem.data[sigmaSymbol]?.get(index)?.pow(-2) ?: 1.0 - } - - override fun derivativeOrNull(symbols: List): Expression = Expression { 0.0 } - } - - override fun toString(): String = "PointWeightBySigma($sigmaSymbol)" - - } - - public val byYSigma: PointWeight = bySigma(Symbol.yError) - } -} - -/** - * An optimization for XY data. - */ -public class XYOptimization( - override val features: FeatureSet, - public val data: XYColumnarData, - public val model: DifferentiableExpression, - internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, - internal val pointWeight: PointWeight = PointWeight.byYSigma, -) : OptimizationProblem { - public fun distance(index: Int): DifferentiableExpression = pointToCurveDistance.distance(this, index) - - public fun weight(index: Int): DifferentiableExpression = pointWeight.weight(this, index) -} - -public fun XYOptimization.withFeature(vararg features: OptimizationFeature): XYOptimization { - return XYOptimization(this.features.with(*features), data, model, pointToCurveDistance, pointWeight) -} - -private val oneOver2Pi = 1.0 / sqrt(2 * PI) - -internal fun XYOptimization.likelihood(): DifferentiableExpression = object : DifferentiableExpression { - override fun derivativeOrNull(symbols: List): Expression = Expression { arguments -> - data.indices.sumOf { index -> - - val d = distance(index)(arguments) - val weight = weight(index)(arguments) - val weightDerivative = weight(index)(arguments) - - // -1 / (sqrt(2 PI) * sigma) + 2 (x-mu)/ 2 sigma^2 * d mu/ d theta - (x-mu)^2 / 2 * d w/ d theta - return@sumOf -oneOver2Pi * sqrt(weight) + //offset derivative - d * model.derivative(symbols)(arguments) * weight - //model derivative - d.pow(2) * weightDerivative / 2 //weight derivative - } - } - - override fun invoke(arguments: Map): Double { - return data.indices.sumOf { index -> - val d = distance(index)(arguments) - val weight = weight(index)(arguments) - //1/sqrt(2 PI sigma^2) - (x-mu)^2/ (2 * sigma^2) - oneOver2Pi * ln(weight) - d.pow(2) * weight - } / 2 - } - -} - -/** - * Optimize given XY (least squares) [problem] using this function [Optimizer]. - * The problem is treated as maximum likelihood problem and is done via maximizing logarithmic likelihood, respecting - * possible weight dependency on the model and parameters. - */ -public suspend fun Optimizer>.maximumLogLikelihood(problem: XYOptimization): XYOptimization { - val functionOptimization = FunctionOptimization(problem.features, problem.likelihood()) - val result = optimize(functionOptimization.withFeatures(FunctionOptimizationTarget.MAXIMIZE)) - return XYOptimization(result.features, problem.data, problem.model) -} - -public suspend fun Optimizer>.maximumLogLikelihood( - data: XYColumnarData, - model: DifferentiableExpression, - builder: XYOptimizationBuilder.() -> Unit, -): XYOptimization = maximumLogLikelihood(XYOptimization(data, model, builder)) - -//public suspend fun XYColumnarData.fitWith( -// optimizer: XYOptimization, -// problemBuilder: XYOptimizationBuilder.() -> Unit = {}, -// -//) - - -// -//@UnstableKMathAPI -//public interface XYFit : OptimizationProblem { -// -// public val algebra: Field -// -// /** -// * Set X-Y data for this fit optionally including x and y errors -// */ -// public fun data( -// dataSet: ColumnarData, -// xSymbol: Symbol, -// ySymbol: Symbol, -// xErrSymbol: Symbol? = null, -// yErrSymbol: Symbol? = null, -// ) -// -// public fun model(model: (T) -> DifferentiableExpression) -// -// /** -// * Set the differentiable model for this fit -// */ -// public fun model( -// autoDiff: AutoDiffProcessor>, -// modelFunction: A.(I) -> I, -// ): Unit where A : ExtendedField, A : ExpressionAlgebra = model { arg -> -// autoDiff.process { modelFunction(const(arg)) } -// } -//} - -// -///** -// * Define a chi-squared-based objective function -// */ -//public fun FunctionOptimization.chiSquared( -// autoDiff: AutoDiffProcessor>, -// x: Buffer, -// y: Buffer, -// yErr: Buffer, -// model: A.(I) -> I, -//) where A : ExtendedField, A : ExpressionAlgebra { -// val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model) -// function(chiSquared) -// maximize = false -//} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f05092bb1..d1cbbe74c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ include( ":kmath-histograms", ":kmath-commons", ":kmath-viktor", + ":kmath-optimization", ":kmath-stat", ":kmath-nd4j", ":kmath-dimensions", From 8d33d6beabf859a75317d087dd4adf70051b6c1a Mon Sep 17 00:00:00 2001 From: darksnake Date: Mon, 16 Aug 2021 16:40:56 +0300 Subject: [PATCH 11/13] Fixed QOW optimization --- .../kotlin/space/kscience/kmath/fit/qowFit.kt | 11 +++++---- .../kmath/optimization/QowOptimizer.kt | 2 +- .../kscience/kmath/optimization/XYFit.kt | 23 ++++++++++++++++++- .../kmath/optimization/logLikelihood.kt | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt b/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt index 944f80697..04764d763 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/fit/qowFit.kt @@ -14,6 +14,7 @@ import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.expressions.binding import space.kscience.kmath.expressions.symbol import space.kscience.kmath.optimization.QowOptimizer +import space.kscience.kmath.optimization.chiSquaredOrNull import space.kscience.kmath.optimization.fitWith import space.kscience.kmath.optimization.resultPoint import space.kscience.kmath.real.map @@ -50,7 +51,7 @@ suspend fun main() { //Perform an operation on each x value (much more effective, than numpy) val y = x.map { it -> - val value = it.pow(2) + it + 100 + val value = it.pow(2) + it + 1 value + chain.next() * sqrt(value) } // this will also work, but less effective: @@ -63,7 +64,7 @@ suspend fun main() { val result = XYErrorColumnarData.of(x, y, yErr).fitWith( QowOptimizer, DSProcessor, - mapOf(a to 1.0, b to 1.2, c to 99.0) + mapOf(a to 0.9, b to 1.2, c to 2.0) ) { arg -> //bind variables to autodiff context val a by binding @@ -96,9 +97,9 @@ suspend fun main() { h3 { +"Fit result: ${result.resultPoint}" } -// h3 { -// +"Chi2/dof = ${result.resultValue / (x.size - 3)}" -// } + h3 { + +"Chi2/dof = ${result.chiSquaredOrNull!! / (x.size - 3)}" + } } page.makeFile() diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt index f0969d2f6..4fe8520da 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/QowOptimizer.kt @@ -50,7 +50,7 @@ public object QowOptimizer : Optimizer { */ val dispersion: Point by lazy { DoubleBuffer(problem.data.size) { d -> - problem.weight(d).invoke(parameters) + 1.0/problem.weight(d).invoke(parameters) } } diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt index cbd765923..07fea3126 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/XYFit.kt @@ -7,6 +7,7 @@ package space.kscience.kmath.optimization import space.kscience.kmath.data.XYColumnarData +import space.kscience.kmath.data.indices import space.kscience.kmath.expressions.* import space.kscience.kmath.misc.FeatureSet import space.kscience.kmath.misc.Loggable @@ -81,6 +82,7 @@ public class XYFit( override val features: FeatureSet, internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY, internal val pointWeight: PointWeight = PointWeight.byYSigma, + public val xSymbol: Symbol = Symbol.x, ) : OptimizationProblem { public fun distance(index: Int): DifferentiableExpression = pointToCurveDistance.distance(this, index) @@ -119,7 +121,26 @@ public suspend fun XYColumnarData.fitWith( modelExpression, actualFeatures, pointToCurveDistance, - pointWeight + pointWeight, + xSymbol ) return optimizer.optimize(problem) +} + +/** + * Compute chi squared value for completed fit. Return null for incomplete fit + */ +public val XYFit.chiSquaredOrNull: Double? get() { + val result = resultPointOrNull ?: return null + + return data.indices.sumOf { index-> + + val x = data.x[index] + val y = data.y[index] + val yErr = data[Symbol.yError]?.get(index) ?: 1.0 + + val mu = model.invoke(result + (xSymbol to x) ) + + ((y - mu)/yErr).pow(2) + } } \ No newline at end of file diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt index 6701887a2..b4cb2f1cf 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/logLikelihood.kt @@ -26,7 +26,7 @@ internal fun XYFit.logLikelihood(): DifferentiableExpression = object : data.indices.sumOf { index -> val d = distance(index)(arguments) val weight = weight(index)(arguments) - val weightDerivative = weight(index)(arguments) + val weightDerivative = weight(index).derivative(symbols)(arguments) // -1 / (sqrt(2 PI) * sigma) + 2 (x-mu)/ 2 sigma^2 * d mu/ d theta - (x-mu)^2 / 2 * d w/ d theta return@sumOf -oneOver2Pi * sqrt(weight) + //offset derivative From 8581b32448e92e94cac04866426377beddc2f2a3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 27 Aug 2021 18:13:54 +0300 Subject: [PATCH 12/13] Apply suggestions from code review Co-authored-by: Iaroslav Postovalov <38042667+CommanderTvis@users.noreply.github.com> --- .gitignore | 2 -- .../src/main/kotlin/space/kscience/kmath/structures/buffers.kt | 2 +- kmath-optimization/build.gradle.kts | 2 +- .../space/kscience/kmath/optimization/OptimizationBuilder.kt | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index c5903368f..d6c4af4e3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,3 @@ out/ # Generated by javac -h and runtime *.class *.log - -/kmath-ejml/src/main/kotlin/space/kscience/kmath/ejml/_generated.kt diff --git a/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt b/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt index eabb16701..0f7fdc46a 100644 --- a/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt +++ b/examples/src/main/kotlin/space/kscience/kmath/structures/buffers.kt @@ -19,4 +19,4 @@ fun main() { with(DoubleField.bufferAlgebra(5)) { println(number(2.0) + produce(1, 2, 3, 4, 5)) } -} \ No newline at end of file +} diff --git a/kmath-optimization/build.gradle.kts b/kmath-optimization/build.gradle.kts index ca013b2f4..68b82ad65 100644 --- a/kmath-optimization/build.gradle.kts +++ b/kmath-optimization/build.gradle.kts @@ -17,4 +17,4 @@ kotlin.sourceSets { readme { maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL -} \ No newline at end of file +} diff --git a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt index 10a25c029..416d0195d 100644 --- a/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt +++ b/kmath-optimization/src/commonMain/kotlin/space/kscience/kmath/optimization/OptimizationBuilder.kt @@ -11,7 +11,7 @@ import space.kscience.kmath.expressions.Symbol import space.kscience.kmath.misc.FeatureSet public abstract class OptimizationBuilder> { - public val features: ArrayList = ArrayList() + public val features: MutableList = ArrayList() public fun addFeature(feature: OptimizationFeature) { features.add(feature) From 55ea3658b99a26200475fd705f208cd4acdc9f8b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 27 Aug 2021 18:16:24 +0300 Subject: [PATCH 13/13] Update CHANGELOG.md Co-authored-by: Iaroslav Postovalov <38042667+CommanderTvis@users.noreply.github.com> --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eadf5877d..ec3f5252a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ - Extended operations for ND4J fields - Jupyter Notebook integration module (kmath-jupyter) - `@PerformancePitfall` annotation to mark possibly slow API -- BigInt operation performance improvement and fixes by @zhelenskiy (#328) - Unified architecture for Integration and Optimization using features. - `BigInt` operation performance improvement and fixes by @zhelenskiy (#328) - Integration between `MST` and Symja `IExpr`