stator
A math, geometry, and utility library
numeric.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 Marcus N Campbell Bannerman <[email protected]>
3 
4  This file is part of stator.
5 
6  stator is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  stator is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with stator. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #pragma once
22 #include <array>
23 
24 namespace stator {
25  namespace numeric {
26  namespace detail {
32  template<class F, size_t Derivatives, class Real>
33  inline int process_iterative_step(const F& f, std::array<Real, Derivatives>& curr_state,
34  Real& x, Real new_x, Real& low_bound, Real& high_bound, Real x_precision) {
35 
36  //std::cout << "new_x = " << new_x << std::endl;
37  //std::cout << "[" << low_bound << "," << high_bound << "]" << std::endl;
38 
39  if ((new_x >= high_bound) || (new_x <= low_bound)) {
40  //std::cout << "Out of bounds" << std::endl;
41  //Out of bounds step, failed!
42  return 0;
43  }
44 
45  //Re evalulate the derivatives
46  auto new_state = f(new_x);
47 
48  //Check for convergence
49  Real delta = new_x - x;
50 
51  if ((std::abs(delta) < std::abs(x_precision * new_x)) || (new_state[0] == Real(0))) {
52  //std::cout << "Converged" << std::endl;
53  //We've converged
54  x = new_x;
55  curr_state = new_state;
56  return 2;
57  }
58 
59  //Check if the function has increased
60  if (std::abs(new_state[0]) > std::abs(curr_state[0])) {
61  //std::cout << "Function increased!" << std::endl;
62  //The function increased! method has failed
63  return 0;
64  }
65 
66  //Not converged or failed, update bounds and continue
67  if (delta >= 0)
68  low_bound = x;
69 
70  if (delta <= 0)
71  high_bound = x;
72 
73  curr_state = new_state;
74  x = new_x;
75 
76  //std::cout << "new bounds [" << low_bound << "," << high_bound << "]" << std::endl;
77 
78  return 1;
79  }
80  }
81 
84  template<class F, size_t Derivatives, class Real>
85  inline int newton_raphson_step(const F& f, std::array<Real, Derivatives>& curr_state,
86  Real& x, Real& low_bound, Real& high_bound, Real x_precision) {
87  static_assert(Derivatives >= 1, "Can only perform newton raphson if at least 1 derivative is available");
88 
89  //std::cout << "NR Method" << std::endl;
90  if (curr_state[1] == 0) {
91  //std::cout << "Zero derivative!" << std::endl;
92  //Zero derivatives cause x to diverge, so abort
93  return 0;
94  }
95 
96  return detail::process_iterative_step(f, curr_state, x, x - curr_state[0] / curr_state[1], low_bound, high_bound, x_precision);
97  }
98 
101  template<class F, size_t Derivatives, class Real>
102  inline int halley_step(const F& f, std::array<Real, Derivatives>& curr_state,
103  Real& x, Real& low_bound, Real& high_bound, Real x_precision) {
104  static_assert(Derivatives >= 2, "Can only perform Halley iteration if at least 1 derivative is available");
105 
106  //std::cout << "Halley Method" << std::endl;
107  Real numerator = 2 * curr_state[0] * curr_state[1];
108  Real denominator = 2 * curr_state[1] * curr_state[1] - curr_state[0] * curr_state[2];
109 
110  if ((denominator == 0) || !std::isfinite(denominator)) {
111  //std::cout << "Halley Overflow!" << std::endl;
112  //Cannot proceed with a zero or infinite denominator, or if the div
113  //has overflowed (+inf)
114  return 0;
115  }
116 
117  Real delta = - numerator / denominator;
118  Real deltaNR = - curr_state[0] / curr_state[1];
119  //std::cout << "delta = " << delta << std::endl;
120  //std::cout << "deltaNR = " << deltaNR << std::endl;
121  if (std::signbit(delta) != std::signbit(deltaNR)) {
122  //std::cout << "Halley != NR" << std::endl;
123  //The Halley and Newton Raphson iterations would proceed in
124  //opposite directions. This happens near multiple roots where
125  //the second derivative causes overcompensation. Fail so NR is
126  //used instead.
127  return 0;
128  }
129 
130  return detail::process_iterative_step(f, curr_state, x, x + delta, low_bound, high_bound, x_precision);
131  }
132 
135  template<class F, size_t Derivatives, class Real>
136  inline int schroeder_step(const F& f, std::array<Real, Derivatives>& curr_state, Real& x, Real& low_bound, Real& high_bound, Real x_precision) {
137  static_assert(Derivatives >= 2, "Can only perform Halley iteration if at least 1 derivative is available");
138 
139  if (curr_state[1] == 0)
140  //Cannot proceed with a zero first derivative
141  return 0;
142 
143  return detail::process_iterative_step(f, curr_state, x, x - 2 * curr_state[0] * curr_state[1] - curr_state[2] * curr_state[0] * curr_state[0] / (2 * curr_state[1] * curr_state[1] * curr_state[1]), low_bound, high_bound, x_precision);
144  }
145 
148  template<class F, size_t Derivatives, class Real>
149  inline int bisection_step(const F& f, std::array<Real, Derivatives>& curr_state, Real& x, Real& low_bound, Real& high_bound, Real x_precision) {
150  if ((low_bound >= high_bound) || std::isinf(low_bound) || std::isinf(high_bound))
151  //This is not a valid interval
152  return 0;
153 
154  //std::cout << "Bisection" << std::endl;
155  auto f_low = f(low_bound);
156  auto f_high = f(high_bound);
157 
158  if (std::signbit(f_low[0]) == std::signbit(f_high[0])) {
159  //std::cout << "No sign change!" << std::endl;
160  //No sign change in the interval
161  return 0;
162  }
163 
164  Real x_mid = (low_bound + high_bound) / 2;
165  auto new_state = f(x_mid);
166 
167  //std::cout << "new_x = " << x_mid << std::endl;
168 
169  Real delta = x_mid - x;
170 
171  if (//Check if the precision has been reached
172  (std::abs(delta) < std::abs(x_precision * x_mid))
173  //Or the function has hit zero on the mid point
174  || (new_state[0] == Real(0))
175  //Or if the bounds have numerically converged
176  || (x_mid == low_bound)
177  || (x_mid == high_bound)
178  )
179  {
180  //std::cout << "Converged!" << std::endl;
181  //We've converged
182  x = x_mid;
183  curr_state = new_state;
184  return 2;
185  }
186 
187  //Update bounds and continue
188  if (std::signbit(new_state[0]) == std::signbit(f_high[0])) {
189  high_bound = x_mid;
190  } else {
191  low_bound = x_mid;
192  }
193 
194  //std::cout << "new bounds [" << low_bound << "," << high_bound << "]" << std::endl;
195  x = x_mid;
196  curr_state = new_state;
197 
198  return 1;
199  }
200 
206  template<class F, class Real>
207  bool newton_raphson(const F& f, Real& x, Real low_bound = -HUGE_VAL, Real high_bound = +HUGE_VAL,
208  size_t iterations = 0, int digits = std::numeric_limits<Real>::digits / 2)
209  {
210  if (!iterations)
211  iterations = std::numeric_limits<size_t>::max();
212 
213  auto last_state = f(x);
214  static_assert(last_state.size() > 1, "Require one derivative of the objective function for Newton-Raphson method");
215 
216  Real x_precision = static_cast<Real>(ldexp(1.0, 1 - digits));
217 
218  if (last_state[0] == 0)
219  return true;
220 
221  int status = 1;
222  while ((--iterations) && (status != 2)) {
223  status = newton_raphson_step(f, last_state, x, low_bound, high_bound, x_precision);
224  if (!status) {
225  status = bisection_step(f, last_state, x, low_bound, high_bound, x_precision);
226  if (!status)
227  return false;
228  }
229  }
230 
231  return (status == 2);
232  }
233 
239  template<class F, class Real>
240  bool halleys_method(const F& f, Real& x, Real low_bound = -HUGE_VAL,
241  Real high_bound = +HUGE_VAL, size_t iterations = 0, int digits = std::numeric_limits<Real>::digits / 2)
242  {
243  if (!iterations)
244  iterations = std::numeric_limits<size_t>::max();
245 
246  auto last_state = f(x);
247 
248  static_assert(last_state.size() > 2, "Require two derivatives of the objective function for Halley's method");
249 
250  Real x_precision = static_cast<Real>(ldexp(1.0, 1 - digits));
251 
252  if (last_state[0] == 0)
253  return true;
254 
255  //std::cout << "x0=" << x << std::endl;
256  int status = 1;
257  while ((--iterations) && (status != 2)) {
258  status = halley_step(f, last_state, x, low_bound, high_bound, x_precision);
259  if (!status) {
260  status = newton_raphson_step(f, last_state, x, low_bound, high_bound, x_precision);
261  if (!status) {
262  status = bisection_step(f, last_state, x, low_bound, high_bound, x_precision);
263  if (!status)
264  return false;
265  }
266  }
267  }
268  return (status == 2);
269  }
270 
271  template<class F, class Real>
272  bool bisection(const F& f, Real& x, Real low_bound,
273  Real high_bound, size_t iterations = 0, int digits = std::numeric_limits<Real>::digits / 2)
274  {
275  //Check for a valid interval
276  if ((low_bound >= high_bound) || std::isinf(low_bound) || std::isinf(high_bound))
277  return false;
278 
279  if (!iterations)
280  iterations = std::numeric_limits<size_t>::max();
281 
282  Real x_precision = static_cast<Real>(ldexp(1.0, 1 - digits));
283 
284  auto f_low = f(low_bound);
285  auto f_high = f(high_bound);
286 
287  if (std::signbit(f_low) == std::signbit(f_high)) {
288  //No sign change in the interval, no root found!
289  return false;
290  }
291 
292  //Initialise the result with a decent guess
293  x = high_bound;
294  if (std::abs(f_low) < std::abs(f_high))
295  x = low_bound;
296 
297  while (--iterations) {
298  Real x_mid = (low_bound + high_bound) / 2;
299  auto new_state = f(x_mid);
300  Real delta = x_mid - x;
301  if (
302  //Check if the precision has been reached
303  (std::abs(delta) < std::abs(x_precision * x_mid))
304  //Or the function has hit zero on the mid point
305  || (new_state == Real(0))
306  //Or if the bounds have numerically converged
307  || (x_mid == low_bound)
308  || (x_mid == high_bound)
309  )
310  {
311  x = x_mid;
312  return true;
313  }
314 
315  //Update bounds and continue iterations
316  if (std::signbit(new_state) == std::signbit(f_high)) {
317  f_high = new_state;
318  high_bound = x_mid;
319  } else {
320  f_low = new_state;
321  low_bound = x_mid;
322  }
323  }
324 
325  //Failed to find the root
326  return false;
327  }
328  }
329 }
int bisection_step(const F &f, std::array< Real, Derivatives > &curr_state, Real &x, Real &low_bound, Real &high_bound, Real x_precision)
A single bisection step.
Definition: numeric.hpp:149
int newton_raphson_step(const F &f, std::array< Real, Derivatives > &curr_state, Real &x, Real &low_bound, Real &high_bound, Real x_precision)
A single step of the Newton-Raphson method for finding roots.
Definition: numeric.hpp:85
int process_iterative_step(const F &f, std::array< Real, Derivatives > &curr_state, Real &x, Real new_x, Real &low_bound, Real &high_bound, Real x_precision)
Update and checking of safeguards, called after an iterative step is taken towards a root...
Definition: numeric.hpp:33
int halley_step(const F &f, std::array< Real, Derivatives > &curr_state, Real &x, Real &low_bound, Real &high_bound, Real x_precision)
A single step of Halley&#39;s method for finding roots.
Definition: numeric.hpp:102
bool bisection(const F &f, Real &x, Real low_bound, Real high_bound, size_t iterations=0, int digits=std::numeric_limits< Real >::digits/2)
Definition: numeric.hpp:272
bool halleys_method(const F &f, Real &x, Real low_bound=-HUGE_VAL, Real high_bound=+HUGE_VAL, size_t iterations=0, int digits=std::numeric_limits< Real >::digits/2)
Safeguarded Halley&#39;s method for detecting a root.
Definition: numeric.hpp:240
bool newton_raphson(const F &f, Real &x, Real low_bound=-HUGE_VAL, Real high_bound=+HUGE_VAL, size_t iterations=0, int digits=std::numeric_limits< Real >::digits/2)
Safeguarded newton_raphson method for detecting a root.
Definition: numeric.hpp:207
int schroeder_step(const F &f, std::array< Real, Derivatives > &curr_state, Real &x, Real &low_bound, Real &high_bound, Real x_precision)
A single step of Schroeder&#39;s method for finding roots.
Definition: numeric.hpp:136
constexpr C<(1 - 2 *(num< 0)) *num,(1 - 2 *(den< 0)) *den > abs(const C< num, den > &a)
Definition: constants.hpp:189
The stator library namespace.
Definition: frontpage.dox:243