From 257337f4fb2a7681ff769b202dd4f16ebb8e0725 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<Double> = { 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<Double> = { 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<Double>): UnivariateIntegrand<Double> {
+    override fun process(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
         val integrator = integratorBuilder(integrand)
         val maxCalls = integrand.getFeature<IntegrandMaxCalls>()?.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<Double> {
 
-    override fun integrate(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
+    override fun process(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
         val range = integrand.getFeature<IntegrationRange>()?.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<Symbol>,
-) : FunctionOptimization<Double>, NoDerivFunctionOptimization<Double>, SymbolIndexer, OptimizationFeature {
+public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
 
-    private val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap()
-    private var optimizerBuilder: (() -> MultivariateOptimizer)? = null
-    public var convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(
-        DEFAULT_RELATIVE_TOLERANCE,
-        DEFAULT_ABSOLUTE_TOLERANCE,
-        DEFAULT_MAX_ITER
-    )
+    override suspend fun process(
+        problem: FunctionOptimization<Double>
+    ): FunctionOptimization<Double> = withSymbols(problem.parameters){
+        val cmOptimizer: MultivariateOptimizer =
+            problem.getFeature<CMOptimizerFactory>()?.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<PointValuePair> = SimpleValueChecker(
+            DEFAULT_RELATIVE_TOLERANCE,
+            DEFAULT_ABSOLUTE_TOLERANCE,
+            DEFAULT_MAX_ITER
+        )
+
+        val optimizationData: HashMap<KClass<out OptimizationData>, 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> = optimizationData.values.toList()
+        fun exportOptimizationData(): List<OptimizationData> = optimizationData.values.toList()
 
-    public override fun initialGuess(map: Map<Symbol, Double>): Unit {
-        addOptimizationData(InitialGuess(map.toDoubleArray()))
-    }
 
-    public override fun function(expression: Expression<Double>): 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<Double>): Unit {
+            val objectiveFunction = ObjectiveFunction {
+                val args = problem.initialGuess + it.toMap()
+                expression(args)
+            }
+            addOptimizationData(objectiveFunction)
         }
-        addOptimizationData(objectiveFunction)
-    }
 
-    public override fun diffFunction(expression: DifferentiableExpression<Double, Expression<Double>>) {
-        function(expression)
-        val gradientFunction = ObjectiveFunctionGradient {
-            val args = it.toMap()
-            DoubleArray(symbols.size) { index ->
-                expression.derivative(symbols[index])(args)
+        public override fun function(expression: DifferentiableExpression<Double, Expression<Double>>) {
+            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<Symbol, Double>) {
+            simplex(NelderMeadSimplex(steps.toDoubleArray()))
         }
-    }
 
-    public fun simplexSteps(steps: Map<Symbol, Double>) {
-        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<Double>) {
+            initialGuess(result.point)
+        }
 
-    override fun update(result: OptimizationResult<Double>) {
-        initialGuess(result.point)
-    }
-
-    override fun optimize(): OptimizationResult<Double> {
-        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<Double> {
+            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<Double, CMOptimization> {
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<Double>.optimize(
+public suspend fun Expression<Double>.optimize(
     vararg symbols: Symbol,
     configuration: CMOptimization.() -> Unit,
-): OptimizationResult<Double> = noDerivOptimizeWith(CMOptimization, symbols = symbols, configuration)
+): OptimizationResult<Double> {
+    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<Double, Expression<Double>>.optimize(
+public suspend fun DifferentiableExpression<Double, Expression<Double>>.optimize(
     vararg symbols: Symbol,
     configuration: CMOptimization.() -> Unit,
 ): OptimizationResult<Double> = optimizeWith(CMOptimization, symbols = symbols, configuration)
 
-public fun DifferentiableExpression<Double, Expression<Double>>.minimize(
+public suspend fun DifferentiableExpression<Double, Expression<Double>>.minimize(
     vararg startPoint: Pair<Symbol, Double>,
     configuration: CMOptimization.() -> Unit = {},
 ): OptimizationResult<Double> {
@@ -67,7 +69,7 @@ public fun DifferentiableExpression<Double, Expression<Double>>.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<T, out X : T, out Y : T> : XYColumnarData<T, X, Y> {
+    public val yErr: Buffer<Y>
+
+    override fun get(symbol: Symbol): Buffer<T> = 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<T, R : Expression<T>> : Differen
 /**
  * A factory that converts an expression in autodiff variables to a [DifferentiableExpression]
  */
-public fun interface AutoDiffProcessor<T : Any, I : Any, A : ExpressionAlgebra<T, I>, out R : Expression<T>> {
+public fun interface AutoDiffProcessor<T, I, A : ExpressionAlgebra<T, I>, out R : Expression<T>> {
     public fun process(function: A.() -> I): DifferentiableExpression<T, R>
 }
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 <T> Point<T>.toMap(): Map<Symbol, T> {
+        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 <T> Structure2D<T>.get(rowSymbol: Symbol, columnSymbol: Symbol): T =
         get(indexOf(rowSymbol), indexOf(columnSymbol))
 
@@ -55,6 +61,10 @@ public interface SymbolIndexer {
     public fun <T> Map<Symbol, T>.toPoint(bufferFactory: BufferFactory<T>): Point<T> =
         bufferFactory(symbols.size) { getValue(symbols[it]) }
 
+    public fun Map<Symbol, Double>.toPoint(): DoubleBuffer =
+        DoubleBuffer(symbols.size) { getValue(symbols[it]) }
+
+
     public fun Map<Symbol, Double>.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 <T : Any, LS : LinearSpace<T, *>> LS.buildSymmetricMatrix(
+    size: Int,
+    builder: (i: Int, j: Int) -> T,
+): Matrix<T> = BufferAccessor2D<T?>(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<F : Any> {
+    public fun <T : F> getFeature(type: KClass<out T>): T?
+}
+
+/**
+ * A container for a set of features
+ */
+public class FeatureSet<F : Any> private constructor(public val features: Map<KClass<out F>, Any>) : Featured<F> {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : F> getFeature(type: KClass<out T>): T? = features[type] as? T
+
+    public inline fun <reified T : F> getFeature(): T? = getFeature(T::class)
+
+    public fun <T : F> with(feature: T, type: KClass<out T> = feature::class): FeatureSet<F> =
+        FeatureSet(features + (type to feature))
+
+    public fun with(other: FeatureSet<F>): FeatureSet<F> = FeatureSet(features + other.features)
+
+    public fun with(vararg otherFeatures: F): FeatureSet<F> =
+        FeatureSet(features + otherFeatures.associateBy { it::class })
+
+    public companion object {
+        public fun <F : Any> of(vararg features: F): FeatureSet<F> = 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<T : Any>(
+internal class BufferAccessor2D<T>(
     public val rowNum: Int,
     public val colNum: Int,
     val factory: MutableBufferFactory<T>,
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<Buffer<Doubl
         DoubleBuffer(DoubleArray(arg.size) { ln(arg[it]) })
 }
 
+public object DoubleL2Norm : Norm<Point<Double>, Double> {
+    override fun norm(arg: Point<Double>): 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<Buffer<Double>> {
+public class DoubleBufferField(public val size: Int) : ExtendedField<Buffer<Double>>, Norm<Buffer<Double>, Double> {
     public override val zero: Buffer<Double> by lazy { DoubleBuffer(size) { 0.0 } }
     public override val one: Buffer<Double> by lazy { DoubleBuffer(size) { 1.0 } }
 
@@ -274,4 +280,6 @@ public class DoubleBufferField(public val size: Int) : ExtendedField<Buffer<Doub
         require(arg.size == size) { "The buffer size ${arg.size} does not match context size $size" }
         return DoubleBufferFieldOperations.ln(arg)
     }
+
+    override fun norm(arg: Buffer<Double>): 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<DoubleVector, Double> {
-    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<T : Any>(
         }
     }
 
-    override fun integrate(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> = with(algebra) {
+    override fun process(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> = with(algebra) {
         val f = integrand.function
         val (points, weights) = buildRule(integrand)
         var res = zero
@@ -63,7 +63,7 @@ public class GaussIntegrator<T : Any>(
             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<T : Any>(
  * * [UnivariateIntegrandRanges] - Set of ranges and number of points per range. Defaults to given [IntegrationRange] and [IntegrandMaxCalls]
  */
 @UnstableKMathAPI
-public fun <T : Any> Field<T>.integrate(
+public fun <T : Any> Field<T>.process(
     vararg features: IntegrandFeature,
     function: (Double) -> T,
-): UnivariateIntegrand<T> = GaussIntegrator(this).integrate(UnivariateIntegrand(function, *features))
+): UnivariateIntegrand<T> = 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 <T : Any> Field<T>.integrate(
+public fun <T : Any> Field<T>.process(
     range: ClosedRange<Double>,
     order: Int = 10,
     intervals: Int = 10,
@@ -104,7 +104,7 @@ public fun <T : Any> Field<T>.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 <T : IntegrandFeature> getFeature(type: KClass<T>): T?
+public interface Integrand: Featured<IntegrandFeature>{
+    public val features: FeatureSet<IntegrandFeature>
+    override fun <T : IntegrandFeature> getFeature(type: KClass<out T>): T?  = features.getFeature(type)
 }
 
 public inline fun <reified T : IntegrandFeature> 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<I : Integrand> {
     /**
      * 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<T : Any> internal constructor(
-    private val features: Map<KClass<*>, IntegrandFeature>,
+    override val features: FeatureSet<IntegrandFeature>,
     public val function: (Point<T>) -> T,
-) : Integrand {
+) : Integrand
 
-    @Suppress("UNCHECKED_CAST")
-    override fun <T : IntegrandFeature> getFeature(type: KClass<T>): T? = features[type] as? T
-
-    public operator fun <F : IntegrandFeature> plus(pair: Pair<KClass<out F>, F>): MultivariateIntegrand<T> =
-        MultivariateIntegrand(features + pair, function)
-
-    public operator fun <F : IntegrandFeature> plus(feature: F): MultivariateIntegrand<T> =
-        plus(feature::class to feature)
-}
+public fun <T : Any> MultivariateIntegrand<T>.with(vararg newFeatures: IntegrandFeature): MultivariateIntegrand<T> =
+    MultivariateIntegrand(features.with(*newFeatures), function)
 
 @Suppress("FunctionName")
 public fun <T : Any> MultivariateIntegrand(
     vararg features: IntegrandFeature,
     function: (Point<T>) -> T,
-): MultivariateIntegrand<T> = MultivariateIntegrand(features.associateBy { it::class }, function)
+): MultivariateIntegrand<T> = MultivariateIntegrand(FeatureSet.of(*features), function)
 
 public val <T : Any> MultivariateIntegrand<T>.value: T? get() = getFeature<IntegrandValue<T>>()?.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<T : Any> internal constructor(
-    private val features: Map<KClass<*>, IntegrandFeature>,
+    override val features: FeatureSet<IntegrandFeature>,
     public val function: (Double) -> T,
-) : Integrand {
+) : Integrand
 
-    @Suppress("UNCHECKED_CAST")
-    override fun <T : IntegrandFeature> getFeature(type: KClass<T>): T? = features[type] as? T
-
-    public operator fun <F : IntegrandFeature> plus(pair: Pair<KClass<out F>, F>): UnivariateIntegrand<T> =
-        UnivariateIntegrand(features + pair, function)
-
-    public operator fun <F : IntegrandFeature> plus(feature: F): UnivariateIntegrand<T> =
-        plus(feature::class to feature)
-}
+public fun <T : Any> UnivariateIntegrand<T>.with(vararg newFeatures: IntegrandFeature): UnivariateIntegrand<T> =
+    UnivariateIntegrand(features.with(*newFeatures), function)
 
 @Suppress("FunctionName")
 public fun <T : Any> UnivariateIntegrand(
     function: (Double) -> T,
     vararg features: IntegrandFeature,
-): UnivariateIntegrand<T> = UnivariateIntegrand(features.associateBy { it::class }, function)
+): UnivariateIntegrand<T> = UnivariateIntegrand(FeatureSet.of(*features), function)
 
 public typealias UnivariateIntegrator<T> = Integrator<UnivariateIntegrand<T>>
 
@@ -46,7 +40,7 @@ public fun UnivariateIntegrator<Double>.integrate(
     range: ClosedRange<Double>,
     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<Double>.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<T : Any> : Optimization<T> {
-    /**
-     * 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<Symbol, T>)
+public class FunctionOptimization<T : Any>(
+    override val features: FeatureSet<OptimizationFeature>,
+    public val expression: DifferentiableExpression<T, Expression<T>>,
+    public val initialGuess: Map<Symbol, T>,
+    public val parameters: Collection<Symbol>,
+    public val maximize: Boolean,
+) : OptimizationProblem
 
-    /**
-     * Set a differentiable expression as objective function as function and gradient provider
-     */
-    public fun diffFunction(expression: DifferentiableExpression<T, Expression<T>>)
-
-    public companion object {
-        /**
-         * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
-         */
-        public fun <T : Any, I : Any, A> chiSquared(
-            autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-            x: Buffer<T>,
-            y: Buffer<T>,
-            yErr: Buffer<T>,
-            model: A.(I) -> I,
-        ): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
-            require(x.size == y.size) { "X and y buffers should be of the same size" }
-            require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
-
-            return autoDiff.process {
-                var sum = zero
-
-                x.indices.forEach {
-                    val xValue = const(x[it])
-                    val yValue = const(y[it])
-                    val yErrValue = const(yErr[it])
-                    val modelValue = model(xValue)
-                    sum += ((yValue - modelValue) / yErrValue).pow(2)
-                }
-
-                sum
-            }
-        }
-    }
-}
-
-/**
- * Define a chi-squared-based objective function
- */
-public fun <T: Any, I : Any, A> FunctionOptimization<T>.chiSquared(
-    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-    x: Buffer<T>,
-    y: Buffer<T>,
-    yErr: Buffer<T>,
-    model: A.(I) -> I,
-) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
-    val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model)
-    diffFunction(chiSquared)
-    maximize = false
-}
-
-/**
- * Optimize differentiable expression using specific [OptimizationProblemFactory]
- */
-public fun <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
-    factory: OptimizationProblemFactory<T, F>,
-    vararg symbols: Symbol,
-    configuration: F.() -> Unit,
-): OptimizationResult<T> {
-    require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
-    val problem = factory(symbols.toList(), configuration)
-    problem.diffFunction(this)
-    return problem.optimize()
-}
+//
+///**
+// * A likelihood function optimization problem with provided derivatives
+// */
+//public interface FunctionOptimizationBuilder<T : Any> {
+//    /**
+//     * 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<Symbol, T>)
+//
+//    /**
+//     * Set a differentiable expression as objective function as function and gradient provider
+//     */
+//    public fun function(expression: DifferentiableExpression<T, Expression<T>>)
+//
+//    public companion object {
+//        /**
+//         * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
+//         */
+//        public fun <T : Any, I : Any, A> chiSquared(
+//            autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
+//            x: Buffer<T>,
+//            y: Buffer<T>,
+//            yErr: Buffer<T>,
+//            model: A.(I) -> I,
+//        ): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
+//            require(x.size == y.size) { "X and y buffers should be of the same size" }
+//            require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
+//
+//            return autoDiff.process {
+//                var sum = zero
+//
+//                x.indices.forEach {
+//                    val xValue = const(x[it])
+//                    val yValue = const(y[it])
+//                    val yErrValue = const(yErr[it])
+//                    val modelValue = model(xValue)
+//                    sum += ((yValue - modelValue) / yErrValue).pow(2)
+//                }
+//
+//                sum
+//            }
+//        }
+//    }
+//}
+//
+///**
+// * Define a chi-squared-based objective function
+// */
+//public fun <T : Any, I : Any, A> FunctionOptimization<T>.chiSquared(
+//    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
+//    x: Buffer<T>,
+//    y: Buffer<T>,
+//    yErr: Buffer<T>,
+//    model: A.(I) -> I,
+//) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
+//    val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model)
+//    function(chiSquared)
+//    maximize = false
+//}
+//
+///**
+// * Optimize differentiable expression using specific [OptimizationProblemFactory]
+// */
+//public suspend fun <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
+//    factory: OptimizationProblemFactory<T, F>,
+//    vararg symbols: Symbol,
+//    configuration: F.() -> Unit,
+//): OptimizationResult<T> {
+//    require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
+//    val problem = factory(symbols.toList(), configuration)
+//    problem.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<T : Any> : Optimization<T> {
-    /**
-     * 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<Symbol, T>)
-
-    /**
-     * Set an objective function expression
-     */
-    public fun function(expression: Expression<T>)
-
-    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<Double>,
-            y: Buffer<Double>,
-            yErr: Buffer<Double>,
-            model: Expression<Double>,
-            xSymbol: Symbol = Symbol.x,
-        ): Expression<Double> {
-            require(x.size == y.size) { "X and y buffers should be of the same size" }
-            require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
-
-            return Expression { arguments ->
-                x.indices.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 <T : Any, F : NoDerivFunctionOptimization<T>> Expression<T>.noDerivOptimizeWith(
-    factory: OptimizationProblemFactory<T, F>,
-    vararg symbols: Symbol,
-    configuration: F.() -> Unit,
-): OptimizationResult<T> {
-    require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
-    val problem = factory(symbols.toList(), configuration)
-    problem.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<T>(
-    public val point: Map<Symbol, T>,
-    public val value: T,
-    public val features: Set<OptimizationFeature> = emptySet(),
-) {
-    override fun toString(): String {
-        return "OptimizationResult(point=$point, value=$value)"
-    }
-}
-
-public operator fun <T> OptimizationResult<T>.plus(
-    feature: OptimizationFeature,
-): OptimizationResult<T> = OptimizationResult(point, value, features + feature)
-
-/**
- * An optimization problem builder over [T] variables
- */
-public interface Optimization<T : Any> {
-
-    /**
-     * Update the problem from previous optimization run
-     */
-    public fun update(result: OptimizationResult<T>)
-
-    /**
-     * Make an optimization run
-     */
-    public fun optimize(): OptimizationResult<T>
-}
-
-public fun interface OptimizationProblemFactory<T : Any, out P : Optimization<T>> {
-    public fun build(symbols: List<Symbol>): P
-}
-
-public operator fun <T : Any, P : Optimization<T>> OptimizationProblemFactory<T, P>.invoke(
-    symbols: List<Symbol>,
-    block: P.() -> Unit,
-): P = build(symbols).apply(block)
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<OptimizationFeature> {
+    public val features: FeatureSet<OptimizationFeature>
+    override fun <T : OptimizationFeature> getFeature(type: KClass<out T>): T? = features.getFeature(type)
+}
+
+public inline fun <reified T : OptimizationFeature> OptimizationProblem.getFeature(): T? = getFeature(T::class)
+
+//public class OptimizationResult<T>(
+//    public val point: Map<Symbol, T>,
+//    public val value: T,
+//    public val features: Set<OptimizationFeature> = emptySet(),
+//) {
+//    override fun toString(): String {
+//        return "OptimizationResult(point=$point, value=$value)"
+//    }
+//}
+//
+//public operator fun <T> OptimizationResult<T>.plus(
+//    feature: OptimizationFeature,
+//): OptimizationResult<T> = OptimizationResult(point, value, features + feature)
+//public fun interface OptimizationProblemFactory<T : Any, out P : OptimizationProblem<T>> {
+//    public fun build(symbols: List<Symbol>): P
+//}
+//
+//public operator fun <T : Any, P : OptimizationProblem<T>> OptimizationProblemFactory<T, P>.invoke(
+//    symbols: List<Symbol>,
+//    block: P.() -> Unit,
+//): P = build(symbols).apply(block)
+
+public interface Optimizer<P : OptimizationProblem> {
+    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<T : Any> : Optimization<T> {
+public interface XYFit<T> : OptimizationProblem<T> {
 
     public val algebra: Field<T>
 
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<Range>,
+    xmnos: MinosError,
+    ymnos: MinosError,
+    nfcn: Int
+) {
+    private val theNFcn: Int
+    private val thePoints: List<Range> = 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<Range> {
+        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<MinimumState>
+    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<MinimumState>, up: Double) {
+        theSeed = seed
+        theStates = states
+        theErrorDef = up
+        theUserState = MnUserParameterState()
+    }
+
+    internal constructor(seed: MinimumSeed, states: MutableList<MinimumState>, up: Double, x: MnReachedCallLimit?) {
+        theSeed = seed
+        theStates = states
+        theErrorDef = up
+        theReachedCallLimit = true
+        theUserState = MnUserParameterState()
+    }
+
+    internal constructor(seed: MinimumSeed, states: MutableList<MinimumState>, 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 <CODE>true</CODE>, 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 <CODE>false</CODE>.
+     * 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<MinimumState> {
+        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<FunctionGradient, RealVector> {
+        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: "<<fcnmin<<std::endl;
+        val dfmin: Double = 4.0 * precision().eps2() * (abs(fcnmin) + theFcn.errorDef())
+        val n: Int = x.getDimension()
+        val dgrd: RealVector = ArrayRealVector(n)
+
+        // initial starting values
+        for (i in 0 until n) {
+            val xtf: Double = x.getEntry(i)
+            val dmin: Double = 4.0 * precision().eps2() * (xtf + precision().eps2())
+            val epspri: Double = precision().eps2() + abs(grd.getEntry(i) * precision().eps2())
+            val optstp: Double = sqrt(dfmin / (abs(g2.getEntry(i)) + epspri))
+            var d: Double = 0.2 * abs(gstep.getEntry(i))
+            if (d > 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<String>, private val errl: DoubleArray?, private val errp: DoubleArray?) :
+    IntervalEstimate {
+    fun getNames(): NameList {
+        return NameList(names)
+    }
+
+    fun getInterval(parName: String?): Pair<Value, Value> {
+        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<String> = 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<String> = 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<String> {
+        return listOf("MINUIT")
+    }
+
+    fun detach() {
+        clearStaticLog()
+        super.detach()
+    }
+
+    class Factory : PluginFactory() {
+        fun build(meta: Meta?): Plugin {
+            return MINUITPlugin()
+        }
+
+        fun getType(): java.lang.Class<out Plugin?> {
+            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<String>): MultiFunction {
+        return MnFunc(source, allPar, fitPars)
+    }
+
+    fun getFitParameters(set: ParamSet, fitPars: Array<String>): 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<String>, 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<String>) : MultiFunction {
+        var source: FitState
+        var allPar: ParamSet
+        var fitPars: Array<String>
+        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<MinuitParameter> {
+        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
+     * <CODE>false</CODE> 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<Range> = java.util.ArrayList<Range>(npoints)
+        val states: List<MnUserParameterState> = java.util.ArrayList<MnUserParameterState>()
+        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<Range> {
+        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<Range> {
+        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<Range> {
+        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<Range> {
+        return scan(par, 41)
+    }
+
+    fun scan(par: Int, maxsteps: Int): List<Range> {
+        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<Range> {
+        var maxsteps = maxsteps
+        var low = low
+        var high = high
+        if (maxsteps > 101) {
+            maxsteps = 101
+        }
+        val result: MutableList<Range> = java.util.ArrayList<Range>(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<Range>) {
+        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<Range>) {
+        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= "<<err<<std::endl;
+        val epspdf: Double = max(1e-6, prec.eps2())
+        var dgmin: Double = err[0, 0]
+        for (i in 0 until err.nrow()) {
+            if (err[i, i] < prec.eps2()) {
+                MINUITPlugin.logStatic("negative or zero diagonal element $i in covariance matrix")
+            }
+            if (err[i, i] < dgmin) {
+                dgmin = err[i, i]
+            }
+        }
+        var dg = 0.0
+        if (dgmin < prec.eps2()) {
+            dg = 1.0 + epspdf - dgmin
+            //     dg = 0.5*(1. + epspdf - dgmin);
+            MINUITPlugin.logStatic("added $dg to diagonal of error matrix")
+        }
+        val s: RealVector = ArrayRealVector(err.nrow())
+        val p = MnAlgebraicSymMatrix(err.nrow())
+        for (i in 0 until err.nrow()) {
+            err[i, i] = err[i, i] + dg
+            if (err[i, i] < 0.0) {
+                err[i, i] = 1.0
+            }
+            s.setEntry(i, 1.0 / sqrt(err[i, i]))
+            for (j in 0..i) {
+                p[i, j] = err[i, j] * s.getEntry(i) * s.getEntry(j)
+            }
+        }
+
+        //   std::cout<<"MnPosDef p: "<<p<<std::endl;
+        val eval: RealVector = p.eigenvalues()
+        val pmin: Double = eval.getEntry(0)
+        var pmax: Double = eval.getEntry(eval.getDimension() - 1)
+        //   std::cout<<"pmin= "<<pmin<<" pmax= "<<pmax<<std::endl;
+        pmax = max(abs(pmax), 1.0)
+        if (pmin > epspdf * pmax) {
+            return e
+        }
+        val padd = 0.001 * pmax - pmin
+        MINUITPlugin.logStatic("eigenvalues: ")
+        for (i in 0 until err.nrow()) {
+            err[i, i] = err[i, i] * (1.0 + padd)
+            MINUITPlugin.logStatic(java.lang.Double.toString(eval.getEntry(i)))
+        }
+        //   std::cout<<"MnPosDef final matrix: "<<err<<std::endl;
+        MINUITPlugin.logStatic("matrix forced pos-def by adding $padd to diagonal")
+        //   std::cout<<"eigenvalues: "<<eval<<std::endl;
+        return MinimumError(err, MnMadePosDef())
+    }
+}
\ No newline at end of file
diff --git a/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPrint.kt b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPrint.kt
new file mode 100644
index 000000000..ce018c2a4
--- /dev/null
+++ b/kmath-stat/src/commonMain/kotlin/space/kscience/kmath/optimization/minuit/MnPrint.kt
@@ -0,0 +1,387 @@
+/* 
+ * 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.*
+
+/**
+ * Utilities for printing various minuit results.
+ *
+ * @version $Id$
+ * @author Darksnake
+ */
+object MnPrint {
+    /**
+     *
+     * print.
+     *
+     * @param os a [PrintWriter] object.
+     * @param vec a [org.apache.commons.math3.linear.RealVector] object.
+     */
+    fun print(os: PrintWriter, vec: RealVector) {
+        os.println("LAVector parameters:")
+        run {
+            os.println()
+            val nrow: Int = vec.getDimension()
+            for (i in 0 until nrow) {
+                os.printf("%g ", vec.getEntry(i))
+            }
+            os.println()
+        }
+    }
+
+    /**
+     *
+     * print.
+     *
+     * @param os a [PrintWriter] object.
+     * @param matrix a [hep.dataforge.MINUIT.MnAlgebraicSymMatrix] object.
+     */
+    fun print(os: PrintWriter, matrix: MnAlgebraicSymMatrix) {
+        os.println("LASymMatrix parameters:")
+        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()
+            }
+        }
+    }
+
+    /**
+     *
+     * print.
+     *
+     * @param os a [PrintWriter] object.
+     * @param min a [hep.dataforge.MINUIT.FunctionMinimum] object.
+     */
+    fun print(os: PrintWriter, min: FunctionMinimum) {
+        os.println()
+        if (!min.isValid()) {
+            os.println()
+            os.println("WARNING: Minuit did not converge.")
+            os.println()
+        } else {
+            os.println()
+            os.println("Minuit did successfully converge.")
+            os.println()
+        }
+        os.printf("# of function calls: %d\n", min.nfcn())
+        os.printf("minimum function value: %g\n", min.fval())
+        os.printf("minimum edm: %g\n", min.edm())
+        os.println("minimum internal state vector: " + min.parameters().vec())
+        if (min.hasValidCovariance()) {
+            os.println("minimum internal covariance matrix: " + min.error().matrix())
+        }
+        os.println(min.userParameters())
+        os.println(min.userCovariance())
+        os.println(min.userState().globalCC())
+        if (!min.isValid()) {
+            os.println("WARNING: FunctionMinimum is invalid.")
+        }
+        os.println()
+    }
+
+    /**
+     *
+     * print.
+     *
+     * @param os a [PrintWriter] object.
+     * @param min a [hep.dataforge.MINUIT.MinimumState] object.
+     */
+    fun print(os: PrintWriter, min: MinimumState) {
+        os.println()
+        os.printf("minimum function value: %g\n", min.fval())
+        os.printf("minimum edm: %g\n", min.edm())
+        os.println("minimum internal state vector: " + min.vec())
+        os.println("minimum internal gradient vector: " + min.gradient().getGradient())
+        if (min.hasCovariance()) {
+            os.println("minimum internal covariance matrix: " + min.error().matrix())
+        }
+        os.println()
+    }
+
+    /**
+     *
+     * print.
+     *
+     * @param os a [PrintWriter] object.
+     * @param par a [hep.dataforge.MINUIT.MnUserParameters] object.
+     */
+    fun print(os: PrintWriter, par: MnUserParameters) {
+        os.println()
+        os.println("# ext. |" + "|   name    |" + "|   type  |" + "|   value   |" + "|  error +/- ")
+        os.println()
+        var atLoLim = false
+        var atHiLim = false
+        for (ipar in par.parameters()) {
+            os.printf(" %5d || %9s || ", ipar.number(), ipar.name())
+            if (ipar.isConst()) {
+                os.printf("         || %10g   ||", ipar.value())
+            } else if (ipar.isFixed()) {
+                os.printf("  fixed  || %10g   ||\n", ipar.value())
+            } else if (ipar.hasLimits()) {
+                if (ipar.error() > 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<Range> {
+        return scan(par, 41)
+    }
+
+    /**
+     *
+     * scan.
+     *
+     * @param par a int.
+     * @param maxsteps a int.
+     * @return a [List] object.
+     */
+    fun scan(par: Int, maxsteps: Int): List<Range> {
+        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<Range> {
+        val scan = MnParameterScan(theFCN, theState.parameters())
+        var amin: Double = scan.fval()
+        val result: List<Range> = 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<FunctionGradient, RealVector> = 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 <I>safe</I> and waste a few function calls
+ * in order to know where it is, or to be <I>fast</I> 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<Double>
+    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<Double>()
+        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<Double>(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<Double>(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<Double>(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<Double>(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<Double>()
+        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<Double>()
+        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
+     * <CODE>true</CODE> if the the state has a valid covariance,
+     * <CODE>false</CODE> 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<Double> {
+        return theIntParameters
+    }
+
+    /**
+     * Returns
+     * <CODE>true</CODE> if the the state is valid,
+     * <CODE>false</CODE> if not
+     *
+     * @return a boolean.
+     */
+    fun isValid(): Boolean {
+        return theValid
+    }
+
+    // facade: forward interface of MnUserParameters and MnUserTransformation
+    fun minuitParameters(): List<MinuitParameter> {
+        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<MinuitParameter> {
+        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<String?, Int> = HashMap()
+    private var theCache: MutableList<Double>
+    private var theExtOfInt: MutableList<Int>
+    private var theParameters: MutableList<MinuitParameter>
+    private var thePrecision: MnMachinePrecision
+
+    constructor() {
+        thePrecision = MnMachinePrecision()
+        theParameters = java.util.ArrayList<MinuitParameter>()
+        theExtOfInt = java.util.ArrayList<Int>()
+        theCache = java.util.ArrayList<Double>(0)
+    }
+
+    private constructor(other: MnUserTransformation) {
+        thePrecision = other.thePrecision
+        theParameters = java.util.ArrayList<MinuitParameter>(other.theParameters.size)
+        for (par in other.theParameters) {
+            theParameters.add(par.copy())
+        }
+        theExtOfInt = java.util.ArrayList<Int>(other.theExtOfInt)
+        theCache = java.util.ArrayList<Double>(other.theCache)
+    }
+
+    constructor(par: DoubleArray, err: DoubleArray) {
+        thePrecision = MnMachinePrecision()
+        theParameters = java.util.ArrayList<MinuitParameter>(par.size)
+        theExtOfInt = java.util.ArrayList<Int>(par.size)
+        theCache = java.util.ArrayList<Double>(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<MinuitParameter> {
+        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<Int>(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<Double?, Double?>(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<MinimumState> = java.util.ArrayList<MinimumState>(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<Pair<Double?, RealVector?>> = java.util.ArrayList<Pair<Double, RealVector>>(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<MinimumState> = java.util.ArrayList<MinimumState>(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<Pair<Double?, RealVector?>>, jh: Int, jl: Int) {
+    private var theJHigh: Int
+    private var theJLow: Int
+    private val theSimplexParameters: MutableList<Pair<Double?, RealVector?>>
+    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<Double?, RealVector?> {
+        return theSimplexParameters[i]
+    }
+
+    fun jh(): Int {
+        return theJHigh
+    }
+
+    fun jl(): Int {
+        return theJLow
+    }
+
+    fun simplex(): List<Pair<Double?, RealVector?>> {
+        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<MinimumState> = java.util.ArrayList<MinimumState>(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<Symbol, Double>
+
+public fun interface FitLogger {
+    public fun log(block: () -> String)
+}
+
+@OptIn(UnstableKMathAPI::class)
+public class QowFit(
+    override val symbols: List<Symbol>,
+    private val space: LinearSpace<Double, DoubleField>,
+    private val solver: LinearSolver<Double>,
+) : XYFit<Double>, SymbolIndexer {
+
+    private var logger: FitLogger? = null
+
+    private var startingPoint: Map<Symbol, Double> = TODO()
+    private var covariance: Matrix<Double>? = TODO()
+    private val prior: DifferentiableExpression<Double, Expression<Double>>? = TODO()
+    private var data: XYErrorColumnarData<Double, Double, Double> = TODO()
+    private var model: DifferentiableExpression<Double, Expression<Double>> = TODO()
+
+    private val features = HashSet<OptimizationFeature>()
+
+    override fun update(result: OptimizationResult<Double>) {
+        TODO("Not yet implemented")
+    }
+
+    override val algebra: Field<Double>
+        get() = TODO("Not yet implemented")
+
+    override fun data(
+        dataSet: ColumnarData<Double>,
+        xSymbol: Symbol,
+        ySymbol: Symbol,
+        xErrSymbol: Symbol?,
+        yErrSymbol: Symbol?,
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun model(model: (Double) -> DifferentiableExpression<Double, *>) {
+        TODO("Not yet implemented")
+    }
+
+    private var x: Symbol = Symbol.x
+
+    /**
+     * The signed distance from the model to the [i]-th point of data.
+     */
+    private fun distance(i: Int, parameters: Map<Symbol, Double>): Double =
+        model(parameters + (x to data.x[i])) - data.y[i]
+
+
+    /**
+     * The derivative of [distance]
+     * TODO use expressions instead
+     */
+    private fun distanceDerivative(symbol: Symbol, i: Int, parameters: Map<Symbol, Double>): Double =
+        model.derivative(symbol)(parameters + (x to data.x[i]))
+
+    /**
+     * The dispersion of [i]-th data point
+     */
+    private fun getDispersion(i: Int, parameters: Map<Symbol, Double>): Double = data.yErr[i].pow(2)
+
+    private fun getCovariance(weight: QoWeight): Matrix<Double> = solver.inverse(getEqDerivValues(weight))
+
+    /**
+     * Теоретическая ковариация весовых функций.
+     *
+     * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2
+     */
+    private fun covarF(weight: QoWeight): Matrix<Double> = space.buildSymmetricMatrix(symbols.size) { k, l ->
+        (0 until data.size).sumOf { i -> weight.derivs[k, i] * weight.derivs[l, i] / weight.dispersion[i] }
+    }
+
+    /**
+     * Экспериментальная ковариация весов. Формула (22) из
+     * http://arxiv.org/abs/physics/0604127
+     *
+     * @param source
+     * @param set
+     * @param fitPars
+     * @param weight
+     * @return
+     */
+    private fun covarFExp(weight: QoWeight, theta: Map<Symbol, Double>): Matrix<Double> = space.run {
+        /*
+         * Важно! Если не делать предварителього вычисления этих производных, то
+         * количество вызывов функции будет dim^2 вместо dim Первый индекс -
+         * номер точки, второй - номер переменной, по которой берется производная
+         */
+        val eqvalues = buildMatrix(data.size, symbols.size) { i, l ->
+            distance(i, theta) * weight.derivs[l, i] / weight.dispersion[i]
+        }
+
+        buildMatrix(symbols.size, symbols.size) { k, l ->
+            (0 until data.size).sumOf { i -> eqvalues[i, l] * eqvalues[i, k] }
+        }
+    }
+
+    /**
+     * производные уравнений для метода Ньютона
+     *
+     * @param source
+     * @param set
+     * @param fitPars
+     * @param weight
+     * @return
+     */
+    private fun getEqDerivValues(
+        weight: QoWeight, theta: Map<Symbol, Double> = weight.theta,
+    ): Matrix<Double> = space.run {
+        val fitDim = symbols.size
+        //Возвращает производную k-того Eq по l-тому параметру
+        val res = Array(fitDim) { DoubleArray(fitDim) }
+        val sderiv = buildMatrix(data.size, symbols.size) { i, l ->
+            distanceDerivative(symbols[l], i, theta)
+        }
+
+        buildMatrix(symbols.size, symbols.size) { k, l ->
+            val base = (0 until data.size).sumOf { i ->
+                require(weight.dispersion[i] > 0)
+                sderiv[i, l] * weight.derivs[k, i] / weight.dispersion[i]
+            }
+            prior?.let { prior ->
+                //Check if this one is correct
+                val pi = prior(theta)
+                val deriv1 = prior.derivative(symbols[k])(theta)
+                val deriv2 = prior.derivative(symbols[l])(theta)
+                base + deriv1 * deriv2 / pi / pi
+            } ?: base
+        }
+    }
+
+
+    /**
+     * Значения уравнений метода квазиоптимальных весов
+     *
+     * @param source
+     * @param set
+     * @param fitPars
+     * @param weight
+     * @return
+     */
+    private fun getEqValues(weight: QoWeight, theta: Map<Symbol, Double> = weight.theta): Point<Double> {
+        val distances = DoubleBuffer(data.size) { i -> distance(i, theta) }
+
+        return DoubleBuffer(symbols.size) { k ->
+            val base = (0 until data.size).sumOf { i -> distances[i] * weight.derivs[k, i] / weight.dispersion[i] }
+            //Поправка на априорную вероятность
+            prior?.let { prior ->
+                base - prior.derivative(symbols[k])(theta) / prior(theta)
+            } ?: base
+        }
+    }
+
+
+    /**
+     * The state of QOW fitter
+     * Created by Alexander Nozik on 17-Oct-16.
+     */
+    private inner class QoWeight(
+        val theta: Map<Symbol, Double>,
+    ) {
+
+        init {
+            require(data.size > 0) { "The state does not contain data" }
+        }
+
+        /**
+         * Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter
+         */
+        val derivs: Matrix<Double> by lazy {
+            space.buildMatrix(data.size, symbols.size) { i, k ->
+                distanceDerivative(symbols[k], i, theta)
+            }
+        }
+
+        /**
+         * Array of dispersions in each point
+         */
+        val dispersion: Point<Double> by lazy {
+            DoubleBuffer(data.size) { i -> getDispersion(i, theta) }
+        }
+
+    }
+
+    private fun newtonianStep(
+        weight: QoWeight,
+        par: Map<Symbol, Double>,
+        eqvalues: Point<Double>,
+    ): Map<Symbol, Double> = space.run {
+        val start = par.toPoint()
+        val invJacob = solver.inverse(getEqDerivValues(weight, par))
+
+        val step = invJacob.dot(eqvalues)
+        return par + (start - step).toMap()
+    }
+
+    private fun newtonianRun(
+        weight: QoWeight,
+        maxSteps: Int = 100,
+        tolerance: Double = 0.0,
+        fast: Boolean = false,
+    ): ParamSet {
+
+        var dis: Double//норма невязки
+        // Для удобства работаем всегда с полным набором параметров
+        var par = startingPoint
+
+        logger?.log { "Starting newtonian iteration from: \n\t$par" }
+
+        var eqvalues = getEqValues(weight, par)//значения функций
+
+        dis = DoubleL2Norm.norm(eqvalues)// невязка
+        logger?.log { "Starting discrepancy is $dis" }
+        var i = 0
+        var flag = false
+        while (!flag) {
+            i++
+            logger?.log { "Starting step number $i" }
+
+            val currentSolution = if (fast) {
+                //Берет значения матрицы в той точке, где считается вес
+                newtonianStep(weight, weight.theta, eqvalues)
+            } else {
+                //Берет значения матрицы в точке par
+                newtonianStep(weight, par, eqvalues)
+            }
+            // здесь должен стоять учет границ параметров
+            logger?.log { "Parameter values after step are: \n\t$currentSolution" }
+
+            eqvalues = getEqValues(weight, currentSolution)
+            val currentDis = DoubleL2Norm.norm(eqvalues)// невязка после шага
+
+            logger?.log { "The discrepancy after step is: $currentDis." }
+
+            if (currentDis >= dis && i > 1) {
+                //дополнительно проверяем, чтобы был сделан хотя бы один шаг
+                flag = true
+                logger?.log { "The discrepancy does not decrease. Stopping iteration." }
+            } else {
+                par = currentSolution
+                dis = currentDis
+            }
+            if (i >= maxSteps) {
+                flag = true
+                logger?.log { "Maximum number of iterations reached. Stopping iteration." }
+            }
+            if (dis <= tolerance) {
+                flag = true
+                logger?.log { "Tolerance threshold is reached. Stopping iteration." }
+            }
+        }
+
+        return par
+    }
+
+
+//
+//    override fun run(state: FitState, parentLog: History?, meta: Meta): FitResult {
+//        val log = Chronicle("QOW", parentLog)
+//        val action = meta.getString(FIT_STAGE_TYPE, TASK_RUN)
+//        log.report("QOW fit engine started task '{}'", action)
+//        return when (action) {
+//            TASK_SINGLE -> makeRun(state, log, meta)
+//            TASK_COVARIANCE -> generateErrors(state, log, meta)
+//            TASK_RUN -> {
+//                var res = makeRun(state, log, meta)
+//                res = makeRun(res.optState().get(), log, meta)
+//                generateErrors(res.optState().get(), log, meta)
+//            }
+//            else -> throw IllegalArgumentException("Unknown task")
+//        }
+//    }
+
+//    private fun makeRun(state: FitState, log: History, meta: Meta): FitResult {
+//        /*Инициализация объектов, задание исходных значений*/
+//        log.report("Starting fit using quasioptimal weights method.")
+//
+//        val fitPars = getFitPars(state, meta)
+//
+//        val curWeight = QoWeight(state, fitPars, state.parameters)
+//
+//        // вычисляем вес в allPar. Потом можно будет попробовать ручное задание веса
+//        log.report("The starting weight is: \n\t{}",
+//            MathUtils.toString(curWeight.theta))
+//
+//        //Стартовая точка такая же как и параметр веса
+//        /*Фитирование*/
+//        val res = newtonianRun(state, curWeight, log, meta)
+//
+//        /*Генерация результата*/
+//
+//        return FitResult.build(state.edit().setPars(res).build(), *fitPars)
+//    }
+
+    /**
+     * generateErrors.
+     */
+    private fun generateErrors(): Matrix<Double> {
+        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<Double> {
+        val curWeight = QoWeight(startingPoint)
+        logger?.log {
+            """
+            Starting fit using quasioptimal weights method. The starting weight is: 
+	            ${curWeight.theta}
+            """.trimIndent()
+        }
+        val res = newtonianRun(curWeight)
+    }
+
+
+    companion object : OptimizationProblemFactory<Double, QowFit> {
+        override fun build(symbols: List<Symbol>): QowFit {
+            TODO("Not yet implemented")
+        }
+
+
+        /**
+         * Constant `QOW_ENGINE_NAME="QOW"`
+         */
+        const val QOW_ENGINE_NAME = "QOW"
+
+        /**
+         * Constant `QOW_METHOD_FAST="fast"`
+         */
+        const val QOW_METHOD_FAST = "fast"
+
+
+    }
+}
+

From 7b6361e59d48d2ee12edbda6f99af91b1e38f0a5 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<OptimizationData>) : OptimizationFeature {
+    public constructor(vararg data: OptimizationData) : this(data.toList())
+}
 
 @OptIn(UnstableKMathAPI::class)
 public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
 
     override suspend fun process(
-        problem: FunctionOptimization<Double>
-    ): FunctionOptimization<Double> = withSymbols(problem.parameters){
-        val cmOptimizer: MultivariateOptimizer =
-            problem.getFeature<CMOptimizerFactory>()?.optimizerBuilder?.invoke() ?: SimplexOptimizer()
-
+        problem: FunctionOptimization<Double>,
+    ): FunctionOptimization<Double> = withSymbols(problem.parameters) {
         val convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(
             DEFAULT_RELATIVE_TOLERANCE,
             DEFAULT_ABSOLUTE_TOLERANCE,
             DEFAULT_MAX_ITER
         )
 
+        val cmOptimizer: MultivariateOptimizer = problem.getFeature<CMOptimizerFactory>()?.optimizerBuilder?.invoke()
+            ?: NonLinearConjugateGradientOptimizer(
+                NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES,
+                convergenceChecker
+            )
+
         val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap()
 
         fun addOptimizationData(data: OptimizationData) {
             optimizationData[data::class] = data
         }
+
         addOptimizationData(MaxEval.unlimited())
         addOptimizationData(InitialGuess(problem.initialGuess.toDoubleArray()))
 
         fun exportOptimizationData(): List<OptimizationData> = optimizationData.values.toList()
 
-
-        /**
-         * Register no-deriv function instead of  differentiable function
-         */
-        /**
-         * Register no-deriv function instead of  differentiable function
-         */
-        fun noDerivFunction(expression: Expression<Double>): 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<Double, Expression<Double>>) {
-            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<OptimizationLog>()
+
+        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<Symbol, Double>) {
-            simplex(NelderMeadSimplex(steps.toDoubleArray()))
-        }
-
-        public fun goal(goalType: GoalType) {
-            addOptimizationData(goalType)
-        }
-
-        public fun optimizer(block: () -> MultivariateOptimizer) {
-            optimizerBuilder = block
-        }
-
-        override fun update(result: OptimizationResult<Double>) {
-            initialGuess(result.point)
-        }
-
-        override suspend fun optimize(): OptimizationResult<Double> {
-            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<Double, CMOptimization> {
+    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<Symbol>): CMOptimization = CMOptimization(symbols)
     }
 }
-
-public fun CMOptimization.initialGuess(vararg pairs: Pair<Symbol, Double>): Unit = initialGuess(pairs.toMap())
-public fun CMOptimization.simplexSteps(vararg pairs: Pair<Symbol, Double>): Unit = simplexSteps(pairs.toMap())
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<Double>,
     y: Buffer<Double>,
     yErr: Buffer<Double>,
     model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure,
-): DifferentiableExpression<Double, Expression<Double>> = chiSquared(DerivativeStructureField, x, y, yErr, model)
+): DifferentiableExpression<Double, Expression<Double>> = 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<Double>,
     y: Iterable<Double>,
     yErr: Iterable<Double>,
     model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure,
-): DifferentiableExpression<Double, Expression<Double>> = chiSquared(
+): DifferentiableExpression<Double, Expression<Double>> = 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<F : Any> {
 /**
  * A container for a set of features
  */
-public class FeatureSet<F : Any> private constructor(public val features: Map<KClass<out F>, Any>) : Featured<F> {
+public class FeatureSet<F : Any> private constructor(public val features: Map<KClass<out F>, F>) : Featured<F> {
     @Suppress("UNCHECKED_CAST")
     override fun <T : F> getFeature(type: KClass<out T>): T? = features[type] as? T
 
@@ -31,6 +31,8 @@ public class FeatureSet<F : Any> private constructor(public val features: Map<KC
     public fun with(vararg otherFeatures: F): FeatureSet<F> =
         FeatureSet(features + otherFeatures.associateBy { it::class })
 
+    public operator fun iterator(): Iterator<F> = features.values.iterator()
+
     public companion object {
         public fun <F : Any> of(vararg features: F): FeatureSet<F> = 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<T>(point: Map<Symbol, T>, public val value: T) : OptimizationResult<T>(point)
 
-public class FunctionOptimization<T : Any>(
+public enum class FunctionOptimizationTarget : OptimizationFeature {
+    MAXIMIZE,
+    MINIMIZE
+}
+
+public class FunctionOptimization<T>(
     override val features: FeatureSet<OptimizationFeature>,
     public val expression: DifferentiableExpression<T, Expression<T>>,
     public val initialGuess: Map<Symbol, T>,
     public val parameters: Collection<Symbol>,
-    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 <T : Any, I : Any, A> chiSquaredExpression(
+            autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
+            x: Buffer<T>,
+            y: Buffer<T>,
+            yErr: Buffer<T>,
+            model: A.(I) -> I,
+        ): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
+            require(x.size == y.size) { "X and y buffers should be of the same size" }
+            require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
+
+            return autoDiff.process {
+                var sum = zero
+
+                x.indices.forEach {
+                    val xValue = const(x[it])
+                    val yValue = const(y[it])
+                    val yErrValue = const(yErr[it])
+                    val modelValue = model(xValue)
+                    sum += ((yValue - modelValue) / yErrValue).pow(2)
+                }
+
+                sum
+            }
+        }
+    }
+}
+
+
+public fun <T> FunctionOptimization<T>.withFeatures(
+    vararg newFeature: OptimizationFeature,
+): FunctionOptimization<T> = FunctionOptimization(
+    features.with(*newFeature),
+    expression,
+    initialGuess,
+    parameters
+)
 
-//
-///**
-// * A likelihood function optimization problem with provided derivatives
-// */
-//public interface FunctionOptimizationBuilder<T : Any> {
-//    /**
-//     * 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<Symbol, T>)
-//
-//    /**
-//     * Set a differentiable expression as objective function as function and gradient provider
-//     */
-//    public fun function(expression: DifferentiableExpression<T, Expression<T>>)
-//
-//    public companion object {
-//        /**
-//         * Generate a chi squared expression from given x-y-sigma data and inline model. Provides automatic differentiation
-//         */
-//        public fun <T : Any, I : Any, A> chiSquared(
-//            autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-//            x: Buffer<T>,
-//            y: Buffer<T>,
-//            yErr: Buffer<T>,
-//            model: A.(I) -> I,
-//        ): DifferentiableExpression<T, Expression<T>> where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
-//            require(x.size == y.size) { "X and y buffers should be of the same size" }
-//            require(y.size == yErr.size) { "Y and yErr buffer should of the same size" }
-//
-//            return autoDiff.process {
-//                var sum = zero
-//
-//                x.indices.forEach {
-//                    val xValue = const(x[it])
-//                    val yValue = const(y[it])
-//                    val yErrValue = const(yErr[it])
-//                    val modelValue = model(xValue)
-//                    sum += ((yValue - modelValue) / yErrValue).pow(2)
-//                }
-//
-//                sum
-//            }
-//        }
-//    }
-//}
-//
-///**
-// * Define a chi-squared-based objective function
-// */
-//public fun <T : Any, I : Any, A> FunctionOptimization<T>.chiSquared(
-//    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-//    x: Buffer<T>,
-//    y: Buffer<T>,
-//    yErr: Buffer<T>,
-//    model: A.(I) -> I,
-//) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
-//    val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model)
-//    function(chiSquared)
-//    maximize = false
-//}
-//
-///**
-// * Optimize differentiable expression using specific [OptimizationProblemFactory]
-// */
-//public suspend fun <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
-//    factory: OptimizationProblemFactory<T, F>,
-//    vararg symbols: Symbol,
-//    configuration: F.() -> Unit,
-//): OptimizationResult<T> {
-//    require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
-//    val problem = factory(symbols.toList(), configuration)
-//    problem.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<OptimizationFeature> {
 
 public inline fun <reified T : OptimizationFeature> OptimizationProblem.getFeature(): T? = getFeature(T::class)
 
+public open class OptimizationResult<T>(public val point: Map<Symbol, T>) : OptimizationFeature
+
+public class OptimizationLog(private val loggable: Loggable) : Loggable by loggable, OptimizationFeature
+
 //public class OptimizationResult<T>(
 //    public val point: Map<Symbol, T>,
 //    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<T> : OptimizationProblem<T> {
+public interface XYFit<T> : OptimizationProblem {
 
     public val algebra: Field<T>
 
@@ -42,4 +44,34 @@ public interface XYFit<T> : OptimizationProblem<T> {
     ): Unit where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> = model { arg ->
         autoDiff.process { modelFunction(const(arg)) }
     }
+}
+
+//
+///**
+// * Define a chi-squared-based objective function
+// */
+//public fun <T : Any, I : Any, A> FunctionOptimization<T>.chiSquared(
+//    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
+//    x: Buffer<T>,
+//    y: Buffer<T>,
+//    yErr: Buffer<T>,
+//    model: A.(I) -> I,
+//) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
+//    val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model)
+//    function(chiSquared)
+//    maximize = false
+//}
+
+/**
+ * Optimize differentiable expression using specific [OptimizationProblemFactory]
+ */
+public suspend fun <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
+    factory: OptimizationProblemFactory<T, F>,
+    vararg symbols: Symbol,
+    configuration: F.() -> Unit,
+): OptimizationResult<T> {
+    require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
+    val problem = factory(symbols.toList(), configuration)
+    problem.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<Symbol, Double>
 
-public fun interface FitLogger {
-    public fun log(block: () -> String)
-}
-
 @OptIn(UnstableKMathAPI::class)
 public class QowFit(
     override val symbols: List<Symbol>,

From 88e94a7fd9cc64dbb4d892b21cedef237bfd0045 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<OptimizationData>) : 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<FunctionOptimization<Double>> {
 
     override suspend fun process(
         problem: FunctionOptimization<Double>,
-    ): FunctionOptimization<Double> = withSymbols(problem.parameters) {
-        val convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(
-            DEFAULT_RELATIVE_TOLERANCE,
-            DEFAULT_ABSOLUTE_TOLERANCE,
-            DEFAULT_MAX_ITER
-        )
+    ): FunctionOptimization<Double> {
+        val startPoint = problem.getFeature<OptimizationStartPoint<Double>>()?.point
+            ?: error("Starting point not defined in $problem")
 
-        val cmOptimizer: MultivariateOptimizer = problem.getFeature<CMOptimizerFactory>()?.optimizerBuilder?.invoke()
-            ?: NonLinearConjugateGradientOptimizer(
-                NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES,
-                convergenceChecker
+        val parameters = problem.getFeature<OptimizationParameters>()?.symbols
+            ?: problem.getFeature<OptimizationStartPoint<Double>>()?.point?.keys
+            ?:startPoint.keys
+
+
+        withSymbols(parameters) {
+            val convergenceChecker: ConvergenceChecker<PointValuePair> = SimpleValueChecker(
+                DEFAULT_RELATIVE_TOLERANCE,
+                DEFAULT_ABSOLUTE_TOLERANCE,
+                DEFAULT_MAX_ITER
             )
 
-        val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap()
+            val cmOptimizer: MultivariateOptimizer = problem.getFeature<CMOptimizer>()?.optimizerBuilder?.invoke()
+                ?: NonLinearConjugateGradientOptimizer(
+                    NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES,
+                    convergenceChecker
+                )
 
-        fun addOptimizationData(data: OptimizationData) {
-            optimizationData[data::class] = data
-        }
+            val optimizationData: HashMap<KClass<out OptimizationData>, OptimizationData> = HashMap()
 
-        addOptimizationData(MaxEval.unlimited())
-        addOptimizationData(InitialGuess(problem.initialGuess.toDoubleArray()))
-
-        fun exportOptimizationData(): List<OptimizationData> = 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<OptimizationLog>()
+            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> = 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<OptimizationLog>()
+
+            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<T : Any>(
         }
     }
 
-    override fun integrate(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> = with(algebra) {
+    override fun process(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> = with(algebra) {
         val f = integrand.function
         val (points, weights) = buildRule(integrand)
         var res = zero
@@ -95,7 +95,7 @@ public fun <T : Any> GaussIntegrator<T>.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<IntegrandFeature>
-    public fun <T : IntegrandFeature> getFeature(type: KClass<T>): T?
+public interface Integrand : Featured<IntegrandFeature> {
+    public val features: FeatureSet<IntegrandFeature>
+    override fun <T : IntegrandFeature> getFeature(type: KClass<out T>): T? = features.getFeature(type)
 }
 
-public inline fun <reified T : IntegrandFeature> Integrand.getFeature(): T? = getFeature(T::class)
+public inline fun <reified T: IntegrandFeature> Integrand.getFeature(): T? = getFeature(T::class)
 
 public class IntegrandValue<T : Any>(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<T : Any> internal constructor(
-    private val featureMap: Map<KClass<*>, IntegrandFeature>,
+    override val features: FeatureSet<IntegrandFeature>,
     public val function: (Point<T>) -> T,
 ) : Integrand {
 
-    override val features: Set<IntegrandFeature> get() = featureMap.values.toSet()
-
-    @Suppress("UNCHECKED_CAST")
-    override fun <T : IntegrandFeature> getFeature(type: KClass<T>): T? = featureMap[type] as? T
-
-    public operator fun <F : IntegrandFeature> plus(pair: Pair<KClass<out F>, F>): MultivariateIntegrand<T> =
-        MultivariateIntegrand(featureMap + pair, function)
-
     public operator fun <F : IntegrandFeature> plus(feature: F): MultivariateIntegrand<T> =
-        plus(feature::class to feature)
+        MultivariateIntegrand(features.with(feature), function)
 }
 
 @Suppress("FunctionName")
 public fun <T : Any> MultivariateIntegrand(
     vararg features: IntegrandFeature,
     function: (Point<T>) -> T,
-): MultivariateIntegrand<T> = MultivariateIntegrand(features.associateBy { it::class }, function)
+): MultivariateIntegrand<T> = MultivariateIntegrand(FeatureSet.of(*features), function)
 
 public val <T : Any> MultivariateIntegrand<T>.value: T? get() = getFeature<IntegrandValue<T>>()?.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<T : Any>(
         return res
     }
 
-    override fun integrate(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> {
+    override fun process(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> {
         val ranges = integrand.getFeature<UnivariateIntegrandRanges>()
         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<Double> {
         return res
     }
 
-    override fun integrate(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
+    override fun process(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
         val ranges = integrand.getFeature<UnivariateIntegrandRanges>()
         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<T : Comparable<T>>(
     public val algebra: Field<T>,
     public val bufferFactory: MutableBufferFactory<T>,
 ) : UnivariateIntegrator<T> {
-    override fun integrate(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> = algebra {
+    override fun process(integrand: UnivariateIntegrand<T>): UnivariateIntegrand<T> = algebra {
         val range = integrand.getFeature<IntegrationRange>()?.range ?: 0.0..1.0
 
         val interpolator: PolynomialInterpolator<T> = SplineInterpolator(algebra, bufferFactory)
@@ -80,7 +80,7 @@ public class SplineIntegrator<T : Comparable<T>>(
  */
 @UnstableKMathAPI
 public object DoubleSplineIntegrator : UnivariateIntegrator<Double> {
-    override fun integrate(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
+    override fun process(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
         val range = integrand.getFeature<IntegrationRange>()?.range ?: 0.0..1.0
 
         val interpolator: PolynomialInterpolator<Double> = 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<T> internal constructor(
-    private val featureMap: Map<KClass<*>, IntegrandFeature>,
+    override val features: FeatureSet<IntegrandFeature>,
     public val function: (Double) -> T,
 ) : Integrand {
-
-    override val features: Set<IntegrandFeature> get() = featureMap.values.toSet()
-
-    @Suppress("UNCHECKED_CAST")
-    override fun <T : IntegrandFeature> getFeature(type: KClass<T>): T? = featureMap[type] as? T
-
-    public operator fun <F : IntegrandFeature> plus(pair: Pair<KClass<out F>, F>): UnivariateIntegrand<T> =
-        UnivariateIntegrand(featureMap + pair, function)
-
     public operator fun <F : IntegrandFeature> plus(feature: F): UnivariateIntegrand<T> =
-        plus(feature::class to feature)
+        UnivariateIntegrand(features.with(feature), function)
 }
 
 @Suppress("FunctionName")
 public fun <T : Any> UnivariateIntegrand(
     function: (Double) -> T,
     vararg features: IntegrandFeature,
-): UnivariateIntegrand<T> = UnivariateIntegrand(features.associateBy { it::class }, function)
+): UnivariateIntegrand<T> = UnivariateIntegrand(FeatureSet.of(*features), function)
 
 public typealias UnivariateIntegrator<T> = Integrator<UnivariateIntegrand<T>>
 
@@ -79,7 +70,7 @@ public val <T : Any> UnivariateIntegrand<T>.value: T get() = valueOrNull ?: erro
 public fun <T : Any> UnivariateIntegrator<T>.integrate(
     vararg features: IntegrandFeature,
     function: (Double) -> T,
-): UnivariateIntegrand<T> = integrate(UnivariateIntegrand(function, *features))
+): UnivariateIntegrand<T> = process(UnivariateIntegrand(function, *features))
 
 /**
  * A shortcut method to integrate a [function] in [range] with additional [features].
@@ -90,7 +81,7 @@ public fun <T : Any> UnivariateIntegrator<T>.integrate(
     range: ClosedRange<Double>,
     vararg features: IntegrandFeature,
     function: (Double) -> T,
-): UnivariateIntegrand<T> = integrate(UnivariateIntegrand(function, IntegrationRange(range), *features))
+): UnivariateIntegrand<T> = process(UnivariateIntegrand(function, IntegrationRange(range), *features))
 
 /**
  * A shortcut method to integrate a [function] in [range] with additional [features].
@@ -107,5 +98,5 @@ public fun <T : Any> UnivariateIntegrator<T>.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<T>(point: Map<Symbol, T>, public val value: T) : OptimizationResult<T>(point)
+public class OptimizationValue<T>(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<T>(
     override val features: FeatureSet<OptimizationFeature>,
     public val expression: DifferentiableExpression<T, Expression<T>>,
-    public val initialGuess: Map<Symbol, T>,
-    public val parameters: Collection<Symbol>,
 ) : 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 <T> FunctionOptimization<T>.withFeatures(
 ): FunctionOptimization<T> = 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<OptimizationFeature> {
     public val features: FeatureSet<OptimizationFeature>
@@ -20,31 +22,22 @@ public interface OptimizationProblem : Featured<OptimizationFeature> {
 
 public inline fun <reified T : OptimizationFeature> OptimizationProblem.getFeature(): T? = getFeature(T::class)
 
-public open class OptimizationResult<T>(public val point: Map<Symbol, T>) : OptimizationFeature
+public open class OptimizationStartPoint<T>(public val point: Map<Symbol, T>) : OptimizationFeature {
+    override fun toString(): String = "StartPoint($point)"
+}
 
-public class OptimizationLog(private val loggable: Loggable) : Loggable by loggable, OptimizationFeature
+public open class OptimizationResult<T>(public val point: Map<Symbol, T>) : 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<Symbol>): OptimizationFeature{
+    override fun toString(): String = "Parameters($symbols)"
+}
 
-//public class OptimizationResult<T>(
-//    public val point: Map<Symbol, T>,
-//    public val value: T,
-//    public val features: Set<OptimizationFeature> = emptySet(),
-//) {
-//    override fun toString(): String {
-//        return "OptimizationResult(point=$point, value=$value)"
-//    }
-//}
-//
-//public operator fun <T> OptimizationResult<T>.plus(
-//    feature: OptimizationFeature,
-//): OptimizationResult<T> = OptimizationResult(point, value, features + feature)
-//public fun interface OptimizationProblemFactory<T : Any, out P : OptimizationProblem<T>> {
-//    public fun build(symbols: List<Symbol>): P
-//}
-//
-//public operator fun <T : Any, P : OptimizationProblem<T>> OptimizationProblemFactory<T, P>.invoke(
-//    symbols: List<Symbol>,
-//    block: P.() -> Unit,
-//): P = build(symbols).apply(block)
 
 public interface Optimizer<P : OptimizationProblem> {
     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<T> : OptimizationProblem {
@@ -59,15 +57,16 @@ public interface XYFit<T> : OptimizationProblem {
 //}
 
 /**
- * Optimize differentiable expression using specific [OptimizationProblemFactory]
+ * Optimize differentiable expression using specific [Optimizer]
  */
 public suspend fun <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
-    factory: OptimizationProblemFactory<T, F>,
-    vararg symbols: Symbol,
-    configuration: F.() -> Unit,
-): OptimizationResult<T> {
-    require(symbols.isNotEmpty()) { "Must provide a list of symbols for optimization" }
-    val problem = factory(symbols.toList(), configuration)
-    problem.function(this)
-    return problem.optimize()
+    optimizer: Optimizer<F>,
+    startingPoint: Map<Symbol,T>,
+    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<T>()
 }
\ 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<Double?, Double?>(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 <altavir@gmail.com>
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 <T, X : T, Y : T> XYColumnarData(x: Buffer<X>, y: Buffer<Y>): XYColum
     }
 }
 
+/**
+ * Represent a [ColumnarData] as an [XYColumnarData]. The presence or respective columns is checked on creation.
+ */
+@UnstableKMathAPI
+public fun <T> ColumnarData<T>.asXYData(
+    xSymbol: Symbol,
+    ySymbol: Symbol,
+): XYColumnarData<T, T, T> = object : XYColumnarData<T, T, T> {
+    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<T> get() = this@asXYData[xSymbol]!!
+    override val y: Buffer<T> get() = this@asXYData[ySymbol]!!
+    override fun get(symbol: Symbol): Buffer<T>? = 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 <T> FunctionOptimization<T>.withFeatures(
     expression,
 )
 
+/**
+ * Optimize differentiable expression using specific [optimizer] form given [startingPoint]
+ */
+public suspend fun <T : Any> DifferentiableExpression<T, Expression<T>>.optimizeWith(
+    optimizer: Optimizer<FunctionOptimization<T>>,
+    startingPoint: Map<Symbol, T>,
+    vararg features: OptimizationFeature,
+): FunctionOptimization<T> {
+    val problem = FunctionOptimization<T>(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<Symbol>): 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<T> : OptimizationProblem {
-
-    public val algebra: Field<T>
-
-    /**
-     * Set X-Y data for this fit optionally including x and y errors
-     */
-    public fun data(
-        dataSet: ColumnarData<T>,
-        xSymbol: Symbol,
-        ySymbol: Symbol,
-        xErrSymbol: Symbol? = null,
-        yErrSymbol: Symbol? = null,
-    )
-
-    public fun model(model: (T) -> DifferentiableExpression<T, *>)
-
-    /**
-     * Set the differentiable model for this fit
-     */
-    public fun <I : Any, A> model(
-        autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-        modelFunction: A.(I) -> I,
-    ): Unit where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> = model { arg ->
-        autoDiff.process { modelFunction(const(arg)) }
-    }
-}
-
-//
-///**
-// * Define a chi-squared-based objective function
-// */
-//public fun <T : Any, I : Any, A> FunctionOptimization<T>.chiSquared(
-//    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-//    x: Buffer<T>,
-//    y: Buffer<T>,
-//    yErr: Buffer<T>,
-//    model: A.(I) -> I,
-//) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
-//    val chiSquared = FunctionOptimization.chiSquared(autoDiff, x, y, yErr, model)
-//    function(chiSquared)
-//    maximize = false
-//}
-
-/**
- * Optimize differentiable expression using specific [Optimizer]
- */
-public suspend fun <T : Any, F : FunctionOptimization<T>> DifferentiableExpression<T, Expression<T>>.optimizeWith(
-    optimizer: Optimizer<F>,
-    startingPoint: Map<Symbol,T>,
-    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<T>()
-}
\ 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<T> {
+    public fun distance(problem: XYOptimization<T>, index: Int): DifferentiableExpression<T, Expression<T>>
+
+    public companion object {
+        public fun <T> byY(
+            algebra: Field<T>,
+        ): PointToCurveDistance<T> = 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<Double>{args, data, index ->
+//
+//        }
+    }
+}
+
+
+public class XYOptimization<T>(
+    override val features: FeatureSet<OptimizationFeature>,
+    public val data: XYColumnarData<T, T, T>,
+    public val model: DifferentiableExpression<T, Expression<T>>,
+) : OptimizationProblem
+
+//
+//@UnstableKMathAPI
+//public interface XYFit<T> : OptimizationProblem {
+//
+//    public val algebra: Field<T>
+//
+//    /**
+//     * Set X-Y data for this fit optionally including x and y errors
+//     */
+//    public fun data(
+//        dataSet: ColumnarData<T>,
+//        xSymbol: Symbol,
+//        ySymbol: Symbol,
+//        xErrSymbol: Symbol? = null,
+//        yErrSymbol: Symbol? = null,
+//    )
+//
+//    public fun model(model: (T) -> DifferentiableExpression<T, *>)
+//
+//    /**
+//     * Set the differentiable model for this fit
+//     */
+//    public fun <I : Any, A> model(
+//        autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
+//        modelFunction: A.(I) -> I,
+//    ): Unit where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> = model { arg ->
+//        autoDiff.process { modelFunction(const(arg)) }
+//    }
+//}
+
+//
+///**
+// * Define a chi-squared-based objective function
+// */
+//public fun <T : Any, I : Any, A> FunctionOptimization<T>.chiSquared(
+//    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
+//    x: Buffer<T>,
+//    y: Buffer<T>,
+//    yErr: Buffer<T>,
+//    model: A.(I) -> I,
+//) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
+//    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<Symbol>,
     private val space: LinearSpace<Double, DoubleField>,
     private val solver: LinearSolver<Double>,
-) : XYFit<Double>, SymbolIndexer {
+) : XYOptimization<Double>, SymbolIndexer {
 
     private var logger: FitLogger? = null
 

From c8621ee5b759cc364f94e1df3fbdf3262e6a7878 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<OptimizationData>) : Optimiza
 @OptIn(UnstableKMathAPI::class)
 public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
 
-    override suspend fun process(
+    override suspend fun optimize(
         problem: FunctionOptimization<Double>,
     ): FunctionOptimization<Double> {
         val startPoint = problem.getFeature<OptimizationStartPoint<Double>>()?.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 <T> FunctionOptimization<T>.withFeatures(
 /**
  * Optimize differentiable expression using specific [optimizer] form given [startingPoint]
  */
-public suspend fun <T : Any> DifferentiableExpression<T, Expression<T>>.optimizeWith(
+public suspend fun <T : Any> DifferentiableExpression<T>.optimizeWith(
     optimizer: Optimizer<FunctionOptimization<T>>,
     startingPoint: Map<Symbol, T>,
     vararg features: OptimizationFeature,
 ): FunctionOptimization<T> {
     val problem = FunctionOptimization<T>(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<Symbol>): Optimizat
 }
 
 
-public interface Optimizer<P : OptimizationProblem> {
-    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<P : OptimizationProblem> {
+    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<T> {
-    public fun distance(problem: XYOptimization<T>, index: Int): DifferentiableExpression<T, Expression<T>>
+public interface PointToCurveDistance : OptimizationFeature {
+    public fun distance(problem: XYOptimization, index: Int): DifferentiableExpression<Double>
 
     public companion object {
-        public fun <T> byY(
-            algebra: Field<T>,
-        ): PointToCurveDistance<T> = PointToCurveDistance { problem, index ->
-            algebra {
+        public val byY: PointToCurveDistance = object : PointToCurveDistance {
+            override fun distance(problem: XYOptimization, index: Int): DifferentiableExpression<Double> {
+
                 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<Double> {
+                    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double>? =
+                        problem.model.derivativeOrNull(symbols)
+
+                    override fun invoke(arguments: Map<Symbol, Double>): Double =
+                        problem.model(arguments + (Symbol.x to x)) - y
+                }
             }
+
+            override fun toString(): String = "PointToCurveDistanceByY"
+
         }
 
-
-//        val default = PointToCurveDistance<Double>{args, data, index ->
-//
-//        }
     }
 }
 
-
-public class XYOptimization<T>(
+public class XYOptimization(
     override val features: FeatureSet<OptimizationFeature>,
-    public val data: XYColumnarData<T, T, T>,
-    public val model: DifferentiableExpression<T, Expression<T>>,
+    public val data: XYColumnarData<Double, Double, Double>,
+    public val model: DifferentiableExpression<Double>,
 ) : OptimizationProblem
 
+
+public suspend fun Optimizer<FunctionOptimization<Double>>.maximumLogLikelihood(problem: XYOptimization): XYOptimization {
+    val distanceBuilder = problem.getFeature() ?: PointToCurveDistance.byY
+    val likelihood: DifferentiableExpression<Double> = object : DifferentiableExpression<Double> {
+        override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double>? {
+            TODO("Not yet implemented")
+        }
+
+        override fun invoke(arguments: Map<Symbol, Double>): 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<T> : OptimizationProblem {

From 7f32348e7a1c1701288c35db3c0dd020baa6726b Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<Double>.symEig(): Pair<DoubleTensor, DoubleTensor> =
-        symEig(epsilon = 1e-15)
+    override fun Tensor<Double>.symEig(): Pair<DoubleTensor, DoubleTensor> = 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<Double>.symEig(epsilon: Double): Pair<DoubleTensor, DoubleTensor> {
         checkSymmetric(tensor, epsilon)
+
+        fun MutableStructure2D<Double>.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 <T> BufferedTensor<T>.vectors: VirtualBuffer<BufferedTensor<T>>
+    get() {
+        val n = shape.size
+        val vectorOffset = shape[n - 1]
+        val vectorShape = intArrayOf(shape.last())
 
-internal fun <T> BufferedTensor<T>.vectorSequence(): Sequence<BufferedTensor<T>> = 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 <T> BufferedTensor<T>.matrixSequence(): Sequence<BufferedTensor<T>> = 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 <T> BufferedTensor<T>.vectorSequence(): Sequence<BufferedTensor<T>> = vectors.asSequence()
+
+/**
+ * A random access alternative to [matrixSequence]
+ */
+internal val <T> BufferedTensor<T>.matrices: VirtualBuffer<BufferedTensor<T>>
+    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 <T> BufferedTensor<T>.matrixSequence(): Sequence<BufferedTensor<T>> = matrices.asSequence()
 
 internal fun dotHelper(
     a: MutableStructure2D<Double>,
     b: MutableStructure2D<Double>,
     res: MutableStructure2D<Double>,
-    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<Double>,
     pivots: MutableStructure1D<Int>,
-    epsilon: Double
+    epsilon: Double,
 ): Boolean {
 
     val m = lu.rowNum
@@ -122,7 +135,7 @@ internal fun <T> BufferedTensor<T>.setUpPivots(): IntTensor {
 
 internal fun DoubleTensorAlgebra.computeLU(
     tensor: DoubleTensor,
-    epsilon: Double
+    epsilon: Double,
 ): Pair<DoubleTensor, IntTensor>? {
 
     checkSquareMatrix(tensor.shape)
@@ -139,7 +152,7 @@ internal fun DoubleTensorAlgebra.computeLU(
 internal fun pivInit(
     p: MutableStructure2D<Double>,
     pivot: MutableStructure1D<Int>,
-    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<Double>,
     u: MutableStructure2D<Double>,
     lu: MutableStructure2D<Double>,
-    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<Double>,
     l: MutableStructure2D<Double>,
-    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<Double>, pivots: MutableStructur
 internal fun luMatrixInv(
     lu: MutableStructure2D<Double>,
     pivots: MutableStructure1D<Int>,
-    invMatrix: MutableStructure2D<Double>
+    invMatrix: MutableStructure2D<Double>,
 ) {
     val m = lu.shape[0]
 
@@ -227,7 +240,7 @@ internal fun luMatrixInv(
 internal fun DoubleTensorAlgebra.qrHelper(
     matrix: DoubleTensor,
     q: DoubleTensor,
-    r: MutableStructure2D<Double>
+    r: MutableStructure2D<Double>,
 ) {
     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<BufferedTensor<Double>, Pair<BufferedTensor<Double>, BufferedTensor<Double>>>,
-    m: Int, n: Int, epsilon: Double
+    USV: Triple<BufferedTensor<Double>, BufferedTensor<Double>, BufferedTensor<Double>>,
+    m: Int, n: Int, epsilon: Double,
 ) {
     val res = ArrayList<Triple<Double, DoubleTensor, DoubleTensor>>(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<Double>, 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 <altavir@gmail.com>
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 <F : StructureFeature> getFeature(structure: Matrix<${type}>, type: KClass<out F>): F? {
+    public override fun <F : StructureFeature> computeFeature(structure: Matrix<${type}>, type: KClass<out F>): 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<MST>() {
     private val ws: Token by regexToken("\\s+".toRegex(), ignore = true)
 
     private val number: Parser<MST> by num use { MST.Numeric(text.toDouble()) }
-    private val singular: Parser<MST> by id use { StringSymbol(text) }
+    private val singular: Parser<MST> by id use { Symbol(text) }
 
     private val unaryFunction: Parser<MST> 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 <V> Map<Symbol, V>.getOrFail(key: String): V = getValue(StringSymbol(key))
+internal fun <V> Map<Symbol, V>.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<Double, DerivativeStructure, DerivativeStructureField, Expression<Double>> {
-        public override fun process(function: DerivativeStructureField.() -> DerivativeStructure): DifferentiableExpression<Double> =
-            DerivativeStructureExpression(function)
-    }
+public object DSProcessor : AutoDiffProcessor<Double, DerivativeStructure, DerivativeStructureField> {
+    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<Double> {
 
-    override fun integrate(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
+    override fun process(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
         val range = integrand.getFeature<IntegrationRange>()?.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<Double> {
 
-    override fun integrate(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
+    override fun process(integrand: UnivariateIntegrand<Double>): UnivariateIntegrand<Double> {
         val integrator = integratorBuilder(integrand)
         val maxCalls = integrand.getFeature<IntegrandMaxCalls>()?.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<Double, DoubleField> {
         v * this
 
     @UnstableKMathAPI
-    override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? {
+    override fun <F : StructureFeature> computeFeature(structure: Matrix<Double>, type: KClass<out F>): 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<Double, DoubleField> {
                 LupDecompositionFeature<Double> {
                 private val lup by lazy { LUDecomposition(origin) }
                 override val determinant: Double by lazy { lup.determinant }
-                override val l: Matrix<Double> by lazy { CMMatrix(lup.l) + LFeature }
-                override val u: Matrix<Double> by lazy { CMMatrix(lup.u) + UFeature }
+                override val l: Matrix<Double> by lazy<Matrix<Double>> { CMMatrix(lup.l).withFeature(LFeature) }
+                override val u: Matrix<Double> by lazy<Matrix<Double>> { CMMatrix(lup.u).withFeature(UFeature) }
                 override val p: Matrix<Double> by lazy { CMMatrix(lup.p) }
             }
 
             CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Double> {
-                override val l: Matrix<Double> by lazy {
+                override val l: Matrix<Double> by lazy<Matrix<Double>> {
                     val cholesky = CholeskyDecomposition(origin)
-                    CMMatrix(cholesky.l) + LFeature
+                    CMMatrix(cholesky.l).withFeature(LFeature)
                 }
             }
 
             QRDecompositionFeature::class -> object : QRDecompositionFeature<Double> {
                 private val qr by lazy { QRDecomposition(origin) }
-                override val q: Matrix<Double> by lazy { CMMatrix(qr.q) + OrthogonalFeature }
-                override val r: Matrix<Double> by lazy { CMMatrix(qr.r) + UFeature }
+                override val q: Matrix<Double> by lazy<Matrix<Double>> { CMMatrix(qr.q).withFeature(OrthogonalFeature) }
+                override val r: Matrix<Double> by lazy<Matrix<Double>> { CMMatrix(qr.r).withFeature(UFeature) }
             }
 
             SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<Double> {
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<Double>,
     decomposition: CMDecomposition = CMDecomposition.LUP,
 ): CMMatrix = solver(a, decomposition).inverse.wrap()
+
+
+public fun CMLinearSpace.solver(decomposition: CMDecomposition): LinearSolver<Double> = object : LinearSolver<Double> {
+    override fun solve(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> = solve(a, b, decomposition)
+
+    override fun solve(a: Matrix<Double>, b: Point<Double>): Point<Double> = solve(a, b, decomposition)
+
+    override fun inverse(matrix: Matrix<Double>): Matrix<Double> = 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<OptimizationData>) : OptimizationFeature {
-    public constructor(vararg data: OptimizationData) : this(data.toList())
+/**
+ * Specify a Commons-maths optimization engine
+ */
+public fun FunctionOptimizationBuilder<Double>.cmEngine(optimizerBuilder: () -> MultivariateOptimizer) {
+    addFeature(CMOptimizerEngine(optimizerBuilder))
+}
+
+public class CMOptimizerData(public val data: List<SymbolIndexer.() -> 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<Double>.cmOptimizationData(data: SymbolIndexer.() -> OptimizationData) {
+    updateFeature<CMOptimizerData> {
+        val newData = (it?.data ?: emptyList()) + data
+        CMOptimizerData(newData)
+    }
+}
+
+public fun FunctionOptimizationBuilder<Double>.simplexSteps(vararg steps: Pair<Symbol, Double>) {
+    //TODO use convergence checker from features
+    cmEngine { SimplexOptimizer(CMOptimizer.defaultConvergenceChecker) }
+    cmOptimizationData { NelderMeadSimplex(mapOf(*steps).toDoubleArray()) }
 }
 
 @OptIn(UnstableKMathAPI::class)
-public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
+public object CMOptimizer : Optimizer<Double, FunctionOptimization<Double>> {
+
+    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<Double>,
     ): FunctionOptimization<Double> {
-        val startPoint = problem.getFeature<OptimizationStartPoint<Double>>()?.point
-            ?: error("Starting point not defined in $problem")
+        val startPoint = problem.startPoint
 
         val parameters = problem.getFeature<OptimizationParameters>()?.symbols
             ?: problem.getFeature<OptimizationStartPoint<Double>>()?.point?.keys
-            ?:startPoint.keys
+            ?: startPoint.keys
 
 
         withSymbols(parameters) {
@@ -53,7 +89,7 @@ public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
                 DEFAULT_MAX_ITER
             )
 
-            val cmOptimizer: MultivariateOptimizer = problem.getFeature<CMOptimizer>()?.optimizerBuilder?.invoke()
+            val cmOptimizer: MultivariateOptimizer = problem.getFeature<CMOptimizerEngine>()?.optimizerBuilder?.invoke()
                 ?: NonLinearConjugateGradientOptimizer(
                     NonLinearConjugateGradientOptimizer.Formula.FLETCHER_REEVES,
                     convergenceChecker
@@ -68,7 +104,7 @@ public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
             addOptimizationData(MaxEval.unlimited())
             addOptimizationData(InitialGuess(startPoint.toDoubleArray()))
 
-            fun exportOptimizationData(): List<OptimizationData> = optimizationData.values.toList()
+            //fun exportOptimizationData(): List<OptimizationData> = optimizationData.values.toList()
 
             val objectiveFunction = ObjectiveFunction {
                 val args = startPoint + it.toMap()
@@ -88,7 +124,9 @@ public class CMOptimization : Optimizer<FunctionOptimization<Double>> {
 
             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<FunctionOptimization<Double>> {
             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<Double>,
-    y: Buffer<Double>,
-    yErr: Buffer<Double>,
-    model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure,
-): DifferentiableExpression<Double> = 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<Double>,
-    y: Iterable<Double>,
-    yErr: Iterable<Double>,
-    model: DerivativeStructureField.(x: DerivativeStructure) -> DerivativeStructure,
-): DifferentiableExpression<Double> = chiSquaredExpression(
-    DerivativeStructureField,
-    x.toList().asBuffer(),
-    y.toList().asBuffer(),
-    yErr.toList().asBuffer(),
-    model
-)
-
-/**
- * Optimize expression without derivatives
- */
-public suspend fun Expression<Double>.optimize(
-    vararg symbols: Symbol,
-    configuration: CMOptimization.() -> Unit,
-): OptimizationResult<Double> {
-    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<Double>.optimize(
-    vararg symbols: Symbol,
-    configuration: CMOptimization.() -> Unit,
-): OptimizationResult<Double> = optimizeWith(CMOptimization, symbols = symbols, configuration)
-
-public suspend fun DifferentiableExpression<Double>.minimize(
-    vararg startPoint: Pair<Symbol, Double>,
-    configuration: CMOptimization.() -> Unit = {},
-): OptimizationResult<Double> {
-    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<out T> {
     public operator fun get(symbol: Symbol): Buffer<T>?
 }
 
+@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<T> : DifferentiableExpression<T>
 
 /**
  * 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<T, I, A : ExpressionAlgebra<T, I>, out R : Expression<T>> {
-    public fun process(function: A.() -> I): DifferentiableExpression<T>
+public fun interface AutoDiffProcessor<T, I, A : Algebra<I>> {
+    public fun differentiate(function: A.() -> I): DifferentiableExpression<T>
 }
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 <sup>-1</sup> &middot; B*, where *A* and *B* are matrices or
  * vectors.
@@ -30,20 +28,3 @@ public interface LinearSolver<T : Any> {
     public fun inverse(matrix: Matrix<T>): Matrix<T>
 }
 
-/**
- * Convert matrix to vector if it is possible.
- */
-public fun <T : Any> Matrix<T>.asVector(): Point<T> =
-    if (this.colNum == 1)
-        as1D()
-    else
-        error("Can't convert matrix with more than one column to vector")
-
-/**
- * Creates an n &times; 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 <T : Any> Point<T>.asMatrix(): VirtualMatrix<T> = 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<T : Any, out A : Ring<T>> {
     public operator fun T.times(v: Point<T>): Point<T> = 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<T : Any, out A : Ring<T>> {
      * @return a feature object or `null` if it isn't present.
      */
     @UnstableKMathAPI
-    public fun <F : StructureFeature> getFeature(structure: Matrix<T>, type: KClass<out F>): F? = structure.getFeature(type)
+    public fun <F : StructureFeature> computeFeature(structure: Matrix<T>, type: KClass<out F>): F? = structure.getFeature(type)
 
     public companion object {
 
@@ -184,7 +184,7 @@ public interface LinearSpace<T : Any, out A : Ring<T>> {
             bufferFactory: BufferFactory<T> = Buffer.Companion::boxing,
         ): LinearSpace<T, A> = BufferedLinearSpace(algebra, bufferFactory)
 
-        public val real: LinearSpace<Double, DoubleField> = buffered(DoubleField, ::DoubleBuffer)
+        public val double: LinearSpace<Double, DoubleField> = buffered(DoubleField, ::DoubleBuffer)
 
         /**
          * Automatic buffered matrix, unboxed if it is possible
@@ -202,9 +202,27 @@ public interface LinearSpace<T : Any, out A : Ring<T>> {
  * @return a feature object or `null` if it isn't present.
  */
 @UnstableKMathAPI
-public inline fun <T : Any, reified F : StructureFeature> LinearSpace<T, *>.getFeature(structure: Matrix<T>): F? =
-    getFeature(structure, F::class)
+public inline fun <T : Any, reified F : StructureFeature> LinearSpace<T, *>.computeFeature(structure: Matrix<T>): F? =
+    computeFeature(structure, F::class)
 
 
-public operator fun <LS : LinearSpace<*, *>, R> LS.invoke(block: LS.() -> R): R = run(block)
+public inline operator fun <LS : LinearSpace<*, *>, R> LS.invoke(block: LS.() -> R): R = run(block)
 
+
+/**
+ * Convert matrix to vector if it is possible.
+ */
+public fun <T : Any> Matrix<T>.asVector(): Point<T> =
+    if (this.colNum == 1)
+        as1D()
+    else
+        error("Can't convert matrix with more than one column to vector")
+
+/**
+ * Creates an n &times; 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 <T : Any> Point<T>.asMatrix(): VirtualMatrix<T> = 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<T : Any>(
             j == i -> elementContext.one
             else -> elementContext.zero
         }
-    } + LFeature
+    }.withFeature(LFeature)
 
 
     /**
@@ -44,7 +45,7 @@ public class LupDecomposition<T : Any>(
      */
     override val u: Matrix<T> = 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 <T : Comparable<T>> LinearSpace<T, Field<T>>.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 <reified T : Comparable<T>> LinearSpace<T, Field<T>>.lup(
     noinline checkSingular: (T) -> Boolean,
 ): LupDecomposition<T> = lup(MutableBuffer.Companion::auto, matrix, checkSingular)
 
-public fun LinearSpace<Double, DoubleField>.lup(matrix: Matrix<Double>): LupDecomposition<Double> =
-    lup(::DoubleBuffer, matrix) { it < 1e-11 }
+public fun LinearSpace<Double, DoubleField>.lup(
+    matrix: Matrix<Double>,
+    singularityThreshold: Double = 1e-11,
+): LupDecomposition<Double> =
+    lup(::DoubleBuffer, matrix) { it < singularityThreshold }
 
-public fun <T : Any> LupDecomposition<T>.solveWithLup(
+internal fun <T : Any> LupDecomposition<T>.solve(
     factory: MutableBufferFactory<T>,
     matrix: Matrix<T>,
 ): Matrix<T> {
@@ -207,41 +211,24 @@ public fun <T : Any> LupDecomposition<T>.solveWithLup(
     }
 }
 
-public inline fun <reified T : Any> LupDecomposition<T>.solveWithLup(matrix: Matrix<T>): Matrix<T> =
-    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 <reified T : Comparable<T>> LinearSpace<T, Field<T>>.solveWithLup(
-    a: Matrix<T>,
-    b: Matrix<T>,
-    noinline bufferFactory: MutableBufferFactory<T> = MutableBuffer.Companion::auto,
-    noinline checkSingular: (T) -> Boolean,
-): Matrix<T> {
-    // Use existing decomposition if it is provided by matrix
-    val decomposition = a.getFeature() ?: lup(bufferFactory, a, checkSingular)
-    return decomposition.solveWithLup(bufferFactory, b)
+public fun <T : Comparable<T>, F : Field<T>> LinearSpace<T, F>.lupSolver(
+    bufferFactory: MutableBufferFactory<T>,
+    singularityCheck: (T) -> Boolean,
+): LinearSolver<T> = object : LinearSolver<T> {
+    override fun solve(a: Matrix<T>, b: Matrix<T>): Matrix<T> {
+        // 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<T>): Matrix<T> = solve(matrix, one(matrix.rowNum, matrix.colNum))
 }
 
-public inline fun <reified T : Comparable<T>> LinearSpace<T, Field<T>>.inverseWithLup(
-    matrix: Matrix<T>,
-    noinline bufferFactory: MutableBufferFactory<T> = MutableBuffer.Companion::auto,
-    noinline checkSingular: (T) -> Boolean,
-): Matrix<T> = solveWithLup(matrix, one(matrix.rowNum, matrix.colNum), bufferFactory, checkSingular)
-
-
-@OptIn(UnstableKMathAPI::class)
-public fun LinearSpace<Double, DoubleField>.solveWithLup(a: Matrix<Double>, b: Matrix<Double>): Matrix<Double> {
-    // Use existing decomposition if it is provided by matrix
-    val bufferFactory: MutableBufferFactory<Double> = ::DoubleBuffer
-    val decomposition: LupDecomposition<Double> = 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<Double, DoubleField>.inverseWithLup(matrix: Matrix<Double>): Matrix<Double> =
-    solveWithLup(matrix, one(matrix.rowNum, matrix.colNum))
\ No newline at end of file
+@PerformancePitfall
+public fun LinearSpace<Double, DoubleField>.lupSolver(singularityThreshold: Double = 1e-11): LinearSolver<Double> =
+    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<T : Any, out A : Ring<T>>(
     public val linearSpace: LinearSpace<T, A>,
@@ -45,4 +47,31 @@ public inline fun <T : Any> LinearSpace<T, Ring<T>>.column(
     crossinline builder: (Int) -> T,
 ): Matrix<T> = buildMatrix(size, 1) { i, _ -> builder(i) }
 
-public fun <T : Any> LinearSpace<T, Ring<T>>.column(vararg values: T): Matrix<T> = column(values.size, values::get)
\ No newline at end of file
+public fun <T : Any> LinearSpace<T, Ring<T>>.column(vararg values: T): Matrix<T> = 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 <T : Any, A : Ring<T>> MatrixBuilder<T, A>.symmetric(
+    builder: (i: Int, j: Int) -> T,
+): Matrix<T> {
+    require(columns == rows) { "In order to build symmetric matrix, number of rows $rows should be equal to number of columns $columns" }
+    return with(BufferAccessor2D<T?>(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<out T : Any> internal constructor(
     public val origin: Matrix<T>,
-    public val features: Set<MatrixFeature>,
+    public val features: FeatureSet<StructureFeature>,
 ) : Matrix<T> by origin {
 
     /**
@@ -27,8 +28,7 @@ public class MatrixWrapper<out T : Any> internal constructor(
     @UnstableKMathAPI
     @Suppress("UNCHECKED_CAST")
     public override fun <F : StructureFeature> getFeature(type: KClass<out F>): 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 <T : Any> Matrix<T>.origin: Matrix<T>
 /**
  * Add a single feature to a [Matrix]
  */
-public operator fun <T : Any> Matrix<T>.plus(newFeature: MatrixFeature): MatrixWrapper<T> = if (this is MatrixWrapper) {
-    MatrixWrapper(origin, features + newFeature)
+public fun <T : Any> Matrix<T>.withFeature(newFeature: MatrixFeature): MatrixWrapper<T> = 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 <T : Any> Matrix<T>.plus(newFeature: MatrixFeature): MatrixWrapper<T> = withFeature(newFeature)
+
 /**
  * Add a collection of features to a [Matrix]
  */
-public operator fun <T : Any> Matrix<T>.plus(newFeatures: Collection<MatrixFeature>): MatrixWrapper<T> =
+public fun <T : Any> Matrix<T>.withFeatures(newFeatures: Iterable<MatrixFeature>): MatrixWrapper<T> =
     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 <T : Any> LinearSpace<T, Ring<T>>.one(
     columns: Int,
 ): Matrix<T> = VirtualMatrix(rows, columns) { i, j ->
     if (i == j) elementAlgebra.one else elementAlgebra.zero
-} + UnitFeature
+}.withFeature(UnitFeature)
 
 
 /**
@@ -79,7 +82,7 @@ public fun <T : Any> LinearSpace<T, Ring<T>>.zero(
     columns: Int,
 ): Matrix<T> = VirtualMatrix(rows, columns) { _, _ ->
     elementAlgebra.zero
-} + ZeroFeature
+}.withFeature(ZeroFeature)
 
 public class TransposedFeature<out T : Any>(public val original: Matrix<T>) : MatrixFeature
 
@@ -90,4 +93,4 @@ public class TransposedFeature<out T : Any>(public val original: Matrix<T>) : Ma
 public fun <T : Any> Matrix<T>.transpose(): Matrix<T> = getFeature<TransposedFeature<T>>()?.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<out T : Any>(
 
     override operator fun get(i: Int, j: Int): T = generator(i, j)
 }
+
+public fun <T : Any> MatrixBuilder<T, *>.virtual(generator: (i: Int, j: Int) -> T): VirtualMatrix<T> =
+    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 <T : Any, LS : LinearSpace<T, *>> LS.buildSymmetricMatrix(
-    size: Int,
-    builder: (i: Int, j: Int) -> T,
-): Matrix<T> = BufferAccessor2D<T?>(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<F : Any> {
-    public fun <T : F> getFeature(type: KClass<out T>): T?
+    public fun <T : F> getFeature(type: FeatureKey<T>): T?
+}
+
+public typealias FeatureKey<T> = KClass<out T>
+
+public interface Feature<F: Feature<F>> {
+
+    /**
+     * A key used for extraction
+     */
+    @Suppress("UNCHECKED_CAST")
+    public val key: FeatureKey<F> get() = this::class as FeatureKey<F>
 }
 
 /**
  * A container for a set of features
  */
-public class FeatureSet<F : Any> private constructor(public val features: Map<KClass<out F>, F>) : Featured<F> {
+public class FeatureSet<F : Feature<F>> private constructor(public val features: Map<FeatureKey<F>, F>) : Featured<F> {
     @Suppress("UNCHECKED_CAST")
-    override fun <T : F> getFeature(type: KClass<out T>): T? = features[type] as? T
+    override fun <T : F> getFeature(type: FeatureKey<T>): T? = features[type] as? T
 
     public inline fun <reified T : F> getFeature(): T? = getFeature(T::class)
 
-    public fun <T : F> with(feature: T, type: KClass<out T> = feature::class): FeatureSet<F> =
+    public fun <T : F> with(feature: T, type: FeatureKey<F> = feature.key): FeatureSet<F> =
         FeatureSet(features + (type to feature))
 
     public fun with(other: FeatureSet<F>): FeatureSet<F> = FeatureSet(features + other.features)
 
     public fun with(vararg otherFeatures: F): FeatureSet<F> =
-        FeatureSet(features + otherFeatures.associateBy { it::class })
+        FeatureSet(features + otherFeatures.associateBy { it.key })
+
+    public fun with(otherFeatures: Iterable<F>): FeatureSet<F> =
+        FeatureSet(features + otherFeatures.associateBy { it.key })
 
     public operator fun iterator(): Iterator<F> = features.values.iterator()
 
     public companion object {
-        public fun <F : Any> of(vararg features: F): FeatureSet<F> = FeatureSet(features.associateBy { it::class })
+        public fun <F : Feature<F>> of(vararg features: F): FeatureSet<F> = FeatureSet(features.associateBy { it.key })
+        public fun <F : Feature<F>> of(features: Iterable<F>): FeatureSet<F> = 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<StructureFeature>
 
 /**
  * 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<out T> {
+public interface StructureND<out T> : Featured<StructureFeature> {
     /**
      * 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<out T> {
      * If the feature is not present, null is returned.
      */
     @UnstableKMathAPI
-    public fun <F : StructureFeature> getFeature(type: KClass<out F>): F? = null
+    override fun <F : StructureFeature> getFeature(type: KClass<out F>): 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<T>(
-    public val rowNum: Int,
-    public val colNum: Int,
+    val rowNum: Int,
+    val colNum: Int,
     val factory: MutableBufferFactory<T>,
 ) {
-    public operator fun Buffer<T>.get(i: Int, j: Int): T = get(i * colNum + j)
+    operator fun Buffer<T>.get(i: Int, j: Int): T = get(i * colNum + j)
 
-    public operator fun MutableBuffer<T>.set(i: Int, j: Int, value: T) {
+    operator fun MutableBuffer<T>.set(i: Int, j: Int, value: T) {
         set(i * colNum + j, value)
     }
 
-    public inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer<T> =
+    inline fun create(crossinline init: (i: Int, j: Int) -> T): MutableBuffer<T> =
         factory(rowNum * colNum) { offset -> init(offset / colNum, offset % colNum) }
 
-    public fun create(mat: Structure2D<T>): MutableBuffer<T> = create { i, j -> mat[i, j] }
+    fun create(mat: Structure2D<T>): MutableBuffer<T> = create { i, j -> mat[i, j] }
 
     //TODO optimize wrapper
-    public fun MutableBuffer<T>.collect(): Structure2D<T> = StructureND.buffered(
+    fun MutableBuffer<T>.collect(): Structure2D<T> = StructureND.buffered(
         DefaultStrides(intArrayOf(rowNum, colNum)),
         factory
     ) { (i, j) ->
         get(i, j)
     }.as2D()
 
-    public inner class Row(public val buffer: MutableBuffer<T>, public val rowIndex: Int) : MutableBuffer<T> {
+    inner class Row(val buffer: MutableBuffer<T>, val rowIndex: Int) : MutableBuffer<T> {
         override val size: Int get() = colNum
 
         override operator fun get(index: Int): T = buffer[rowIndex, index]
@@ -54,5 +54,5 @@ internal class BufferAccessor2D<T>(
     /**
      * Get row
      */
-    public fun MutableBuffer<T>.row(i: Int): Row = Row(this, i)
+    fun MutableBuffer<T>.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<Double>.pow(power: Int): Matrix<Double> {
             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<T : Any, out A : Ring<T>>(public val context:
         context.run { (this@transpose as Matrix<T>).transpose() }.coerce()
 
     public companion object {
-        public val real: DMatrixContext<Double, DoubleField> = DMatrixContext(LinearSpace.real)
+        public val real: DMatrixContext<Double, DoubleField> = 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<out M : DMatrix>(public override val origin: M) : EjmlVector<Double, M>(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<out M : FMatrix>(public override val origin: M) : EjmlVector<Float, M>(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<out M : DMatrix>(public override val origin: M) : EjmlMatrix<Double, M>(origin) {
-    public override operator fun get(i: Int, j: Int): Double = origin[i, j]
-}
-
-/**
- * [EjmlMatrix] specialization for [Float].
- */
-public class EjmlFloatMatrix<out M : FMatrix>(public override val origin: M) : EjmlMatrix<Float, M>(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<Double, DoubleField, DMatrixRMaj>() {
-    /**
-     * The [DoubleField] reference.
-     */
-    public override val elementAlgebra: DoubleField get() = DoubleField
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Matrix<Double>.toEjml(): EjmlDoubleMatrix<DMatrixRMaj> = when {
-        this is EjmlDoubleMatrix<*> && origin is DMatrixRMaj -> this as EjmlDoubleMatrix<DMatrixRMaj>
-        else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Point<Double>.toEjml(): EjmlDoubleVector<DMatrixRMaj> = when {
-        this is EjmlDoubleVector<*> && origin is DMatrixRMaj -> this as EjmlDoubleVector<DMatrixRMaj>
-        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> = 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<DMatrixRMaj> = EjmlDoubleVector(DMatrixRMaj(size, 1).also {
-        (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) }
-    })
-
-    private fun <T : DMatrix> T.wrapMatrix() = EjmlDoubleMatrix(this)
-    private fun <T : DMatrix> T.wrapVector() = EjmlDoubleVector(this)
-
-    public override fun Matrix<Double>.unaryMinus(): Matrix<Double> = this * elementAlgebra { -one }
-
-    public override fun Matrix<Double>.dot(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
-        val out = DMatrixRMaj(1, 1)
-        CommonOps_DDRM.mult(toEjml().origin, other.toEjml().origin, out)
-        return out.wrapMatrix()
-    }
-
-    public override fun Matrix<Double>.dot(vector: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
-        val out = DMatrixRMaj(1, 1)
-        CommonOps_DDRM.mult(toEjml().origin, vector.toEjml().origin, out)
-        return out.wrapVector()
-    }
-
-    public override operator fun Matrix<Double>.minus(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
-        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<Double>.times(value: Double): EjmlDoubleMatrix<DMatrixRMaj> {
-        val res = DMatrixRMaj(1, 1)
-        CommonOps_DDRM.scale(value, toEjml().origin, res)
-        return res.wrapMatrix()
-    }
-
-    public override fun Point<Double>.unaryMinus(): EjmlDoubleVector<DMatrixRMaj> {
-        val res = DMatrixRMaj(1, 1)
-        CommonOps_DDRM.changeSign(toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Matrix<Double>.plus(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
-        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<Double>.plus(other: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
-        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<Double>.minus(other: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
-        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<Double>): EjmlDoubleMatrix<DMatrixRMaj> = m * this
-
-    public override fun Point<Double>.times(value: Double): EjmlDoubleVector<DMatrixRMaj> {
-        val res = DMatrixRMaj(1, 1)
-        CommonOps_DDRM.scale(value, toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Double.times(v: Point<Double>): EjmlDoubleVector<DMatrixRMaj> = v * this
-
-    @UnstableKMathAPI
-    public override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? {
-        structure.getFeature(type)?.let { return it }
-        val origin = structure.toEjml().origin
-
-        return when (type) {
-            InverseMatrixFeature::class -> object : InverseMatrixFeature<Double> {
-                override val inverse: Matrix<Double> by lazy {
-                    val res = origin.copy()
-                    CommonOps_DDRM.invert(res)
-                    res.wrapMatrix()
-                }
-            }
-
-            DeterminantFeature::class -> object : DeterminantFeature<Double> {
-                override val determinant: Double by lazy { CommonOps_DDRM.det(origin) }
-            }
-
-            SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<Double> {
-                private val svd by lazy {
-                    DecompositionFactory_DDRM.svd(origin.numRows, origin.numCols, true, true, false)
-                        .apply { decompose(origin.copy()) }
-                }
-
-                override val u: Matrix<Double> by lazy { svd.getU(null, false).wrapMatrix() }
-                override val s: Matrix<Double> by lazy { svd.getW(null).wrapMatrix() }
-                override val v: Matrix<Double> by lazy { svd.getV(null, false).wrapMatrix() }
-                override val singularValues: Point<Double> by lazy { DoubleBuffer(svd.singularValues) }
-            }
-
-            QRDecompositionFeature::class -> object : QRDecompositionFeature<Double> {
-                private val qr by lazy {
-                    DecompositionFactory_DDRM.qr().apply { decompose(origin.copy()) }
-                }
-
-                override val q: Matrix<Double> by lazy {
-                    qr.getQ(null, false).wrapMatrix() + OrthogonalFeature
-                }
-
-                override val r: Matrix<Double> by lazy { qr.getR(null, false).wrapMatrix() + UFeature }
-            }
-
-            CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Double> {
-                override val l: Matrix<Double> by lazy {
-                    val cholesky =
-                        DecompositionFactory_DDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) }
-
-                    cholesky.getT(null).wrapMatrix() + LFeature
-                }
-            }
-
-            LupDecompositionFeature::class -> object : LupDecompositionFeature<Double> {
-                private val lup by lazy {
-                    DecompositionFactory_DDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) }
-                }
-
-                override val l: Matrix<Double> by lazy {
-                    lup.getLower(null).wrapMatrix() + LFeature
-                }
-
-                override val u: Matrix<Double> by lazy {
-                    lup.getUpper(null).wrapMatrix() + UFeature
-                }
-
-                override val p: Matrix<Double> by lazy { lup.getRowPivot(null).wrapMatrix() }
-            }
-
-            else -> null
-        }?.let(type::cast)
-    }
-
-    /**
-     * Solves for *x* in the following equation: *x = [a] <sup>-1</sup> &middot; [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<Double>, b: Matrix<Double>): EjmlDoubleMatrix<DMatrixRMaj> {
-        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] <sup>-1</sup> &middot; [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<Double>, b: Point<Double>): EjmlDoubleVector<DMatrixRMaj> {
-        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<Float, FloatField, FMatrixRMaj>() {
-    /**
-     * The [FloatField] reference.
-     */
-    public override val elementAlgebra: FloatField get() = FloatField
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Matrix<Float>.toEjml(): EjmlFloatMatrix<FMatrixRMaj> = when {
-        this is EjmlFloatMatrix<*> && origin is FMatrixRMaj -> this as EjmlFloatMatrix<FMatrixRMaj>
-        else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Point<Float>.toEjml(): EjmlFloatVector<FMatrixRMaj> = when {
-        this is EjmlFloatVector<*> && origin is FMatrixRMaj -> this as EjmlFloatVector<FMatrixRMaj>
-        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> = 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<FMatrixRMaj> = EjmlFloatVector(FMatrixRMaj(size, 1).also {
-        (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) }
-    })
-
-    private fun <T : FMatrix> T.wrapMatrix() = EjmlFloatMatrix(this)
-    private fun <T : FMatrix> T.wrapVector() = EjmlFloatVector(this)
-
-    public override fun Matrix<Float>.unaryMinus(): Matrix<Float> = this * elementAlgebra { -one }
-
-    public override fun Matrix<Float>.dot(other: Matrix<Float>): EjmlFloatMatrix<FMatrixRMaj> {
-        val out = FMatrixRMaj(1, 1)
-        CommonOps_FDRM.mult(toEjml().origin, other.toEjml().origin, out)
-        return out.wrapMatrix()
-    }
-
-    public override fun Matrix<Float>.dot(vector: Point<Float>): EjmlFloatVector<FMatrixRMaj> {
-        val out = FMatrixRMaj(1, 1)
-        CommonOps_FDRM.mult(toEjml().origin, vector.toEjml().origin, out)
-        return out.wrapVector()
-    }
-
-    public override operator fun Matrix<Float>.minus(other: Matrix<Float>): EjmlFloatMatrix<FMatrixRMaj> {
-        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<Float>.times(value: Float): EjmlFloatMatrix<FMatrixRMaj> {
-        val res = FMatrixRMaj(1, 1)
-        CommonOps_FDRM.scale(value, toEjml().origin, res)
-        return res.wrapMatrix()
-    }
-
-    public override fun Point<Float>.unaryMinus(): EjmlFloatVector<FMatrixRMaj> {
-        val res = FMatrixRMaj(1, 1)
-        CommonOps_FDRM.changeSign(toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Matrix<Float>.plus(other: Matrix<Float>): EjmlFloatMatrix<FMatrixRMaj> {
-        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<Float>.plus(other: Point<Float>): EjmlFloatVector<FMatrixRMaj> {
-        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<Float>.minus(other: Point<Float>): EjmlFloatVector<FMatrixRMaj> {
-        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<Float>): EjmlFloatMatrix<FMatrixRMaj> = m * this
-
-    public override fun Point<Float>.times(value: Float): EjmlFloatVector<FMatrixRMaj> {
-        val res = FMatrixRMaj(1, 1)
-        CommonOps_FDRM.scale(value, toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Float.times(v: Point<Float>): EjmlFloatVector<FMatrixRMaj> = v * this
-
-    @UnstableKMathAPI
-    public override fun <F : StructureFeature> getFeature(structure: Matrix<Float>, type: KClass<out F>): F? {
-        structure.getFeature(type)?.let { return it }
-        val origin = structure.toEjml().origin
-
-        return when (type) {
-            InverseMatrixFeature::class -> object : InverseMatrixFeature<Float> {
-                override val inverse: Matrix<Float> by lazy {
-                    val res = origin.copy()
-                    CommonOps_FDRM.invert(res)
-                    res.wrapMatrix()
-                }
-            }
-
-            DeterminantFeature::class -> object : DeterminantFeature<Float> {
-                override val determinant: Float by lazy { CommonOps_FDRM.det(origin) }
-            }
-
-            SingularValueDecompositionFeature::class -> object : SingularValueDecompositionFeature<Float> {
-                private val svd by lazy {
-                    DecompositionFactory_FDRM.svd(origin.numRows, origin.numCols, true, true, false)
-                        .apply { decompose(origin.copy()) }
-                }
-
-                override val u: Matrix<Float> by lazy { svd.getU(null, false).wrapMatrix() }
-                override val s: Matrix<Float> by lazy { svd.getW(null).wrapMatrix() }
-                override val v: Matrix<Float> by lazy { svd.getV(null, false).wrapMatrix() }
-                override val singularValues: Point<Float> by lazy { FloatBuffer(svd.singularValues) }
-            }
-
-            QRDecompositionFeature::class -> object : QRDecompositionFeature<Float> {
-                private val qr by lazy {
-                    DecompositionFactory_FDRM.qr().apply { decompose(origin.copy()) }
-                }
-
-                override val q: Matrix<Float> by lazy {
-                    qr.getQ(null, false).wrapMatrix() + OrthogonalFeature
-                }
-
-                override val r: Matrix<Float> by lazy { qr.getR(null, false).wrapMatrix() + UFeature }
-            }
-
-            CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Float> {
-                override val l: Matrix<Float> by lazy {
-                    val cholesky =
-                        DecompositionFactory_FDRM.chol(structure.rowNum, true).apply { decompose(origin.copy()) }
-
-                    cholesky.getT(null).wrapMatrix() + LFeature
-                }
-            }
-
-            LupDecompositionFeature::class -> object : LupDecompositionFeature<Float> {
-                private val lup by lazy {
-                    DecompositionFactory_FDRM.lu(origin.numRows, origin.numCols).apply { decompose(origin.copy()) }
-                }
-
-                override val l: Matrix<Float> by lazy {
-                    lup.getLower(null).wrapMatrix() + LFeature
-                }
-
-                override val u: Matrix<Float> by lazy {
-                    lup.getUpper(null).wrapMatrix() + UFeature
-                }
-
-                override val p: Matrix<Float> by lazy { lup.getRowPivot(null).wrapMatrix() }
-            }
-
-            else -> null
-        }?.let(type::cast)
-    }
-
-    /**
-     * Solves for *x* in the following equation: *x = [a] <sup>-1</sup> &middot; [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<Float>, b: Matrix<Float>): EjmlFloatMatrix<FMatrixRMaj> {
-        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] <sup>-1</sup> &middot; [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<Float>, b: Point<Float>): EjmlFloatVector<FMatrixRMaj> {
-        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<Double, DoubleField, DMatrixSparseCSC>() {
-    /**
-     * The [DoubleField] reference.
-     */
-    public override val elementAlgebra: DoubleField get() = DoubleField
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Matrix<Double>.toEjml(): EjmlDoubleMatrix<DMatrixSparseCSC> = when {
-        this is EjmlDoubleMatrix<*> && origin is DMatrixSparseCSC -> this as EjmlDoubleMatrix<DMatrixSparseCSC>
-        else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Point<Double>.toEjml(): EjmlDoubleVector<DMatrixSparseCSC> = when {
-        this is EjmlDoubleVector<*> && origin is DMatrixSparseCSC -> this as EjmlDoubleVector<DMatrixSparseCSC>
-        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> = 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<DMatrixSparseCSC> = EjmlDoubleVector(DMatrixSparseCSC(size, 1).also {
-        (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) }
-    })
-
-    private fun <T : DMatrix> T.wrapMatrix() = EjmlDoubleMatrix(this)
-    private fun <T : DMatrix> T.wrapVector() = EjmlDoubleVector(this)
-
-    public override fun Matrix<Double>.unaryMinus(): Matrix<Double> = this * elementAlgebra { -one }
-
-    public override fun Matrix<Double>.dot(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixSparseCSC> {
-        val out = DMatrixSparseCSC(1, 1)
-        CommonOps_DSCC.mult(toEjml().origin, other.toEjml().origin, out)
-        return out.wrapMatrix()
-    }
-
-    public override fun Matrix<Double>.dot(vector: Point<Double>): EjmlDoubleVector<DMatrixSparseCSC> {
-        val out = DMatrixSparseCSC(1, 1)
-        CommonOps_DSCC.mult(toEjml().origin, vector.toEjml().origin, out)
-        return out.wrapVector()
-    }
-
-    public override operator fun Matrix<Double>.minus(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixSparseCSC> {
-        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<Double>.times(value: Double): EjmlDoubleMatrix<DMatrixSparseCSC> {
-        val res = DMatrixSparseCSC(1, 1)
-        CommonOps_DSCC.scale(value, toEjml().origin, res)
-        return res.wrapMatrix()
-    }
-
-    public override fun Point<Double>.unaryMinus(): EjmlDoubleVector<DMatrixSparseCSC> {
-        val res = DMatrixSparseCSC(1, 1)
-        CommonOps_DSCC.changeSign(toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Matrix<Double>.plus(other: Matrix<Double>): EjmlDoubleMatrix<DMatrixSparseCSC> {
-        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<Double>.plus(other: Point<Double>): EjmlDoubleVector<DMatrixSparseCSC> {
-        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<Double>.minus(other: Point<Double>): EjmlDoubleVector<DMatrixSparseCSC> {
-        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<Double>): EjmlDoubleMatrix<DMatrixSparseCSC> = m * this
-
-    public override fun Point<Double>.times(value: Double): EjmlDoubleVector<DMatrixSparseCSC> {
-        val res = DMatrixSparseCSC(1, 1)
-        CommonOps_DSCC.scale(value, toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Double.times(v: Point<Double>): EjmlDoubleVector<DMatrixSparseCSC> = v * this
-
-    @UnstableKMathAPI
-    public override fun <F : StructureFeature> getFeature(structure: Matrix<Double>, type: KClass<out F>): F? {
-        structure.getFeature(type)?.let { return it }
-        val origin = structure.toEjml().origin
-
-        return when (type) {
-            QRDecompositionFeature::class -> object : QRDecompositionFeature<Double> {
-                private val qr by lazy {
-                    DecompositionFactory_DSCC.qr(FillReducing.NONE).apply { decompose(origin.copy()) }
-                }
-
-                override val q: Matrix<Double> by lazy {
-                    qr.getQ(null, false).wrapMatrix() + OrthogonalFeature
-                }
-
-                override val r: Matrix<Double> by lazy { qr.getR(null, false).wrapMatrix() + UFeature }
-            }
-
-            CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Double> {
-                override val l: Matrix<Double> 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<Double>, DeterminantFeature<Double>, InverseMatrixFeature<Double> {
-                private val lu by lazy {
-                    DecompositionFactory_DSCC.lu(FillReducing.NONE).apply { decompose(origin.copy()) }
-                }
-
-                override val l: Matrix<Double> by lazy {
-                    lu.getLower(null).wrapMatrix() + LFeature
-                }
-
-                override val u: Matrix<Double> by lazy {
-                    lu.getUpper(null).wrapMatrix() + UFeature
-                }
-
-                override val inverse: Matrix<Double> 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] <sup>-1</sup> &middot; [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<Double>, b: Matrix<Double>): EjmlDoubleMatrix<DMatrixSparseCSC> {
-        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] <sup>-1</sup> &middot; [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<Double>, b: Point<Double>): EjmlDoubleVector<DMatrixSparseCSC> {
-        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<Float, FloatField, FMatrixSparseCSC>() {
-    /**
-     * The [FloatField] reference.
-     */
-    public override val elementAlgebra: FloatField get() = FloatField
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Matrix<Float>.toEjml(): EjmlFloatMatrix<FMatrixSparseCSC> = when {
-        this is EjmlFloatMatrix<*> && origin is FMatrixSparseCSC -> this as EjmlFloatMatrix<FMatrixSparseCSC>
-        else -> buildMatrix(rowNum, colNum) { i, j -> get(i, j) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    public override fun Point<Float>.toEjml(): EjmlFloatVector<FMatrixSparseCSC> = when {
-        this is EjmlFloatVector<*> && origin is FMatrixSparseCSC -> this as EjmlFloatVector<FMatrixSparseCSC>
-        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> = 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<FMatrixSparseCSC> = EjmlFloatVector(FMatrixSparseCSC(size, 1).also {
-        (0 until it.numRows).forEach { row -> it[row, 0] = elementAlgebra.initializer(row) }
-    })
-
-    private fun <T : FMatrix> T.wrapMatrix() = EjmlFloatMatrix(this)
-    private fun <T : FMatrix> T.wrapVector() = EjmlFloatVector(this)
-
-    public override fun Matrix<Float>.unaryMinus(): Matrix<Float> = this * elementAlgebra { -one }
-
-    public override fun Matrix<Float>.dot(other: Matrix<Float>): EjmlFloatMatrix<FMatrixSparseCSC> {
-        val out = FMatrixSparseCSC(1, 1)
-        CommonOps_FSCC.mult(toEjml().origin, other.toEjml().origin, out)
-        return out.wrapMatrix()
-    }
-
-    public override fun Matrix<Float>.dot(vector: Point<Float>): EjmlFloatVector<FMatrixSparseCSC> {
-        val out = FMatrixSparseCSC(1, 1)
-        CommonOps_FSCC.mult(toEjml().origin, vector.toEjml().origin, out)
-        return out.wrapVector()
-    }
-
-    public override operator fun Matrix<Float>.minus(other: Matrix<Float>): EjmlFloatMatrix<FMatrixSparseCSC> {
-        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<Float>.times(value: Float): EjmlFloatMatrix<FMatrixSparseCSC> {
-        val res = FMatrixSparseCSC(1, 1)
-        CommonOps_FSCC.scale(value, toEjml().origin, res)
-        return res.wrapMatrix()
-    }
-
-    public override fun Point<Float>.unaryMinus(): EjmlFloatVector<FMatrixSparseCSC> {
-        val res = FMatrixSparseCSC(1, 1)
-        CommonOps_FSCC.changeSign(toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Matrix<Float>.plus(other: Matrix<Float>): EjmlFloatMatrix<FMatrixSparseCSC> {
-        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<Float>.plus(other: Point<Float>): EjmlFloatVector<FMatrixSparseCSC> {
-        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<Float>.minus(other: Point<Float>): EjmlFloatVector<FMatrixSparseCSC> {
-        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<Float>): EjmlFloatMatrix<FMatrixSparseCSC> = m * this
-
-    public override fun Point<Float>.times(value: Float): EjmlFloatVector<FMatrixSparseCSC> {
-        val res = FMatrixSparseCSC(1, 1)
-        CommonOps_FSCC.scale(value, toEjml().origin, res)
-        return res.wrapVector()
-    }
-
-    public override fun Float.times(v: Point<Float>): EjmlFloatVector<FMatrixSparseCSC> = v * this
-
-    @UnstableKMathAPI
-    public override fun <F : StructureFeature> getFeature(structure: Matrix<Float>, type: KClass<out F>): F? {
-        structure.getFeature(type)?.let { return it }
-        val origin = structure.toEjml().origin
-
-        return when (type) {
-            QRDecompositionFeature::class -> object : QRDecompositionFeature<Float> {
-                private val qr by lazy {
-                    DecompositionFactory_FSCC.qr(FillReducing.NONE).apply { decompose(origin.copy()) }
-                }
-
-                override val q: Matrix<Float> by lazy {
-                    qr.getQ(null, false).wrapMatrix() + OrthogonalFeature
-                }
-
-                override val r: Matrix<Float> by lazy { qr.getR(null, false).wrapMatrix() + UFeature }
-            }
-
-            CholeskyDecompositionFeature::class -> object : CholeskyDecompositionFeature<Float> {
-                override val l: Matrix<Float> 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<Float>, DeterminantFeature<Float>, InverseMatrixFeature<Float> {
-                private val lu by lazy {
-                    DecompositionFactory_FSCC.lu(FillReducing.NONE).apply { decompose(origin.copy()) }
-                }
-
-                override val l: Matrix<Float> by lazy {
-                    lu.getLower(null).wrapMatrix() + LFeature
-                }
-
-                override val u: Matrix<Float> by lazy {
-                    lu.getUpper(null).wrapMatrix() + UFeature
-                }
-
-                override val inverse: Matrix<Float> 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] <sup>-1</sup> &middot; [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<Float>, b: Matrix<Float>): EjmlFloatMatrix<FMatrixSparseCSC> {
-        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] <sup>-1</sup> &middot; [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<Float>, b: Point<Float>): EjmlFloatVector<FMatrixSparseCSC> {
-        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<Double> = EjmlLinearSpaceDDRM.getFeature(w) ?: fail()
+        val det: DeterminantFeature<Double> = EjmlLinearSpaceDDRM.computeFeature(w) ?: fail()
         assertEquals(CommonOps_DDRM.det(m), det.determinant)
-        val lup: LupDecompositionFeature<Double> = EjmlLinearSpaceDDRM.getFeature(w) ?: fail()
+        val lup: LupDecompositionFeature<Double> = 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<Double>
 
 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<Double, DoubleField> =
-    LinearSpace.real.matrix(rowNum, colNum)
+    LinearSpace.double.matrix(rowNum, colNum)
 
 public fun Array<DoubleArray>.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<DoubleArray>.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>) -> 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<Double>) -
     }
 
 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<Double>.dot(other: Matrix<Double>): Matrix<Double> = LinearSpace.real.run {
+public infix fun Matrix<Double>.dot(other: Matrix<Double>): Matrix<Double> = 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<IntegrandFeature> {
     override fun toString(): String
 }
 
@@ -18,7 +19,7 @@ public interface Integrand : Featured<IntegrandFeature> {
     override fun <T : IntegrandFeature> getFeature(type: KClass<out T>): T? = features.getFeature(type)
 }
 
-public inline fun <reified T: IntegrandFeature> Integrand.getFeature(): T? = getFeature(T::class)
+public inline fun <reified T : IntegrandFeature> Integrand.getFeature(): T? = getFeature(T::class)
 
 public class IntegrandValue<T : Any>(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<T : Number, A : NumericAlgebra<T>>(
 ) : SpecialDifferentiableExpression<T, KotlingradExpression<T, A>> {
     public override fun invoke(arguments: Map<Symbol, T>): T = mst.interpret(algebra, arguments)
 
-    public override fun derivativeOrNull(symbols: List<Symbol>): KotlingradExpression<T, A> =
-        KotlingradExpression(
-            algebra,
-            symbols.map(Symbol::identity)
-                .map(MstNumericAlgebra::bindSymbol)
-                .map<Symbol, SVar<KMathNumber<T, A>>>(Symbol::toSVar)
-                .fold(mst.toSFun(), SFun<KMathNumber<T, A>>::d)
-                .toMst(),
-        )
+    public override fun derivativeOrNull(
+        symbols: List<Symbol>,
+    ): KotlingradExpression<T, A> = KotlingradExpression(
+        algebra,
+        symbols.map(Symbol::identity)
+            .map(MstNumericAlgebra::bindSymbol)
+            .map<Symbol, SVar<KMathNumber<T, A>>>(Symbol::toSVar)
+            .fold(mst.toSFun(), SFun<KMathNumber<T, A>>::d)
+            .toMst(),
+    )
+}
+
+/**
+ * A diff processor using [MST] to Kotlingrad converter
+ */
+public class KotlingradProcessor<T : Number, A : NumericAlgebra<T>>(
+    public val algebra: A,
+) : AutoDiffProcessor<T, MST, MstExtendedField> {
+    override fun differentiate(function: MstExtendedField.() -> MST): DifferentiableExpression<T> =
+        MstExtendedField.function().toKotlingradExpression(algebra)
 }
 
 /**
  * Wraps this [MST] into [KotlingradExpression].
  */
-public fun <T : Number, A : NumericAlgebra<T>> MST.toDiffExpression(algebra: A): KotlingradExpression<T, A> =
+public fun <T : Number, A : NumericAlgebra<T>> MST.toKotlingradExpression(algebra: A): KotlingradExpression<T, A> =
     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<T>(
     override val features: FeatureSet<OptimizationFeature>,
     public val expression: DifferentiableExpression<T>,
-) : OptimizationProblem{
+) : OptimizationProblem<T>{
 
     public companion object{
         /**
@@ -56,7 +56,6 @@ public class FunctionOptimization<T>(
     }
 }
 
-
 public fun <T> FunctionOptimization<T>.withFeatures(
     vararg newFeature: OptimizationFeature,
 ): FunctionOptimization<T> = FunctionOptimization(
@@ -68,7 +67,7 @@ public fun <T> FunctionOptimization<T>.withFeatures(
  * Optimize differentiable expression using specific [optimizer] form given [startingPoint]
  */
 public suspend fun <T : Any> DifferentiableExpression<T>.optimizeWith(
-    optimizer: Optimizer<FunctionOptimization<T>>,
+    optimizer: Optimizer<T, FunctionOptimization<T>>,
     startingPoint: Map<Symbol, T>,
     vararg features: OptimizationFeature,
 ): FunctionOptimization<T> {
@@ -76,3 +75,8 @@ public suspend fun <T : Any> DifferentiableExpression<T>.optimizeWith(
     return optimizer.optimize(problem)
 }
 
+public val <T> FunctionOptimization<T>.resultValueOrNull:T?
+    get() = getFeature<OptimizationResult<T>>()?.point?.let { expression(it) }
+
+public val <T> FunctionOptimization<T>.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<T, R : OptimizationProblem<T>> {
+    public val features: ArrayList<OptimizationFeature> = ArrayList()
+
+    public fun addFeature(feature: OptimizationFeature) {
+        features.add(feature)
+    }
+
+    public inline fun <reified T : OptimizationFeature> 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 <T> OptimizationBuilder<T, *>.startAt(startingPoint: Map<Symbol, T>) {
+    addFeature(OptimizationStartPoint(startingPoint))
+}
+
+public class FunctionOptimizationBuilder<T>(
+    private val expression: DifferentiableExpression<T>,
+) : OptimizationBuilder<T, FunctionOptimization<T>>() {
+    override fun build(): FunctionOptimization<T> = FunctionOptimization(FeatureSet.of(features), expression)
+}
+
+public fun <T> FunctionOptimization(
+    expression: DifferentiableExpression<T>,
+    builder: FunctionOptimizationBuilder<T>.() -> Unit,
+): FunctionOptimization<T> = FunctionOptimizationBuilder(expression).apply(builder).build()
+
+public suspend fun <T> DifferentiableExpression<T>.optimizeWith(
+    optimizer: Optimizer<T, FunctionOptimization<T>>,
+    startingPoint: Map<Symbol, T>,
+    builder: FunctionOptimizationBuilder<T>.() -> Unit = {},
+): FunctionOptimization<T> {
+    val problem = FunctionOptimization<T>(this) {
+        startAt(startingPoint)
+        builder()
+    }
+    return optimizer.optimize(problem)
+}
+
+public suspend fun <T> DifferentiableExpression<T>.optimizeWith(
+    optimizer: Optimizer<T, FunctionOptimization<T>>,
+    vararg startingPoint: Pair<Symbol, T>,
+    builder: FunctionOptimizationBuilder<T>.() -> Unit = {},
+): FunctionOptimization<T> {
+    val problem = FunctionOptimization<T>(this) {
+        startAt(mapOf(*startingPoint))
+        builder()
+    }
+    return optimizer.optimize(problem)
+}
+
+
+public class XYOptimizationBuilder(
+    public val data: XYColumnarData<Double, Double, Double>,
+    public val model: DifferentiableExpression<Double>,
+) : OptimizationBuilder<Double, XYOptimization>() {
+
+    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<Double, Double, Double>,
+    model: DifferentiableExpression<Double>,
+    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<OptimizationFeature> {
+    // enforce toString override
     override fun toString(): String
 }
 
-public interface OptimizationProblem : Featured<OptimizationFeature> {
+public interface OptimizationProblem<T> : Featured<OptimizationFeature> {
     public val features: FeatureSet<OptimizationFeature>
-    override fun <T : OptimizationFeature> getFeature(type: KClass<out T>): T? = features.getFeature(type)
+    override fun <F : OptimizationFeature> getFeature(type: KClass<out F>): F? = features.getFeature(type)
 }
 
-public inline fun <reified T : OptimizationFeature> OptimizationProblem.getFeature(): T? = getFeature(T::class)
+public inline fun <reified F : OptimizationFeature> OptimizationProblem<*>.getFeature(): F? = getFeature(F::class)
 
 public open class OptimizationStartPoint<T>(public val point: Map<Symbol, T>) : OptimizationFeature {
     override fun toString(): String = "StartPoint($point)"
 }
 
+
+public interface OptimizationPrior<T> : OptimizationFeature, DifferentiableExpression<T> {
+    override val key: FeatureKey<OptimizationFeature> get() = OptimizationPrior::class
+}
+
+public class OptimizationCovariance<T>(public val covariance: Matrix<T>) : OptimizationFeature {
+    override fun toString(): String = "Covariance($covariance)"
+}
+
+/**
+ * Get the starting point for optimization. Throws error if not defined.
+ */
+public val <T> OptimizationProblem<T>.startPoint: Map<Symbol, T>
+    get() = getFeature<OptimizationStartPoint<T>>()?.point
+        ?: error("Starting point not defined in $this")
+
 public open class OptimizationResult<T>(public val point: Map<Symbol, T>) : OptimizationFeature {
     override fun toString(): String = "Result($point)"
 }
 
+public val <T> OptimizationProblem<T>.resultPointOrNull: Map<Symbol, T>?
+    get() = getFeature<OptimizationResult<T>>()?.point
+
+public val <T> OptimizationProblem<T>.resultPoint: Map<Symbol, T>
+    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<Symbol>): OptimizationFeature{
+public class OptimizationParameters(public val symbols: List<Symbol>) : 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<P : OptimizationProblem> {
+public interface Optimizer<T, P : OptimizationProblem<T>> {
     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<Double, XYOptimization> {
+
+    private val linearSpace: LinearSpace<Double, DoubleField> = LinearSpace.double
+    private val solver: LinearSolver<Double> = linearSpace.lupSolver()
+
+    @OptIn(UnstableKMathAPI::class)
+    private inner class QoWeight(
+        val problem: XYOptimization,
+        val parameters: Map<Symbol, Double>,
+    ) : Map<Symbol, Double> by parameters, SymbolIndexer {
+        override val symbols: List<Symbol> = 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<Double> 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<Double> by lazy {
+            DoubleBuffer(problem.data.size) { i ->
+                problem.weight(i).invoke(parameters)
+            }
+        }
+
+        val prior: DifferentiableExpression<Double>? get() = problem.getFeature<OptimizationPrior<Double>>()
+    }
+
+    /**
+     * The signed distance from the model to the [i]-th point of data.
+     */
+    private fun QoWeight.distance(i: Int, parameters: Map<Symbol, Double>): Double = problem.distance(i)(parameters)
+
+
+    /**
+     * The derivative of [distance]
+     */
+    private fun QoWeight.distanceDerivative(symbol: Symbol, i: Int, parameters: Map<Symbol, Double>): 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<Double> =
+        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<Symbol, Double>): Matrix<Double> =
+        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<Symbol, Double> = parameters,
+    ): Matrix<Double> = 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<Symbol, Double> = this): Point<Double> {
+        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<Symbol, Double>,
+        eqvalues: Point<Double>,
+    ): 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<OptimizationLog>()
+
+        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<Double> {
+        val logger = problem.getFeature<OptimizationLog>()
+
+        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<Double>
 
@@ -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<Double>
+
+    public companion object {
+        public fun bySigma(sigmaSymbol: Symbol): PointWeight = object : PointWeight {
+            override fun weight(problem: XYOptimization, index: Int): DifferentiableExpression<Double> =
+                object : DifferentiableExpression<Double> {
+                    override fun invoke(arguments: Map<Symbol, Double>): Double {
+                        return problem.data[sigmaSymbol]?.get(index)?.pow(-2) ?: 1.0
+                    }
+
+                    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double> = 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<OptimizationFeature>,
     public val data: XYColumnarData<Double, Double, Double>,
     public val model: DifferentiableExpression<Double>,
-) : OptimizationProblem
+    internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY,
+    internal val pointWeight: PointWeight = PointWeight.byYSigma,
+) : OptimizationProblem<Double> {
+    public fun distance(index: Int): DifferentiableExpression<Double> = pointToCurveDistance.distance(this, index)
 
+    public fun weight(index: Int): DifferentiableExpression<Double> = pointWeight.weight(this, index)
+}
 
-public suspend fun Optimizer<FunctionOptimization<Double>>.maximumLogLikelihood(problem: XYOptimization): XYOptimization {
-    val distanceBuilder = problem.getFeature() ?: PointToCurveDistance.byY
-    val likelihood: DifferentiableExpression<Double> = object : DifferentiableExpression<Double> {
-        override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double>? {
-            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<Double> = object : DifferentiableExpression<Double> {
+    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double> = 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<Symbol, Double>): 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<Symbol, Double>): 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<Double, FunctionOptimization<Double>>.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<Double, FunctionOptimization<Double>>.maximumLogLikelihood(
+    data: XYColumnarData<Double, Double, Double>,
+    model: DifferentiableExpression<Double>,
+    builder: XYOptimizationBuilder.() -> Unit,
+): XYOptimization = maximumLogLikelihood(XYOptimization(data, model, builder))
+
+//public suspend fun XYColumnarData<Double, Double, Double>.fitWith(
+//    optimizer: XYOptimization,
+//    problemBuilder: XYOptimizationBuilder.() -> Unit = {},
+//
+//)
+
+
 //
 //@UnstableKMathAPI
 //public interface XYFit<T> : 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 <altavir@gmail.com>
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 <reified R : Any> MutableBuffer.Companion.same(
+    n: Int,
+    value: R
+): MutableBuffer<R> = 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<T, A : Algebra<T>> : Algebra<Buffer<T>> {
+    public val bufferFactory: BufferFactory<T>
+    public val elementAlgebra: A
+
+    //TODO move to multi-receiver inline extension
+    public fun Buffer<T>.map(block: (T) -> T): Buffer<T> = bufferFactory(size) { block(get(it)) }
+
+    public fun Buffer<T>.zip(other: Buffer<T>, block: (left: T, right: T) -> T): Buffer<T> {
+        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<T>) -> Buffer<T> {
+        val operationFunction = elementAlgebra.unaryOperationFunction(operation)
+        return { arg -> bufferFactory(arg.size) { operationFunction(arg[it]) } }
+    }
+
+    override fun binaryOperationFunction(operation: String): (left: Buffer<T>, right: Buffer<T>) -> Buffer<T> {
+        val operationFunction = elementAlgebra.binaryOperationFunction(operation)
+        return { left, right ->
+            bufferFactory(left.size) { operationFunction(left[it], right[it]) }
+        }
+    }
+}
+
+@UnstableKMathAPI
+public fun <T, A : TrigonometricOperations<T>> BufferAlgebra<T, A>.sin(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::sin)
+
+@UnstableKMathAPI
+public fun <T, A : TrigonometricOperations<T>> BufferAlgebra<T, A>.cos(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::cos)
+
+@UnstableKMathAPI
+public fun <T, A : TrigonometricOperations<T>> BufferAlgebra<T, A>.tan(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::tan)
+
+@UnstableKMathAPI
+public fun <T, A : TrigonometricOperations<T>> BufferAlgebra<T, A>.asin(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::asin)
+
+@UnstableKMathAPI
+public fun <T, A : TrigonometricOperations<T>> BufferAlgebra<T, A>.acos(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::acos)
+
+@UnstableKMathAPI
+public fun <T, A : TrigonometricOperations<T>> BufferAlgebra<T, A>.atan(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::atan)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.exp(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::exp)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.ln(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::ln)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.sinh(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::sinh)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.cosh(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::cosh)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.tanh(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::tanh)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.asinh(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::asinh)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.acosh(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::acosh)
+
+@UnstableKMathAPI
+public fun <T, A : ExponentialOperations<T>> BufferAlgebra<T, A>.atanh(arg: Buffer<T>): Buffer<T> =
+    arg.map(elementAlgebra::atanh)
+
+@UnstableKMathAPI
+public fun <T, A : PowerOperations<T>> BufferAlgebra<T, A>.pow(arg: Buffer<T>, pow: Number): Buffer<T> =
+    with(elementAlgebra) { arg.map { power(it, pow) } }
+
+
+@UnstableKMathAPI
+public class BufferField<T, A : Field<T>>(
+    override val bufferFactory: BufferFactory<T>,
+    override val elementAlgebra: A,
+    public val size: Int
+) : BufferAlgebra<T, A>, Field<Buffer<T>> {
+
+    public fun produce(vararg elements: T): Buffer<T> {
+        require(elements.size == size) { "Expected $size elements but found ${elements.size}" }
+        return bufferFactory(size) { elements[it] }
+    }
+
+    override val zero: Buffer<T> = bufferFactory(size) { elementAlgebra.zero }
+    override val one: Buffer<T> = bufferFactory(size) { elementAlgebra.one }
+
+
+    override fun add(a: Buffer<T>, b: Buffer<T>): Buffer<T> = a.zip(b, elementAlgebra::add)
+    override fun multiply(a: Buffer<T>, b: Buffer<T>): Buffer<T> = a.zip(b, elementAlgebra::multiply)
+    override fun divide(a: Buffer<T>, b: Buffer<T>): Buffer<T> = a.zip(b, elementAlgebra::divide)
+
+    override fun scale(a: Buffer<T>, value: Double): Buffer<T> = with(elementAlgebra) { a.map { scale(it, value) } }
+    override fun Buffer<T>.unaryMinus(): Buffer<T> = with(elementAlgebra) { map { -it } }
+
+    override fun unaryOperationFunction(operation: String): (arg: Buffer<T>) -> Buffer<T> {
+        return super<BufferAlgebra>.unaryOperationFunction(operation)
+    }
+
+    override fun binaryOperationFunction(operation: String): (left: Buffer<T>, right: Buffer<T>) -> Buffer<T> {
+        return super<BufferAlgebra>.binaryOperationFunction(operation)
+    }
+}
+
+//Double buffer specialization
+
+@UnstableKMathAPI
+public fun BufferField<Double, *>.produce(vararg elements: Number): Buffer<Double> {
+    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<Double, DoubleField> =
+    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<Buffer<Double>> {
     override fun Buffer<Double>.unaryMinus(): DoubleBuffer = if (this is DoubleBuffer) {
         DoubleBuffer(size) { -array[it] }
@@ -172,6 +175,7 @@ public object DoubleL2Norm : Norm<Point<Double>, 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<Buffer<Double>>, Norm<Buffer<Double>, Double> {
     public override val zero: Buffer<Double> by lazy { DoubleBuffer(size) { 0.0 } }
     public override val one: Buffer<Double> by lazy { DoubleBuffer(size) { 1.0 } }

From 9b8da4cdcc07648dbd337a2aa254e1338325a966 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<F : Any> {
 
 public typealias FeatureKey<T> = KClass<out T>
 
-public interface Feature<F: Feature<F>> {
+public interface Feature<F : Feature<F>> {
 
     /**
      * A key used for extraction
      */
     @Suppress("UNCHECKED_CAST")
-    public val key: FeatureKey<F> get() = this::class as FeatureKey<F>
+    public val key: FeatureKey<F>
+        get() = this::class as FeatureKey<F>
 }
 
 /**
@@ -30,7 +31,7 @@ public interface Feature<F: Feature<F>> {
  */
 public class FeatureSet<F : Feature<F>> private constructor(public val features: Map<FeatureKey<F>, F>) : Featured<F> {
     @Suppress("UNCHECKED_CAST")
-    override fun <T : F> getFeature(type: FeatureKey<T>): T? = features[type] as? T
+    override fun <T : F> getFeature(type: FeatureKey<T>): T? = features[type]?.let { it as T }
 
     public inline fun <reified T : F> getFeature(): T? = getFeature(T::class)
 
@@ -49,6 +50,7 @@ public class FeatureSet<F : Feature<F>> private constructor(public val features:
 
     public companion object {
         public fun <F : Feature<F>> of(vararg features: F): FeatureSet<F> = FeatureSet(features.associateBy { it.key })
-        public fun <F : Feature<F>> of(features: Iterable<F>): FeatureSet<F> = FeatureSet(features.associateBy { it.key })
+        public fun <F : Feature<F>> of(features: Iterable<F>): FeatureSet<F> =
+            FeatureSet(features.associateBy { it.key })
     }
 }

From aaa298616d2668f80062079ede547f4228246b5e Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
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<Double> = { x: Double -> 3 * number(x).pow(2) + 2 * diagonal(x) + 1 }
+        val function: (Double) -> StructureND<Double> = { 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<Double, FunctionOptimization<Double>> {
 
             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<out T, out X : T, out Y : T> : ColumnarData<T> {
         Symbol.y -> y
         else -> null
     }
-}
 
-@Suppress("FunctionName")
-@UnstableKMathAPI
-public fun <T, X : T, Y : T> XYColumnarData(x: Buffer<X>, y: Buffer<Y>): XYColumnarData<T, X, Y> {
-    require(x.size == y.size) { "Buffer size mismatch. x buffer size is ${x.size}, y buffer size is ${y.size}" }
-    return object : XYColumnarData<T, X, Y> {
-        override val size: Int = x.size
-        override val x: Buffer<X> = x
-        override val y: Buffer<Y> = y
+    public companion object{
+        @UnstableKMathAPI
+        public fun <T, X : T, Y : T> of(x: Buffer<X>, y: Buffer<Y>): XYColumnarData<T, X, Y> {
+            require(x.size == y.size) { "Buffer size mismatch. x buffer size is ${x.size}, y buffer size is ${y.size}" }
+            return object : XYColumnarData<T, X, Y> {
+                override val size: Int = x.size
+                override val x: Buffer<X> = x
+                override val y: Buffer<Y> = 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<T, out X : T, out Y : T> : XYColumnarData<T
     override fun get(symbol: Symbol): Buffer<T> = 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 <T, X : T, Y : T> of(
+            x: Buffer<X>, y: Buffer<Y>, yErr: Buffer<Y>
+        ): XYErrorColumnarData<T, X, Y> {
+            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<T, X, Y> {
+                override val size: Int = x.size
+                override val x: Buffer<X> = x
+                override val y: Buffer<Y> = y
+                override val yErr: Buffer<Y> = 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<in T, E> : Algebra<E> {
 /**
  * Bind a symbol by name inside the [ExpressionAlgebra]
  */
-public fun <T, E> ExpressionAlgebra<T, E>.binding(): ReadOnlyProperty<Any?, E> = ReadOnlyProperty { _, property ->
-    bindSymbol(property.name) ?: error("A variable with name ${property.name} does not exist")
-}
+public val <T, E> ExpressionAlgebra<T, E>.binding: ReadOnlyProperty<Any?, E>
+    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 <T : Any, I : Any, A> AutoDiffProcessor<T, I, A>.chiSquaredExpression(
+@JvmName("genericChiSquaredExpression")
+public fun <T : Comparable<T>, I : Any, A> AutoDiffProcessor<T, I, A>.chiSquaredExpression(
     x: Buffer<T>,
     y: Buffer<T>,
     yErr: Buffer<T>,
@@ -35,4 +40,14 @@ public fun <T : Any, I : Any, A> AutoDiffProcessor<T, I, A>.chiSquaredExpression
 
         sum
     }
+}
+
+public fun <I : Any, A> AutoDiffProcessor<Double, I, A>.chiSquaredExpression(
+    x: Buffer<Double>,
+    y: Buffer<Double>,
+    yErr: Buffer<Double>,
+    model: A.(I) -> I,
+): DifferentiableExpression<Double> where A : ExtendedField<I>, A : ExpressionAlgebra<Double, I> {
+    require(yErr.asIterable().all { it > 0.0 }) { "All errors must be strictly positive" }
+    return chiSquaredExpression<Double, I, A>(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<F : Feature<F>> {
 /**
  * A container for a set of features
  */
-public class FeatureSet<F : Feature<F>> private constructor(public val features: Map<FeatureKey<F>, F>) : Featured<F> {
+@JvmInline
+public value class FeatureSet<F : Feature<F>> private constructor(public val features: Map<FeatureKey<F>, F>) : Featured<F> {
     @Suppress("UNCHECKED_CAST")
     override fun <T : F> getFeature(type: FeatureKey<T>): T? = features[type]?.let { it as T }
 
@@ -48,6 +50,9 @@ public class FeatureSet<F : Feature<F>> private constructor(public val features:
 
     public operator fun iterator(): Iterator<F> = features.values.iterator()
 
+    override fun toString(): String = features.values.joinToString(prefix = "[ ", postfix = " ]")
+
+
     public companion object {
         public fun <F : Feature<F>> of(vararg features: F): FeatureSet<F> = FeatureSet(features.associateBy { it.key })
         public fun <F : Feature<F>> of(features: Iterable<F>): FeatureSet<F> =
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 <T> FunctionalExpressionField<T, *>.expression(): Expression<T> {
-            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<Double, *>.() -> Expression<Double> = {
-            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<T : Any>(
 }
 
 /**
- * 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 <T : Any> Field<T>.gaussIntegrator: GaussIntegrator<T> 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 <T : Comparable<T>> PolynomialInterpolator<T>.interpolatePolynomials(
     x: Buffer<T>,
     y: Buffer<T>,
 ): PiecewisePolynomial<T> {
-    val pointSet = XYColumnarData(x, y)
+    val pointSet = XYColumnarData.of(x, y)
     return interpolatePolynomials(pointSet)
 }
 
 public fun <T : Comparable<T>> PolynomialInterpolator<T>.interpolatePolynomials(
     data: Map<T, T>,
 ): PiecewisePolynomial<T> {
-    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 <T : Comparable<T>> PolynomialInterpolator<T>.interpolatePolynomials(
     data: List<Pair<T, T>>,
 ): PiecewisePolynomial<T> {
-    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<T>(public val value: T) : OptimizationFeature{
+public class OptimizationValue<T>(public val value: T) : OptimizationFeature {
     override fun toString(): String = "Value($value)"
 }
 
@@ -23,9 +21,28 @@ public enum class FunctionOptimizationTarget : OptimizationFeature {
 public class FunctionOptimization<T>(
     override val features: FeatureSet<OptimizationFeature>,
     public val expression: DifferentiableExpression<T>,
-) : OptimizationProblem<T>{
+) : OptimizationProblem<T> {
 
-    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 <T> FunctionOptimization<T>.withFeatures(
@@ -47,7 +64,7 @@ public suspend fun <T : Any> DifferentiableExpression<T>.optimizeWith(
     return optimizer.optimize(problem)
 }
 
-public val <T> FunctionOptimization<T>.resultValueOrNull:T?
+public val <T> FunctionOptimization<T>.resultValueOrNull: T?
     get() = getFeature<OptimizationResult<T>>()?.point?.let { expression(it) }
 
 public val <T> FunctionOptimization<T>.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 <T> DifferentiableExpression<T>.optimizeWith(
 public class XYOptimizationBuilder(
     public val data: XYColumnarData<Double, Double, Double>,
     public val model: DifferentiableExpression<Double>,
-) : OptimizationBuilder<Double, XYOptimization>() {
+) : OptimizationBuilder<Double, XYFit>() {
 
     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<Double, Double, Double>,
     model: DifferentiableExpression<Double>,
     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<Double, XYOptimization> {
+public object QowOptimizer : Optimizer<Double, XYFit> {
 
     private val linearSpace: LinearSpace<Double, DoubleField> = LinearSpace.double
     private val solver: LinearSolver<Double> = linearSpace.lupSolver()
 
     @OptIn(UnstableKMathAPI::class)
-    private inner class QoWeight(
-        val problem: XYOptimization,
+    private class QoWeight(
+        val problem: XYFit,
         val parameters: Map<Symbol, Double>,
     ) : Map<Symbol, Double> by parameters, SymbolIndexer {
         override val symbols: List<Symbol> = parameters.keys.toList()
@@ -39,8 +40,8 @@ public class QowOptimizer : Optimizer<Double, XYOptimization> {
          * Derivatives of the spectrum over parameters. First index in the point number, second one - index of parameter
          */
         val derivs: Matrix<Double> 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<Double, XYOptimization> {
          * Array of dispersions in each point
          */
         val dispersion: Point<Double> 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<Double>? get() = problem.getFeature<OptimizationPrior<Double>>()
+
+        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<Symbol, Double>): Double = problem.distance(i)(parameters)
+    private fun QoWeight.distance(d: Int, parameters: Map<Symbol, Double>): Double = problem.distance(d)(parameters)
 
 
     /**
      * The derivative of [distance]
      */
-    private fun QoWeight.distanceDerivative(symbol: Symbol, i: Int, parameters: Map<Symbol, Double>): Double =
-        problem.distance(i).derivative(symbol)(parameters)
+    private fun QoWeight.distanceDerivative(symbol: Symbol, d: Int, parameters: Map<Symbol, Double>): Double =
+        problem.distance(d).derivative(symbol)(parameters)
 
     /**
      * Теоретическая ковариация весовых функций.
@@ -74,8 +77,8 @@ public class QowOptimizer : Optimizer<Double, XYOptimization> {
      * D(\phi)=E(\phi_k(\theta_0) \phi_l(\theta_0))= disDeriv_k * disDeriv_l /sigma^2
      */
     private fun QoWeight.covarF(): Matrix<Double> =
-        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<Double, XYOptimization> {
              * количество вызывов функции будет 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<Double, XYOptimization> {
     ): Matrix<Double> = 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<Double, XYOptimization> {
      * Значения уравнений метода квазиоптимальных весов
      */
     private fun QoWeight.getEqValues(theta: Map<Symbol, Double> = this): Point<Double> {
-        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<Double, XYOptimization> {
 
         val logger = problem.getFeature<OptimizationLog>()
 
-        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<Double, XYOptimization> {
         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<Double>
+
+    public companion object {
+        public val byY: PointToCurveDistance = object : PointToCurveDistance {
+            override fun distance(problem: XYFit, index: Int): DifferentiableExpression<Double> {
+                val x = problem.data.x[index]
+                val y = problem.data.y[index]
+
+                return object : DifferentiableExpression<Double> {
+                    override fun derivativeOrNull(
+                        symbols: List<Symbol>
+                    ): Expression<Double>? = problem.model.derivativeOrNull(symbols)?.let { derivExpression ->
+                        Expression { arguments ->
+                            derivExpression.invoke(arguments + (Symbol.x to x))
+                        }
+                    }
+
+                    override fun invoke(arguments: Map<Symbol, Double>): 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<Double>
+
+    public companion object {
+        public fun bySigma(sigmaSymbol: Symbol): PointWeight = object : PointWeight {
+            override fun weight(problem: XYFit, index: Int): DifferentiableExpression<Double> =
+                object : DifferentiableExpression<Double> {
+                    override fun invoke(arguments: Map<Symbol, Double>): Double {
+                        return problem.data[sigmaSymbol]?.get(index)?.pow(-2) ?: 1.0
+                    }
+
+                    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double> = 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<Double, Double, Double>,
+    public val model: DifferentiableExpression<Double>,
+    override val features: FeatureSet<OptimizationFeature>,
+    internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY,
+    internal val pointWeight: PointWeight = PointWeight.byYSigma,
+) : OptimizationProblem<Double> {
+    public fun distance(index: Int): DifferentiableExpression<Double> = pointToCurveDistance.distance(this, index)
+
+    public fun weight(index: Int): DifferentiableExpression<Double> = 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 <I : Any, A> XYColumnarData<Double, Double, Double>.fitWith(
+    optimizer: Optimizer<Double, XYFit>,
+    processor: AutoDiffProcessor<Double, I, A>,
+    startingPoint: Map<Symbol, Double>,
+    vararg features: OptimizationFeature = emptyArray(),
+    xSymbol: Symbol = Symbol.x,
+    pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY,
+    pointWeight: PointWeight = PointWeight.byYSigma,
+    model: A.(I) -> I
+): XYFit where A : ExtendedField<I>, A : ExpressionAlgebra<Double, I> {
+    val modelExpression = processor.differentiate {
+        val x = bindSymbol(xSymbol)
+        model(x)
+    }
+
+    var actualFeatures = FeatureSet.of(*features, OptimizationStartPoint(startingPoint))
+
+    if (actualFeatures.getFeature<OptimizationLog>() == 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<Double> = object : DifferentiableExpression<Double> {
+    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double> = 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<Symbol, Double>): 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<Double, FunctionOptimization<Double>>.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<Double, FunctionOptimization<Double>>.maximumLogLikelihood(
+    data: XYColumnarData<Double, Double, Double>,
+    model: DifferentiableExpression<Double>,
+    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<Double>
-
-    public companion object {
-        public val byY: PointToCurveDistance = object : PointToCurveDistance {
-            override fun distance(problem: XYOptimization, index: Int): DifferentiableExpression<Double> {
-
-                val x = problem.data.x[index]
-                val y = problem.data.y[index]
-                return object : DifferentiableExpression<Double> {
-                    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double>? =
-                        problem.model.derivativeOrNull(symbols)
-
-                    override fun invoke(arguments: Map<Symbol, Double>): 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<Double>
-
-    public companion object {
-        public fun bySigma(sigmaSymbol: Symbol): PointWeight = object : PointWeight {
-            override fun weight(problem: XYOptimization, index: Int): DifferentiableExpression<Double> =
-                object : DifferentiableExpression<Double> {
-                    override fun invoke(arguments: Map<Symbol, Double>): Double {
-                        return problem.data[sigmaSymbol]?.get(index)?.pow(-2) ?: 1.0
-                    }
-
-                    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double> = 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<OptimizationFeature>,
-    public val data: XYColumnarData<Double, Double, Double>,
-    public val model: DifferentiableExpression<Double>,
-    internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY,
-    internal val pointWeight: PointWeight = PointWeight.byYSigma,
-) : OptimizationProblem<Double> {
-    public fun distance(index: Int): DifferentiableExpression<Double> = pointToCurveDistance.distance(this, index)
-
-    public fun weight(index: Int): DifferentiableExpression<Double> = 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<Double> = object : DifferentiableExpression<Double> {
-    override fun derivativeOrNull(symbols: List<Symbol>): Expression<Double> = 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<Symbol, Double>): 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<Double, FunctionOptimization<Double>>.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<Double, FunctionOptimization<Double>>.maximumLogLikelihood(
-    data: XYColumnarData<Double, Double, Double>,
-    model: DifferentiableExpression<Double>,
-    builder: XYOptimizationBuilder.() -> Unit,
-): XYOptimization = maximumLogLikelihood(XYOptimization(data, model, builder))
-
-//public suspend fun XYColumnarData<Double, Double, Double>.fitWith(
-//    optimizer: XYOptimization,
-//    problemBuilder: XYOptimizationBuilder.() -> Unit = {},
-//
-//)
-
-
-//
-//@UnstableKMathAPI
-//public interface XYFit<T> : OptimizationProblem {
-//
-//    public val algebra: Field<T>
-//
-//    /**
-//     * Set X-Y data for this fit optionally including x and y errors
-//     */
-//    public fun data(
-//        dataSet: ColumnarData<T>,
-//        xSymbol: Symbol,
-//        ySymbol: Symbol,
-//        xErrSymbol: Symbol? = null,
-//        yErrSymbol: Symbol? = null,
-//    )
-//
-//    public fun model(model: (T) -> DifferentiableExpression<T, *>)
-//
-//    /**
-//     * Set the differentiable model for this fit
-//     */
-//    public fun <I : Any, A> model(
-//        autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-//        modelFunction: A.(I) -> I,
-//    ): Unit where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> = model { arg ->
-//        autoDiff.process { modelFunction(const(arg)) }
-//    }
-//}
-
-//
-///**
-// * Define a chi-squared-based objective function
-// */
-//public fun <T : Any, I : Any, A> FunctionOptimization<T>.chiSquared(
-//    autoDiff: AutoDiffProcessor<T, I, A, Expression<T>>,
-//    x: Buffer<T>,
-//    y: Buffer<T>,
-//    yErr: Buffer<T>,
-//    model: A.(I) -> I,
-//) where A : ExtendedField<I>, A : ExpressionAlgebra<T, I> {
-//    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 <altavir@gmail.com>
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<Double, XYFit> {
          */
         val dispersion: Point<Double> 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<OptimizationFeature>,
     internal val pointToCurveDistance: PointToCurveDistance = PointToCurveDistance.byY,
     internal val pointWeight: PointWeight = PointWeight.byYSigma,
+    public val xSymbol: Symbol = Symbol.x,
 ) : OptimizationProblem<Double> {
     public fun distance(index: Int): DifferentiableExpression<Double> = pointToCurveDistance.distance(this, index)
 
@@ -119,7 +121,26 @@ public suspend fun <I : Any, A> XYColumnarData<Double, Double, Double>.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<Double> = 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 <altavir@users.noreply.github.com>
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<T, R : OptimizationProblem<T>> {
-    public val features: ArrayList<OptimizationFeature> = ArrayList()
+    public val features: MutableList<OptimizationFeature> = ArrayList()
 
     public fun addFeature(feature: OptimizationFeature) {
         features.add(feature)

From 55ea3658b99a26200475fd705f208cd4acdc9f8b Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@users.noreply.github.com>
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`