Optimist  0.0.0
A C++ library for optimization
Loading...
Searching...
No Matches
Algo748.hh
Go to the documentation of this file.
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
2 * Copyright (c) 2025, Davide Stocco and Enrico Bertolazzi. *
3 * *
4 * The Optimist project is distributed under the BSD 2-Clause License. *
5 * *
6 * Davide Stocco Enrico Bertolazzi *
7 * University of Trento University of Trento *
8 * davide.stocco@unitn.it enrico.bertolazzi@unitn.it *
9\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
10
11#pragma once
12
13#ifndef OPTIMIST_ROOTFINDER_ALGO748_HH
14#define OPTIMIST_ROOTFINDER_ALGO748_HH
15
17
18namespace Optimist {
19 namespace RootFinder {
20
21 /*\
22 | _ _ _____ _ _ ___
23 | / \ | | __ _ __|___ | || | ( _ )
24 | / _ \ | |/ _` |/ _ \ / /| || |_ / _ \
25 | / ___ \| | (_| | (_) / / |__ _| (_) |
26 | /_/ \_\_|\__, |\___/_/ |_| \___/
27 | |___/
28 \*/
29
41 template <typename Scalar>
42 class Algo748 : public Bracketing<Scalar, Algo748<Scalar>> {
43 public:
44 static constexpr bool RequiresFunction{true};
45 static constexpr bool RequiresFirstDerivative{false};
46 static constexpr bool RequiresSecondDerivative{false};
47
49
50
54
59 constexpr std::string name_impl() const {
60 return "Algo748";
61 }
62
63 private:
73 bool all_different(Scalar a, Scalar b, Scalar c, Scalar d) const {
74 return a != b && a != c && a != d && b != c && b != d && c != d;
75 }
76
91 template <typename FunctionLambda>
92 bool bracketing(FunctionLambda &&function) {
93#define CMD "Optimist::RootFinder::Algo748::bracketing(...): "
94
95 {
96 Scalar tolerance{static_cast<Scalar>(0.7) *
98 Scalar hba{(this->m_b - this->m_a) / static_cast<Scalar>(2.0)};
99 if (hba <= tolerance) {
100 this->m_c = this->m_a + hba;
101 } else if (this->m_c <= this->m_a + tolerance) {
102 this->m_c = this->m_a + tolerance;
103 } else if (this->m_c >= this->m_b - tolerance) {
104 this->m_c = this->m_b - tolerance;
105 }
106 }
107
108 // If f(c) = 0 set a = c and return true
109 bool success{
110 this->evaluate_function(std::forward<FunctionLambda>(function),
111 this->m_c,
112 this->m_fc)};
113 OPTIMIST_ASSERT(success,
114 CMD "function evaluation failed during bracketing.");
115 if (this->m_fc == 0) {
116 this->m_a = this->m_c;
117 this->m_fa = 0.0;
118 this->m_d = 0.0;
119 this->m_fd = 0.0;
120 return true;
121 } else {
122 // If f(c) is not zero, then determine the new enclosing interval
123 if (this->m_fa * this->m_fc < 0) {
124 // D <- B <- C
125 this->m_d = this->m_b;
126 this->m_fd = this->m_fb;
127 this->m_b = this->m_c;
128 this->m_fb = this->m_fc;
129 } else {
130 // D <- A <- C
131 this->m_d = this->m_a;
132 this->m_fd = this->m_fa;
133 this->m_a = this->m_c;
134 this->m_fa = this->m_fc;
135 }
136 return false;
137 }
138
139#undef CMD
140 }
141
150 // Compute the coefficients for the inverse cubic interpolation
151 Scalar d_1{this->m_b - this->m_a};
152 Scalar d_2{this->m_d - this->m_a};
153 Scalar d_3{this->m_e - this->m_a};
154
155 Scalar dd_0{d_1 / (this->m_fb - this->m_fa)};
156 Scalar dd_1{(d_1 - d_2) / (this->m_fb - this->m_fd)};
157 Scalar dd_2{(d_2 - d_3) / (this->m_fd - this->m_fe)};
158
159 Scalar ddd_0{(dd_0 - dd_1) / (this->m_fa - this->m_fd)};
160 Scalar ddd_1{(dd_1 - dd_2) / (this->m_fb - this->m_fe)};
161
162 Scalar dddd_0{(ddd_0 - ddd_1) / (this->m_fa - this->m_fe)};
163
164 // Compute the approximate root
165 Scalar root{this->m_a -
166 this->m_fa *
167 (dd_0 - this->m_fb * (ddd_0 - this->m_fd * dddd_0))};
168 Scalar tolerance{static_cast<Scalar>(0.7) *
170 if (root <= this->m_a + tolerance || root >= this->m_b - tolerance) {
171 root = (this->m_a + this->m_b) / 2.0;
172 }
173 return root;
174 }
175
185 // Compute the coefficients for the quadratic interpolation
186 Scalar a_0{this->m_fa};
187 Scalar a_1{(this->m_fb - this->m_fa) / (this->m_b - this->m_a)};
188 Scalar a_2{((this->m_fd - this->m_fb) / (this->m_d - this->m_b) - a_1) /
189 (this->m_d - this->m_a)};
190
191 // Safeguard to avoid overflow
192 if (a_2 == 0) {
193 return this->m_a - a_0 / a_1;
194 }
195
196 // Determine the starting point of newton steps
197 Scalar c{a_2 * this->m_fa > 0 ? this->m_a : this->m_b};
198
199 // Start the safeguarded newton steps
200 bool is_nonzero{true};
201 Scalar pc{0.0}, pdc{0.0};
202 for (Integer i{0}; i < n && is_nonzero; ++i) {
203 pc = a_0 + (a_1 + a_2 * (c - this->m_b)) * (c - this->m_a);
204 pdc = a_1 + a_2 * ((2.0 * c) - (this->m_a + this->m_b));
205 is_nonzero = pdc != 0;
206 if (is_nonzero) {
207 c -= pc / pdc;
208 }
209 }
210
211 if (is_nonzero) {
212 return c;
213 } else {
214 return this->m_a - a_0 / a_1;
215 }
216 }
217
218 public:
237 template <typename FunctionLambda>
238 Scalar find_root_impl(FunctionLambda &&function) {
239#define CMD "Optimist::RootFinder::Algo748::find_root_impl(...): "
240
241 bool success;
242 Scalar tolerance_step{this->m_tolerance_bracketing};
243 Scalar tolerance_function{this->m_tolerance_bracketing};
244
245 // Check for trivial solution
246 this->m_converged = this->m_fa == 0;
247 if (this->m_verbose) {
248 this->info(std::abs(this->m_fa));
249 }
250 if (this->m_converged) {
251 return this->m_fa;
252 }
253 this->m_converged = this->m_fb == 0;
254 if (this->m_verbose) {
255 this->info(std::abs(this->m_fb));
256 }
257 if (this->m_converged) {
258 return this->m_fb;
259 }
260
261 // Initialize to dumb values
262 this->m_e = QUIET_NAN;
263 this->m_fe = QUIET_NAN;
264
265 // While f(left) or f(right) are infinite perform bisection
266 while (!(std::isfinite(this->m_fa) && std::isfinite(this->m_fb))) {
267 ++this->m_iterations;
268 this->m_c = (this->m_a + this->m_b) / 2.0;
269 success =
270 this->evaluate_function(std::forward<FunctionLambda>(function),
271 this->m_c,
272 this->m_fc);
273 OPTIMIST_ASSERT(success,
274 CMD "function evaluation failed at iteration "
275 << this->m_iterations << ".");
276 this->m_converged = this->m_fc == 0;
277 if (this->m_verbose) {
278 this->info(std::abs(this->m_fc));
279 }
280 if (this->m_converged) {
281 return this->m_fc;
282 }
283 if (this->m_fa * this->m_fc < 0) {
284 // -> [a, c]
285 this->m_b = this->m_c;
286 this->m_fb = this->m_fc;
287 } else {
288 // -> [c, b]
289 this->m_a = this->m_c;
290 this->m_fa = this->m_fc;
291 }
292
293 // Check for convergence
294 Scalar abs_fa{std::abs(this->m_fa)};
295 Scalar abs_fb{std::abs(this->m_fb)};
296 if (this->m_verbose) {
297 this->info(abs_fa < abs_fb ? abs_fa : abs_fb);
298 }
299 this->m_converged = (this->m_b - this->m_a) < tolerance_step ||
300 abs_fa < tolerance_function ||
301 abs_fb < tolerance_function;
302 if (this->m_converged) {
303 return abs_fb < abs_fa ? this->m_b : this->m_a;
304 }
305 }
306
307 {
308 // Impede the step c to be too close to the bounds a or b.
309 Scalar b_a{this->m_b - this->m_a};
310 Scalar r{b_a / (this->m_fb - this->m_fa)};
311 this->m_c = std::abs(this->m_fb) < std::abs(this->m_fa)
312 ? this->m_b + this->m_fb * r
313 : this->m_a - this->m_fa * r;
314 Scalar delta{this->m_interval_shink * b_a}, a_min, b_max;
315 if (this->m_c < (a_min = this->m_a + delta)) {
316 this->m_c = a_min;
317 } else if (this->m_c > (b_max = this->m_b - delta)) {
318 this->m_c = b_max;
319 }
320 }
321
322 // Call "bracketing" to get a shrinked enclosing interval and to update
323 // the termination criterion
324 this->m_converged =
325 this->bracketing(std::forward<FunctionLambda>(function));
326 if (this->m_verbose) {
327 this->info(std::abs(this->m_fa));
328 }
329 if (this->m_converged) {
330 return this->m_a;
331 }
332 this->m_converged = false;
333
334 // The enclosing interval is recorded as [a0, b0] before executing the
335 // iteration steps
337 ++this->m_iterations;
338 Scalar a0_b0{this->m_b - this->m_a};
339
340 // Check the termination criterion
341 {
342 Scalar abs_fa{std::abs(this->m_fa)};
343 Scalar abs_fb{std::abs(this->m_fb)};
344 if (this->m_verbose) {
345 this->info(abs_fa < abs_fb ? abs_fa : abs_fb);
346 }
347 this->m_converged =
348 abs_fa < tolerance_function || abs_fb < tolerance_function;
349 if (this->m_converged) {
350 return abs_fa < abs_fb ? this->m_a : this->m_b;
351 }
352 }
353
354 // Starting with the second iteration: newton quadratic or cubic
355 // interpolation
356 bool do_newton_quadratic{false};
357 if (!std::isfinite(this->m_fe)) {
358 do_newton_quadratic = true;
359 } else if (!this->all_different(this->m_fa,
360 this->m_fb,
361 this->m_fd,
362 this->m_fe)) {
363 do_newton_quadratic = true;
364 } else {
365 this->m_c = this->cubic_interpolation();
366 do_newton_quadratic =
367 (this->m_c - this->m_a) * (this->m_c - this->m_b) >= 0.0;
368 }
369 if (do_newton_quadratic) {
370 this->m_c = this->newton_quadratic(2);
371 }
372
373 this->m_e = this->m_d;
374 this->m_fe = this->m_fd;
375
376 // Call "bracketing" to get a shrinked enclosing interval and to
377 // update the termination criterion
378 this->m_converged =
379 this->bracketing(std::forward<FunctionLambda>(function)) ||
380 (this->m_b - this->m_a) <= this->m_tolerance;
381 if (this->m_verbose) {
382 this->info(std::abs(this->m_fa) < std::abs(this->m_fb)
383 ? std::abs(this->m_fa)
384 : std::abs(this->m_fb));
385 }
386 if (this->m_converged) {
387 return std::abs(this->m_fa) < std::abs(this->m_fb) ? this->m_a
388 : this->m_b;
389 }
390 if (!this->all_different(this->m_fa,
391 this->m_fb,
392 this->m_fd,
393 this->m_fe)) {
394 do_newton_quadratic = true;
395 } else {
396 this->m_c = this->cubic_interpolation();
397 do_newton_quadratic =
398 (this->m_c - this->m_a) * (this->m_c - this->m_b) >= 0.0;
399 }
400 if (do_newton_quadratic) {
401 this->m_c = this->newton_quadratic(3);
402 }
403
404 // Call "bracketing" to get a shrinked enclosing interval and to
405 // update the termination criterion
406 {
407 this->m_converged =
408 this->bracketing(std::forward<FunctionLambda>(function)) ||
409 (this->m_b - this->m_a) <= this->m_tolerance;
410 Scalar abs_fa{std::abs(this->m_fa)};
411 Scalar abs_fb{std::abs(this->m_fb)};
412 if (this->m_verbose) {
413 this->info(abs_fa < abs_fb ? abs_fa : abs_fb);
414 }
415 if (this->m_converged) {
416 return abs_fa < abs_fb ? this->m_a : this->m_b;
417 }
418
419 this->m_e = this->m_d;
420 this->m_fe = this->m_fd;
421
422 // Takes the double-size secant step
423 Scalar u, fu;
424 if (abs_fa < abs_fb) {
425 u = this->m_a;
426 fu = this->m_fa;
427 } else {
428 u = this->m_b;
429 fu = this->m_fb;
430 }
431 Scalar hba{(this->m_b - this->m_a) / static_cast<Scalar>(2.0)};
432 this->m_c = u - 4.0 * (fu / (this->m_fb - this->m_fa)) * hba;
433 if (std::abs(this->m_c - u) > hba) {
434 this->m_c = this->m_a + hba;
435 }
436 }
437
438 // Call "bracketing" to get a shrinked enclosing interval and to
439 // update the termination criterion
440 this->m_converged =
441 this->bracketing(std::forward<FunctionLambda>(function)) ||
442 (this->m_b - this->m_a) <= this->m_tolerance_bracketing;
443 if (this->m_converged) {
444 return std::abs(this->m_fa) < std::abs(this->m_fb) ? this->m_a
445 : this->m_b;
446 }
447
448 // Determine whether an additional bisection step is needed
449 if ((this->m_b - this->m_a) < this->m_mu * a0_b0) {
450 continue;
451 }
452
453 this->m_e = this->m_d;
454 this->m_fe = this->m_fd;
455
456 // Call "bracketing" to get a shrinked enclosing interval and to
457 // update the termination criterion
458 {
459 Scalar b_a{this->m_b - this->m_a};
460 this->m_c = this->m_a + b_a / 2.0;
461 this->m_converged =
462 this->bracketing(std::forward<FunctionLambda>(function)) ||
463 b_a <= this->m_tolerance_bracketing;
464 }
465 }
466 // Return the approximate root
467 return std::abs(this->m_fa) < std::abs(this->m_fb) ? this->m_a
468 : this->m_b;
469
470#undef CMD
471 }
472
473 }; // class Algo748
474
475 } // namespace RootFinder
476
477} // namespace Optimist
478
479#endif // OPTIMIST_ROOTFINDER_ALGO748_HH
#define OPTIMIST_BASIC_CONSTANTS(Scalar)
Definition Optimist.hh:69
#define OPTIMIST_ASSERT(COND, MSG)
Definition Optimist.hh:47
#define CMD
Scalar find_root_impl(FunctionLambda &&function)
Definition Algo748.hh:238
bool all_different(Scalar a, Scalar b, Scalar c, Scalar d) const
Definition Algo748.hh:73
static constexpr bool RequiresFunction
Definition Algo748.hh:44
Algo748()
Definition Algo748.hh:53
constexpr std::string name_impl() const
Definition Algo748.hh:59
static constexpr bool RequiresFirstDerivative
Definition Algo748.hh:45
static constexpr bool RequiresSecondDerivative
Definition Algo748.hh:46
Scalar cubic_interpolation()
Definition Algo748.hh:149
Scalar newton_quadratic(Integer n)
Definition Algo748.hh:184
bool bracketing(FunctionLambda &&function)
Definition Algo748.hh:92
typename TypeTrait< Scalar >::Scalar Scalar
Definition RootFinder.hh:53
void info(Scalar residuals, const std::string &notes="-")
Definition SolverBase.hh:1230
bool evaluate_function(FunctionLambda &&function, const Scalar &x, Scalar &out)
Definition SolverBase.hh:1040
Integer m_max_iterations
Definition SolverBase.hh:121
Scalar tolerance() const
Definition SolverBase.hh:513
Integer m_iterations
Definition SolverBase.hh:120
Namespace for the Optimist library.
Definition Optimist.hh:89
OPTIMIST_DEFAULT_INTEGER_TYPE Integer
The Integer type as used for the API.
Definition Optimist.hh:97