RandLib 2.0.0
Probability distributions, fitting, and heavy-tail tools for C++20
Loading...
Searching...
No Matches
optimize.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <cmath>
7#include <concepts>
8#include <functional>
9#include <limits>
10#include <numeric>
11#include <span>
12#include <tuple>
13#include <vector>
14
15namespace randlib::math
16{
17
27template <typename RealType>
28double parabolicMinimum(RealType a, RealType b, RealType c, double fa, double fb, double fc)
29{
30 RealType bma = b - a, cmb = c - b;
31 RealType aux1 = bma * (fb - fc);
32 RealType aux2 = cmb * (fb - fa);
33 RealType numerator = bma * aux1 - cmb * aux2;
34 RealType denominator = aux1 + aux2;
35 return b - 0.5 * numerator / denominator;
36}
37
47template <typename RealType>
48bool findMin(const std::function<double(RealType)> &funPtr, const Triplet<RealType> &abc, double &fx, RealType &root,
49 double epsilon)
50{
51 static constexpr double K = 0.5 * (3 - randlib::consts::sqrt5);
52 auto [a, x, c] = abc;
53 double w = x, v = x, fw = fx, fv = fx;
54 double d = c - a, e = d;
55 double u = a - 1;
58 auto tolerance = [epsilon](double point) { return epsilon * (1 + std::fabs(point)); };
59 do {
60 const double tol = tolerance(x);
61 double g = e;
62 e = d;
63 bool acceptParabolicU = false;
64 if (x != w && x != v && w != v && fx != fw && fx != fv && fw != fv) {
65 if (v < w) {
66 if (x < v)
67 u = parabolicMinimum<RealType>(x, v, w, fx, fv, fw);
68 else if (x < w)
69 u = parabolicMinimum<RealType>(v, x, w, fv, fx, fw);
70 else
71 u = parabolicMinimum<RealType>(v, w, x, fv, fw, fx);
72 } else {
73 if (x < w)
74 u = parabolicMinimum<RealType>(x, w, v, fx, fv, fw);
75 else if (x < v)
76 u = parabolicMinimum<RealType>(w, x, v, fw, fx, fv);
77 else
78 u = parabolicMinimum<RealType>(w, v, x, fw, fv, fx);
79 }
80 double absumx = std::fabs(u - x);
81 if (u >= a + tol && u <= c - tol && absumx < 0.5 * g) {
82 acceptParabolicU = true;
83 d = absumx;
84 }
85 }
86
87 if (!acceptParabolicU) {
89 if (x < 0.5 * (c + a)) {
90 d = c - x;
91 u = x + K * d;
92 } else {
93 d = x - a;
94 u = x - K * d;
95 }
96 }
97
98 if (std::fabs(u - x) < tol) {
99 u = x + ((u >= x) ? tol : -tol);
100 }
101
102 double fu = funPtr(u);
103 if (fu <= fx) {
104 if (u >= x)
105 a = x;
106 else
107 c = x;
108 v = w;
109 w = x;
110 x = u;
111 fv = fw;
112 fw = fx;
113 fx = fu;
114 } else {
115 if (u >= x)
116 c = u;
117 else
118 a = u;
119 if (fu <= fw || w == x) {
120 v = w;
121 w = u;
122 fv = fw;
123 fw = fu;
124 } else if (fu <= fv || v == x || v == w) {
125 v = u;
126 fv = fu;
127 }
128 }
129 } while (0.49 * (c - a) > tolerance(x));
130 root = x;
131 return true;
132}
133
142template <typename RealType>
143bool findMin(const std::function<double(RealType)> &funPtr, RealType closePoint, RealType &root,
144 long double epsilon = 1e-8)
145{
147 static constexpr double K = 0.5 * (randlib::consts::sqrt5 + 1);
148 static constexpr int L = 100;
149 double a = closePoint, fa = funPtr(a);
150 double b = a + 1.0, fb = funPtr(b);
151 double c, fc;
152 if (fb < fa) {
153 c = b + K * (b - a);
154 fc = funPtr(c);
156 while (fc < fb) {
158 double u = parabolicMinimum<RealType>(a, b, c, fa, fb, fc);
159 double cmb = c - b;
160 double fu, uLim = c + L * cmb;
161 if (u < c && u > b) {
162 fu = funPtr(u);
163 if (fu < fc) {
164 abc = std::make_tuple(b, u, c);
165 return findMin(funPtr, abc, fu, root, epsilon);
166 }
167 if (fu > fb) {
168 abc = std::make_tuple(a, b, u);
169 return findMin(funPtr, abc, fb, root, epsilon);
170 }
171 u = c + K * cmb;
172 fu = funPtr(u);
173 } else if (u > c && u < uLim) {
174 fu = funPtr(u);
175 if (fu < fc) {
176 b = c;
177 c = u;
178 u = c + K * cmb;
179 fb = fc, fc = fu, fu = funPtr(u);
180 }
181 } else if (u > uLim) {
182 u = uLim;
183 fu = funPtr(u);
184 } else {
185 u = c + K * cmb;
186 fu = funPtr(u);
187 }
188 a = b;
189 b = c;
190 c = u;
191 fa = fb;
192 fb = fc;
193 fc = fu;
194 }
195 abc = std::make_tuple(a, b, c);
196 return findMin(funPtr, abc, fb, root, epsilon);
197 } else {
198 c = b;
199 fc = fb;
200 b = a;
201 fb = fa;
202 a = b - K * (c - b);
203 fa = funPtr(a);
205 while (fa < fb) {
207 double u = parabolicMinimum<RealType>(a, b, c, fa, fb, fc);
208 double bma = b - a;
209 double fu, uLim = a - L * bma;
210 if (u < b && u > a) {
211 fu = funPtr(u);
212 if (fu < fa) {
213 abc = std::make_tuple(a, u, b);
214 return findMin(funPtr, abc, fu, root, epsilon);
215 }
216 if (fu > fb) {
217 abc = std::make_tuple(u, b, c);
218 return findMin(funPtr, abc, fb, root, epsilon);
219 }
220 u = a - K * bma;
221 fu = funPtr(u);
222 } else if (u < a && u > uLim) {
223 fu = funPtr(u);
224 if (fu < fa) {
225 b = a;
226 a = u;
227 u = a - K * bma;
228 fb = fa, fa = fu, fu = funPtr(u);
229 }
230 } else if (u < uLim) {
231 u = uLim;
232 fu = funPtr(u);
233 } else {
234 u = a - K * bma;
235 fu = funPtr(u);
236 }
237 c = b;
238 b = a;
239 a = u;
240 fc = fb;
241 fb = fa;
242 fa = fu;
243 }
244 abc = std::make_tuple(a, b, c);
245 return findMin(funPtr, abc, fb, root, epsilon);
246 }
247}
248
253template <std::floating_point Real = double>
255{
256 std::vector<Real> x;
257 Real value = 0;
258 int iterations = 0;
259 bool converged = false;
260};
261
272template <std::floating_point Real = double, class F>
273MinimizeResult<Real> nelderMead(const F &f, std::vector<Real> start, Real tolerance = 1e-10, int maxIterations = 20000)
274{
275 const std::size_t n = start.size();
276 const Real dimension = static_cast<Real>(std::max<std::size_t>(n, 1));
277 const Real reflection = 1, expansion = 1 + 2 / dimension, contraction = Real(0.75) - 1 / (2 * dimension),
278 shrinkage = 1 - 1 / dimension;
279 std::vector<std::vector<Real>> simplex(n + 1, start);
280 for (std::size_t i = 0; i < n; ++i)
281 simplex[i + 1][i] = (start[i] != 0) ? start[i] * Real(1.05) : Real(0.00025);
282 std::vector<Real> values(n + 1);
283 for (std::size_t i = 0; i <= n; ++i)
284 values[i] = f(std::span<const Real>(simplex[i]));
285
286 std::vector<std::size_t> order(n + 1);
287 auto point = [n](const std::vector<Real> &from, const std::vector<Real> &to, Real t) {
288 std::vector<Real> x(n);
289 for (std::size_t j = 0; j < n; ++j)
290 x[j] = from[j] + t * (to[j] - from[j]);
291 return x;
292 };
294 for (result.iterations = 0; result.iterations < maxIterations; ++result.iterations) {
295 std::iota(order.begin(), order.end(), 0);
296 std::sort(order.begin(), order.end(),
297 [&values](std::size_t i, std::size_t j) { return values[i] < values[j]; });
298 const std::size_t best = order.front(), worst = order.back(), secondWorst = order[n > 0 ? n - 1 : 0];
299
300 Real valueSpread = 0, pointSpread = 0, scale = 0;
301 for (std::size_t i = 0; i <= n; ++i) {
302 valueSpread = std::max(valueSpread, std::fabs(values[i] - values[best]));
303 for (std::size_t j = 0; j < n; ++j) {
304 pointSpread = std::max(pointSpread, std::fabs(simplex[i][j] - simplex[best][j]));
305 scale = std::max(scale, std::fabs(simplex[best][j]));
306 }
307 }
308 if (valueSpread <= tolerance * (std::fabs(values[best]) + tolerance) &&
309 pointSpread <= tolerance * (scale + 1)) {
310 result.converged = true;
311 break;
312 }
313
314 std::vector<Real> centroid(n, 0);
315 for (std::size_t i : order) {
316 if (i == worst)
317 continue;
318 for (std::size_t j = 0; j < n; ++j)
319 centroid[j] += simplex[i][j] / dimension;
320 }
321 std::vector<Real> reflected = point(centroid, simplex[worst], -reflection);
322 Real reflectedValue = f(std::span<const Real>(reflected));
323 if (reflectedValue < values[best]) {
324 std::vector<Real> expanded = point(centroid, simplex[worst], -reflection * expansion);
325 Real expandedValue = f(std::span<const Real>(expanded));
326 if (expandedValue < reflectedValue) {
327 simplex[worst] = expanded;
328 values[worst] = expandedValue;
329 } else {
330 simplex[worst] = reflected;
331 values[worst] = reflectedValue;
332 }
333 continue;
334 }
335 if (reflectedValue < values[secondWorst]) {
336 simplex[worst] = reflected;
337 values[worst] = reflectedValue;
338 continue;
339 }
340 const bool outside = reflectedValue < values[worst];
341 std::vector<Real> contracted =
342 outside ? point(centroid, reflected, contraction) : point(centroid, simplex[worst], contraction);
343 Real contractedValue = f(std::span<const Real>(contracted));
344 if (contractedValue < (outside ? reflectedValue : values[worst])) {
345 simplex[worst] = contracted;
346 values[worst] = contractedValue;
347 continue;
348 }
349 for (std::size_t i = 0; i <= n; ++i) {
350 if (i == best)
351 continue;
352 simplex[i] = point(simplex[best], simplex[i], shrinkage);
353 values[i] = f(std::span<const Real>(simplex[i]));
354 }
355 }
356 std::size_t best = std::min_element(values.begin(), values.end()) - values.begin();
357 result.x = simplex[best];
358 result.value = values[best];
359 return result;
360}
361
367template <std::floating_point Real = double, class F>
368std::vector<Real> numericalGradient(const F &f, const std::vector<Real> &x)
369{
370 const Real h0 = std::cbrt(std::numeric_limits<Real>::epsilon());
371 std::vector<Real> gradient(x.size()), y = x;
372 for (std::size_t i = 0; i < x.size(); ++i) {
373 const Real h = h0 * std::max<Real>(std::fabs(x[i]), 1);
374 y[i] = x[i] + h;
375 const Real up = f(std::span<const Real>(y));
376 y[i] = x[i] - h;
377 const Real down = f(std::span<const Real>(y));
378 y[i] = x[i];
379 gradient[i] = (up - down) / (2 * h);
380 }
381 return gradient;
382}
383
389template <std::floating_point Real = double, class F>
390std::vector<std::vector<Real>> numericalHessian(const F &f, const std::vector<Real> &x)
391{
392 const Real h0 = std::sqrt(std::sqrt(std::numeric_limits<Real>::epsilon()));
393 const std::size_t n = x.size();
394 std::vector<Real> steps(n);
395 for (std::size_t i = 0; i < n; ++i)
396 steps[i] = h0 * std::max<Real>(std::fabs(x[i]), 1);
397
398 std::vector<std::vector<Real>> hessian(n, std::vector<Real>(n));
399 std::vector<Real> y = x;
400 const Real center = f(std::span<const Real>(x));
401 for (std::size_t i = 0; i < n; ++i) {
402 y[i] = x[i] + steps[i];
403 const Real up = f(std::span<const Real>(y));
404 y[i] = x[i] - steps[i];
405 const Real down = f(std::span<const Real>(y));
406 y[i] = x[i];
407 hessian[i][i] = (up - 2 * center + down) / (steps[i] * steps[i]);
408
409 for (std::size_t j = 0; j < i; ++j) {
410 y[i] = x[i] + steps[i];
411 y[j] = x[j] + steps[j];
412 const Real upUp = f(std::span<const Real>(y));
413 y[j] = x[j] - steps[j];
414 const Real upDown = f(std::span<const Real>(y));
415 y[i] = x[i] - steps[i];
416 const Real downDown = f(std::span<const Real>(y));
417 y[j] = x[j] + steps[j];
418 const Real downUp = f(std::span<const Real>(y));
419 y[i] = x[i];
420 y[j] = x[j];
421 hessian[i][j] = hessian[j][i] = (upUp - upDown - downUp + downDown) / (4 * steps[i] * steps[j]);
422 }
423 }
424 return hessian;
425}
426
436template <std::floating_point Real = double, class F>
437MinimizeResult<Real> bfgs(const F &f, std::vector<Real> start, Real tolerance = 1e-8, int maxIterations = 1000)
438{
439 const std::size_t n = start.size();
440 auto setIdentity = [n](std::vector<std::vector<Real>> &matrix) {
441 for (auto &row : matrix)
442 std::fill(row.begin(), row.end(), Real{0});
443 for (std::size_t i = 0; i < n; ++i)
444 matrix[i][i] = 1;
445 };
446 auto dot = [](const std::vector<Real> &u, const std::vector<Real> &v) {
447 return std::inner_product(u.begin(), u.end(), v.begin(), Real(0));
448 };
449 std::vector<std::vector<Real>> H(n, std::vector<Real>(n));
450 setIdentity(H);
451 std::vector<Real> x = std::move(start);
452 Real value = f(std::span<const Real>(x));
453 std::vector<Real> gradient = numericalGradient<Real>(f, x);
455 for (result.iterations = 0; result.iterations < maxIterations; ++result.iterations) {
456 Real largest = 0;
457 for (Real g : gradient)
458 largest = std::max(largest, std::fabs(g));
459 if (largest <= tolerance * std::max<Real>(1, std::fabs(value))) {
460 result.converged = true;
461 break;
462 }
463 std::vector<Real> direction(n, 0);
464 for (std::size_t i = 0; i < n; ++i)
465 for (std::size_t j = 0; j < n; ++j)
466 direction[i] -= H[i][j] * gradient[j];
467 Real slope = dot(gradient, direction);
468 if (!(slope < 0)) {
469 setIdentity(H);
470 for (std::size_t i = 0; i < n; ++i)
471 direction[i] = -gradient[i];
472 slope = -dot(gradient, gradient);
473 }
474 Real step = 1, newValue = value;
475 std::vector<Real> next(n);
476 for (int k = 0; k < 60; ++k, step /= 2) {
477 for (std::size_t i = 0; i < n; ++i)
478 next[i] = x[i] + step * direction[i];
479 newValue = f(std::span<const Real>(next));
480 if (newValue <= value + Real(1e-4) * step * slope)
481 break;
482 }
483 if (!(newValue < value)) {
484 result.converged = newValue == value;
485 break;
486 }
487 std::vector<Real> newGradient = numericalGradient<Real>(f, next);
488 std::vector<Real> s(n), y(n);
489 for (std::size_t i = 0; i < n; ++i) {
490 s[i] = next[i] - x[i];
491 y[i] = newGradient[i] - gradient[i];
492 }
493 const Real sy = dot(s, y);
494 if (sy > 0) {
496 const Real rho = 1 / sy;
497 std::vector<Real> Hy(n, 0);
498 for (std::size_t i = 0; i < n; ++i)
499 for (std::size_t j = 0; j < n; ++j)
500 Hy[i] += H[i][j] * y[j];
501 const Real yHy = dot(y, Hy);
502 for (std::size_t i = 0; i < n; ++i)
503 for (std::size_t j = 0; j < n; ++j)
504 H[i][j] += rho * ((1 + rho * yHy) * s[i] * s[j] - Hy[i] * s[j] - s[i] * Hy[j]);
505 }
506 x = std::move(next);
507 value = newValue;
508 gradient = std::move(newGradient);
509 }
510 result.x = std::move(x);
511 result.value = value;
512 return result;
513}
514
515} // namespace randlib::math
constexpr long double sqrt5
Definition constants.hpp:30
Beta-related functions.
Definition math/beta.hpp:14
std::vector< Real > numericalGradient(const F &f, const std::vector< Real > &x)
Definition optimize.hpp:368
double parabolicMinimum(RealType a, RealType b, RealType c, double fa, double fb, double fc)
Definition optimize.hpp:28
MinimizeResult< Real > bfgs(const F &f, std::vector< Real > start, Real tolerance=1e-8, int maxIterations=1000)
Quasi-Newton minimization of Broyden, Fletcher, Goldfarb and Shanno with central-difference gradients...
Definition optimize.hpp:437
std::vector< std::vector< Real > > numericalHessian(const F &f, const std::vector< Real > &x)
Definition optimize.hpp:390
bool findMin(const std::function< double(RealType)> &funPtr, const Triplet< RealType > &abc, double &fx, RealType &root, double epsilon)
Combined Brent's method.
Definition optimize.hpp:48
MinimizeResult< Real > nelderMead(const F &f, std::vector< Real > start, Real tolerance=1e-10, int maxIterations=20000)
Downhill simplex method of Nelder and Mead with the dimension-dependent coefficients of Gao and Han (...
Definition optimize.hpp:273
std::tuple< T, T, T > Triplet
Definition fwd.hpp:36
The MinimizeResult struct Outcome of a multivariate minimization.
Definition optimize.hpp:255
Real value
function value there
Definition optimize.hpp:257
bool converged
false if the iteration limit was reached first
Definition optimize.hpp:259
std::vector< Real > x
point of the minimum
Definition optimize.hpp:256
int iterations
number of iterations
Definition optimize.hpp:258