Browse Source

Reworked ObjectiveModel.

pull/614/head
diluculo 8 years ago
parent
commit
22d9041d59
  1. 64
      src/Numerics.Tests/OptimizationTests/NonLinearCurveFittingTests.cs
  2. 36
      src/Numerics/Optimization/IObjectiveModel.cs
  3. 45
      src/Numerics/Optimization/LevenbergMarquardtMinimizer.cs
  4. 54
      src/Numerics/Optimization/NonlinearMinimizationResult.cs
  5. 12
      src/Numerics/Optimization/ObjectiveModel.cs
  6. 500
      src/Numerics/Optimization/ObjectiveModels/FittingObjectiveModel.cs
  7. 6
      src/Numerics/Optimization/Subproblems/DogLegSubproblem.cs
  8. 2
      src/Numerics/Optimization/Subproblems/NewtonCGSubproblem.cs
  9. 64
      src/Numerics/Optimization/TrustRegionMinimizerBase.cs

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

@ -55,10 +55,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
} }
// box constrained // box constrained
obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockPrime, RosenbrockX, RosenbrockY, obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockPrime, RosenbrockX, RosenbrockY);
lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound);
solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000); solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000);
result = solver.FindMinimum(obj, RosenbrockStart1); result = solver.FindMinimum(obj, RosenbrockStart1, lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -80,11 +79,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
} }
// box constrained // box constrained
obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockX, RosenbrockY, obj = ObjectiveModel.FittingModel(RosenbrockModel, RosenbrockX, RosenbrockY, accuracyOrder: 6);
lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound,
accuracyOrder: 6);
solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000); solver = new LevenbergMarquardtMinimizer(maximumIterations: 10000);
result = solver.FindMinimum(obj, RosenbrockStart1); result = solver.FindMinimum(obj, RosenbrockStart1, lowerBound: RosebbrockLowerBound, upperBound: RosenbrockUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -272,10 +269,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// 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 = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -285,10 +281,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// lower < parameters, no scales // lower < parameters, no scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY, obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
lowerBound: BoxBodLowerBound);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -298,10 +293,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// lower < parameters, scales // lower < parameters, scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY, obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
lowerBound: BoxBodLowerBound, scales: BoxBodScales);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, scales: BoxBodScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -311,10 +305,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// parameters < upper, no scales // parameters < upper, no scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY, obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
upperBound: BoxBodUpperBound);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, upperBound: BoxBodUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -324,10 +317,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// parameters < upper, scales // parameters < upper, scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY, obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
upperBound: BoxBodUpperBound, scales: BoxBodScales);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, upperBound: BoxBodUpperBound, scales: BoxBodScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -337,10 +329,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
// only scales // only scales
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY, obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
scales: BoxBodScales);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, scales: BoxBodScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -364,11 +355,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
} }
// box constrained // box constrained
obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6);
lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound,
accuracyOrder: 6);
solver = new LevenbergMarquardtMinimizer(); solver = new LevenbergMarquardtMinimizer();
result = solver.FindMinimum(obj, BoxBodStart1); result = solver.FindMinimum(obj, BoxBodStart1, lowerBound: BoxBodLowerBound, upperBound: BoxBodUpperBound);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -394,9 +383,10 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void BoxBod_TRNCG_Dif() public void BoxBod_TRNCG_Dif()
{ {
var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6); var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodPrime, BoxBodX, BoxBodY);
//var obj = ObjectiveModel.FittingModel(BoxBodModel, BoxBodX, BoxBodY, accuracyOrder: 6);
var solver = new TrustRegionNewtonCGMinimizer(); var solver = new TrustRegionNewtonCGMinimizer();
var result = solver.FindMinimum(obj, BoxBodStart1); var result = solver.FindMinimum(obj, BoxBodStart2);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -528,11 +518,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_TRDL_Dif() public void Thurber_TRDL_Dif()
{ {
var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
scales: ThurberScales,
accuracyOrder: 6);
var solver = new TrustRegionDogLegMinimizer(); var solver = new TrustRegionDogLegMinimizer();
var result = solver.FindMinimum(obj, ThurberStart); var result = solver.FindMinimum(obj, ThurberStart, scales: ThurberScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {
@ -544,11 +532,9 @@ namespace MathNet.Numerics.UnitTests.OptimizationTests
[Test] [Test]
public void Thurber_TRNCG_Dif() public void Thurber_TRNCG_Dif()
{ {
var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, var obj = ObjectiveModel.FittingModel(ThurberModel, ThurberX, ThurberY, accuracyOrder: 6);
scales: ThurberScales,
accuracyOrder: 6);
var solver = new TrustRegionNewtonCGMinimizer(); var solver = new TrustRegionNewtonCGMinimizer();
var result = solver.FindMinimum(obj, ThurberStart); var result = solver.FindMinimum(obj, ThurberStart, scales: ThurberScales);
for (int i = 0; i < result.BestFitParameters.Count; i++) for (int i = 0; i < result.BestFitParameters.Count; i++)
{ {

36
src/Numerics/Optimization/IObjectiveModel.cs

@ -1,4 +1,5 @@
using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearAlgebra;
using System.Collections.Generic;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
{ {
@ -19,44 +20,33 @@ namespace MathNet.Numerics.Optimization
/// <summary> /// <summary>
/// Get the y-values of the fitted model that correspond to the independent values. /// Get the y-values of the fitted model that correspond to the independent values.
/// </summary> /// </summary>
Vector<double> Values { get; } Vector<double> ModelValues { get; }
/// <summary> /// <summary>
/// Get the values of the parameters. /// Get the values of the parameters.
/// </summary> /// </summary>
Vector<double> Parameters { get; } Vector<double> Point { get; }
/// <summary> /// <summary>
/// Get the residual sum of squares. /// Get the residual sum of squares.
/// </summary> /// </summary>
double Residue { get; } double Value { get; }
/// <summary>
/// Get the Jacobian matrix, J(x; p) = df(x; p)/dp.
/// </summary>
Matrix<double> Jacobian { get; }
/// <summary> /// <summary>
/// Get the Gradient vector. G = J'(y - f(x; p)) /// Get the Gradient vector. G = J'(y - f(x; p))
/// </summary> /// </summary>
Vector<double> Gradient { get; } Vector<double> Gradient { get; }
/// <summary> /// <summary>
/// Get the approximated Hessian matrix. H = J'J /// Get the approximated Hessian matrix. H = J'J
/// </summary> /// </summary>
Matrix<double> Hessian { get; } Matrix<double> Hessian { get; }
/// <summary>
/// Get the covariance matrix.
/// </summary>
Matrix<double> Covariance { get; }
/// <summary>
/// Get the correlation matrix.
/// </summary>
Matrix<double> Correlation { get; }
/// <summary> /// <summary>
/// Get the number of calls to function. /// Get the number of calls to function.
/// </summary> /// </summary>
int FunctionEvaluations { get; set; } int FunctionEvaluations { get; set; }
/// <summary> /// <summary>
/// Get the number of calls to jacobian. /// Get the number of calls to jacobian.
/// </summary> /// </summary>
@ -67,17 +57,17 @@ namespace MathNet.Numerics.Optimization
/// </summary> /// </summary>
int DegreeOfFreedom { get; } int DegreeOfFreedom { get; }
/// <summary> bool IsGradientSupported { get; }
/// Get whether or not the analytical jacobian is supported. bool IsHessianSupported { get; }
/// </summary>
bool IsJacobianSupported { get; } bool IsFinished { get; set; }
} }
public interface IObjectiveModel : IObjectiveModelEvaluation public interface IObjectiveModel : IObjectiveModelEvaluation
{ {
void EvaluateFunction(Vector<double> parameters); void SetParameters(Vector<double> initialGuess, Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null);
void EvaluateJacobian(Vector<double> parameters);
void EvaluateCovariance(Vector<double> parameters); void EvaluateAt(Vector<double> parameters);
/// <summary>Create a new independent copy of this objective function, evaluated at the same point.</summary> /// <summary>Create a new independent copy of this objective function, evaluated at the same point.</summary>
IObjectiveModel Fork(); IObjectiveModel Fork();

45
src/Numerics/Optimization/LevenbergMarquardtMinimizer.cs

@ -1,5 +1,6 @@
using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearAlgebra;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
@ -44,24 +45,31 @@ namespace MathNet.Numerics.Optimization
MaximumIterations = maximumIterations; 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)
{ {
if (objective == null) if (objective == null)
throw new ArgumentNullException("objective"); throw new ArgumentNullException("objective");
if (initialGuess == null) if (initialGuess == null)
throw new ArgumentNullException("initialGuess"); throw new ArgumentNullException("initialGuess");
return Minimum(objective, initialGuess, InitialMu, FunctionTolerance, GradientTolerance, StepTolerance, MaximumIterations); 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,
double[] lowerBound = null, double[] upperBound = null, double[] scales = null, bool[] isFixed = null)
{ {
if (objective == null) if (objective == null)
throw new ArgumentNullException("objective"); throw new ArgumentNullException("objective");
if (initialGuess == null) if (initialGuess == null)
throw new ArgumentNullException("initialGuess"); throw new ArgumentNullException("initialGuess");
return Minimum(objective, CreateVector.DenseOfArray<double>(initialGuess), InitialMu, GradientTolerance, StepTolerance, FunctionTolerance, MaximumIterations); var lb = (lowerBound == null) ? null : CreateVector.Dense<double>(lowerBound);
var ub = (upperBound == null) ? null : CreateVector.Dense<double>(upperBound);
var sc = (scales == null) ? null : CreateVector.Dense<double>(scales);
var fx = (isFixed == null) ? null : isFixed.ToList();
return Minimum(objective, CreateVector.DenseOfArray<double>(initialGuess), lb, ub, sc, fx, InitialMu, GradientTolerance, StepTolerance, FunctionTolerance, MaximumIterations);
} }
/// <summary> /// <summary>
@ -75,7 +83,9 @@ namespace MathNet.Numerics.Optimization
/// <param name="functionTolerance">The stopping threshold for L2 norm of the residuals.</param> /// <param name="functionTolerance">The stopping threshold for L2 norm of the residuals.</param>
/// <param name="maximumIterations">The max iterations.</param> /// <param name="maximumIterations">The max iterations.</param>
/// <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, double initialMu = 1E-3, double gradientTolerance = 1E-18, double stepTolerance = 1E-18, double functionTolerance = 1E-18, int maximumIterations = -1) 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,
double initialMu = 1E-3, double gradientTolerance = 1E-18, double stepTolerance = 1E-18, double functionTolerance = 1E-18, int maximumIterations = -1)
{ {
// Non-linear least square fitting by the Levenberg-Marduardt algorithm. // Non-linear least square fitting by the Levenberg-Marduardt algorithm.
// //
@ -118,23 +128,24 @@ namespace MathNet.Numerics.Optimization
if (initialGuess == null) if (initialGuess == null)
throw new ArgumentNullException("initialGuess"); throw new ArgumentNullException("initialGuess");
objective.SetParameters(initialGuess, lowerBound, upperBound, scales, isFixed);
ExitCondition exitCondition = ExitCondition.None; ExitCondition exitCondition = ExitCondition.None;
// Initialize objective // Initialize objective
objective.FunctionEvaluations = 0; objective.FunctionEvaluations = 0;
objective.JacobianEvaluations = 0; objective.JacobianEvaluations = 0;
objective.IsFinished = false;
// First, calculate function values and setup variables // First, calculate function values and setup variables
objective.EvaluateFunction(initialGuess); objective.EvaluateAt(initialGuess);
var P = objective.Parameters; // current 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.Residue; // Residual Sum of Squares = R'R var RSS = objective.Value; // Residual Sum of Squares = R'R
if (maximumIterations < 0) if (maximumIterations < 0)
{ {
maximumIterations = (objective.IsJacobianSupported) maximumIterations = 200 * (initialGuess.Count + 1);
? 100 * (initialGuess.Count + 1)
: 200 * (initialGuess.Count + 1);
} }
// if RSS == NaN, stop // if RSS == NaN, stop
@ -157,9 +168,8 @@ namespace MathNet.Numerics.Optimization
} }
// Evaluate gradient and Hessian // Evaluate gradient and Hessian
objective.EvaluateJacobian(P);
var Gradient = objective.Gradient; var Gradient = objective.Gradient;
var Hessian = objective.Hessian; var Hessian = 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
@ -170,7 +180,6 @@ namespace MathNet.Numerics.Optimization
if (exitCondition != ExitCondition.None) if (exitCondition != ExitCondition.None)
{ {
objective.EvaluateCovariance(P);
return new NonlinearMinimizationResult(objective, -1, exitCondition); return new NonlinearMinimizationResult(objective, -1, exitCondition);
} }
@ -197,8 +206,8 @@ namespace MathNet.Numerics.Optimization
var Pnew = P + Pstep; // new parameters to test var Pnew = P + Pstep; // new parameters to test
objective.EvaluateFunction(Pnew); objective.EvaluateAt(Pnew);
var RSSnew = objective.Residue; var RSSnew = objective.Value;
if (double.IsNaN(RSSnew)) if (double.IsNaN(RSSnew))
{ {
@ -220,7 +229,6 @@ namespace MathNet.Numerics.Optimization
RSS = RSSnew; RSS = RSSnew;
// update gradient and Hessian // update gradient and Hessian
objective.EvaluateJacobian(P);
Gradient = objective.Gradient; Gradient = objective.Gradient;
Hessian = objective.Hessian; Hessian = objective.Hessian;
diagonalOfHessian = Hessian.Diagonal(); diagonalOfHessian = Hessian.Diagonal();
@ -258,9 +266,6 @@ namespace MathNet.Numerics.Optimization
exitCondition = ExitCondition.ExceedIterations; exitCondition = ExitCondition.ExceedIterations;
} }
// finalize
objective.EvaluateCovariance(P);
return new NonlinearMinimizationResult(objective, iterations, exitCondition); return new NonlinearMinimizationResult(objective, iterations, exitCondition);
} }
} }

54
src/Numerics/Optimization/NonlinearMinimizationResult.cs

@ -13,31 +13,25 @@ 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.Parameters; } } public Vector<double> BestFitParameters { get { return ModelInfoAtMinimum.Point; } }
/// <summary> /// <summary>
/// Returns the standard errors of the corresponding parameters /// Returns the standard errors of the corresponding parameters
/// </summary> /// </summary>
public Vector<double> StandardErrors public Vector<double> StandardErrors { get; private set; }
{
get
{
if (ModelInfoAtMinimum.Covariance == null)
return null;
return ModelInfoAtMinimum.Covariance.Diagonal().PointwiseSqrt();
}
}
/// <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.Values; } } public Vector<double> BestFitValues { get { return ModelInfoAtMinimum.ModelValues; } }
/// <summary> /// <summary>
/// Returns the residual sum of squares. /// Returns the residual sum of squares.
/// </summary> /// </summary>
public double Residue { get { return ModelInfoAtMinimum.Residue; } } public double Residue { get { return ModelInfoAtMinimum.Value; } }
public double DegreeOfFreedom { get { return ModelInfoAtMinimum.DegreeOfFreedom; } } public double DegreeOfFreedom { get { return ModelInfoAtMinimum.DegreeOfFreedom; } }
public Matrix<double> Covariance { 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; }
@ -47,6 +41,40 @@ namespace MathNet.Numerics.Optimization
ModelInfoAtMinimum = modelInfo; ModelInfoAtMinimum = modelInfo;
Iterations = iterations; Iterations = iterations;
ReasonForExit = reasonForExit; ReasonForExit = reasonForExit;
AnalyzeResult(modelInfo);
}
private void AnalyzeResult(IObjectiveModel objective)
{
objective.IsFinished = true;
objective.EvaluateAt(objective.Point);
var Hessian = objective.Hessian;
if (Hessian == null || DegreeOfFreedom < 1)
{
Covariance = null;
Correlation = null;
StandardErrors = null;
return;
}
Covariance = Hessian.PseudoInverse() * objective.Value / DegreeOfFreedom;
if (Covariance != null)
{
StandardErrors = Covariance.Diagonal().PointwiseSqrt();
var correlation = Covariance.Clone();
var d = correlation.Diagonal().PointwiseSqrt();
var dd = d.OuterProduct(d);
Correlation = correlation.PointwiseDivide(dd);
}
else
{
StandardErrors = null;
Correlation = null;
}
} }
} }
} }

12
src/Numerics/Optimization/ObjectiveModel.cs

@ -1,9 +1,6 @@
using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Optimization.ObjectiveModels; using MathNet.Numerics.Optimization.ObjectiveModels;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
{ {
@ -13,27 +10,22 @@ namespace MathNet.Numerics.Optimization
/// Fitting model with a user supplied jacobian for non-linear least squares regression. /// Fitting model with a user supplied jacobian for non-linear least squares regression.
/// </summary> /// </summary>
public static IObjectiveModel FittingModel(Func<Vector<double>, double, double> function, Func<Vector<double>, double, Vector<double>> derivatives, 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, Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null)
Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null)
{ {
var objective = new FittingObjectiveModel(function, derivatives); var objective = new FittingObjectiveModel(function, derivatives);
objective.SetObserved(observedX, observedY, weight); objective.SetObserved(observedX, observedY, weight);
objective.SetParameters(lowerBound, upperBound, scales, isFixed);
return objective; return objective;
} }
/// <summary> /// <summary>
/// Fitting model for non-linear least squares regression. /// Fitting model for non-linear least squares regression.
/// The numerical jacobian with accuracy order is used.
/// </summary> /// </summary>
public static IObjectiveModel FittingModel(Func<Vector<double>, double, double> function, public static IObjectiveModel FittingModel(Func<Vector<double>, double, double> function,
Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null, Vector<double> observedX, Vector<double> observedY, Vector<double> weight = null,
Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null,
int accuracyOrder = 2) int accuracyOrder = 2)
{ {
var objective = new FittingObjectiveModel(function, null, accuracyOrder: accuracyOrder); var objective = new FittingObjectiveModel(function, accuracyOrder: accuracyOrder);
objective.SetObserved(observedX, observedY, weight); objective.SetObserved(observedX, observedY, weight);
objective.SetParameters(lowerBound, upperBound, scales, isFixed);
return objective; return objective;
} }

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

@ -8,10 +8,30 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
{ {
internal class FittingObjectiveModel : IObjectiveModel internal class FittingObjectiveModel : IObjectiveModel
{ {
#region Private Variables
readonly Func<Vector<double>, double, double> userFunction; // (p, x) => f(x; p) 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 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 #region Public Variables - Observed Data
/// <summary> /// <summary>
/// Set or get the values of the independent variable. /// Set or get the values of the independent variable.
@ -25,178 +45,183 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
/// <summary> /// <summary>
/// Set or get the values of the weights for the observations. /// Set or get the values of the weights for the observations.
/// inverse of the standard measurement errors
/// If null, unity weighting is used.
/// </summary> /// </summary>
public Matrix<double> Weights { get; private set; } public Matrix<double> Weights { get; private set; }
// W = LL' private Vector<double> L; // Weights = LL'
private Vector<double> L;
/// <summary> /// <summary>
/// Set or get the values of the parameters. /// Get the number of observations.
/// </summary> /// </summary>
public Vector<double> Parameters { get; private set; } public int NumberOfObservations { get { return (ObservedY == null) ? 0 : ObservedY.Count; } }
/// <summary> #endregion Public Variables - Observed Data
/// Set or get the values of the parameters.
/// </summary>
public List<bool> IsFixed { get; set; }
/// <summary> #region Public Variables - Bounds of Parameter
/// Set or get the values of the parameters.
/// </summary>
public Vector<double> LowerBound { get; set; }
/// <summary> /// <summary>
/// Set or get the values of the parameters. /// Get the values of the parameters.
/// </summary> /// </summary>
public Vector<double> UpperBound { get; set; } public List<bool> IsFixed { get; private set; }
/// <summary> /// <summary>
/// Set or get the scale factor of the parameters. /// Get the values of the parameters.
/// </summary> /// </summary>
public Vector<double> Scales { get; set; } public Vector<double> LowerBound { get; private set; }
/// <summary> /// <summary>
/// Set of get whether or not the parameters are bounded. /// Get the values of the parameters.
/// </summary> /// </summary>
public bool IsBounded { get; set; } public Vector<double> UpperBound { get; private set; }
/// <summary> /// <summary>
/// Get the y-values of the fitted model that correspond to the independent values. /// Get the scale factor of the parameters.
/// </summary> /// </summary>
public Vector<double> Values { get; private set; } public Vector<double> Scales { get; private set; }
/// <summary> /// <summary>
/// Get the error values, R(x; p) = L * (y - f(x; p)) where L = sqrt(W) /// Get the number of unknown parameters.
/// </summary> /// </summary>
private Vector<double> Residuals; public int NumberOfParameters { get { return (Point == null) ? 0 : Point.Count; } }
/// <summary> #endregion Public Variables - Bounds of Parameter
/// Get the residual sum of squares, R.DotProduct(R)
/// </summary> #region Public Variables - Others
public double Residue { get; private set; }
/// <summary> /// <summary>
/// Get the Jacobian matrix of x and p, J(x; p). /// Get the number of calls to function.
/// </summary> /// </summary>
public Matrix<double> Jacobian { get; private set; } public int FunctionEvaluations { get; set; }
/// <summary> /// <summary>
/// Get the Gradient vector of x and p, J'WR /// Get the number of calls to jacobian.
/// </summary> /// </summary>
public Vector<double> Gradient { get; private set; } public int JacobianEvaluations { get; set; }
/// <summary> #endregion Public Variables - Others
/// Get the Hessian matrix of x and p, J'WJ
/// </summary> public FittingObjectiveModel(Func<Vector<double>, double, double>function, Func<Vector<double>, double, Vector<double>> derivatives = null, int accuracyOrder = 2)
public Matrix<double> Hessian { get; private set; } {
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> /// <summary>
/// Get the number of observations. /// Set or get the values of the parameters.
/// </summary> /// </summary>
public int NumberOfObservations { get { return (ObservedY == null) ? 0 : ObservedY.Count; } } public Vector<double> Point { get { return coefficients; } }
/// <summary> /// <summary>
/// Get the number of unknown parameters. /// Get the y-values of the fitted model that correspond to the independent values.
/// </summary> /// </summary>
public int NumberOfParameters { get { return (Parameters == null) ? 0 : Parameters.Count; } } public Vector<double> ModelValues { get; private set; }
/// <summary> /// <summary>
/// Get the degree of freedom /// Get the residual sum of squares.
/// </summary> /// </summary>
public int DegreeOfFreedom public double Value
{ {
get get
{ {
var dof = NumberOfObservations - NumberOfParameters; if (!hasFunctionValue)
if (IsFixed != null)
{ {
dof = dof + IsFixed.Count(p => p == true); EvaluateFunction();
hasFunctionValue = true;
} }
return dof; return functionValue;
} }
} }
/// <summary> /// <summary>
/// Get the covariance matrix. /// Get the Gradient vector of x and p.
/// </summary>
public Matrix<double> Covariance { get; private set; }
/// <summary>
/// Get the correlation matrix.
/// </summary>
public Matrix<double> Correlation { get; private set; }
/// <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; }
/// <summary>
/// Set or get the desired accuracy order of the numerical jacobian.
/// </summary> /// </summary>
public int AccuracyOrder { get; set; } public Vector<double> Gradient
{
get
{
if (!hasJacobianValue)
{
EvaluateJacobian();
hasJacobianValue = true;
}
return gradientValue;
}
}
/// <summary> /// <summary>
/// Get whether or not the analytical jacobian is supported. /// Get the Hessian matrix of x and p, J'WJ
/// </summary> /// </summary>
public bool IsJacobianSupported { get { return userDerivatives != null; } } public Matrix<double> Hessian
#endregion Public Variables
public FittingObjectiveModel(Func<Vector<double>, double, double>function, Func<Vector<double>, double, Vector<double>> derivatives, int accuracyOrder = 2)
{ {
userFunction = function; get
userDerivatives = derivatives; {
AccuracyOrder = Math.Min(6, Math.Max(1, accuracyOrder)); if (!hasJacobianValue)
{
EvaluateJacobian();
hasJacobianValue = true;
}
return hessianValue;
}
} }
public IObjectiveModel Fork() /// <summary>
/// Get the degree of freedom
/// </summary>
public int DegreeOfFreedom
{ {
return new FittingObjectiveModel(userFunction, userDerivatives, AccuracyOrder) get
{ {
ObservedX = ObservedX, var df = NumberOfObservations - NumberOfParameters;
ObservedY = ObservedY, if (IsFixed != null)
Weights = Weights, {
df = df + IsFixed.Count(p => p == true);
Parameters = Parameters, }
LowerBound = LowerBound, return df;
UpperBound = UpperBound, }
IsFixed = IsFixed,
Scales = Scales,
IsBounded = IsBounded,
Residue = Residue,
Jacobian = Jacobian
};
} }
public IObjectiveModel CreateNew() public bool IsGradientSupported { get { return true; } }
{ public bool IsHessianSupported { get { return true; } }
return new FittingObjectiveModel(userFunction, userDerivatives, AccuracyOrder);
} public bool IsFinished { get; set; }
public IObjectiveFunction ToObjectiveFunction() public IObjectiveFunction ToObjectiveFunction()
{ {
Tuple<double, Vector<double>, Matrix<double>> function(Vector<double> point) Tuple<double, Vector<double>, Matrix<double>> function(Vector<double> point)
{ {
EvaluateFunction(point); EvaluateAt(point);
EvaluateJacobian(point);
return new Tuple<double, Vector<double>, Matrix<double>>(Residue, Gradient, Hessian); return new Tuple<double, Vector<double>, Matrix<double>>(Value, Gradient, Hessian);
} }
LowerBound = null;
UpperBound = null;
Scales = null;
IsFixed = null;
IsBounded = false;
var objective = new GradientHessianObjectiveFunction(function); var objective = new GradientHessianObjectiveFunction(function);
return objective; return objective;
} }
@ -244,50 +269,37 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
} }
/// <summary> /// <summary>
/// Set observed data to fit. /// Set parameters and bounds.
/// </summary>
public void SetObserved(double[] observedX, double[] observedY, double[] weights = null)
{
if (observedX == null || observedY == null)
{
throw new ArgumentNullException("The data set can't be null.");
}
if (observedX.Length != observedY.Length)
{
throw new ArgumentException("The observed x data can't have different from observed y data.");
}
var wVector = (weights == null)
? null
: Vector<double>.Build.DenseOfArray(weights);
SetObserved(Vector<double>.Build.DenseOfArray(observedX), Vector<double>.Build.DenseOfArray(observedY), wVector);
}
/// <summary>
/// Set parameters.
/// <para/>
/// If bounded, the paramneters will be projected to unconstrained range by the mapping rule from the MINPACK.
/// If the projection is not needed, set IsBounded = false befre calling the Minimization method.
/// </summary> /// </summary>
/// <param name="lowerBound">The lower bounds of parameters.</param> /// <param name="lowerBound">The lower bounds of parameters.</param>
/// <param name="upperBound">The upper bounds of parameters.</param> /// <param name="upperBound">The upper bounds of parameters.</param>
/// /// <param name="scales">The scaling constants of parameters</param> /// <param name="scales">The scaling constants of parameters</param>
/// <param name="isFixed">The list to the parameters fix or free.</param> /// <param name="isFixed">The list to the parameters fix or free.</param>
public void SetParameters(Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null) 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) if (lowerBound != null && lowerBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{ {
throw new ArgumentException("The lower bounds must be finite."); 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; LowerBound = lowerBound;
if (upperBound != null && upperBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0) if (upperBound != null && upperBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0)
{ {
throw new ArgumentException("The upper bounds must be finite."); throw new ArgumentException("The upper bounds must be finite.");
} }
if (upperBound != null && lowerBound != null && upperBound.Count != lowerBound.Count) if (upperBound != null && upperBound.Count != initialGuess.Count)
{ {
throw new ArgumentException("The upper bounds can't have different elements from the lower bounds."); throw new ArgumentException("The upper bounds can't have different elements from the initial guess.");
} }
UpperBound = upperBound; UpperBound = upperBound;
@ -295,13 +307,9 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
{ {
throw new ArgumentException("The scales must be finite."); throw new ArgumentException("The scales must be finite.");
} }
if (scales != null && lowerBound != null && scales.Count != lowerBound.Count) if (scales != null && scales.Count != initialGuess.Count)
{
throw new ArgumentException("The upper bounds can't have different elements from the lower bounds.");
}
if (scales != null && upperBound != null && scales.Count != upperBound.Count)
{ {
throw new ArgumentException("The upper bounds can't have different elements from the upper bounds."); throw new ArgumentException("The upper bounds can't have different elements from the initial guess.");
} }
if (scales != null && scales.Count(x => x < 0) > 0) if (scales != null && scales.Count(x => x < 0) > 0)
{ {
@ -309,48 +317,20 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
} }
Scales = scales; Scales = scales;
IsBounded = (LowerBound != null || UpperBound != null || Scales != null); if (isFixed != null && isFixed.Count != initialGuess.Count)
if (isFixed != null && lowerBound != null && isFixed.Count != lowerBound.Count)
{
throw new ArgumentException("The initial guess can't have different elements from the lower bounds.");
}
if (isFixed != null && upperBound != null && isFixed.Count != upperBound.Count)
{ {
throw new ArgumentException("The initial guess can't have different elements from the upper bounds."); throw new ArgumentException("The isFixed can't have different elements from the initial guess.");
}
if (isFixed != null && scales != null && isFixed.Count != scales.Count)
{
throw new ArgumentException("The initial guess can't have different elements from the scales.");
} }
if (isFixed != null && isFixed.Count(p => p == true) == isFixed.Count) if (isFixed != null && isFixed.Count(p => p == true) == isFixed.Count)
{ {
throw new ArgumentException("All the parameters can't be fixed."); throw new ArgumentException("All the parameters can't be fixed.");
} }
IsFixed = isFixed; IsFixed = isFixed;
}
/// <summary>
/// Set parameters.
/// <para/>
/// If bounded, the paramneters will be projected to unconstrained range by the mapping rule.
/// If the projection is not needed, set IsBounded = false befre calling the Minimization method.
/// </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(double[] lowerBound = null, double[] upperBound = null, double[] scales = null, bool[] isFixed = null)
{
var lb = (lowerBound == null) ? null : Vector<double>.Build.DenseOfArray(lowerBound);
var ub = (upperBound == null) ? null : Vector<double>.Build.DenseOfArray(upperBound);
var sc = (scales == null) ? null : Vector<double>.Build.DenseOfArray(scales);
var fp = (isFixed == null) ? null : isFixed.ToList();
SetParameters(lb, ub, sc, fp); isBounded = LowerBound != null || UpperBound != null || Scales != null;
} }
public void EvaluateFunction(Vector<double> parameters) public void EvaluateAt(Vector<double> parameters)
{ {
ValidateParameters(parameters); ValidateParameters(parameters);
@ -386,69 +366,84 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
// //
// Except when it is initial guess, the parameters argument is always internal parameter. // 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. // So, first map the parameters argument to the external parameters in order to calculate function values.
var Pext = (FunctionEvaluations > 0 && this.IsBounded)
? ProjectParametersToExternal(parameters)
: parameters.Clone();
// Project parameters, now this.Parameters are the internal parameters. Pext = (FunctionEvaluations > 0 && isBounded)
Parameters = (this.IsBounded) ? ProjectParametersToExternal(parameters)
: parameters.Clone();
Pint = (isBounded)
? ProjectParametersToInternal(Pext) ? ProjectParametersToInternal(Pext)
: 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] // Calculates the residuals, (y[i] - f(x[i]; p)) * L[i]
if (Values == null) if (ModelValues == null)
{ {
Values = Vector<double>.Build.Dense(NumberOfObservations); ModelValues = Vector<double>.Build.Dense(NumberOfObservations);
} }
for (int i = 0; i < NumberOfObservations; i++) for (int i = 0; i < NumberOfObservations; i++)
{ {
Values[i] = userFunction(Pext, ObservedX[i]); ModelValues[i] = userFunction(Pext, ObservedX[i]);
} }
FunctionEvaluations++; FunctionEvaluations++;
// calculate the weighted residuals // calculate the weighted residuals
Residuals = (Weights == null) residuals = (Weights == null)
? ObservedY - Values ? ObservedY - ModelValues
: (ObservedY - Values).PointwiseMultiply(L); : (ObservedY - ModelValues).PointwiseMultiply(L);
// Calculate the residual sum of squares // Calculate the residual sum of squares
Residue = Residuals.DotProduct(Residuals); functionValue = residuals.DotProduct(residuals);
return; return;
} }
public void EvaluateJacobian(Vector<double> parameters) private void EvaluateJacobian()
{ {
var Pext = (IsBounded)
? ProjectParametersToExternal(parameters)
: parameters.Clone();
// Calculates the jacobian of x and p. // Calculates the jacobian of x and p.
if (userDerivatives != null) if (userDerivatives != null)
{ {
// analytical jacobian // analytical jacobian
if (Jacobian == null) if (jacobianValue == null)
{ {
Jacobian = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters); jacobianValue = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters);
} }
for (int i = 0; i < NumberOfObservations; i++) for (int i = 0; i < NumberOfObservations; i++)
{ {
Jacobian.SetRow(i, userDerivatives(Pext, ObservedX[i])); jacobianValue.SetRow(i, userDerivatives(Pext, ObservedX[i]));
} }
JacobianEvaluations++; JacobianEvaluations++;
} }
else else
{ {
// numerical jacobian // numerical jacobian
Jacobian = NumericalJacobian(Pext, Values, AccuracyOrder); jacobianValue = NumericalJacobian(Pext, ModelValues, accuracyOrder);
FunctionEvaluations += AccuracyOrder; FunctionEvaluations += accuracyOrder;
} }
var scaleFactors = (this.IsBounded) var scaleFactors = (isBounded && !IsFinished)
? ScaleFactorsOfJacobian(Parameters) ? ScaleFactorsOfJacobian(Pint)
: Vector<double>.Build.Dense(Parameters.Count, 1.0); : Vector<double>.Build.Dense(Pint.Count, 1.0);
// Jint(x; Pint) = Jext(x; Pext) * scale where scale = dPext/dPint // project jacobian: Jint(x; Pint) = Jext(x; Pext) * scale where scale = dPext/dPint
for (int i = 0; i < NumberOfObservations; i++) for (int i = 0; i < NumberOfObservations; i++)
{ {
for (int j = 0; j < NumberOfParameters; j++) for (int j = 0; j < NumberOfParameters; j++)
@ -456,58 +451,24 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
if (IsFixed != null && IsFixed[j]) if (IsFixed != null && IsFixed[j])
{ {
// if j-th parameter is fixed, set J[i, j] = 0 // if j-th parameter is fixed, set J[i, j] = 0
Jacobian[i, j] = 0.0; jacobianValue[i, j] = 0.0;
} }
else else
{ {
Jacobian[i, j] = Jacobian[i, j] * scaleFactors[j]; jacobianValue[i, j] = jacobianValue[i, j] * scaleFactors[j];
} }
} }
} }
// Gradient, g = -J'W(y − f(x; p)) = -J'L(L'E) = -J'LR // Gradient, g = -J'W(y − f(x; p)) = -J'L(L'E) = -J'LR
Gradient = (Weights == null) gradientValue = (Weights == null)
? -Jacobian.Transpose() * (ObservedY - Values) ? -jacobianValue.Transpose() * (ObservedY - ModelValues)
: -Jacobian.Transpose() * Weights * (ObservedY - Values); : -jacobianValue.Transpose() * Weights * (ObservedY - ModelValues);
// approximated Hessian, H = J'WJ + ∑LRiHi ~ J'WJ near the minimum // approximated Hessian, H = J'WJ + ∑LRiHi ~ J'WJ near the minimum
Hessian = (Weights == null) hessianValue = (Weights == null)
? Jacobian.Transpose() * Jacobian ? jacobianValue.Transpose() * jacobianValue
: Jacobian.Transpose() * Weights * Jacobian; : jacobianValue.Transpose() * Weights * jacobianValue;
}
public void EvaluateCovariance(Vector<double> parameters)
{
// convert to bounded(external) parameters
var Pext = (IsBounded)
? ProjectParametersToExternal(parameters)
: parameters.Clone();
// set IsBounded = false to get external Parameters and covariance matrix
this.IsBounded = false;
EvaluateFunction(Pext);
EvaluateJacobian(Pext);
// restore isBounded
this.IsBounded = (LowerBound != null || UpperBound != null);
if (Hessian == null || Residuals == null || DegreeOfFreedom < 1)
{
Covariance = null;
Correlation = null;
return;
}
var covariance = Hessian.PseudoInverse() * Residuals.DotProduct(Residuals) / DegreeOfFreedom;
Covariance = covariance;
var correlation = covariance.Clone();
var d = correlation.Diagonal().PointwiseSqrt();
var dd = d.OuterProduct(d);
Correlation = correlation.PointwiseDivide(dd);
return;
} }
private void ValidateParameters(Vector<double> parameters) private void ValidateParameters(Vector<double> parameters)
@ -538,16 +499,13 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
} }
} }
#region Numerical Derivatives private Matrix<double> NumericalJacobian(Vector<double> Pext, Vector<double> currentValues, int accuracyOrder = 2)
// Numerical derivatives by using the central or forward finite difference
private Matrix<double> NumericalJacobian(Vector<double> parameters, Vector<double> currentValues, int accuracyOrder = 2)
{ {
const double sqrtEpsilon = 1.4901161193847656250E-8; // sqrt(machineEpsilon) const double sqrtEpsilon = 1.4901161193847656250E-8; // sqrt(machineEpsilon)
Matrix<double> derivertives = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters); Matrix<double> derivertives = Matrix<double>.Build.Dense(NumberOfObservations, NumberOfParameters);
var d = 0.000003 * parameters.PointwiseAbs().PointwiseMaximum(sqrtEpsilon); var d = 0.000003 * Pext.PointwiseAbs().PointwiseMaximum(sqrtEpsilon);
var h = Vector<double>.Build.Dense(NumberOfParameters); var h = Vector<double>.Build.Dense(NumberOfParameters);
for (int i = 0; i < NumberOfObservations; i++) for (int i = 0; i < NumberOfObservations; i++)
@ -560,12 +518,12 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
if (accuracyOrder >= 6) 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) // 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, x); var f1 = userFunction(Pext - 3 * h, x);
var f2 = userFunction(parameters - 2 * h, x); var f2 = userFunction(Pext - 2 * h, x);
var f3 = userFunction(parameters - h, x); var f3 = userFunction(Pext - h, x);
var f4 = userFunction(parameters + h, x); var f4 = userFunction(Pext + h, x);
var f5 = userFunction(parameters + 2 * h, x); var f5 = userFunction(Pext + 2 * h, x);
var f6 = userFunction(parameters + 3 * h, x); var f6 = userFunction(Pext + 3 * h, x);
var prime = (-f1 + 9 * f2 - 45 * f3 + 45 * f4 - 9 * f5 + f6) / (60 * h[j]); var prime = (-f1 + 9 * f2 - 45 * f3 + 45 * f4 - 9 * f5 + f6) / (60 * h[j]);
derivertives[i, j] = prime; derivertives[i, j] = prime;
@ -574,11 +532,11 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
{ {
// f'(x) = {-137f(x) + 300f(x + h) - 300f(x + 2h) + 200f(x + 3h) - 75f(x + 4h) + 12f(x + 5h)} / 60h + O(h^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 f1 = currentValues[i];
var f2 = userFunction(parameters + h, x); var f2 = userFunction(Pext + h, x);
var f3 = userFunction(parameters + 2 * h, x); var f3 = userFunction(Pext + 2 * h, x);
var f4 = userFunction(parameters + 3 * h, x); var f4 = userFunction(Pext + 3 * h, x);
var f5 = userFunction(parameters + 4 * h, x); var f5 = userFunction(Pext + 4 * h, x);
var f6 = userFunction(parameters + 5 * 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]); var prime = (-137 * f1 + 300 * f2 - 300 * f3 + 200 * f4 - 75 * f5 + 12 * f6) / (60 * h[j]);
derivertives[i, j] = prime; derivertives[i, j] = prime;
@ -586,10 +544,10 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
else if (accuracyOrder == 4) else if (accuracyOrder == 4)
{ {
// f'(x) = {f(x - 2h) - 8f(x - h) + 8f(x + h) - f(x + 2h)} / 12h + O(h^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, x); var f1 = userFunction(Pext - 2 * h, x);
var f2 = userFunction(parameters - h, x); var f2 = userFunction(Pext - h, x);
var f3 = userFunction(parameters + h, x); var f3 = userFunction(Pext + h, x);
var f4 = userFunction(parameters + 2 * h, x); var f4 = userFunction(Pext + 2 * h, x);
var prime = (f1 - 8 * f2 + 8 * f3 - f4) / (12 * h[j]); var prime = (f1 - 8 * f2 + 8 * f3 - f4) / (12 * h[j]);
derivertives[i, j] = prime; derivertives[i, j] = prime;
@ -598,9 +556,9 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
{ {
// f'(x) = {-11f(x) + 18f(x + h) - 9f(x + 2h) + 2f(x + 3h)} / 6h + O(h^3) // f'(x) = {-11f(x) + 18f(x + h) - 9f(x + 2h) + 2f(x + 3h)} / 6h + O(h^3)
var f1 = currentValues[i]; var f1 = currentValues[i];
var f2 = userFunction(parameters + h, x); var f2 = userFunction(Pext + h, x);
var f3 = userFunction(parameters + 2 * h, x); var f3 = userFunction(Pext + 2 * h, x);
var f4 = userFunction(parameters + 3 * h, x); var f4 = userFunction(Pext + 3 * h, x);
var prime = (-11 * f1 + 18 * f2 - 9 * f3 + 2 * f4) / (6 * h[j]); var prime = (-11 * f1 + 18 * f2 - 9 * f3 + 2 * f4) / (6 * h[j]);
derivertives[i, j] = prime; derivertives[i, j] = prime;
@ -608,8 +566,8 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
else if (accuracyOrder == 2) else if (accuracyOrder == 2)
{ {
// f'(x) = {f(x + h) - f(x - h)} / 2h + O(h^2) // f'(x) = {f(x + h) - f(x - h)} / 2h + O(h^2)
var f1 = userFunction(parameters + h, x); var f1 = userFunction(Pext + h, x);
var f2 = userFunction(parameters - h, x); var f2 = userFunction(Pext - h, x);
var prime = (f1 - f2) / (2 * h[j]); var prime = (f1 - f2) / (2 * h[j]);
derivertives[i, j] = prime; derivertives[i, j] = prime;
@ -618,7 +576,7 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
{ {
// f'(x) = {- f(x) + f(x + h)} / h + O(h) // f'(x) = {- f(x) + f(x + h)} / h + O(h)
var f1 = currentValues[i]; var f1 = currentValues[i];
var f2 = userFunction(parameters + h, x); var f2 = userFunction(Pext + h, x);
var prime = (-f1 + f2) / h[j]; var prime = (-f1 + f2) / h[j];
derivertives[i, j] = prime; derivertives[i, j] = prime;
@ -631,10 +589,6 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
return derivertives; return derivertives;
} }
#endregion Numerical Derivatives
#region Projection
private Vector<double> ProjectParametersToInternal(Vector<double> Pext) private Vector<double> ProjectParametersToInternal(Vector<double> Pext)
{ {
var Pint = Pext.Clone(); var Pint = Pext.Clone();
@ -771,6 +725,6 @@ namespace MathNet.Numerics.Optimization.ObjectiveModels
return scale; return scale;
} }
#endregion Projection #endregion Private Methods
} }
} }

6
src/Numerics/Optimization/Subproblems/DogLegSubproblem.cs

@ -13,12 +13,10 @@ namespace MathNet.Numerics.Optimization.Subproblems
var Gradient = objective.Gradient; var Gradient = objective.Gradient;
var Hessian = objective.Hessian; var Hessian = objective.Hessian;
// newton point // newton point, the Gauss–Newton step by solving the normal equations
// the Gauss–Newton step by solving the normal equations
var Pgn = -Hessian.PseudoInverse() * Gradient; // Hessian.Solve(Gradient) fails so many times... var Pgn = -Hessian.PseudoInverse() * Gradient; // Hessian.Solve(Gradient) fails so many times...
// cauchy point // cauchy point, steepest descent direction is given by
// steepest descent direction is given by
var alpha = Gradient.DotProduct(Gradient) / (Hessian * Gradient).DotProduct(Gradient); var alpha = Gradient.DotProduct(Gradient) / (Hessian * Gradient).DotProduct(Gradient);
var Psd = -alpha * Gradient; var Psd = -alpha * Gradient;

2
src/Numerics/Optimization/Subproblems/NewtonCGSubproblem.cs

@ -58,7 +58,7 @@ namespace MathNet.Numerics.Optimization.Subproblems
z = znext; z = znext;
r = rnext; r = rnext;
d = -rnext + rnext_sq / r_sq * d; ; d = -rnext + rnext_sq / r_sq * d;
} }
} }
} }

64
src/Numerics/Optimization/TrustRegionMinimizerBase.cs

@ -1,5 +1,7 @@
using MathNet.Numerics.LinearAlgebra; using MathNet.Numerics.LinearAlgebra;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace MathNet.Numerics.Optimization namespace MathNet.Numerics.Optimization
{ {
@ -46,39 +48,50 @@ namespace MathNet.Numerics.Optimization
MaximumIterations = maximumIterations; 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)
{ {
if (objective == null) if (objective == null)
throw new ArgumentNullException("objective"); throw new ArgumentNullException("objective");
if (initialGuess == null) if (initialGuess == null)
throw new ArgumentNullException("initialGuess"); throw new ArgumentNullException("initialGuess");
return Minimum(objective, initialGuess, Subproblem, GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations); return Minimum(Subproblem, objective, initialGuess, lowerBound, upperBound, scales, isFixed,
GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations);
} }
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)
{ {
if (objective == null) if (objective == null)
throw new ArgumentNullException("objective"); throw new ArgumentNullException("objective");
if (initialGuess == null) if (initialGuess == null)
throw new ArgumentNullException("initialGuess"); throw new ArgumentNullException("initialGuess");
return Minimum(objective, CreateVector.DenseOfArray<double>(initialGuess), Subproblem, GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations); var lb = (lowerBound == null) ? null : CreateVector.Dense<double>(lowerBound);
var ub = (upperBound == null) ? null : CreateVector.Dense<double>(upperBound);
var sc = (scales == null) ? null : CreateVector.Dense<double>(scales);
var fx = (isFixed == null) ? null : isFixed.ToList();
return Minimum(Subproblem, objective, CreateVector.DenseOfArray<double>(initialGuess), lb, ub, sc, fx,
GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations);
} }
/// <summary> /// <summary>
/// Non-linear least square fitting by the trust-region algorithm. /// Non-linear least square fitting by the trust-region algorithm.
/// </summary> /// </summary>
/// <param name="objective">The objective model, including function, jacobian, observations, and parameter bounds.</param> /// <param name="objective">The objective model, including function, jacobian, observations, and parameter bounds.</param>
/// <param name="subproblem">The subproblem</param>
/// <param name="initialGuess">The initial guess values.</param> /// <param name="initialGuess">The initial guess values.</param>
/// <param name="subproblem">The subproblem</param>
/// <param name="functionTolerance">The stopping threshold for L2 norm of the residuals.</param> /// <param name="functionTolerance">The stopping threshold for L2 norm of the residuals.</param>
/// <param name="gradientTolerance">The stopping threshold for infinity norm of the gradient vector.</param> /// <param name="gradientTolerance">The stopping threshold for infinity norm of the gradient vector.</param>
/// <param name="stepTolerance">The stopping threshold for L2 norm of the change of parameters.</param> /// <param name="stepTolerance">The stopping threshold for L2 norm of the change of parameters.</param>
/// <param name="radiusTolerance">The stopping threshold for trust region radius</param> /// <param name="radiusTolerance">The stopping threshold for trust region radius</param>
/// <param name="maximumIterations">The max iterations.</param> /// <param name="maximumIterations">The max iterations.</param>
/// <returns></returns> /// <returns></returns>
public static NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector<double> initialGuess, ITrustRegionSubproblem subproblem, double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-18, int maximumIterations = -1) public static NonlinearMinimizationResult Minimum(ITrustRegionSubproblem subproblem, IObjectiveModel objective, Vector<double> initialGuess,
Vector<double> lowerBound = null, Vector<double> upperBound = null, Vector<double> scales = null, List<bool> isFixed = null,
double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-18, int maximumIterations = -1)
{ {
// Non-linear least square fitting by the trust-region algorithm. // Non-linear least square fitting by the trust-region algorithm.
// //
@ -123,16 +136,19 @@ namespace MathNet.Numerics.Optimization
if (initialGuess == null) if (initialGuess == null)
throw new ArgumentNullException("initialGuess"); throw new ArgumentNullException("initialGuess");
objective.SetParameters(initialGuess, lowerBound, upperBound, scales, isFixed);
ExitCondition exitCondition = ExitCondition.None; ExitCondition exitCondition = ExitCondition.None;
// Initialize objective // Initialize objective
objective.FunctionEvaluations = 0; objective.FunctionEvaluations = 0;
objective.JacobianEvaluations = 0; objective.JacobianEvaluations = 0;
objective.IsFinished = false;
// First, calculate function values and setup variables // First, calculate function values and setup variables
objective.EvaluateFunction(initialGuess); objective.EvaluateAt(initialGuess);
var P = objective.Parameters; // current parameters var P = objective.Point; // current parameters
var RSS = objective.Residue; // Residual Sum of Squares = R'R var RSS = objective.Value; // Residual Sum of Squares = R'R
var RSSinit = RSS; // RSS at initial gussing parameters var RSSinit = RSS; // RSS at initial gussing parameters
if (maximumIterations < 0) if (maximumIterations < 0)
@ -140,7 +156,7 @@ namespace MathNet.Numerics.Optimization
maximumIterations = 200 * (initialGuess.Count + 1); maximumIterations = 200 * (initialGuess.Count + 1);
} }
// if R == NaN, stop // if RSS == NaN, stop
if (double.IsNaN(RSS)) if (double.IsNaN(RSS))
{ {
exitCondition = ExitCondition.InvalidValues; exitCondition = ExitCondition.InvalidValues;
@ -159,10 +175,9 @@ namespace MathNet.Numerics.Optimization
exitCondition = ExitCondition.Converged; // SmallRSS exitCondition = ExitCondition.Converged; // SmallRSS
} }
// Evaluate projected Hessian, and gradient // evaluate projected gradient and Hessian
objective.EvaluateJacobian(P);
var Hessian = objective.Hessian;
var Gradient = objective.Gradient; var Gradient = objective.Gradient;
var Hessian = objective.Hessian;
// if ||g||_oo <= gtol, found and stop // if ||g||_oo <= gtol, found and stop
if (Gradient.InfinityNorm() <= gradientTolerance) if (Gradient.InfinityNorm() <= gradientTolerance)
@ -172,8 +187,6 @@ namespace MathNet.Numerics.Optimization
if (exitCondition != ExitCondition.None) if (exitCondition != ExitCondition.None)
{ {
// finalize
objective.EvaluateCovariance(P);
return new NonlinearMinimizationResult(objective, -1, exitCondition); return new NonlinearMinimizationResult(objective, -1, exitCondition);
} }
@ -191,7 +204,7 @@ namespace MathNet.Numerics.Optimization
var Pstep = subproblem.Pstep; var Pstep = subproblem.Pstep;
var hitBoundary = subproblem.HitBoundary; var 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 = -objective.Gradient.DotProduct(Pstep) - 0.5 * Pstep.DotProduct(objective.Hessian * Pstep); var predictedReduction = -Gradient.DotProduct(Pstep) - 0.5 * Pstep.DotProduct(Hessian * Pstep);
if (Pstep.L2Norm() <= stepTolerance * (stepTolerance + P.L2Norm())) if (Pstep.L2Norm() <= stepTolerance * (stepTolerance + P.L2Norm()))
{ {
@ -201,13 +214,20 @@ namespace MathNet.Numerics.Optimization
var Pnew = P + Pstep; // parameters to test var Pnew = P + Pstep; // parameters to test
objective.EvaluateFunction(Pnew); objective.EvaluateAt(Pnew);
var RSSnew = objective.Residue; var RSSnew = objective.Value;
// if RSS == NaN, stop
if (double.IsNaN(RSSnew))
{
exitCondition = ExitCondition.InvalidValues;
break;
}
// calculate the ratio of the actual to the predicted reduction. // calculate the ratio of the actual to the predicted reduction.
double rho = (predictedReduction != 0) double rho = (predictedReduction != 0)
? (RSS - RSSnew) / predictedReduction ? (RSS - RSSnew) / predictedReduction
: 0; : 0.0;
if (rho > 0.75 && hitBoundary) if (rho > 0.75 && hitBoundary)
{ {
@ -229,8 +249,7 @@ namespace MathNet.Numerics.Optimization
Pnew.CopyTo(P); Pnew.CopyTo(P);
RSS = RSSnew; RSS = RSSnew;
// update Jacobian, Hessian, and gradient // evaluate projected gradient and Hessian
objective.EvaluateJacobian(P);
Gradient = objective.Gradient; Gradient = objective.Gradient;
Hessian = objective.Hessian; Hessian = objective.Hessian;
@ -253,9 +272,6 @@ namespace MathNet.Numerics.Optimization
exitCondition = ExitCondition.ExceedIterations; exitCondition = ExitCondition.ExceedIterations;
} }
// finalize
objective.EvaluateCovariance(P);
return new NonlinearMinimizationResult(objective, iterations, exitCondition); return new NonlinearMinimizationResult(objective, iterations, exitCondition);
} }
} }

Loading…
Cancel
Save