Browse Source

Reworked objective model and nonlinear minimizers to match the scheme of the existing minimizers.

arrays
diluculo 8 years ago
parent
commit
19bd5f576d
  1. 280
      src/Numerics.Tests/OptimizationTests/NonLinearCurveFittingTests.cs
  2. 5
      src/Numerics/Optimization/IObjectiveModel.cs
  3. 76
      src/Numerics/Optimization/LevenbergMarquardtMinimizer.cs
  4. 28
      src/Numerics/Optimization/NonlinearMinimizationResult.cs
  5. 302
      src/Numerics/Optimization/NonlinearMinimizerBase.cs
  6. 106
      src/Numerics/Optimization/ObjectiveFunction.cs
  7. 436
      src/Numerics/Optimization/ObjectiveFunctions/NonlinearObjectiveFunction.cs
  8. 56
      src/Numerics/Optimization/ObjectiveModel.cs
  9. 730
      src/Numerics/Optimization/ObjectiveModels/FittingObjectiveModel.cs
  10. 81
      src/Numerics/Optimization/TrustRegionMinimizerBase.cs

280
src/Numerics.Tests/OptimizationTests/NonLinearCurveFittingTests.cs

@ -19,16 +19,23 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// best fitted parameters: // best fitted parameters:
// a = 1 // a = 1
// b = 1 // b = 1
private double RosenbrockModel(Vector<double> p, double x) private Vector<double> RosenbrockModel(Vector<double> p, Vector<double> x)
{ {
var y = Math.Pow(1.0 - p[0], 2) + 100.0 * Math.Pow(p[1] - p[0] * p[0], 2); var y = CreateVector.Dense<double>(x.Count);
for (int i = 0; i < x.Count; i++)
{
y[i] = Math.Pow(1.0 - p[0], 2) + 100.0 * Math.Pow(p[1] - p[0] * p[0], 2);
}
return y; return y;
} }
private Vector<double> RosenbrockPrime(Vector<double> p, double x) private Matrix<double> RosenbrockPrime(Vector<double> p, Vector<double> x)
{ {
var prime = Vector<double>.Build.Dense(p.Count); var prime = Matrix<double>.Build.Dense(x.Count, p.Count);
prime[0] = 400.0 * p[0] * p[0] * p[0] - 400.0 * p[0] * p[1] + 2.0 * p[0] - 2.0; for (int i = 0; i < x.Count; i++)
prime[1] = 200.0 * (p[1] - p[0] * p[0]); {
prime[i, 0] = 400.0 * p[0] * p[0] * p[0] - 400.0 * p[0] * p[1] + 2.0 * p[0] - 2.0;
prime[i, 1] = 200.0 * (p[1] - p[0] * p[0]);
}
return prime; return prime;
} }
private Vector<double> RosenbrockX = Vector<double>.Build.Dense(2); private Vector<double> RosenbrockX = Vector<double>.Build.Dense(2);
@ -39,29 +46,27 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
private Vector<double> RosebbrockLowerBound = new DenseVector(new double[] { -5.0, -5.0 }); private Vector<double> RosebbrockLowerBound = new DenseVector(new double[] { -5.0, -5.0 });
private Vector<double> RosenbrockUpperBound = new DenseVector(new double[] { 5.0, 5.0 }); private Vector<double> RosenbrockUpperBound = new DenseVector(new double[] { 5.0, 5.0 });
#endregion Rosenbrock
[Test] [Test]
public void Rosenbrock_LM_Der() public void Rosenbrock_LM_Der()
{ {
// unconstrained // unconstrained
var obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockPrime, RosenbrockX, RosenbrockY); var obj = ObjectiveFunction.NonlinearModel(RosenbrockModel, RosenbrockPrime, RosenbrockX, RosenbrockY);
var solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000); var solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000);
var result = solver.FindMinimum(obj, RosenbrockStart1); var result = solver.FindMinimum(obj, RosenbrockStart1);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 2);
} }
// box constrained // box constrained
obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockPrime, RosenbrockX, RosenbrockY); obj = ObjectiveFunction.NonlinearModel(RosenbrockModel, RosenbrockPrime, RosenbrockX, RosenbrockY);
solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000); solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000);
result = solver.FindMinimum(obj, RosenbrockStart1, lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound); result = solver.FindMinimum(obj, RosenbrockStart1, lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 2);
} }
} }
@ -69,52 +74,54 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void Rosenbrock_LM_Dif() public void Rosenbrock_LM_Dif()
{ {
// unconstrained // unconstrained
var obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder:2); var obj = ObjectiveFunction.NonlinearModel(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder:2);
var solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000); var solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000);
var result = solver.FindMinimum(obj, RosenbrockStart1); var result = solver.FindMinimum(obj, RosenbrockStart1);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 2);
} }
// box constrained // box constrained
obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6); obj = ObjectiveFunction.NonlinearModel(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6);
solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000); solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000);
result = solver.FindMinimum(obj, RosenbrockStart1, lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound); result = solver.FindMinimum(obj, RosenbrockStart1, lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 2);
} }
} }
[Test] [Test]
public void Rosenbrock_Bfgs_Dif() public void Rosenbrock_Bfgs_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6);
var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new BfgsMinimizer(1e-8, 1e-8, 1e-8, 1000);
var result = solver.FindMinimum(obj, RosenbrockStart1); var result = solver.FindMinimum(obj, RosenbrockStart1);
for (int i = 0; i < result.MinimizingPoint.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 3); AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 2);
} }
} }
[Test] [Test]
public void Rosenbrock_LBfgs_Dif() public void Rosenbrock_LBfgs_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6);
var solver = new LimitedMemoryBfgsMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new LimitedMemoryBfgsMinimizer(1e-8, 1e-8, 1e-8, 1000);
var result = solver.FindMinimum(obj, RosenbrockStart1); var result = solver.FindMinimum(obj, RosenbrockStart1);
for (int i = 0; i < result.MinimizingPoint.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 3); AssertHelpers.AlmostEqualRelative(RosenbrockPbest[i], result.MinimizingPoint[i], 2);
} }
} }
#endregion Rosenbrock
#region Rat43 #region Rat43
// model: Rat43 (https://www.itl.nist.gov/div898/strd/nls/data/ratkowsky3.shtml) // model: Rat43 (https://www.itl.nist.gov/div898/strd/nls/data/ratkowsky3.shtml)
@ -124,9 +131,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// b = 5.2771253025E+00 +/- 2.0828735829E+00 // b = 5.2771253025E+00 +/- 2.0828735829E+00
// c = 7.5962938329E-01 +/- 1.9566123451E-01 // c = 7.5962938329E-01 +/- 1.9566123451E-01
// d = 1.2792483859E+00 +/- 6.8761936385E-01 // d = 1.2792483859E+00 +/- 6.8761936385E-01
private double Rat43Model(Vector<double> p, double x) private Vector<double> Rat43Model(Vector<double> p, Vector<double> x)
{ {
var y = p[0] / Math.Pow(1.0 + Math.Exp(p[1] - p[2] * x), 1.0 / p[3]); var y = CreateVector.Dense<double>(x.Count);
for (int i = 0; i < x.Count; i++)
{
y[i] = p[0] / Math.Pow(1.0 + Math.Exp(p[1] - p[2] * x[i]), 1.0 / p[3]);
}
return y; return y;
} }
private Vector<double> Rat43X = new DenseVector(new double[] { private Vector<double> Rat43X = new DenseVector(new double[] {
@ -147,18 +158,16 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
private Vector<double> Rat43Start1 = new DenseVector(new double[] { 100, 10, 1, 1 }); private Vector<double> Rat43Start1 = new DenseVector(new double[] { 100, 10, 1, 1 });
private Vector<double> Rat43Start2 = new DenseVector(new double[] { 700, 5, 0.75, 1.3 }); private Vector<double> Rat43Start2 = new DenseVector(new double[] { 700, 5, 0.75, 1.3 });
#endregion Rat43
[Test] [Test]
public void Rat43_LM_Dif() public void Rat43_LM_Dif()
{ {
var obj = ObjectiveModel.FittingModel(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6);
var solver = new LevenbergMarquardtMinimizer(); var solver = new LevenbergMarquardtMinimizer();
var result = solver.FindMinimum(obj, Rat43Start1); var result = solver.FindMinimum(obj, Rat43Start1);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(Rat43Pbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(Rat43Pbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(Rat43Pstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(Rat43Pstd[i], result.StandardErrors[i], 6);
} }
} }
@ -166,13 +175,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Rat43_TRDL_Dif() public void Rat43_TRDL_Dif()
{ {
var obj = ObjectiveModel.FittingModel(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6);
var solver = new TrustRegionDogLegMinimizer(); var solver = new TrustRegionDogLegMinimizer();
var result = solver.FindMinimum(obj, Rat43Start2); var result = solver.FindMinimum(obj, Rat43Start2);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(Rat43Pbest[i], result.BestFitParameters[i], 2); AssertHelpers.AlmostEqualRelative(Rat43Pbest[i], result.MinimizingPoint[i], 2);
AssertHelpers.AlmostEqualRelative(Rat43Pstd[i], result.StandardErrors[i], 2); AssertHelpers.AlmostEqualRelative(Rat43Pstd[i], result.StandardErrors[i], 2);
} }
} }
@ -180,13 +189,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Rat43_TRNCG_Dif() public void Rat43_TRNCG_Dif()
{ {
var obj = ObjectiveModel.FittingModel(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6);
var solver = new TrustRegionNewtonCGMinimizer(); var solver = new TrustRegionNewtonCGMinimizer();
var result = solver.FindMinimum(obj, Rat43Start2); var result = solver.FindMinimum(obj, Rat43Start2);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(Rat43Pbest[i], result.BestFitParameters[i], 2); AssertHelpers.AlmostEqualRelative(Rat43Pbest[i], result.MinimizingPoint[i], 2);
AssertHelpers.AlmostEqualRelative(Rat43Pstd[i], result.StandardErrors[i], 2); AssertHelpers.AlmostEqualRelative(Rat43Pstd[i], result.StandardErrors[i], 2);
} }
} }
@ -194,7 +203,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Rat43_Bfgs_Dif() public void Rat43_Bfgs_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6);
var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 1000);
var result = solver.FindMinimum(obj, Rat43Start2); var result = solver.FindMinimum(obj, Rat43Start2);
@ -207,7 +216,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Rat43_LBfgs_Dif() public void Rat43_LBfgs_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(Rat43Model, Rat43X, Rat43Y, accuracyOrder: 6);
var solver = new LimitedMemoryBfgsMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new LimitedMemoryBfgsMinimizer(1e-10, 1e-10, 1e-10, 1000);
var result = solver.FindMinimum(obj, Rat43Start2); var result = solver.FindMinimum(obj, Rat43Start2);
@ -217,6 +226,8 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
} }
} }
#endregion Rat43
#region BoxBod #region BoxBod
// model: BoxBod (https://www.itl.nist.gov/div898/strd/nls/data/boxbod.shtml) // model: BoxBod (https://www.itl.nist.gov/div898/strd/nls/data/boxbod.shtml)
@ -227,16 +238,23 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// best fitted parameters: // best fitted parameters:
// a = 2.1380940889E+02 +/- 1.2354515176E+01 // a = 2.1380940889E+02 +/- 1.2354515176E+01
// b = 5.4723748542E-01 +/- 1.0455993237E-01 // b = 5.4723748542E-01 +/- 1.0455993237E-01
private double BoxBodModel(Vector<double> p, double x) private Vector<double> BoxBodModel(Vector<double> p, Vector<double> x)
{ {
var y = p[0] * (1.0 - Math.Exp(-p[1] * x)); var y = CreateVector.Dense<double>(x.Count);
for (int i = 0; i < x.Count; i++)
{
y[i] = p[0] * (1.0 - Math.Exp(-p[1] * x[i]));
}
return y; return y;
} }
private Vector<double> BoxBodPrime(Vector<double> p, double x) private Matrix<double> BoxBodPrime(Vector<double> p, Vector<double> x)
{ {
var prime = Vector<double>.Build.Dense(p.Count); var prime = Matrix<double>.Build.Dense(x.Count, p.Count);
prime[0] = 1.0 - Math.Exp(-p[1] * x); for (int i = 0; i < x.Count; i++)
prime[1] = p[0] * x * Math.Exp(-p[1] * x); {
prime[i, 0] = 1.0 - Math.Exp(-p[1] * x[i]);
prime[i, 1] = p[0] * x[i] * Math.Exp(-p[1] * x[i]);
}
return prime; return prime;
} }
private Vector<double> BoxBodX = new DenseVector(new double[] { 1, 2, 3, 5, 7, 10 }); private Vector<double> BoxBodX = new DenseVector(new double[] { 1, 2, 3, 5, 7, 10 });
@ -250,92 +268,90 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
private Vector<double> BoxBodUpperBound = new DenseVector(new double[] { 1000.0, 100 }); private Vector<double> BoxBodUpperBound = new DenseVector(new double[] { 1000.0, 100 });
private Vector<double> BoxBodScales = new DenseVector(new double[] { 100.0, 0.1 }); private Vector<double> BoxBodScales = new DenseVector(new double[] { 100.0, 0.1 });
#endregion BoxBod
[Test] [Test]
public void BoxBod_LM_Der() public void BoxBod_LM_Der()
{ {
// unconstrained // unconstrained
var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); var obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
var solver = new LevenbergMarquardtMinimizer(); var solver = new LevenbergMarquardtMinimizer();
var result = solver.FindMinimum(obj, BoxBodStart1); var result = solver.FindMinimum(obj, BoxBodStart1);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// lower < parameters < upper // lower < parameters < upper
// Note that in this case, scales have no effect. // Note that in this case, scales have no effect.
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// lower < parameters, no scales // lower < parameters, no scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// lower < parameters, scales // lower < parameters, scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, scales: BoxBodScales); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, scales: BoxBodScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// parameters < upper, no scales // parameters < upper, no scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, upperBound: BoxBodUpperBound); result = solver.FindMinimum(obj, BoxBodStart1, upperBound: BoxBodUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// parameters < upper, scales // parameters < upper, scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, upperBound: BoxBodUpperBound, scales: BoxBodScales); result = solver.FindMinimum(obj, BoxBodStart1, upperBound: BoxBodUpperBound, scales: BoxBodScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// only scales // only scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, scales: BoxBodScales); result = solver.FindMinimum(obj, BoxBodStart1, scales: BoxBodScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
} }
@ -344,24 +360,24 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
public void BoxBod_LM_Dif() public void BoxBod_LM_Dif()
{ {
// unconstrained // unconstrained
var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder:6); var obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder:6);
var solver = new LevenbergMarquardtMinimizer(); var solver = new LevenbergMarquardtMinimizer();
var result = solver.FindMinimum(obj, BoxBodStart1); var result = solver.FindMinimum(obj, BoxBodStart1);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
// box constrained // box constrained
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6); obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 6);
} }
} }
@ -369,13 +385,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void BoxBod_TRDL_Dif() public void BoxBod_TRDL_Dif()
{ {
var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6);
var solver = new TrustRegionDogLegMinimizer(); var solver = new TrustRegionDogLegMinimizer();
var result = solver.FindMinimum(obj, BoxBodStart1); var result = solver.FindMinimum(obj, BoxBodStart1);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 3);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 3); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 3);
} }
} }
@ -383,14 +399,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void BoxBod_TRNCG_Dif() public void BoxBod_TRNCG_Dif()
{ {
var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); var obj = ObjectiveFunction.NonlinearModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6);
//var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6);
var solver = new TrustRegionNewtonCGMinimizer(); var solver = new TrustRegionNewtonCGMinimizer();
var result = solver.FindMinimum(obj, BoxBodStart2); var result = solver.FindMinimum(obj, BoxBodStart2);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 3);
AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 3); AssertHelpers.AlmostEqualRelative(BoxBodPstd[i], result.StandardErrors[i], 3);
} }
} }
@ -398,7 +413,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void BoxBod_Bfgs_Der() public void BoxBod_Bfgs_Der()
{ {
var obj = ObjectiveModel.FittingFunction(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY); var obj = ObjectiveFunction.NonlinearFunction(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 100); var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 100);
var result = solver.FindMinimum(obj, BoxBodStart2); var result = solver.FindMinimum(obj, BoxBodStart2);
@ -408,6 +423,21 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
} }
} }
[Test]
public void BoxBod_Newton_Der()
{
var obj = ObjectiveFunction.NonlinearFunction(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
var solver = new NewtonMinimizer(1e-10, 100);
var result = solver.FindMinimum(obj, BoxBodStart2);
for (int i = 0; i < result.MinimizingPoint.Count; i++)
{
AssertHelpers.AlmostEqualRelative(BoxBodPbest[i], result.MinimizingPoint[i], 6);
}
}
#endregion BoxBod
#region Thurber #region Thurber
// model : Thurber (https://www.itl.nist.gov/div898/strd/nls/data/thurber.shtml) // model : Thurber (https://www.itl.nist.gov/div898/strd/nls/data/thurber.shtml)
@ -428,32 +458,38 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// b5 = 9.6629502864E-01 +/- 3.1333340687E-02 // b5 = 9.6629502864E-01 +/- 3.1333340687E-02
// b6 = 3.9797285797E-01 +/- 1.4984928198E-02 // b6 = 3.9797285797E-01 +/- 1.4984928198E-02
// b7 = 4.9727297349E-02 +/- 6.5842344623E-03 // b7 = 4.9727297349E-02 +/- 6.5842344623E-03
private double ThurberModel(Vector<double> p, double x) private Vector<double> ThurberModel(Vector<double> p, Vector<double> x)
{ {
var xSq = x * x; var y = CreateVector.Dense<double>(x.Count);
var xCb = xSq * x; for (int i = 0; i < x.Count; i++)
{
var xSq = x[i] * x[i];
var xCb = xSq * x[i];
var y = (p[0] + p[1] * x + p[2] * xSq + p[3] * xCb) y[i] = (p[0] + p[1] * x[i] + p[2] * xSq + p[3] * xCb)
/ (1 + p[4] * x + p[5] * xSq + p[6] * xCb); / (1 + p[4] * x[i] + p[5] * xSq + p[6] * xCb);
}
return y; return y;
} }
private Vector<double> ThurberPrime(Vector<double> p, double x) private Matrix<double> ThurberPrime(Vector<double> p, Vector<double> x)
{ {
var prime = Vector<double>.Build.Dense(p.Count); var prime = Matrix<double>.Build.Dense(x.Count, p.Count);
for (int i = 0; i < x.Count; i++)
var xSq = x * x; {
var xCb = xSq * x; var xSq = x[i] * x[i];
var num = (p[0] + x * (p[1] + x * (p[2] + p[3] * x))); var xCb = xSq * x[i];
var den = (p[4] * x + p[5] * xSq + p[6] * xCb + 1.0); var num = p[0] + x[i] * (p[1] + x[i] * (p[2] + p[3] * x[i]));
var denSq = den * den; var den = p[4] * x[i] + p[5] * xSq + p[6] * xCb + 1.0;
var denSq = den * den;
prime[0] = 1.0 / den;
prime[1] = x / den; prime[i, 0] = 1.0 / den;
prime[2] = xSq / den; prime[i, 1] = x[i] / den;
prime[3] = xCb / den; prime[i, 2] = xSq / den;
prime[4] = -(x * num) / denSq; prime[i, 3] = xCb / den;
prime[5] = -(xSq * num) / denSq; prime[i, 4] = -(x[i] * num) / denSq;
prime[6] = -(xCb * num) / denSq; prime[i, 5] = -(xSq * num) / denSq;
prime[i, 6] = -(xCb * num) / denSq;
}
return prime; return prime;
} }
private Vector<double> ThurberX = new DenseVector(new double[] { private Vector<double> ThurberX = new DenseVector(new double[] {
@ -485,18 +521,16 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
private Vector<double> ThurberUpperBound = new DenseVector(new double[] { 1E6, 1E6, 1E6, 1E6, 1E6, 1E6, 1E6 }); private Vector<double> ThurberUpperBound = new DenseVector(new double[] { 1E6, 1E6, 1E6, 1E6, 1E6, 1E6, 1E6 });
private Vector<double> ThurberScales = new DenseVector(new double[7] { 1000, 1000, 400, 40, 0.7, 0.3, 0.03 }); private Vector<double> ThurberScales = new DenseVector(new double[7] { 1000, 1000, 400, 40, 0.7, 0.3, 0.03 });
#endregion Thurber
[Test] [Test]
public void Thurber_LM_Der() public void Thurber_LM_Der()
{ {
var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberPrime, ThurberX, ThurberY); var obj = ObjectiveFunction.NonlinearModel(ThurberModel, ThurberPrime, ThurberX, ThurberY);
var solver = new LevenbergMarquardtMinimizer(); var solver = new LevenbergMarquardtMinimizer();
var result = solver.FindMinimum(obj, ThurberStart); var result = solver.FindMinimum(obj, ThurberStart);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 6);
} }
} }
@ -504,13 +538,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_LM_Dif() public void Thurber_LM_Dif()
{ {
var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
var solver = new LevenbergMarquardtMinimizer(); var solver = new LevenbergMarquardtMinimizer();
var result = solver.FindMinimum(obj, ThurberStart); var result = solver.FindMinimum(obj, ThurberStart);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.BestFitParameters[i], 6); AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.MinimizingPoint[i], 6);
AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 6); AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 6);
} }
} }
@ -518,13 +552,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_TRDL_Dif() public void Thurber_TRDL_Dif()
{ {
var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
var solver = new TrustRegionDogLegMinimizer(); var solver = new TrustRegionDogLegMinimizer();
var result = solver.FindMinimum(obj, ThurberStart, scales: ThurberScales); var result = solver.FindMinimum(obj, ThurberStart, scales: ThurberScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.MinimizingPoint[i], 3);
AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 3); AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 3);
} }
} }
@ -532,13 +566,13 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_TRNCG_Dif() public void Thurber_TRNCG_Dif()
{ {
var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
var solver = new TrustRegionNewtonCGMinimizer(); var solver = new TrustRegionNewtonCGMinimizer();
var result = solver.FindMinimum(obj, ThurberStart, scales: ThurberScales); var result = solver.FindMinimum(obj, ThurberStart, scales: ThurberScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.MinimizingPoint.Count; i++)
{ {
AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.BestFitParameters[i], 3); AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.MinimizingPoint[i], 3);
AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 3); AssertHelpers.AlmostEqualRelative(ThurberPstd[i], result.StandardErrors[i], 3);
} }
} }
@ -546,7 +580,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_Bfgs_Dif() public void Thurber_Bfgs_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new BfgsMinimizer(1e-10, 1e-10, 1e-10, 1000);
var result = solver.FindMinimum(obj, ThurberStart); var result = solver.FindMinimum(obj, ThurberStart);
@ -559,7 +593,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_BfgsB_Dif() public void Thurber_BfgsB_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
var solver = new BfgsBMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new BfgsBMinimizer(1e-10, 1e-10, 1e-10, 1000);
var result = solver.FindMinimum(obj, ThurberLowerBound, ThurberUpperBound, ThurberStart); var result = solver.FindMinimum(obj, ThurberLowerBound, ThurberUpperBound, ThurberStart);
@ -572,7 +606,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_LBfgs_Dif() public void Thurber_LBfgs_Dif()
{ {
var obj = ObjectiveModel.FittingFunction(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6); var obj = ObjectiveFunction.NonlinearFunction(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
var solver = new LimitedMemoryBfgsMinimizer(1e-10, 1e-10, 1e-10, 1000); var solver = new LimitedMemoryBfgsMinimizer(1e-10, 1e-10, 1e-10, 1000);
var result = solver.FindMinimum(obj, ThurberStart); var result = solver.FindMinimum(obj, ThurberStart);
@ -581,5 +615,7 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.MinimizingPoint[i], 6); AssertHelpers.AlmostEqualRelative(ThurberPbest[i], result.MinimizingPoint[i], 6);
} }
} }
#endregion Thurber
} }
} }

5
src/Numerics/Optimization/IObjectiveModel.cs

@ -59,17 +59,14 @@ namespace MathNet.Numerics.Optimization
bool IsGradientSupported { get; } bool IsGradientSupported { get; }
bool IsHessianSupported { get; } bool IsHessianSupported { get; }
bool IsFinished { get; set; }
} }
public interface IObjectiveModel : IObjectiveModelEvaluation public interface IObjectiveModel : IObjectiveModelEvaluation
{ {
void SetParameters(Vector<double> initialGuess, Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null); void SetParameters(Vector<double> initialGuess, List<bool> isFixed = null);
void EvaluateAt(Vector<double> parameters); void EvaluateAt(Vector<double> parameters);
/// <summary>Create a new independent copy of this objective function, evaluated at the same point.</summary>
IObjectiveModel Fork(); IObjectiveModel Fork();
IObjectiveFunction ToObjectiveFunction(); IObjectiveFunction ToObjectiveFunction();

76
src/Numerics/Optimization/LevenbergMarquardtMinimizer.cs

@ -5,55 +5,23 @@ using System.Linq;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
{ {
public sealed class LevenbergMarquardtMinimizer public class LevenbergMarquardtMinimizer : NonlinearMinimizerBase
{ {
#region Tolerances and options
/// <summary> /// <summary>
/// The scale factor for initial mu /// The scale factor for initial mu
/// </summary> /// </summary>
public static double InitialMu { get; set; } public static double InitialMu { get; set; }
/// <summary> public LevenbergMarquardtMinimizer(double initialMu = 1E-3, double gradientTolerance = 1E-15, double stepTolerance = 1E-15, double functionTolerance = 1E-15, int maximumIterations = -1)
/// The stopping threshold for infinity norm of the gradient. : base(gradientTolerance, stepTolerance, functionTolerance, maximumIterations)
/// </summary>
public static double GradientTolerance { get; set; }
/// <summary>
/// The stopping threshold for L2 norm of the change of the parameters.
/// </summary>
public static double StepTolerance { get; set; }
/// <summary>
/// The stopping threshold for the function value or L2 norm of the residuals.
/// </summary>
public static double FunctionTolerance { get; set; }
/// <summary>
/// The maximum number of iterations.
/// </summary>
public int MaximumIterations { get; set; }
#endregion Tolerances and options
public LevenbergMarquardtMinimizer(double initialMu = 1E-3, double gradientTolerance = 1E-18, double stepTolerance = 1E-18, double functionTolerance = 1E-18, int maximumIterations = -1)
{ {
InitialMu = initialMu; InitialMu = initialMu;
GradientTolerance = gradientTolerance;
StepTolerance = stepTolerance;
FunctionTolerance = functionTolerance;
MaximumIterations = maximumIterations;
} }
public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, Vector<double> initialGuess, public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, Vector<double> initialGuess,
Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null) Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null)
{ {
if (objective == null) return Minimum(objective, initialGuess, lowerBound, upperBound, scales, isFixed, InitialMu, GradientTolerance, StepTolerance, FunctionTolerance, MaximumIterations);
throw new ArgumentNullException("objective");
if (initialGuess == null)
throw new ArgumentNullException("initialGuess");
return Minimum(objective, initialGuess, lowerBound, upperBound, scales, isFixed, InitialMu, FunctionTolerance, GradientTolerance, StepTolerance, MaximumIterations);
} }
public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, double[] initialGuess, public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, double[] initialGuess,
@ -85,7 +53,7 @@ namespace MathNet.Numerics.Optimization
/// <returns>The result of the Levenberg-Marquardt minimization</returns> /// <returns>The result of the Levenberg-Marquardt minimization</returns>
public static NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<double> initialGuess, public static NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<double> initialGuess,
Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null, Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null,
double initialMu = 1E-3, double gradientTolerance = 1E-18, double stepTolerance = 1E-18, double functionTolerance = 1E-18, int maximumIterations = -1) double initialMu = 1E-3, double gradientTolerance = 1E-15, double stepTolerance = 1E-15, double functionTolerance = 1E-15, int maximumIterations = -1)
{ {
// Non-linear least square fitting by the Levenberg-Marduardt algorithm. // Non-linear least square fitting by the Levenberg-Marduardt algorithm.
// //
@ -125,23 +93,16 @@ namespace MathNet.Numerics.Optimization
if (objective == null) if (objective == null)
throw new ArgumentNullException("objective"); throw new ArgumentNullException("objective");
if (initialGuess == null) ValidateBounds(initialGuess, lowerBound, upperBound, scales);
throw new ArgumentNullException("initialGuess");
objective.SetParameters(initialGuess, isFixed);
objective.SetParameters(initialGuess, lowerBound, upperBound, scales, isFixed);
ExitCondition exitCondition = ExitCondition.None; ExitCondition exitCondition = ExitCondition.None;
// Initialize objective
objective.FunctionEvaluations = 0;
objective.JacobianEvaluations = 0;
objective.IsFinished = false;
// First, calculate function values and setup variables // First, calculate function values and setup variables
objective.EvaluateAt(initialGuess); var P = ProjectToInternalParameters(initialGuess); // current internal parameters
var P = objective.Point; // current parameters
var Pstep = Vector<double>.Build.Dense(P.Count); // the change of parameters var Pstep = Vector<double>.Build.Dense(P.Count); // the change of parameters
var RSS = objective.Value; // Residual Sum of Squares = R'R var RSS = EvaluateFunction(objective, P); // Residual Sum of Squares = R'R
if (maximumIterations < 0) if (maximumIterations < 0)
{ {
@ -168,8 +129,9 @@ namespace MathNet.Numerics.Optimization
} }
// Evaluate gradient and Hessian // Evaluate gradient and Hessian
var Gradient = objective.Gradient; var jac = EvaluateJacobian(objective, P);
var Hessian = objective.Hessian; var Gradient = jac.Item1; // objective.Gradient;
var Hessian = jac.Item2; // objective.Hessian;
var diagonalOfHessian = Hessian.Diagonal(); // diag(H) var diagonalOfHessian = Hessian.Diagonal(); // diag(H)
// if ||g||oo <= gtol, found and stop // if ||g||oo <= gtol, found and stop
@ -200,14 +162,13 @@ namespace MathNet.Numerics.Optimization
// if ||ΔP|| <= xTol * (||P|| + xTol), found and stop // if ||ΔP|| <= xTol * (||P|| + xTol), found and stop
if (Pstep.L2Norm() <= stepTolerance * (stepTolerance + P.DotProduct(P))) if (Pstep.L2Norm() <= stepTolerance * (stepTolerance + P.DotProduct(P)))
{ {
exitCondition = ExitCondition.RelativePoints; // SmallRelativeParameters exitCondition = ExitCondition.RelativePoints;
break; break;
} }
var Pnew = P + Pstep; // new parameters to test var Pnew = P + Pstep; // new parameters to test
// evaluate function at Pnew
objective.EvaluateAt(Pnew); var RSSnew = EvaluateFunction(objective, Pnew);
var RSSnew = objective.Value;
if (double.IsNaN(RSSnew)) if (double.IsNaN(RSSnew))
{ {
@ -229,8 +190,9 @@ namespace MathNet.Numerics.Optimization
RSS = RSSnew; RSS = RSSnew;
// update gradient and Hessian // update gradient and Hessian
Gradient = objective.Gradient; jac = EvaluateJacobian(objective, P);
Hessian = objective.Hessian; Gradient = jac.Item1; // objective.Gradient;
Hessian = jac.Item2; // objective.Hessian;
diagonalOfHessian = Hessian.Diagonal(); diagonalOfHessian = Hessian.Diagonal();
// if ||g||_oo <= gtol, found and stop // if ||g||_oo <= gtol, found and stop

28
src/Numerics/Optimization/NonlinearMinimizationResult.cs

@ -1,8 +1,4 @@
using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearAlgebra;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
{ {
@ -13,7 +9,7 @@ namespace MathNet.Numerics.Optimization
/// <summary> /// <summary>
/// Returns the best fit parameters. /// Returns the best fit parameters.
/// </summary> /// </summary>
public Vector<double> BestFitParameters { get { return ModelInfoAtMinimum.Point; } } public Vector<double> MinimizingPoint { get { return ModelInfoAtMinimum.Point; } }
/// <summary> /// <summary>
/// Returns the standard errors of the corresponding parameters /// Returns the standard errors of the corresponding parameters
@ -23,17 +19,20 @@ namespace MathNet.Numerics.Optimization
/// <summary> /// <summary>
/// Returns the y-values of the fitted model that correspond to the independent values. /// Returns the y-values of the fitted model that correspond to the independent values.
/// </summary> /// </summary>
public Vector<double> BestFitValues { get { return ModelInfoAtMinimum.ModelValues; } } public Vector<double> MinimizedValues { get { return ModelInfoAtMinimum.ModelValues; } }
/// <summary> /// <summary>
/// Returns the residual sum of squares. /// Returns the covariance matrix at minimizing point.
/// </summary> /// </summary>
public double Residue { get { return ModelInfoAtMinimum.Value; } }
public double DegreeOfFreedom { get { return ModelInfoAtMinimum.DegreeOfFreedom; } }
public Matrix<double> Covariance { get; private set; } public Matrix<double> Covariance { get; private set; }
/// <summary>
/// Returns the correlation matrix at minimizing point.
/// </summary>
public Matrix<double> Correlation { get; private set; } public Matrix<double> Correlation { get; private set; }
public int Iterations { get; private set; } public int Iterations { get; private set; }
public ExitCondition ReasonForExit { get; private set; } public ExitCondition ReasonForExit { get; private set; }
public NonlinearMinimizationResult(IObjectiveModel modelInfo, int iterations, ExitCondition reasonForExit) public NonlinearMinimizationResult(IObjectiveModel modelInfo, int iterations, ExitCondition reasonForExit)
@ -42,16 +41,15 @@ namespace MathNet.Numerics.Optimization
Iterations = iterations; Iterations = iterations;
ReasonForExit = reasonForExit; ReasonForExit = reasonForExit;
AnalyzeResult(modelInfo); EvaluateCovariance(modelInfo);
} }
private void AnalyzeResult(IObjectiveModel objective) private void EvaluateCovariance(IObjectiveModel objective)
{ {
objective.IsFinished = true; objective.EvaluateAt(objective.Point); // Hessian may be not yet updated.
objective.EvaluateAt(objective.Point);
var Hessian = objective.Hessian; var Hessian = objective.Hessian;
if (Hessian == null || DegreeOfFreedom < 1) if (Hessian == null || objective.DegreeOfFreedom < 1)
{ {
Covariance = null; Covariance = null;
Correlation = null; Correlation = null;
@ -59,7 +57,7 @@ namespace MathNet.Numerics.Optimization
return; return;
} }
Covariance = Hessian.PseudoInverse() * objective.Value / DegreeOfFreedom; Covariance = Hessian.PseudoInverse() * objective.Value / objective.DegreeOfFreedom;
if (Covariance != null) if (Covariance != null)
{ {

302
src/Numerics/Optimization/NonlinearMinimizerBase.cs

@ -0,0 +1,302 @@
using MathNet.Numerics.LinearAlgebra;
using System;
using System.Linq;
namespace MathNet.Numerics.Optimization
{
public abstract class NonlinearMinimizerBase
{
/// <summary>
/// The stopping threshold for the function value or L2 norm of the residuals.
/// </summary>
public static double FunctionTolerance { get; set; }
/// <summary>
/// The stopping threshold for L2 norm of the change of the parameters.
/// </summary>
public static double StepTolerance { get; set; }
/// <summary>
/// The stopping threshold for infinity norm of the gradient.
/// </summary>
public static double GradientTolerance { get; set; }
/// <summary>
/// The maximum number of iterations.
/// </summary>
public static int MaximumIterations { get; set; }
/// <summary>
/// The lower bound of the parameters.
/// </summary>
public static Vector<double> LowerBound { get; private set; }
/// <summary>
/// The upper bound of the parameters.
/// </summary>
public static Vector<double> UpperBound { get; private set; }
/// <summary>
/// The scale factors for the parameters.
/// </summary>
public static Vector<double> Scales { get; private set; }
private static bool IsBounded { get { return LowerBound != null || UpperBound != null || Scales != null; } }
protected NonlinearMinimizerBase(double gradientTolerance = 1E-18, double stepTolerance = 1E-18, double functionTolerance = 1E-18, int maximumIterations = -1)
{
GradientTolerance = gradientTolerance;
StepTolerance = stepTolerance;
FunctionTolerance = functionTolerance;
MaximumIterations = maximumIterations;
}
protected static void ValidateBounds(Vector<double> parameters, Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null)
{
if (parameters == null)
{
throw new ArgumentNullException("parameters");
}
if (lowerBound != null && lowerBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{
throw new ArgumentException("The lower bounds must be finite.");
}
if (lowerBound != null && lowerBound.Count != parameters.Count)
{
throw new ArgumentException("The lower bounds can't have different size from the parameters.");
}
LowerBound = lowerBound;
if (upperBound != null && upperBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{
throw new ArgumentException("The upper bounds must be finite.");
}
if (upperBound != null && upperBound.Count != parameters.Count)
{
throw new ArgumentException("The upper bounds can't have different size from the parameetrs.");
}
UpperBound = upperBound;
if (scales != null && scales.Count(x => double.IsInfinity(x) || double.IsNaN(x) || x == 0) > 0)
{
throw new ArgumentException("The scales must be finite.");
}
if (scales != null && scales.Count != parameters.Count)
{
throw new ArgumentException("The scales can't have different size from the parameters.");
}
if (scales != null && scales.Count(x => x < 0) > 0)
{
scales.PointwiseAbs();
}
Scales = scales;
}
protected static double EvaluateFunction(IObjectiveModel objective, Vector<double> Pint)
{
var Pext = ProjectToExternalParameters(Pint);
objective.EvaluateAt(Pext);
return objective.Value;
}
protected static Tuple<Vector<double>, Matrix<double>> EvaluateJacobian(IObjectiveModel objective, Vector<double> Pint)
{
var gradient = objective.Gradient;
var hessian = objective.Hessian;
if (IsBounded)
{
var scaleFactors = ScaleFactorsOfJacobian(Pint); // the parameters argument is always internal.
for (int i = 0; i < gradient.Count; i++)
{
gradient[i] = gradient[i] * scaleFactors[i];
}
for (int i = 0; i < hessian.RowCount; i++)
{
for (int j = 0; j < hessian.ColumnCount; j++)
{
hessian[i, j] = hessian[i, j] * scaleFactors[i] * scaleFactors[j];
}
}
}
return new Tuple<Vector<double>, Matrix<double>>(gradient, hessian);
}
#region Projection of Parameters
// To handle the box constrained minimization as the unconstrained minimization,
// the parameters are mapping by the following rules,
// which are modified the rules shown in the ref[1] in order to introduce scales.
//
// 1. lower < Pext < upper
// Pint = asin(2 * (Pext - lower) / (upper - lower) - 1)
// Pext = lower + (sin(Pint) + 1) * (upper - lower) / 2
// dPext/dPint = (upper - lower) / 2 * cos(Pint)
//
// 2. lower < Pext
// Pint = sqrt((Pext/scale - lower/scale + 1)^2 - 1)
// Pext = lower + scale * (sqrt(Pint^2 + 1) - 1)
// dPext/dPint = scale * Pint / sqrt(Pint^2 + 1)
//
// 3. Pext < upper
// Pint = sqrt((upper / scale - Pext / scale + 1)^2 - 1)
// Pext = upper + scale - scale * sqrt(Pint^2 + 1)
// dPext/dPint = - scale * Pint / sqrt(Pint^2 + 1)
//
// 4. no bounds, but scales
// Pint = Pext / scale
// Pext = Pint * scale
// dPext/dPint = scale
//
// The rules are applied in ProjectParametersToInternal, ProjectParametersToExternal, and ScaleFactorsOfJacobian methods.
//
// References:
// [1] https://lmfit.github.io/lmfit-py/bounds.html
// [2] MINUIT User's Guide, https://root.cern.ch/download/minuit.pdf
//
// Except when it is initial guess, the parameters argument is always internal parameter.
// So, first map the parameters argument to the external parameters in order to calculate function values.
protected static Vector<double> ProjectToInternalParameters(Vector<double> Pext)
{
var Pint = Pext.Clone();
if (LowerBound != null && UpperBound != null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = Math.Asin((2.0 * (Pext[i] - LowerBound[i]) / (UpperBound[i] - LowerBound[i])) - 1.0);
}
return Pint;
}
else if (LowerBound != null && UpperBound == null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = (Scales == null)
? Math.Sqrt(Math.Pow(Pext[i] - LowerBound[i] + 1.0, 2) - 1.0)
: Math.Sqrt(Math.Pow((Pext[i] - LowerBound[i]) / Scales[i] + 1.0, 2) - 1.0);
}
return Pint;
}
else if (LowerBound == null && UpperBound != null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = (Scales == null)
? Math.Sqrt(Math.Pow(UpperBound[i] - Pext[i] + 1.0, 2) - 1.0)
: Math.Sqrt(Math.Pow((UpperBound[i] - Pext[i]) / Scales[i] + 1.0, 2) - 1.0);
}
return Pint;
}
else if (Scales != null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = Pext[i] / Scales[i];
}
return Pint;
}
return Pint;
}
protected static Vector<double> ProjectToExternalParameters(Vector<double> Pint)
{
var Pext = Pint.Clone();
if (LowerBound != null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = LowerBound[i] + (UpperBound[i] / 2.0 - LowerBound[i] / 2.0) * (Math.Sin(Pint[i]) + 1.0);
}
return Pext;
}
else if (LowerBound != null && UpperBound == null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = (Scales == null)
? LowerBound[i] + Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0
: LowerBound[i] + Scales[i] * (Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0);
}
return Pext;
}
else if (LowerBound == null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = (Scales == null)
? UpperBound[i] - Math.Sqrt(Pint[i] * Pint[i] + 1.0) + 1.0
: UpperBound[i] - Scales[i] * (Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0);
}
return Pext;
}
else if (Scales != null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = Pint[i] * Scales[i];
}
return Pext;
}
return Pext;
}
protected static Vector<double> ScaleFactorsOfJacobian(Vector<double> Pint)
{
var scale = Vector<double>.Build.Dense(Pint.Count, 1.0);
if (LowerBound != null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
scale[i] = (UpperBound[i] - LowerBound[i]) / 2.0 * Math.Cos(Pint[i]);
}
return scale;
}
else if (LowerBound != null && UpperBound == null)
{
for (int i = 0; i < Pint.Count; i++)
{
scale[i] = (Scales == null)
? Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0)
: Scales[i] * Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0);
}
return scale;
}
else if (LowerBound == null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
scale[i] = (Scales == null)
? -Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0)
: -Scales[i] * Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0);
}
return scale;
}
else if (Scales != null)
{
return Scales;
}
return scale;
}
#endregion Projection of Parameters
}
}

106
src/Numerics/Optimization/ObjectiveFunction.cs

@ -114,5 +114,111 @@ namespace MathNet.Numerics.Optimization
{ {
return new ScalarObjectiveFunction(function, derivative, secondDerivative); return new ScalarObjectiveFunction(function, derivative, secondDerivative);
} }
/// <summary>
/// objective model with a user supplied jacobian for non-linear least squares regression.
/// </summary>
public static IObjectiveModel NonlinearModel(Func<Vector<double>, Vector<double>, Vector<double>> function,
Func<Vector<double>, Vector<double>, Matrix<double>> derivatives,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null)
{
var objective = new NonlinearObjectiveFunction(function, derivatives);
objective.SetObserved(observedX, observedY, weight);
return objective;
}
/// <summary>
/// Objective model for non-linear least squares regression.
/// </summary>
public static IObjectiveModel NonlinearModel(Func<Vector<double>, Vector<double>, Vector<double>> function,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null,
int accuracyOrder = 2)
{
var objective = new NonlinearObjectiveFunction(function, accuracyOrder: accuracyOrder);
objective.SetObserved(observedX, observedY, weight);
return objective;
}
/// <summary>
/// Objective model with a user supplied jacobian for non-linear least squares regression.
/// </summary>
public static IObjectiveModel NonlinearModel(Func<Vector<double>, double, double> function,
Func<Vector<double>, double, Vector<double>> derivatives,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null)
{
Vector<double> func(Vector<double> point, Vector<double> x)
{
var functionValues = CreateVector.Dense<double>(x.Count);
for (int i = 0; i < x.Count; i++)
{
functionValues[i] = function(point, x[i]);
}
return functionValues;
}
Matrix<double> prime(Vector<double> point, Vector<double> x)
{
var derivativeValues = CreateMatrix.Dense<double>(x.Count, point.Count);
for (int i = 0; i < x.Count; i++)
{
derivativeValues.SetRow(i, derivatives(point, x[i]));
}
return derivativeValues;
}
var objective = new NonlinearObjectiveFunction(func, prime);
objective.SetObserved(observedX, observedY, weight);
return objective;
}
/// <summary>
/// Objective model for non-linear least squares regression.
/// </summary>
public static IObjectiveModel NonlinearModel(Func<Vector<double>, double, double> function,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null,
int accuracyOrder = 2)
{
Vector<double> func(Vector<double> point, Vector<double> x)
{
var functionValues = CreateVector.Dense<double>(x.Count);
for (int i = 0; i < x.Count; i++)
{
functionValues[i] = function(point, x[i]);
}
return functionValues;
}
var objective = new NonlinearObjectiveFunction(func, accuracyOrder: accuracyOrder);
objective.SetObserved(observedX, observedY, weight);
return objective;
}
/// <summary>
/// Objective function with a user supplied jacobian for nonlinear least squares regression.
/// </summary>
public static IObjectiveFunction NonlinearFunction(Func<Vector<double>, Vector<double>, Vector<double>> function,
Func<Vector<double>, Vector<double>, Matrix<double>> derivatives,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null)
{
var objective = new NonlinearObjectiveFunction(function, derivatives);
objective.SetObserved(observedX, observedY, weight);
return objective.ToObjectiveFunction();
}
/// <summary>
/// Objective function for nonlinear least squares regression.
/// The numerical jacobian with accuracy order is used.
/// </summary>
public static IObjectiveFunction NonlinearFunction(Func<Vector<double>, Vector<double>, Vector<double>> function,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null,
int accuracyOrder = 2)
{
var objective = new NonlinearObjectiveFunction(function, null, accuracyOrder: accuracyOrder);
objective.SetObserved(observedX, observedY, weight);
return objective.ToObjectiveFunction();
}
} }
} }

436
src/Numerics/Optimization/ObjectiveFunctions/NonlinearObjectiveFunction.cs

@ -0,0 +1,436 @@
using MathNet.Numerics.LinearAlgebra;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MathNet.Numerics.Optimization.ObjectiveFunctions
{
internal class NonlinearObjectiveFunction : IObjectiveModel
{
#region Private Variables
readonly Func<Vector<double>, Vector<double>, Vector<double>> userFunction; // (p, x) => f(x; p)
readonly Func<Vector<double>, Vector<double>, Matrix<double>> userDerivative; // (p, x) => df(x; p)/dp
readonly int accuracyOrder; // the desired accuracy order to evaluate the jacobian by numerical approximaiton.
Vector<double> coefficients;
bool hasFunctionValue;
double functionValue; // the residual sum of squares, residuals * residuals.
Vector<double> residuals; // the weighted error values
bool hasJacobianValue;
Matrix<double> jacobianValue; // the Jacobian matrix.
Vector<double> gradientValue; // the Gradient vector.
Matrix<double> hessianValue; // the Hessian matrix.
#endregion Private Variables
#region Public Variables
/// <summary>
/// Set or get the values of the independent variable.
/// </summary>
public Vector<double> ObservedX { get; private set; }
/// <summary>
/// Set or get the values of the observations.
/// </summary>
public Vector<double> ObservedY { get; private set; }
/// <summary>
/// Set or get the values of the weights for the observations.
/// </summary>
public Matrix<double> Weights { get; private set; }
private Vector<double> L; // Weights = LL'
/// <summary>
/// Get whether parameters are fixed or free.
/// </summary>
public List<bool> IsFixed { get; private set; }
/// <summary>
/// Get the number of observations.
/// </summary>
public int NumberOfObservations { get { return (ObservedY == null) ? 0 : ObservedY.Count; } }
/// <summary>
/// Get the number of unknown parameters.
/// </summary>
public int NumberOfParameters { get { return (Point == null) ? 0 : Point.Count; } }
/// <summary>
/// Get the degree of freedom
/// </summary>
public int DegreeOfFreedom
{
get
{
var df = NumberOfObservations - NumberOfParameters;
if (IsFixed != null)
{
df = df + IsFixed.Count(p => p == true);
}
return df;
}
}
/// <summary>
/// Get the number of calls to function.
/// </summary>
public int FunctionEvaluations { get; set; }
/// <summary>
/// Get the number of calls to jacobian.
/// </summary>
public int JacobianEvaluations { get; set; }
#endregion Public Variables
public NonlinearObjectiveFunction(Func<Vector<double>, Vector<double>, Vector<double>> function,
Func<Vector<double>, Vector<double>, Matrix<double>> derivative = null, int accuracyOrder = 2)
{
this.userFunction = function;
this.userDerivative = derivative;
this.accuracyOrder = Math.Min(6, Math.Max(1, accuracyOrder));
}
public IObjectiveModel Fork()
{
return new NonlinearObjectiveFunction(userFunction, userDerivative, accuracyOrder)
{
ObservedX = ObservedX,
ObservedY = ObservedY,
Weights = Weights,
coefficients = coefficients,
hasFunctionValue = hasFunctionValue,
functionValue = functionValue,
hasJacobianValue = hasJacobianValue,
jacobianValue = jacobianValue,
gradientValue = gradientValue,
hessianValue = hessianValue
};
}
public IObjectiveModel CreateNew()
{
return new NonlinearObjectiveFunction(userFunction, userDerivative, accuracyOrder);
}
/// <summary>
/// Set or get the values of the parameters.
/// </summary>
public Vector<double> Point { get { return coefficients; } }
/// <summary>
/// Get the y-values of the fitted model that correspond to the independent values.
/// </summary>
public Vector<double> ModelValues { get; private set; }
/// <summary>
/// Get the residual sum of squares.
/// </summary>
public double Value
{
get
{
if (!hasFunctionValue)
{
EvaluateFunction();
hasFunctionValue = true;
}
return functionValue;
}
}
/// <summary>
/// Get the Gradient vector of x and p.
/// </summary>
public Vector<double> Gradient
{
get
{
if (!hasJacobianValue)
{
EvaluateJacobian();
hasJacobianValue = true;
}
return gradientValue;
}
}
/// <summary>
/// Get the Hessian matrix of x and p, J'WJ
/// </summary>
public Matrix<double> Hessian
{
get
{
if (!hasJacobianValue)
{
EvaluateJacobian();
hasJacobianValue = true;
}
return hessianValue;
}
}
public bool IsGradientSupported { get { return true; } }
public bool IsHessianSupported { get { return true; } }
/// <summary>
/// Set observed data to fit.
/// </summary>
public void SetObserved(Vector<double> observedX, Vector<double> observedY, Vector<double> weights = null)
{
if (observedX == null || observedY == null)
{
throw new ArgumentNullException("The data set can't be null.");
}
if (observedX.Count != observedY.Count)
{
throw new ArgumentException("The observed x data can't have different from observed y data.");
}
ObservedX = observedX;
ObservedY = observedY;
if (weights != null && weights.Count != observedY.Count)
{
throw new ArgumentException("The weightings can't have different from observations.");
}
if (weights != null && weights.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{
throw new ArgumentException("The weightings are not well-defined.");
}
if (weights != null && weights.Count(x => x == 0) == weights.Count)
{
throw new ArgumentException("All the weightings can't be zero.");
}
if (weights != null && weights.Count(x => x < 0) > 0)
{
weights = weights.PointwiseAbs();
}
Weights = (weights == null)
? null
: Matrix<double>.Build.DenseOfDiagonalVector(weights);
L = (weights == null)
? null
: Weights.Diagonal().PointwiseSqrt();
}
/// <summary>
/// Set parameters and bounds.
/// </summary>
/// <param name="initialGuess">The initial values of parameters.</param>
/// <param name="isFixed">The list to the parameters fix or free.</param>
public void SetParameters(Vector<double> initialGuess, List<bool> isFixed = null)
{
if (initialGuess == null)
{
throw new ArgumentNullException("initialGuess");
}
coefficients = initialGuess;
if (isFixed != null && isFixed.Count != initialGuess.Count)
{
throw new ArgumentException("The isFixed can't have different size from the initial guess.");
}
if (isFixed != null && isFixed.Count(p => p == true) == isFixed.Count)
{
throw new ArgumentException("All the parameters can't be fixed.");
}
IsFixed = isFixed;
}
public void EvaluateAt(Vector<double> parameters)
{
if (parameters == null)
{
throw new ArgumentNullException("parameters");
}
if (parameters.Count(p => double.IsNaN(p) || double.IsInfinity(p)) > 0)
{
throw new ArgumentException("The parameters must be finite.");
}
coefficients = parameters;
hasFunctionValue = false;
hasJacobianValue = false;
jacobianValue = null;
gradientValue = null;
hessianValue = null;
}
public IObjectiveFunction ToObjectiveFunction()
{
Tuple<double, Vector<double>, Matrix<double>> function(Vector<double> point)
{
EvaluateAt(point);
return new Tuple<double, Vector<double>, Matrix<double>>(Value, Gradient, Hessian);
}
var objective = new GradientHessianObjectiveFunction(function);
return objective;
}
#region Private Methods
private void EvaluateFunction()
{
// Calculates the residuals, (y[i] - f(x[i]; p)) * L[i]
if (ModelValues == null)
{
ModelValues = Vector<double>.Build.Dense(NumberOfObservations);
}
ModelValues = userFunction(Point, ObservedX);
FunctionEvaluations++;
// calculate the weighted residuals
residuals = (Weights == null)
? ObservedY - ModelValues
: (ObservedY - ModelValues).PointwiseMultiply(L);
// Calculate the residual sum of squares
functionValue = residuals.DotProduct(residuals);
return;
}
private void EvaluateJacobian()
{
// Calculates the jacobian of x and p.
if (userDerivative != null)
{
// analytical jacobian
jacobianValue = userDerivative(Point, ObservedX);
JacobianEvaluations++;
}
else
{
// numerical jacobian
jacobianValue = NumericalJacobian(Point, ModelValues, accuracyOrder);
FunctionEvaluations += accuracyOrder;
}
// weighted jacobian
for (int i = 0; i < NumberOfObservations; i++)
{
for (int j = 0; j < NumberOfParameters; j++)
{
if (IsFixed != null && IsFixed[j])
{
// if j-th parameter is fixed, set J[i, j] = 0
jacobianValue[i, j] = 0.0;
}
else
{
jacobianValue[i, j] = (Weights == null)
? jacobianValue[i, j]
: jacobianValue[i, j] * L[j];
}
}
}
// Gradient, g = -J'W(y − f(x; p)) = -J'L(L'E) = -J'LR
gradientValue = -jacobianValue.Transpose() * residuals;
// approximated Hessian, H = J'WJ + ∑LRiHi ~ J'WJ near the minimum
hessianValue = jacobianValue.Transpose() * jacobianValue;
}
private Matrix<double> NumericalJacobian(Vector<double> parameters, Vector<double> currentValues, int accuracyOrder = 2)
{
const double sqrtEpsilon = 1.4901161193847656250E-8; // sqrt(machineEpsilon)
Matrix<double> derivertives = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters);
var d = 0.000003 * parameters.PointwiseAbs().PointwiseMaximum(sqrtEpsilon);
var h = Vector<double>.Build.Dense(NumberOfParameters);
for (int j = 0; j < NumberOfParameters; j++)
{
h[j] = d[j];
if (accuracyOrder >= 6)
{
// f'(x) = {- f(x - 3h) + 9f(x - 2h) - 45f(x - h) + 45f(x + h) - 9f(x + 2h) + f(x + 3h)} / 60h + O(h^6)
var f1 = userFunction(parameters - 3 * h, ObservedX);
var f2 = userFunction(parameters - 2 * h, ObservedX);
var f3 = userFunction(parameters - h, ObservedX);
var f4 = userFunction(parameters + h, ObservedX);
var f5 = userFunction(parameters + 2 * h, ObservedX);
var f6 = userFunction(parameters + 3 * h, ObservedX);
var prime = (-f1 + 9 * f2 - 45 * f3 + 45 * f4 - 9 * f5 + f6) / (60 * h[j]);
derivertives.SetColumn(j, prime);
}
else if (accuracyOrder == 5)
{
// f'(x) = {-137f(x) + 300f(x + h) - 300f(x + 2h) + 200f(x + 3h) - 75f(x + 4h) + 12f(x + 5h)} / 60h + O(h^5)
var f1 = currentValues;
var f2 = userFunction(parameters + h, ObservedX);
var f3 = userFunction(parameters + 2 * h, ObservedX);
var f4 = userFunction(parameters + 3 * h, ObservedX);
var f5 = userFunction(parameters + 4 * h, ObservedX);
var f6 = userFunction(parameters + 5 * h, ObservedX);
var prime = (-137 * f1 + 300 * f2 - 300 * f3 + 200 * f4 - 75 * f5 + 12 * f6) / (60 * h[j]);
derivertives.SetColumn(j, prime);
}
else if (accuracyOrder == 4)
{
// f'(x) = {f(x - 2h) - 8f(x - h) + 8f(x + h) - f(x + 2h)} / 12h + O(h^4)
var f1 = userFunction(parameters - 2 * h, ObservedX);
var f2 = userFunction(parameters - h, ObservedX);
var f3 = userFunction(parameters + h, ObservedX);
var f4 = userFunction(parameters + 2 * h, ObservedX);
var prime = (f1 - 8 * f2 + 8 * f3 - f4) / (12 * h[j]);
derivertives.SetColumn(j, prime);
}
else if (accuracyOrder == 3)
{
// f'(x) = {-11f(x) + 18f(x + h) - 9f(x + 2h) + 2f(x + 3h)} / 6h + O(h^3)
var f1 = currentValues;
var f2 = userFunction(parameters + h, ObservedX);
var f3 = userFunction(parameters + 2 * h, ObservedX);
var f4 = userFunction(parameters + 3 * h, ObservedX);
var prime = (-11 * f1 + 18 * f2 - 9 * f3 + 2 * f4) / (6 * h[j]);
derivertives.SetColumn(j, prime);
}
else if (accuracyOrder == 2)
{
// f'(x) = {f(x + h) - f(x - h)} / 2h + O(h^2)
var f1 = userFunction(parameters + h, ObservedX);
var f2 = userFunction(parameters - h, ObservedX);
var prime = (f1 - f2) / (2 * h[j]);
derivertives.SetColumn(j, prime);
}
else
{
// f'(x) = {- f(x) + f(x + h)} / h + O(h)
var f1 = currentValues;
var f2 = userFunction(parameters + h, ObservedX);
var prime = (-f1 + f2) / h[j];
derivertives.SetColumn(j, prime);
}
h[j] = 0;
}
return derivertives;
}
#endregion Private Methods
}
}

56
src/Numerics/Optimization/ObjectiveModel.cs

@ -1,56 +0,0 @@
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Optimization.ObjectiveModels;
using System;
namespace MathNet.Numerics.Optimization
{
public static class ObjectiveModel
{
/// <summary>
/// Fitting model with a user supplied jacobian for non-linear least squares regression.
/// </summary>
public static IObjectiveModel FittingModel(Func<Vector<double>, double, double> function, Func<Vector<double>, double, Vector<double>> derivatives,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null)
{
var objective = new FittingObjectiveModel(function, derivatives);
objective.SetObserved(observedX, observedY, weight);
return objective;
}
/// <summary>
/// Fitting model for non-linear least squares regression.
/// </summary>
public static IObjectiveModel FittingModel(Func<Vector<double>, double, double> function,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null,
int accuracyOrder = 2)
{
var objective = new FittingObjectiveModel(function, accuracyOrder: accuracyOrder);
objective.SetObserved(observedX, observedY, weight);
return objective;
}
/// <summary>
/// Fitting function with a user supplied jacobian for nonlinear least squares regression by the line search algorithm.
/// </summary>
public static IObjectiveFunction FittingFunction(Func<Vector<double>, double, double> function, Func<Vector<double>, double, Vector<double>> derivatives,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null)
{
var objective = new FittingObjectiveModel(function, derivatives);
objective.SetObserved(observedX, observedY, weight);
return objective.ToObjectiveFunction();
}
/// <summary>
/// Fitting function for nonlinear least squares regression by the line search algorithm.
/// The numerical jacobian with accuracy order is used.
/// </summary>
public static IObjectiveFunction FittingFunction(Func<Vector<double>, double, double> function,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null,
int accuracyOrder = 2)
{
var objective = new FittingObjectiveModel(function, null, accuracyOrder: accuracyOrder);
objective.SetObserved(observedX, observedY, weight);
return objective.ToObjectiveFunction();
}
}
}

730
src/Numerics/Optimization/ObjectiveModels/FittingObjectiveModel.cs

@ -1,730 +0,0 @@
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Optimization.ObjectiveFunctions;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MathNet.Numerics.Optimization.ObjectiveModels
{
internal class FittingObjectiveModel : IObjectiveModel
{
#region Private Variables
readonly Func<Vector<double>, double, double> userFunction; // (p, x) => f(x; p)
readonly Func<Vector<double>, double, Vector<double>> userDerivatives; // (p, x) => df(x; p)/dp
readonly int accuracyOrder; // the desired accuracy order to evaluate the jacobian by numerical approximaiton.
Vector<double> coefficients;
Vector<double> Pint; // internal(unbounded) coefficients
public Vector<double> Pext; // external(bounded) coefficients
bool hasFunctionValue;
double functionValue; // the residual sum of squares. Residuals * Residuals
Vector<double> residuals; // the error values
bool hasJacobianValue;
Matrix<double> jacobianValue; // the Jacobian matrix.
Vector<double> gradientValue; // the Gradient vector.
Matrix<double> hessianValue; // the Hessian matrix.
bool isBounded;
#endregion Private Variables
#region Public Variables - Observed Data
/// <summary>
/// Set or get the values of the independent variable.
/// </summary>
public Vector<double> ObservedX { get; private set; }
/// <summary>
/// Set or get the values of the observations.
/// </summary>
public Vector<double> ObservedY { get; private set; }
/// <summary>
/// Set or get the values of the weights for the observations.
/// </summary>
public Matrix<double> Weights { get; private set; }
private Vector<double> L; // Weights = LL'
/// <summary>
/// Get the number of observations.
/// </summary>
public int NumberOfObservations { get { return (ObservedY == null) ? 0 : ObservedY.Count; } }
#endregion Public Variables - Observed Data
#region Public Variables - Bounds of Parameter
/// <summary>
/// Get the values of the parameters.
/// </summary>
public List<bool> IsFixed { get; private set; }
/// <summary>
/// Get the values of the parameters.
/// </summary>
public Vector<double> LowerBound { get; private set; }
/// <summary>
/// Get the values of the parameters.
/// </summary>
public Vector<double> UpperBound { get; private set; }
/// <summary>
/// Get the scale factor of the parameters.
/// </summary>
public Vector<double> Scales { get; private set; }
/// <summary>
/// Get the number of unknown parameters.
/// </summary>
public int NumberOfParameters { get { return (Point == null) ? 0 : Point.Count; } }
#endregion Public Variables - Bounds of Parameter
#region Public Variables - Others
/// <summary>
/// Get the number of calls to function.
/// </summary>
public int FunctionEvaluations { get; set; }
/// <summary>
/// Get the number of calls to jacobian.
/// </summary>
public int JacobianEvaluations { get; set; }
#endregion Public Variables - Others
public FittingObjectiveModel(Func<Vector<double>, double, double>function, Func<Vector<double>, double, Vector<double>> derivatives = null, int accuracyOrder = 2)
{
this.userFunction = function;
this.userDerivatives = derivatives;
this.accuracyOrder = Math.Min(6, Math.Max(1, accuracyOrder));
IsFinished = false;
}
public IObjectiveModel Fork()
{
return new FittingObjectiveModel(userFunction, userDerivatives, accuracyOrder)
{
ObservedX = ObservedX,
ObservedY = ObservedY,
Weights = Weights,
coefficients = coefficients,
Pint = Pint,
Pext = Pext,
hasFunctionValue = hasFunctionValue,
functionValue = functionValue,
hasJacobianValue = hasJacobianValue,
jacobianValue = jacobianValue,
gradientValue = gradientValue,
hessianValue = hessianValue
};
}
public IObjectiveModel CreateNew()
{
return new FittingObjectiveModel(userFunction, userDerivatives, accuracyOrder);
}
/// <summary>
/// Set or get the values of the parameters.
/// </summary>
public Vector<double> Point { get { return coefficients; } }
/// <summary>
/// Get the y-values of the fitted model that correspond to the independent values.
/// </summary>
public Vector<double> ModelValues { get; private set; }
/// <summary>
/// Get the residual sum of squares.
/// </summary>
public double Value
{
get
{
if (!hasFunctionValue)
{
EvaluateFunction();
hasFunctionValue = true;
}
return functionValue;
}
}
/// <summary>
/// Get the Gradient vector of x and p.
/// </summary>
public Vector<double> Gradient
{
get
{
if (!hasJacobianValue)
{
EvaluateJacobian();
hasJacobianValue = true;
}
return gradientValue;
}
}
/// <summary>
/// Get the Hessian matrix of x and p, J'WJ
/// </summary>
public Matrix<double> Hessian
{
get
{
if (!hasJacobianValue)
{
EvaluateJacobian();
hasJacobianValue = true;
}
return hessianValue;
}
}
/// <summary>
/// Get the degree of freedom
/// </summary>
public int DegreeOfFreedom
{
get
{
var df = NumberOfObservations - NumberOfParameters;
if (IsFixed != null)
{
df = df + IsFixed.Count(p => p == true);
}
return df;
}
}
public bool IsGradientSupported { get { return true; } }
public bool IsHessianSupported { get { return true; } }
public bool IsFinished { get; set; }
public IObjectiveFunction ToObjectiveFunction()
{
Tuple<double, Vector<double>, Matrix<double>> function(Vector<double> point)
{
EvaluateAt(point);
return new Tuple<double, Vector<double>, Matrix<double>>(Value, Gradient, Hessian);
}
var objective = new GradientHessianObjectiveFunction(function);
return objective;
}
/// <summary>
/// Set observed data to fit.
/// </summary>
public void SetObserved(Vector<double> observedX, Vector<double> observedY, Vector<double> weights = null)
{
if (observedX == null || observedY == null)
{
throw new ArgumentNullException("The data set can't be null.");
}
if (observedX.Count != observedY.Count)
{
throw new ArgumentException("The observed x data can't have different from observed y data.");
}
ObservedX = observedX;
ObservedY = observedY;
if (weights != null && weights.Count != observedY.Count)
{
throw new ArgumentException("The weightings can't have different from observations.");
}
if (weights != null && weights.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{
throw new ArgumentException("The weightings are not well-defined.");
}
if (weights != null && weights.Count(x => x == 0) == weights.Count)
{
throw new ArgumentException("All the weightings can't be zero.");
}
if (weights != null && weights.Count(x => x < 0) > 0)
{
weights = weights.PointwiseAbs();
}
Weights = (weights == null)
? null
: Matrix<double>.Build.DenseOfDiagonalVector(weights);
L = (weights == null)
? null
: Weights.Diagonal().PointwiseSqrt();
}
/// <summary>
/// Set parameters and bounds.
/// </summary>
/// <param name="lowerBound">The lower bounds of parameters.</param>
/// <param name="upperBound">The upper bounds of parameters.</param>
/// <param name="scales">The scaling constants of parameters</param>
/// <param name="isFixed">The list to the parameters fix or free.</param>
public void SetParameters(Vector<double> initialGuess, Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null)
{
if (initialGuess == null)
{
throw new ArgumentNullException("initialGuess");
}
coefficients = initialGuess;
if (lowerBound != null && lowerBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{
throw new ArgumentException("The lower bounds must be finite.");
}
if (lowerBound != null && lowerBound.Count != initialGuess.Count)
{
throw new ArgumentException("The upper bounds can't have different elements from the initial guess.");
}
LowerBound = lowerBound;
if (upperBound != null && upperBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{
throw new ArgumentException("The upper bounds must be finite.");
}
if (upperBound != null && upperBound.Count != initialGuess.Count)
{
throw new ArgumentException("The upper bounds can't have different elements from the initial guess.");
}
UpperBound = upperBound;
if (scales != null && scales.Count(x => double.IsInfinity(x) || double.IsNaN(x) || x == 0) > 0)
{
throw new ArgumentException("The scales must be finite.");
}
if (scales != null && scales.Count != initialGuess.Count)
{
throw new ArgumentException("The upper bounds can't have different elements from the initial guess.");
}
if (scales != null && scales.Count(x => x < 0) > 0)
{
scales.PointwiseAbs();
}
Scales = scales;
if (isFixed != null && isFixed.Count != initialGuess.Count)
{
throw new ArgumentException("The isFixed can't have different elements from the initial guess.");
}
if (isFixed != null && isFixed.Count(p => p == true) == isFixed.Count)
{
throw new ArgumentException("All the parameters can't be fixed.");
}
IsFixed = isFixed;
isBounded = LowerBound != null || UpperBound != null || Scales != null;
}
public void EvaluateAt(Vector<double> parameters)
{
ValidateParameters(parameters);
// To handle the box constrained minimization as the unconstrained minimization,
// the parameters are mapping by the following rules,
// which are modified the rules shown in the ref[1] in order to introduce scales.
//
// 1. lower < Pext < upper
// Pint = asin(2 * (Pext - lower) / (upper - lower) - 1)
// Pext = lower + (sin(Pint) + 1) * (upper - lower) / 2
// dPext/dPint = (upper - lower) / 2 * cos(Pint)
//
// 2. lower < Pext
// Pint = sqrt((Pext/scale - lower/scale + 1)^2 - 1)
// Pext = lower + scale * (sqrt(Pint^2 + 1) - 1)
// dPext/dPint = scale * Pint / sqrt(Pint^2 + 1)
//
// 3. Pext < upper
// Pint = sqrt((upper / scale - Pext / scale + 1)^2 - 1)
// Pext = upper + scale - scale * sqrt(Pint^2 + 1)
// dPext/dPint = - scale * Pint / sqrt(Pint^2 + 1)
//
// 4. no bounds, but scales
// Pint = Pext / scale
// Pext = Pint * scale
// dPext/dPint = scale
//
// The rules are applied in ProjectParametersToInternal, ProjectParametersToExternal, and ScaleFactorsOfJacobian methods.
//
// References:
// [1] https://lmfit.github.io/lmfit-py/bounds.html
// [2] MINUIT User's Guide, https://root.cern.ch/download/minuit.pdf
//
// Except when it is initial guess, the parameters argument is always internal parameter.
// So, first map the parameters argument to the external parameters in order to calculate function values.
Pext = (FunctionEvaluations > 0 && isBounded)
? ProjectParametersToExternal(parameters)
: parameters.Clone();
Pint = (isBounded)
? ProjectParametersToInternal(Pext)
: Pext;
this.coefficients = Pint;
if (IsFinished)
{
this.coefficients = Pext;
}
hasFunctionValue = false;
hasJacobianValue = false;
// don't keep references unnecessarily
jacobianValue = null;
gradientValue = null;
hessianValue = null;
}
#region Private Methods
private void EvaluateFunction()
{
// Calculates the residuals, (y[i] - f(x[i]; p)) * L[i]
if (ModelValues == null)
{
ModelValues = Vector<double>.Build.Dense(NumberOfObservations);
}
for (int i = 0; i < NumberOfObservations; i++)
{
ModelValues[i] = userFunction(Pext, ObservedX[i]);
}
FunctionEvaluations++;
// calculate the weighted residuals
residuals = (Weights == null)
? ObservedY - ModelValues
: (ObservedY - ModelValues).PointwiseMultiply(L);
// Calculate the residual sum of squares
functionValue = residuals.DotProduct(residuals);
return;
}
private void EvaluateJacobian()
{
// Calculates the jacobian of x and p.
if (userDerivatives != null)
{
// analytical jacobian
if (jacobianValue == null)
{
jacobianValue = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters);
}
for (int i = 0; i < NumberOfObservations; i++)
{
jacobianValue.SetRow(i, userDerivatives(Pext, ObservedX[i]));
}
JacobianEvaluations++;
}
else
{
// numerical jacobian
jacobianValue = NumericalJacobian(Pext, ModelValues, accuracyOrder);
FunctionEvaluations += accuracyOrder;
}
var scaleFactors = (isBounded && !IsFinished)
? ScaleFactorsOfJacobian(Pint)
: Vector<double>.Build.Dense(Pint.Count, 1.0);
// project jacobian: Jint(x; Pint) = Jext(x; Pext) * scale where scale = dPext/dPint
for (int i = 0; i < NumberOfObservations; i++)
{
for (int j = 0; j < NumberOfParameters; j++)
{
if (IsFixed != null && IsFixed[j])
{
// if j-th parameter is fixed, set J[i, j] = 0
jacobianValue[i, j] = 0.0;
}
else
{
jacobianValue[i, j] = jacobianValue[i, j] * scaleFactors[j];
}
}
}
// Gradient, g = -J'W(y − f(x; p)) = -J'L(L'E) = -J'LR
gradientValue = (Weights == null)
? -jacobianValue.Transpose() * (ObservedY - ModelValues)
: -jacobianValue.Transpose() * Weights * (ObservedY - ModelValues);
// approximated Hessian, H = J'WJ + ∑LRiHi ~ J'WJ near the minimum
hessianValue = (Weights == null)
? jacobianValue.Transpose() * jacobianValue
: jacobianValue.Transpose() * Weights * jacobianValue;
}
private void ValidateParameters(Vector<double> parameters)
{
if (parameters == null)
{
throw new ArgumentNullException("parameters");
}
else if (parameters.Count(p => double.IsNaN(p) || double.IsInfinity(p)) > 0)
{
throw new ArgumentException("the parameters must be finite.");
}
if (LowerBound != null && parameters.Count != LowerBound.Count)
{
throw new ArgumentException("The parameters can't have different size from the lower bounds.");
}
if (UpperBound != null && parameters.Count != UpperBound.Count)
{
throw new ArgumentException("The parameters can't have different size from the upper bounds.");
}
if (Scales != null && parameters.Count != Scales.Count)
{
throw new ArgumentException("The parameters can't have different size from the scales.");
}
if (IsFixed != null && parameters.Count != IsFixed.Count)
{
throw new ArgumentException("The parameters can't have different size from the IsFixed list.");
}
}
private Matrix<double> NumericalJacobian(Vector<double> Pext, Vector<double> currentValues, int accuracyOrder = 2)
{
const double sqrtEpsilon = 1.4901161193847656250E-8; // sqrt(machineEpsilon)
Matrix<double> derivertives = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters);
var d = 0.000003 * Pext.PointwiseAbs().PointwiseMaximum(sqrtEpsilon);
var h = Vector<double>.Build.Dense(NumberOfParameters);
for (int i = 0; i < NumberOfObservations; i++)
{
var x = ObservedX[i];
for (int j = 0; j < NumberOfParameters; j++)
{
h[j] = d[j];
if (accuracyOrder >= 6)
{
// f'(x) = {- f(x - 3h) + 9f(x - 2h) - 45f(x - h) + 45f(x + h) - 9f(x + 2h) + f(x + 3h)} / 60h + O(h^6)
var f1 = userFunction(Pext - 3 * h, x);
var f2 = userFunction(Pext - 2 * h, x);
var f3 = userFunction(Pext - h, x);
var f4 = userFunction(Pext + h, x);
var f5 = userFunction(Pext + 2 * h, x);
var f6 = userFunction(Pext + 3 * h, x);
var prime = (-f1 + 9 * f2 - 45 * f3 + 45 * f4 - 9 * f5 + f6) / (60 * h[j]);
derivertives[i, j] = prime;
}
else if (accuracyOrder == 5)
{
// f'(x) = {-137f(x) + 300f(x + h) - 300f(x + 2h) + 200f(x + 3h) - 75f(x + 4h) + 12f(x + 5h)} / 60h + O(h^5)
var f1 = currentValues[i];
var f2 = userFunction(Pext + h, x);
var f3 = userFunction(Pext + 2 * h, x);
var f4 = userFunction(Pext + 3 * h, x);
var f5 = userFunction(Pext + 4 * h, x);
var f6 = userFunction(Pext + 5 * h, x);
var prime = (-137 * f1 + 300 * f2 - 300 * f3 + 200 * f4 - 75 * f5 + 12 * f6) / (60 * h[j]);
derivertives[i, j] = prime;
}
else if (accuracyOrder == 4)
{
// f'(x) = {f(x - 2h) - 8f(x - h) + 8f(x + h) - f(x + 2h)} / 12h + O(h^4)
var f1 = userFunction(Pext - 2 * h, x);
var f2 = userFunction(Pext - h, x);
var f3 = userFunction(Pext + h, x);
var f4 = userFunction(Pext + 2 * h, x);
var prime = (f1 - 8 * f2 + 8 * f3 - f4) / (12 * h[j]);
derivertives[i, j] = prime;
}
else if (accuracyOrder == 3)
{
// f'(x) = {-11f(x) + 18f(x + h) - 9f(x + 2h) + 2f(x + 3h)} / 6h + O(h^3)
var f1 = currentValues[i];
var f2 = userFunction(Pext + h, x);
var f3 = userFunction(Pext + 2 * h, x);
var f4 = userFunction(Pext + 3 * h, x);
var prime = (-11 * f1 + 18 * f2 - 9 * f3 + 2 * f4) / (6 * h[j]);
derivertives[i, j] = prime;
}
else if (accuracyOrder == 2)
{
// f'(x) = {f(x + h) - f(x - h)} / 2h + O(h^2)
var f1 = userFunction(Pext + h, x);
var f2 = userFunction(Pext - h, x);
var prime = (f1 - f2) / (2 * h[j]);
derivertives[i, j] = prime;
}
else
{
// f'(x) = {- f(x) + f(x + h)} / h + O(h)
var f1 = currentValues[i];
var f2 = userFunction(Pext + h, x);
var prime = (-f1 + f2) / h[j];
derivertives[i, j] = prime;
}
h[j] = 0;
}
}
return derivertives;
}
private Vector<double> ProjectParametersToInternal(Vector<double> Pext)
{
var Pint = Pext.Clone();
if (LowerBound != null && UpperBound != null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = Math.Asin((2.0 * (Pext[i] - LowerBound[i]) / (UpperBound[i] - LowerBound[i])) - 1.0);
}
return Pint;
}
else if (LowerBound != null && UpperBound == null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = (Scales == null)
? Math.Sqrt(Math.Pow(Pext[i] - LowerBound[i] + 1.0, 2) - 1.0)
: Math.Sqrt(Math.Pow((Pext[i] - LowerBound[i]) / Scales[i] + 1.0, 2) - 1.0);
}
return Pint;
}
else if (LowerBound == null && UpperBound != null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = (Scales == null)
? Math.Sqrt(Math.Pow(UpperBound[i] - Pext[i] + 1.0, 2) - 1.0)
: Math.Sqrt(Math.Pow((UpperBound[i] - Pext[i]) / Scales[i] + 1.0, 2) - 1.0);
}
return Pint;
}
else if (Scales != null)
{
for (int i = 0; i < Pext.Count; i++)
{
Pint[i] = Pext[i] / Scales[i];
}
return Pint;
}
return Pint;
}
private Vector<double> ProjectParametersToExternal(Vector<double> Pint)
{
var Pext = Pint.Clone();
if (LowerBound != null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = LowerBound[i] + (UpperBound[i] / 2.0 - LowerBound[i] / 2.0) * (Math.Sin(Pint[i]) + 1.0);
}
return Pext;
}
else if (LowerBound != null && UpperBound == null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = (Scales == null)
? LowerBound[i] + Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0
: LowerBound[i] + Scales[i] * (Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0);
}
return Pext;
}
else if (LowerBound == null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = (Scales == null)
? UpperBound[i] - Math.Sqrt(Pint[i] * Pint[i] + 1.0) + 1.0
: UpperBound[i] - Scales[i] * (Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0);
}
return Pext;
}
else if (Scales != null)
{
for (int i = 0; i < Pint.Count; i++)
{
Pext[i] = Pint[i] * Scales[i];
}
return Pext;
}
return Pext;
}
private Vector<double> ScaleFactorsOfJacobian(Vector<double> Pint)
{
var scale = Vector<double>.Build.Dense(Pint.Count, 1.0);
if (LowerBound != null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
scale[i] = (UpperBound[i] - LowerBound[i]) / 2.0 * Math.Cos(Pint[i]);
}
return scale;
}
else if (LowerBound != null && UpperBound == null)
{
for (int i = 0; i < Pint.Count; i++)
{
scale[i] = (Scales == null)
? Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0)
: Scales[i] * Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0);
}
return scale;
}
else if (LowerBound == null && UpperBound != null)
{
for (int i = 0; i < Pint.Count; i++)
{
scale[i] = (Scales == null)
? -Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0)
: -Scales[i] * Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0);
}
return scale;
}
else if (Scales != null)
{
return Scales;
}
return scale;
}
#endregion Private Methods
}
}

81
src/Numerics/Optimization/TrustRegionMinimizerBase.cs

@ -5,57 +5,32 @@ using System.Linq;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
{ {
public abstract class TrustRegionMinimizerBase public abstract class TrustRegionMinimizerBase : NonlinearMinimizerBase
{ {
public static ITrustRegionSubproblem Subproblem;
/// <summary>
/// The stopping threshold for infinity norm of the gradient.
/// </summary>
public static double GradientTolerance { get; set; }
/// <summary> /// <summary>
/// The stopping threshold for L2 norm of the change of the parameters. /// The trust region subproblem.
/// </summary> /// </summary>
public static double StepTolerance { get; set; } public static ITrustRegionSubproblem Subproblem;
/// <summary>
/// The stopping threshold for the function value or L2 norm of the residuals.
/// </summary>
public static double FunctionTolerance { get; set; }
/// <summary> /// <summary>
/// The stopping threshold for the trust region radius. /// The stopping threshold for the trust region radius.
/// </summary> /// </summary>
public static double RadiusTolerance { get; set; } public static double RadiusTolerance { get; set; }
/// <summary>
/// The maximum number of iterations.
/// </summary>
public int MaximumIterations { get; set; }
public TrustRegionMinimizerBase(ITrustRegionSubproblem subproblem, public TrustRegionMinimizerBase(ITrustRegionSubproblem subproblem,
double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-8, int maximumIterations = -1) double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-8, int maximumIterations = -1)
: base(gradientTolerance, stepTolerance, functionTolerance, maximumIterations)
{ {
if (subproblem == null) if (subproblem == null)
throw new ArgumentNullException("subproblem"); throw new ArgumentNullException("subproblem");
Subproblem = subproblem; Subproblem = subproblem;
FunctionTolerance = functionTolerance;
GradientTolerance = gradientTolerance;
StepTolerance = stepTolerance;
RadiusTolerance = radiusTolerance; RadiusTolerance = radiusTolerance;
MaximumIterations = maximumIterations;
} }
public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, Vector<double> initialGuess, public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, Vector<double> initialGuess,
Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null) Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null)
{ {
if (objective == null)
throw new ArgumentNullException("objective");
if (initialGuess == null)
throw new ArgumentNullException("initialGuess");
return Minimum(Subproblem, objective, initialGuess, lowerBound, upperBound, scales, isFixed, return Minimum(Subproblem, objective, initialGuess, lowerBound, upperBound, scales, isFixed,
GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations); GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations);
} }
@ -63,11 +38,6 @@ namespace MathNet.Numerics.Optimization
public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, double[] initialGuess, public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, double[] initialGuess,
double[] lowerBound = null, double[] upperBound = null, double[] scales = null, bool[] isFixed = null) double[] lowerBound = null, double[] upperBound = null, double[] scales = null, bool[] isFixed = null)
{ {
if (objective == null)
throw new ArgumentNullException("objective");
if (initialGuess == null)
throw new ArgumentNullException("initialGuess");
var lb = (lowerBound == null) ? null : CreateVector.Dense<double>(lowerBound); var lb = (lowerBound == null) ? null : CreateVector.Dense<double>(lowerBound);
var ub = (upperBound == null) ? null : CreateVector.Dense<double>(upperBound); var ub = (upperBound == null) ? null : CreateVector.Dense<double>(upperBound);
var sc = (scales == null) ? null : CreateVector.Dense<double>(scales); var sc = (scales == null) ? null : CreateVector.Dense<double>(scales);
@ -133,23 +103,17 @@ namespace MathNet.Numerics.Optimization
if (objective == null) if (objective == null)
throw new ArgumentNullException("objective"); throw new ArgumentNullException("objective");
if (initialGuess == null)
throw new ArgumentNullException("initialGuess");
objective.SetParameters(initialGuess, lowerBound, upperBound, scales, isFixed); ValidateBounds(initialGuess, lowerBound, upperBound, scales);
ExitCondition exitCondition = ExitCondition.None; objective.SetParameters(initialGuess, isFixed);
// Initialize objective ExitCondition exitCondition = ExitCondition.None;
objective.FunctionEvaluations = 0;
objective.JacobianEvaluations = 0;
objective.IsFinished = false;
// First, calculate function values and setup variables // First, calculate function values and setup variables
objective.EvaluateAt(initialGuess); var P = ProjectToInternalParameters(initialGuess); // current internal parameters
var P = objective.Point; // current parameters var Pstep = Vector<double>.Build.Dense(P.Count); // the change of parameters
var RSS = objective.Value; // Residual Sum of Squares = R'R var RSS = EvaluateFunction(objective, initialGuess); // Residual Sum of Squares
var RSSinit = RSS; // RSS at initial gussing parameters
if (maximumIterations < 0) if (maximumIterations < 0)
{ {
@ -176,8 +140,9 @@ namespace MathNet.Numerics.Optimization
} }
// evaluate projected gradient and Hessian // evaluate projected gradient and Hessian
var Gradient = objective.Gradient; var jac = EvaluateJacobian(objective, P);
var Hessian = objective.Hessian; var Gradient = jac.Item1; // objective.Gradient;
var Hessian = jac.Item2; // objective.Hessian;
// if ||g||_oo <= gtol, found and stop // if ||g||_oo <= gtol, found and stop
if (Gradient.InfinityNorm() <= gradientTolerance) if (Gradient.InfinityNorm() <= gradientTolerance)
@ -195,14 +160,16 @@ namespace MathNet.Numerics.Optimization
delta = Math.Max(1.0, Math.Min(delta, maxDelta)); delta = Math.Max(1.0, Math.Min(delta, maxDelta));
int iterations = 0; int iterations = 0;
bool hitBoundary = false;
while (iterations < maximumIterations && exitCondition == ExitCondition.None) while (iterations < maximumIterations && exitCondition == ExitCondition.None)
{ {
iterations++; iterations++;
// solve the subproblem // solve the subproblem
subproblem.Solve(objective, delta); subproblem.Solve(objective, delta);
var Pstep = subproblem.Pstep; Pstep = subproblem.Pstep;
var hitBoundary = subproblem.HitBoundary; hitBoundary = subproblem.HitBoundary;
// predicted reduction = L(0) - L(Δp) = -Δp'g - 1/2 * Δp'HΔp // predicted reduction = L(0) - L(Δp) = -Δp'g - 1/2 * Δp'HΔp
var predictedReduction = -Gradient.DotProduct(Pstep) - 0.5 * Pstep.DotProduct(Hessian * Pstep); var predictedReduction = -Gradient.DotProduct(Pstep) - 0.5 * Pstep.DotProduct(Hessian * Pstep);
@ -213,10 +180,9 @@ namespace MathNet.Numerics.Optimization
} }
var Pnew = P + Pstep; // parameters to test var Pnew = P + Pstep; // parameters to test
// evaluate function at Pnew
objective.EvaluateAt(Pnew); var RSSnew = EvaluateFunction(objective, Pnew);
var RSSnew = objective.Value;
// if RSS == NaN, stop // if RSS == NaN, stop
if (double.IsNaN(RSSnew)) if (double.IsNaN(RSSnew))
{ {
@ -238,7 +204,7 @@ namespace MathNet.Numerics.Optimization
delta = delta * 0.25; delta = delta * 0.25;
if (delta <= radiusTolerance * (radiusTolerance + P.DotProduct(P))) if (delta <= radiusTolerance * (radiusTolerance + P.DotProduct(P)))
{ {
exitCondition = ExitCondition.RelativePoints; // SmallRelativeParameters exitCondition = ExitCondition.LackOfProgress;
break; break;
} }
} }
@ -250,8 +216,9 @@ namespace MathNet.Numerics.Optimization
RSS = RSSnew; RSS = RSSnew;
// evaluate projected gradient and Hessian // evaluate projected gradient and Hessian
Gradient = objective.Gradient; jac = EvaluateJacobian(objective, P);
Hessian = objective.Hessian; Gradient = jac.Item1; // objective.Gradient;
Hessian = jac.Item2; // objective.Hessian;
// if ||g||_oo <= gtol, found and stop // if ||g||_oo <= gtol, found and stop
if (Gradient.InfinityNorm() <= gradientTolerance) if (Gradient.InfinityNorm() <= gradientTolerance)

Loading…
Cancel
Save