Mantid
Loading...
Searching...
No Matches
LevenbergMarquardtMDMinimizer.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
13
16#include "MantidAPI/IFunction.h"
17
18#include "MantidKernel/Logger.h"
19
20#include <cmath>
21#include <gsl/gsl_blas.h>
22
24namespace {
26Kernel::Logger g_log("LevenbergMarquardMD");
27} // namespace
28
29// clang-format off
30DECLARE_FUNCMINIMIZER(LevenbergMarquardtMDMinimizer, Levenberg-MarquardtMD)
31// clang-format on
32
33
35 : IFuncMinimizer(), m_tau(1e-6), m_mu(1e-6), m_nu(2.0), m_rho(1.0), m_F(0.0) {
36 declareProperty("MuMax", 1e6, "Maximum value of mu - a stopping parameter in failure.");
37 declareProperty("AbsError", 0.0001,
38 "Absolute error allowed for parameters - "
39 "a stopping parameter in success.");
40 declareProperty("Verbose", false, "Make output more verbose.");
41}
42
45 m_costFunction = std::dynamic_pointer_cast<CostFunctions::CostFuncFitting>(function);
46 if (!m_costFunction) {
47 throw std::invalid_argument("Levenberg-Marquardt minimizer works only with "
48 "functions which define the Hessian. Different function was given.");
49 }
50 m_mu = 0;
51 m_nu = 2.0;
52 m_rho = 1.0;
53}
54
56bool LevenbergMarquardtMDMinimizer::iterate(size_t /*iteration*/) {
57 const bool verbose = getProperty("Verbose");
58 const double muMax = getProperty("MuMax");
59 const double absError = getProperty("AbsError");
60
61 if (!m_costFunction) {
62 throw std::runtime_error("Cost function isn't set up.");
63 }
64 size_t n = m_costFunction->nParams();
65
66 if (n == 0) {
67 m_errorString = "No parameters to fit.";
68 return false;
69 }
70
71 if (m_mu > muMax) {
72 m_errorString = "Failed to converge, maximum mu reached.";
73 return false;
74 }
75
76 // calculate the first and second derivatives of the cost function.
77 if (m_mu == 0.0 || m_rho > 0) {
78 // calculate everything first time or
79 // if last iteration was good
80 m_F = m_costFunction->valDerivHessian();
81 }
82 // else if m_rho < 0 last iteration was bad: reuse m_der and m_hessian
83
84 // Calculate damping to hessian
85 if (m_mu == 0) // first iteration or accidental zero
86 {
87 m_mu = m_tau;
88 m_nu = 2.0;
89 }
90
91 if (verbose) {
92 g_log.warning() << "===========================================================\n";
93 g_log.warning() << "mu=" << m_mu << "\n\n";
94 }
95
96 if (m_D.empty()) {
97 m_D.resize(n);
98 }
99
100 // copy the hessian
101 EigenMatrix H(m_costFunction->getHessian());
102 EigenVector dd(m_costFunction->getDeriv());
103
104 // scaling factors
105 std::vector<double> sf(n);
106
107 for (size_t i = 0; i < n; ++i) {
108 double d = fabs(dd.get(i));
109 if (m_D[i] > d)
110 d = m_D[i];
111 m_D[i] = d;
112 double tmp = H.get(i, i) + m_mu * d;
113 H.set(i, i, tmp);
114 sf[i] = sqrt(tmp);
115 if (tmp == 0.0) {
116 m_errorString = "Function doesn't depend on parameter " + m_costFunction->parameterName(i);
117 return false;
118 }
119 }
120
121 // apply scaling
122 for (size_t i = 0; i < n; ++i) {
123 double d = dd.get(i);
124 dd.set(i, d / sf[i]);
125 for (size_t j = i; j < n; ++j) {
126 const double f = sf[i] * sf[j];
127 double tmp = H.get(i, j);
128 H.set(i, j, tmp / f);
129 if (i != j) {
130 tmp = H.get(j, i);
131 H.set(j, i, tmp / f);
132 }
133 }
134 }
135
136 if (verbose && m_rho > 0) {
137 g_log.warning() << "Hessian:\n" << H;
138 g_log.warning() << "Right-hand side:\n";
139 for (size_t j = 0; j < n; ++j) {
140 g_log.warning() << dd.get(j) << ' ';
141 }
142 g_log.warning() << '\n';
143 g_log.warning() << "Determinant=" << H.det() << '\n';
144 }
145
146 // Parameter corrections
147 EigenVector dx(n);
148 // To find dx solve the system of linear equations H * dx == -m_der
149 dd *= -1.0;
150 try {
151 H.solve(dd, dx);
152 } catch (std::runtime_error &error) {
153 m_errorString = error.what();
154 return false;
155 }
156
157 if (verbose) {
158 g_log.warning() << "\nScaling factors:\n";
159 for (size_t j = 0; j < n; ++j) {
160 g_log.warning() << sf[j] << ' ';
161 }
162 g_log.warning() << '\n';
163 g_log.warning() << "Corrections:\n";
164 for (size_t j = 0; j < n; ++j) {
165 g_log.warning() << dx.get(j) << ' ';
166 }
167 g_log.warning() << "\n\n";
168 }
169
170 // restore scaling
171 for (size_t i = 0; i < n; ++i) {
172 double d = dx.get(i);
173 dx.set(i, d / sf[i]);
174 d = dd.get(i);
175 dd.set(i, d * sf[i]);
176 }
177
178 // save previous state
179 m_costFunction->push();
180 // Update the parameters of the cost function.
181 EigenVector parameters(n);
182 m_costFunction->getParameters(parameters);
183 parameters += dx;
184 m_costFunction->setParameters(parameters);
185 if (verbose) {
186 for (size_t i = 0; i < n; ++i) {
187 g_log.warning() << "Parameter(" << i << ")=" << parameters[i] << '\n';
188 }
189 }
190 m_costFunction->getFittingFunction()->applyTies();
191
192 // --- prepare for the next iteration --- //
193
194 double dL;
195 // der -> - der - 0.5 * hessian * dx
196
197 EigenMatrix tempHessianTr = m_costFunction->getHessian().tr();
198 const gsl_matrix_const_view tempHessianTrGSL = getGSLMatrixView_const(tempHessianTr.inspector());
199 const gsl_vector_const_view dxGSL = getGSLVectorView_const(dx.inspector());
200 gsl_vector_view ddGSL = getGSLVectorView(dd.mutator());
201
202 gsl_blas_dgemv(CblasNoTrans, -0.5, &tempHessianTrGSL.matrix, &dxGSL.vector, 1., &ddGSL.vector);
203 // calculate the linear part of the change in cost function
204 // dL = - der * dx - 0.5 * dx * hessian * dx
205 gsl_blas_ddot(&ddGSL.vector, &dxGSL.vector, &dL);
206
207 double F1 = m_costFunction->val();
208 if (verbose) {
209 g_log.warning() << '\n';
210 g_log.warning() << "Old cost function " << m_F << '\n';
211 g_log.warning() << "New cost function " << F1 << '\n';
212 g_log.warning() << "Linear part " << dL << '\n';
213 }
214
215 // Try the stop condition
216 if (m_rho >= 0) {
217 EigenVector p(n);
218 m_costFunction->getParameters(p);
219 double dx_norm = gsl_blas_dnrm2(&dxGSL.vector);
220 if (dx_norm < absError) {
221 if (verbose) {
222 g_log.warning() << "Successful fit, parameters changed by less than " << absError << '\n';
223 }
224 return false;
225 }
226 if (m_rho == 0) {
227 if (m_F != F1) {
228 this->m_errorString = "Failed to converge, rho == 0";
229 }
230 if (verbose) {
231 g_log.warning() << "Successful fit, cost function didn't change.\n";
232 }
233 return false;
234 }
235 }
236
237 if (fabs(dL) == 0.0) {
238 if (m_F == F1)
239 m_rho = 1.0;
240 else
241 m_rho = 0;
242 } else {
243 m_rho = (m_F - F1) / dL;
244 if (m_rho == 0) {
245 return false;
246 }
247 }
248 if (verbose) {
249 g_log.warning() << "rho=" << m_rho << '\n';
250 }
251
252 if (m_rho > 0) { // good progress, decrease m_mu but no more than by 1/3
253 // rho = 1 - (2*rho - 1)^3
254 m_rho = 2.0 * m_rho - 1.0;
255 m_rho = 1.0 - m_rho * m_rho * m_rho;
256 const double I3 = 1.0 / 3.0;
257 if (m_rho > I3)
258 m_rho = I3;
259 if (m_rho < 0.0001)
260 m_rho = 0.1;
261 m_mu *= m_rho;
262 m_nu = 2.0;
263 m_F = F1;
264 if (verbose) {
265 g_log.warning() << "Good iteration, accept new parameters.\n";
266 g_log.warning() << "rho=" << m_rho << '\n';
267 }
268 // drop saved state, accept new parameters
269 m_costFunction->drop();
270 } else { // bad iteration. increase m_mu and revert changes to parameters
271 m_mu *= m_nu;
272 m_nu *= 2.0;
273 // undo parameter update
274 m_costFunction->pop();
275 m_F = m_costFunction->val();
276 if (verbose) {
277 g_log.warning() << "Bad iteration, increase mu and revert changes to parameters.\n";
278 }
279 }
280
281 return true;
282}
283
286 if (!m_costFunction) {
287 throw std::runtime_error("Cost function isn't set up.");
288 }
289 return m_costFunction->val();
290}
291
292} // namespace Mantid::CurveFitting::FuncMinimisers
gsl_vector * tmp
#define DECLARE_FUNCMINIMIZER(classname, username)
Macro for declaring a new type of minimizers to be used with the FuncMinimizerFactory.
double error
Definition: IndexPeaks.cpp:133
#define fabs(x)
Definition: Matrix.cpp:22
An interface for function minimizers.
std::string m_errorString
Error string.
A wrapper around Eigen::Matrix.
Definition: EigenMatrix.h:33
const map_type inspector() const
Get a const copy of the Eigen matrix.
Definition: EigenMatrix.h:58
A wrapper around Eigen::Vector.
Definition: EigenVector.h:27
void set(const size_t i, const double value)
Set an element.
double get(const size_t i) const
Get an element.
vec_map_type & mutator()
Get the map of the eigen vector.
Definition: EigenVector.h:51
const vec_map_type inspector() const
Get the const map of the eigen vector.
Definition: EigenVector.h:53
double m_mu
The damping mu parameter in the Levenberg-Marquardt method.
std::shared_ptr< CostFunctions::CostFuncFitting > m_costFunction
Pointer to the cost function.
double m_rho
The rho parameter in the Levenberg-Marquardt method.
void initialize(API::ICostFunction_sptr function, size_t maxIterations=0) override
Initialize minimizer, i.e. pass a function to minimize.
double m_nu
The nu parameter in the Levenberg-Marquardt method.
double costFunctionVal() override
Return current value of the cost function.
double m_tau
The tau parameter in the Levenberg-Marquardt method.
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< ICostFunction > ICostFunction_sptr
define a shared pointer to a cost function
Definition: ICostFunction.h:60
gsl_vector_view getGSLVectorView(vec_map_type &v)
take data from Eigen Vector and take a gsl view
Definition: GSLFunctions.h:54
gsl_matrix_const_view const getGSLMatrixView_const(const map_type m)
take data from a constEigen Matrix and return a transposed gsl view.
Definition: GSLFunctions.h:63
gsl_vector_const_view const getGSLVectorView_const(const vec_map_type v)
take const data from Eigen Vector and take a gsl view
Definition: GSLFunctions.h:59