Mantid
Loading...
Searching...
No Matches
AugmentedLagrangianOptimizer.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 +
9
10#include <cmath>
11#include <memory>
12
13#include <gsl/gsl_multimin.h>
14
15#include <algorithm>
16#include <cassert>
17#include <cmath>
18#include <sstream>
19
20namespace Mantid::CurveFitting {
22using std::fabs;
23using std::max;
24using std::min;
25
26namespace {
27// Absolute tolerance on function value
28double FTOL_ABS = 1e-10;
29// Relative tolerance on function value
30double FTOL_REL = 1e-10;
31// Absolute tolerance on the X values
32double XTOL_ABS = 1e-8;
33// Relative toleranceon the X values
34double XTOL_REL = 1e-8;
35// Tolerance on constraint violation
36double CONSTRAINT_TOL = 1e-08;
38int MAX_SUBOPT_ITER = 100;
39
41struct FunctionData {
42 size_t n; // number of parameters
43 const AugmentedLagrangianOptimizer::ObjFunction *userfunc; // user supplied function
44 const DblMatrix *eqmatrix; // equality constraints
45 const std::vector<double> *lambda; // lagrange multiplier for equality
46 const DblMatrix *ineqmatrix; // inequality constraints
47 const std::vector<double> *mu; // lagrange multiplier for inequality
48 double rho; // scaling parameter
49 gsl_vector *tmp; // gsl vector of size n (used for numerical derivative calc
50 // to avoid constant reallocation)
51};
52
61double evaluateConstraint(const DblMatrix &cmatrix, const size_t index, const size_t /*unused*/, const double *x) {
62 assert(index < cmatrix.numRows());
63 const double *row = cmatrix[index];
64
65 double res(0.0);
66 for (size_t j = 0; j < cmatrix.numCols(); ++j) {
67 res += row[j] * x[j];
68 }
69 return res;
70}
71
80int relstop(double vold, double vnew, double reltol, double abstol) {
81 if (vold != vold)
82 return 0; // nan
83 return (fabs(vnew - vold) < abstol || fabs(vnew - vold) < reltol * (fabs(vnew) + fabs(vold)) * 0.5 ||
84 (reltol > 0 && vnew == vold)); /* catch vnew == vold == 0 */
85}
86
95int relstopX(const std::vector<double> &xvOld, const std::vector<double> &xvNew, double reltol, double abstol) {
96 for (size_t i = 0; i < xvOld.size(); ++i) {
97 if (!relstop(xvOld[i], xvNew[i], reltol, abstol))
98 return 0;
99 }
100 return 1;
101}
110int relstopX(const std::vector<double> &xvOld, const gsl_vector *xvNew, double reltol, double abstol) {
111 for (size_t i = 0; i < xvOld.size(); ++i) {
112 if (std::isnan(gsl_vector_get(xvNew, i)))
113 return 1;
114 if (!relstop(xvOld[i], gsl_vector_get(xvNew, i), reltol, abstol))
115 return 0;
116 }
117 return 1;
118}
119} // namespace
120
121//---------------------------------------------------------------------------------------------
122// AugmentedLagrangianOptimizer
123//---------------------------------------------------------------------------------------------
124
130void AugmentedLagrangianOptimizer::minimize(std::vector<double> &xv) const {
131 assert(numParameters() == xv.size());
132
133 double ICM(HUGE_VAL), minf_penalty(HUGE_VAL), rho(0.0);
134 double minf(HUGE_VAL), penalty(0.0);
135 std::vector<double> xcur(xv), lambda(numEqualityConstraints(), 0), mu(numInequalityConstraints());
136 int minfIsFeasible = 0;
137 int auglagIters = 0;
138
139 /* magic parameters from Birgin & Martinez */
140 const double tau = 0.5, gam = 10;
141 const double lam_min = -1e20, lam_max = 1e20, mu_max = 1e20;
142
144 double con2 = 0;
145 double fcur = m_userfunc(numParameters(), xcur.data());
146 int feasible = 1;
147 for (size_t i = 0; i < numEqualityConstraints(); ++i) {
148 double hi = evaluateConstraint(m_eq, i, numParameters(), xcur.data());
149 penalty += fabs(hi);
150 feasible = (feasible && fabs(hi) <= CONSTRAINT_TOL);
151 con2 += hi * hi;
152 }
153 for (size_t i = 0; i < numInequalityConstraints(); ++i) {
154 double fci = evaluateConstraint(m_ineq, i, numParameters(), xcur.data());
155 penalty += fci > 0 ? fci : 0;
156 feasible = feasible && fci <= CONSTRAINT_TOL;
157 if (fci > 0)
158 con2 += fci * fci;
159 }
160 minf = fcur;
161 minf_penalty = penalty;
162 minfIsFeasible = feasible;
163 rho = max(1e-6, min(10.0, 2.0 * fabs(minf) / con2));
164 } else {
165 rho = 1; /* doesn't matter */
166 }
167
168 do {
169 double prevICM = ICM;
170
172
173 double fcur = m_userfunc(numParameters(), xcur.data());
174 ICM = 0.0;
175 penalty = 0.0;
176 int feasible = 1;
177 for (size_t i = 0; i < numEqualityConstraints(); ++i) {
178 double hi = evaluateConstraint(m_eq, i, numParameters(), xcur.data());
179 double newlam = lambda[i] + rho * hi;
180 penalty += fabs(hi);
181 feasible = feasible && (fabs(hi) <= CONSTRAINT_TOL);
182 ICM = max(ICM, fabs(hi));
183 lambda[i] = min(max(lam_min, newlam), lam_max);
184 }
185 for (size_t i = 0; i < numInequalityConstraints(); ++i) {
186 double fci = evaluateConstraint(m_ineq, i, numParameters(), xcur.data());
187 double newmu = mu[i] + rho * fci;
188 penalty += fci > 0 ? fci : 0;
189 feasible = feasible && fci <= CONSTRAINT_TOL;
190 ICM = max(ICM, fabs(max(fci, -mu[i] / rho)));
191 mu[i] = min(max(0.0, newmu), mu_max);
192 }
193
194 if (ICM > tau * prevICM) {
195 rho *= gam;
196 }
197 ++auglagIters;
198
199 if ((feasible && (!minfIsFeasible || penalty <= minf_penalty || fcur < minf)) ||
200 (!minfIsFeasible && penalty <= minf_penalty)) {
202 if (feasible) {
203 if (relstop(minf, fcur, FTOL_REL, FTOL_ABS))
204 ret = FTolReached;
205 else if (relstopX(xv, xcur, XTOL_REL, XTOL_ABS))
206 ret = XTolReached;
207 }
208 minf = fcur;
209 minf_penalty = penalty;
210 minfIsFeasible = feasible;
211 std::copy(xcur.begin(), xcur.end(), xv.begin());
212 if (ret != Success)
213 break;
214 }
215 if (ICM == 0.0) {
216 break;
217 }
218 } while (auglagIters < m_maxIter);
219}
220
221//--------------------------------------------------------------------------------------------------------
222// Private methods
223//--------------------------------------------------------------------------------------------------------
224
225namespace {
232double costf(const gsl_vector *v, void *params) {
233 auto *d = static_cast<FunctionData *>(params);
234
235 double lagrangian = (*d->userfunc)(d->n, v->data);
236 for (size_t i = 0; i < d->eqmatrix->numRows(); ++i) {
237 double h = evaluateConstraint(*d->eqmatrix, i, d->n, v->data) + ((*d->lambda)[i] / d->rho);
238 lagrangian += 0.5 * d->rho * h * h;
239 }
240 for (size_t i = 0; i < d->ineqmatrix->numRows(); ++i) {
241 double fc = evaluateConstraint(*d->ineqmatrix, i, d->n, v->data) + ((*d->mu)[i] / d->rho);
242 if (fc > 0.0)
243 lagrangian += 0.5 * d->rho * fc * fc;
244 }
245 return lagrangian;
246}
247
254void costdf(const gsl_vector *v, void *params, gsl_vector *df) {
255 auto *d = static_cast<FunctionData *>(params);
256 double f0 = costf(v, params);
257 gsl_vector *tmp = d->tmp;
258 std::copy(v->data, v->data + d->n, tmp->data);
259
260 const double epsilon(1e-08);
261 for (size_t i = 0; i < d->n; ++i) {
262 const double curx = gsl_vector_get(tmp, i);
263 gsl_vector_set(tmp, i, curx + epsilon);
264 gsl_vector_set(df, i, (costf(tmp, params) - f0) / epsilon);
265 gsl_vector_set(tmp, i, curx);
266 }
267}
268
276void costfdf(const gsl_vector *x, void *params, double *f, gsl_vector *df) {
277 *f = costf(x, params);
278 costdf(x, params, df);
279}
280} // namespace
281
291 const std::vector<double> &mu, const double rho,
292 std::vector<double> &xcur) const {
293 // Data required to calculate function
294 FunctionData d;
295 d.n = numParameters();
296 d.userfunc = &m_userfunc;
297 d.eqmatrix = &m_eq;
298 d.ineqmatrix = &m_ineq;
299 d.lambda = &lambda;
300 d.mu = &mu;
301 d.rho = rho;
302
303 gsl_vector *x = gsl_vector_alloc(d.n);
304 std::copy(xcur.begin(), xcur.end(), x->data);
305 gsl_vector *tmp = gsl_vector_alloc(d.n); // Used for numerical derivative calculation
306 d.tmp = tmp;
307
308 // Unconstrained const function
309 gsl_multimin_function_fdf costFunc;
310 costFunc.n = d.n;
311 costFunc.f = costf;
312 costFunc.df = costdf;
313 costFunc.fdf = costfdf;
314 costFunc.params = static_cast<void *>(&d);
315
316 // Declare minimizer
317 const gsl_multimin_fdfminimizer_type *T = gsl_multimin_fdfminimizer_conjugate_pr;
318 gsl_multimin_fdfminimizer *s = gsl_multimin_fdfminimizer_alloc(T, costFunc.n);
319 double tol = (xcur[0] > 1e-3 ? 1e-4 : 1e-3); // Adjust the tolerance for the scale of the first param
320 gsl_multimin_fdfminimizer_set(s, &costFunc, x, 0.01, tol);
321
322 int iter = 0;
323 int status = 0;
324
325 do {
326 iter++;
327 status = gsl_multimin_fdfminimizer_iterate(s);
328 if (status)
329 break;
330 status = gsl_multimin_test_gradient(s->gradient, 1e-3);
331
332 if (relstopX(xcur, s->x, XTOL_REL, XTOL_ABS))
333 break; // If the X's don't change then assume we're done
334 std::copy(s->x->data, s->x->data + d.n, xcur.begin());
335 } while (status == GSL_CONTINUE && iter < MAX_SUBOPT_ITER);
336 // Final parameter update
337 std::copy(s->x->data, s->x->data + d.n, xcur.begin());
338
339 gsl_multimin_fdfminimizer_free(s);
340 gsl_vector_free(x);
341 gsl_vector_free(tmp);
342}
343
353 const size_t totalNumConstr = numEqualityConstraints() + numInequalityConstraints();
354 if (totalNumConstr == 0)
355 return;
356
357 // Sanity checks on matrix sizes
358 for (size_t i = 0; i < 2; ++i) {
359 size_t ncols(0);
360 std::string matrix;
361 if (i == 0) {
362 ncols = equality.numCols();
363 matrix = "equality";
364 } else {
365 ncols = inequality.numCols();
366 matrix = "inequality";
367 }
368
369 if (ncols > 0 && ncols != numParameters()) {
370 std::ostringstream os;
371 os << "AugmentedLagrangianOptimizer::initializeConstraints - Invalid " << matrix
372 << " constraint matrix. Number of columns must match number "
373 "of parameters. ncols="
374 << ncols << ", nparams=" << numParameters();
375 throw std::invalid_argument(os.str());
376 }
377 }
378}
379
380} // namespace Mantid::CurveFitting
const DblMatrix * ineqmatrix
const std::vector< double > * lambda
gsl_vector * tmp
const DblMatrix * eqmatrix
const AugmentedLagrangianOptimizer::ObjFunction * userfunc
std::map< DeltaEMode::Type, std::string > index
Definition: DeltaEMode.cpp:19
#define fabs(x)
Definition: Matrix.cpp:22
Kernel::DblMatrix m_eq
Defines the equality constraints.
void checkConstraints(const Kernel::DblMatrix &equality, const Kernel::DblMatrix &inequality)
Sanity check for constraint inputs.
void unconstrainedOptimization(const std::vector< double > &lambda, const std::vector< double > &mu, const double rho, std::vector< double > &xcur) const
Using gradient optimizer to perform limited optimization of current set.
boost::function< double(const size_t, const double *)> ObjFunction
Function type.
void minimize(std::vector< double > &xv) const
Perform the minimization.
Kernel::DblMatrix m_ineq
Defines the inequality constraints.
size_t numCols() const
Return the number of columns in the matrix.
Definition: Matrix.h:147
OptimizerResult
The results of the optimization.
Mantid::Kernel::Matrix< double > DblMatrix
Definition: Matrix.h:206