G+Smo  24.08.0
Geometry + Simulation Modules
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
gsBairstowSolver.h
1 #pragma once
2 
3 #include <gismo.h>
4 
5 #include "gsMonomialBasis.h"
6 #include "gsMonomialPoly.h"
7 
8 namespace gismo
9 {
10 
11 template<typename C> class gsBairstowSolver
12 {
13  C eps, b0, c0;
14  unsigned int max_iter;
15  C* aux; //this array is needed for division-with-remainder
16  int auxLen;
17  gsMatrix<C> Q[2]; //Q[0] and Q[1] store the coefficients of the polynomials generated by quadratic deflation
18  std::vector< std::vector< std::complex<C> > > roots; //we cannot use gsMatrix, since the individual lengths may differ
19  int status; //0 = nothing happened yet, -1 = failure, 1 = success
20  int init_mode; //0 = (b0, c0), 1 = leading coefs
21  bool polish_roots;
22  int component; //auxiliary variable, storing the component of the coefficient vector currently considered
23 
24 public:
25 
26  // default constructor
27  gsBairstowSolver() : eps(0.001), b0(0), c0(0), max_iter(50), aux(0), auxLen(0), status(-1), init_mode(0), polish_roots(true) { }
28 
29  // destructor
30  ~gsBairstowSolver(){
31  if (aux) delete[] aux;
32  }
33 
34  /* \brief This is the main function of class 'gsBairstowSolver'.
35  It tries to find the roots of all components of the n-dimensional polynomial 'poly' and stores them
36  in vector 'roots' (which can then be accessed by 'getRoots').
37  As another side effect, he function sets the value of 'status' to -1 if something went wrong,
38  and to 1 if the method succeeded.
39  \param poly is the n-dimensional polynomial whose roots shall be computed.
40  */
41  void solve(const gsMonomialPoly<C> & poly){
42  Q[0] = poly.coefs();
43  int n = Q[0].cols();
44 
45  if (n == 0){
46  // nothing to do
47  status = -1;
48  return;
49  }
50  if (Q[0].rows() > auxLen){
51  // resize the auxiliary array 'aux' which is needed for division-with-remainder.
52  if (aux) delete[] aux;
53  auxLen = Q[0].rows();
54  aux = new C[auxLen];
55  }
56 
57  // resize the vector containing the roots
58  if (roots.size() != (unsigned int)n) roots.resize(n);
59 
60  /*
61  The role of Q[0] and Q[1] is as follows:
62  After the 'i'-th quadratic deflation of the 'j'-th component of 'poly',
63  Q[i mod 2, j] contains the coefficients of the 'j'-th component of the
64  current (i.e. the deflated) polynomial. Q[(i+1) mod 2] is then used in the next
65  step to store the quotient, which will in turn become the next deflated polynomial.
66  The memory needed by Q[0] and Q[1] is allocated only once, to speed up the computation.
67  */
68  Q[1] = gsMatrix<C>(Q[0].rows(), n);
69 
70  // we solve each component individually
71  for (component = 0; component < n; ++component){
72  if ((status = solveComponent()) < 0) return;
73  }
74 
75  if (polish_roots){
76  // if requested, the found roots are polished
77 
78  /* Since we have to use the already-computed roots as initial values,
79  we temporarily set init_mode to 0 (i.e. use b0 and c0 as initial values).
80  b0 and c0 are set to the already-computed roots in function 'polishRoots',
81  and reset to their original values afterwards. */
82  int init_mode_orig(init_mode);
83  C b0_orig(b0), c0_orig(c0);
84  init_mode = 0;
85  Q[0] = poly.coefs();
86 
87  // we polish each component individually
88  for (component = 0; component < n; ++component){
89  if ((status = polishRoots()) < 0) break;
90  }
91 
92  // init_mode, b0 and c0 are reset to their original values
93  init_mode = init_mode_orig;
94  b0 = b0_orig;
95  c0 = c0_orig;
96  }
97  }
98 
99  /* \brief Returns the roots of the 'i'-th component that were computed by the last call to 'solve'.
100  \param i refers to the component of the polynomial whose roots shall be returned.
101  */
102  std::vector< std::complex<C> > getRoots(unsigned int i){
103  GISMO_ASSERT( i >= 0 && i < roots.size(),
104  "Root-vector which is out of range requested.");
105  return roots[i];
106  }
107 
108  /* \brief Returns the roots of the 'i'-th component that were computed by the last call to 'solve'.
109  \param i refers to the component of the polynomial whose roots shall be returned
110  */
111  const std::vector< std::complex<C> > getRoots(unsigned int i) const{
112  GISMO_ASSERT( i >= 0 && i < roots.size(),
113  "Root-vector which is out of range requested.");
114  return roots[i];
115  }
116 
117  // \brief Returns the roots of all components that were computed by the last call to 'solve'.
118  std::vector< std::vector< std::complex<C> > > getRoots(){
119  return this->roots;
120  }
121 
122  // \brief Returns the roots of all components that were computed by the last call to 'solve'.
123  const std::vector< std::vector< std::complex<C> > > getRoots() const{
124  return this->roots;
125  }
126 
127  // \brief Returns the maximum number of Newton-iterations in one step of quadratic deflation.
128  unsigned int getMaxIterations() const { return max_iter; }
129 
130  /* \brief Sets the maximum number of Newton-iterations in one step of quadratic deflation.
131  \param value is the new maximum number of iterations.
132  */
133  void setMaxIterations(unsigned int value) { max_iter = value; }
134 
135  // \brief Returns the tolerance for aborting Newton's method.
136  C getEps() const { return eps; }
137 
138  /* \brief Sets the tolerance for aborting Newton's method.
139  \param value is the new tolerance.
140  */
141  void setEps(const C & value) { eps = value; }
142 
143  // \brief Returns the 'i'-th initial coefficient for Newton's method, where 'i' has to be either 0 or 1.
144  C getInitial(int i){
145  GISMO_ASSERT( i >= 0 && i <= 1,
146  "Initial coefficient which is out of range requested.");
147  switch (i){
148  case 0:
149  return c0;
150  case 1:
151  return b0;
152  }
153  return 0;
154  }
155 
156  /* \brief Sets the two initial coefficients for Newton's method.
157  Note that this only has an effect if the initialization mode is 0 (default).
158  \param coef0 is the new value of the constant coefficient.
159  \param coef1 is the new value of the coefficient of the linear term.
160  */
161  void setInitial(const C & coef0, const C & coef1){
162  c0 = coef0;
163  b0 = coef1;
164  }
165 
166  // \brief Returns the mode for obtaining the initial values in Newton's method.
167  int getInitMode(){ return init_mode; }
168 
169  /* \brief Sets the mode for obtaining the initial values in Newton's method.
170  \param value is the new initialization mode.
171  */
172  void setInitMode(int value){ init_mode = value; }
173 
174  // \brief Returns the value which indicates whether the computed roots are polished or not.
175  int getPolish(){ return polish_roots; }
176 
177  /* \brief Sets the value which indicates whether the computed roots are polished or not.
178  \param value specifies whether the roots shall be polished or not.
179  */
180  void setPolish(bool value){ polish_roots = value; }
181 
182  /* \brief Returns the status of the last root computation.
183  \param[out] The result is 0 if nothing happend yet, -1 if something went wrong, and 1 in case of success.
184  */
185  int stat() const { return status; }
186 
187 private:
188 
189  /*
190  This function computes the roots for a single component. It stores the results in
191  member 'roots', and returns a value indicating the status of the computation (see member 'status').
192  */
193  int solveComponent(){
194  /* Compute the degree of the 'component'-th component of Q[0].
195  Note that 'component' is a private class member referring to the component currently under consideration. */
196  int deg = this->degree(Q[0]);
197 
198  if (deg < 0) return -1; // zero polynomial
199 
200  roots[component] = std::vector< std::complex<C> >();
201 
202  if (deg > 0){
203  // we reserve enough space to store all roots
204  roots[component].reserve(deg);
205 
206  int i = 0, res;
207 
208  /* As long as the degree is > 2, we perform quadratic deflation.
209  The current polynomial under consideration is Q[i], and the quotient of Q[i] and
210  the quadratic factor found will be stored in Q[1 - i]. */
211  for (; deg > 2; deg -= 2){
212  res = findQuFactor(i, deg);
213  if (res < 0) return res;
214  i = 1 - i;
215  }
216 
217  // depending on the degree of the remaining factor, we still have to solve a linear/quadratic equation
218  switch (deg){
219  case 1:
220  roots[component].push_back(std::complex<C>(-Q[i](0, component) / Q[i](1, component)));
221  break;
222  case 2:
223  quadraticSolve(Q[i](1, component) / Q[i](2, component), Q[i](0, component) / Q[i](2, component));
224  break;
225  }
226  }
227 
228  return 1;
229  }
230 
231  /*
232  This version of 'findQuFactor' tries to find a quadratic factor of Q[i],
233  computes its roots, adds them to 'roots', and sets Q[1-i] to the quotient.
234  As 'solveComponent', it returns a value indicating the status of the computation.
235  */
236  int findQuFactor(int i, int d){
237  std::complex<C> s1, s2;
238 
239  // we basically just call the other version of 'findQuFactor'
240  int out = findQuFactor(i, d, s1, s2);
241  if (out >= 0){
242  roots[component].push_back(s1);
243  roots[component].push_back(s2);
244  }
245  return out;
246  }
247 
248  /*
249  This version of 'findQuFactor' tries to find a quadratic factor of Q[i],
250  computes its roots, stores them in 'sol1' and 'sol2', and sets Q[1-i] to the quotient.
251  As 'solveComponent', it returns a value indicating the status of the computation.
252  */
253  int findQuFactor(int i, int d, std::complex<C> & sol1, std::complex<C> & sol2){
254  C b, c, b2, c2, r, s, rb, sb, rc, sc, dv, db, dc, eps2(eps * eps);
255  int out = -1;
256 
257  // depending on init_mode we initialize b and c ...
258  switch (init_mode){
259  case 1:
260  // ... either by the leading coefficients of Q[i], or ...
261  b = Q[i](d - 1, component) / Q[i](d, component);
262  c = Q[i](d - 2, component) / Q[i](d, component);
263  break;
264  default:
265  // ... by b0 and c0
266  b = b0;
267  c = c0;
268  break;
269  }
270 
271  /* implementation of Bairstow's method according to
272  "Numerical Recipes in C: The Art of Scientific Computing", Chapter 9.6, page 378 */
273 
274  for (unsigned int k = 0; k < max_iter; ++k){
275  // First division-with-remainder. The quotient is stored in Q[1-i], and the remainder is 'aux[1]*x + aux[0]'.
276  div(Q[i], d, b, c, Q[1 - i]);
277  r = aux[1];
278  s = aux[0];
279 
280  if (r*r <= eps2 && s*s <= eps2){
281  // we can stop immediately, since we've found a quadratic factor
282  out = 1;
283  break;
284  }
285 
286  // Second division-with-remainder. The quotient is not needed, and the remainder is again 'aux[1]*x + aux[0]'.
287  div(Q[1 - i], d - 2, b, c);
288 
289  // partial derivatives
290  rc = -aux[1];
291  sc = -aux[0];
292  sb = -c * rc;
293  rb = -b * rc + sc;
294 
295  // solve 2x2 system, finally yielding db, dc
296  dv = sb * rc - sc * rb;
297  if (dv == 0) return -1;
298 
299  db = (r * sc - s * rc) / dv;
300  dc = (-r * sb + s * rb) / dv;
301 
302  // update b and c
303  b += db;
304  c += dc;
305 
306  if ((db*db <= eps2*(b2 = b*b) || b2 <= eps2) && (dc*dc <= eps2*(c2 = c*c) || c2 <= eps2)){
307  out = 1;
308  break;
309  }
310  }
311 
312  /* If we have found a quadratic factor, we add its roots to the root-vector.
313  The quotient of Q[i] and the quadratic factor found is stored in Q[1-i]. */
314  if (out > 0) quadraticSolve(b, c, sol1, sol2);
315  return out;
316  }
317 
318  /*
319  This function polishes the roots of a single component.
320  It does so by running again Bairstow's method for finding quadratic factors *of the original input polynomial*,
321  using the already-computed roots as initial values.
322  */
323  int polishRoots(){
324  if (roots[component].size() > 2){ //no need to polish the first two roots
325  int deg = roots[component].size(), out;
326  for (typename std::vector< std::complex<C> >::iterator it = roots[component].begin() + 2;
327  it != roots[component].end() && it + 1 != roots[component].end();
328  it += 2)
329  {
330  // set the initial values
331  b0 = -(it->real() + (it + 1)->real());
332  c0 = it->real() * (it + 1)->real() - it->imag() * (it + 1)->imag();
333 
334  /* We are not interested in the quadratic factor or the quotient,
335  but 'findQuFactor' also updates roots *it and *(it+1). */
336  out = findQuFactor(0, deg, *it, *(it + 1));
337  if (out < 0) return out;
338  }
339  }
340 
341  return 1;
342  }
343 
344  /*
345  This functions computes the degree of the 'component'-th component of the
346  coefficient vector 'coefs'.
347  Note that the degree might be strictly less than the length of the
348  vector, since some leading coefficients might be 0.
349  */
350  short_t degree(const gsMatrix<C> & coefs){
351  for (short_t d = coefs.rows() - 1; d >= 0; --d){
352  if (coefs(d, component) != 0) return d;
353  }
354  return -1;
355  }
356 
357  /*
358  This version of 'quadraticSolve' solves the quadratic equation 'x^2 + b*x + c == 0'
359  and stores the two solutions in vector 'roots' (at component 'component').
360  */
361  void quadraticSolve(const C & b, const C & c){
362  std::complex<C> s1, s2;
363 
364  // we basically just call the other version of 'quadraticSolve'
365  quadraticSolve(b, c, s1, s2);
366  roots[component].push_back(s1);
367  roots[component].push_back(s2);
368  }
369 
370  /*
371  This version of 'quadraticSolve' solves the quadratic equation 'x^2 + b*x + c == 0'
372  and stores the two solutions in 's1' and 's2'.
373  */
374  void quadraticSolve(const C & b, const C & c, std::complex<C> & s1, std::complex<C> & s2){
375  // straight-forward solving of 'x^2 + b*x + c == 0' by x_{1,2} = (-b +- sqrt(b^2 - 4*c)) / 2
376  C discriminant(b * b - C(4) * c);
377  C TWO = C(2);
378  if (discriminant >= 0)
379  {
380  discriminant = sqrt(discriminant);
381  s1 = std::complex<C>((-b - discriminant) / TWO);
382  s2 = std::complex<C>((-b + discriminant) / TWO);
383  }else{
384  discriminant = sqrt(-discriminant) / TWO;
385  s1 = std::complex<C>(-b / TWO, -discriminant);
386  s2 = std::complex<C>(-b / TWO, discriminant);
387  }
388  }
389 
390  /*
391  This version of 'div' divides an arbitrary polynomial 'p', given as a coefficient-vector,
392  by a quadratic polynomial 'x^2+b*x+c'. The coefficient-vector of the quotient is stored in
393  'quo', and the coefficients of the remainder are stored in the private class member 'aux';
394  This array is also used for storing intermediate results.
395  */
396  void div(const gsMatrix<C> & p, int dp, const C & b, const C & c, gsMatrix<C> & quo){
397  GISMO_ASSERT( p.rows() > dp && auxLen > dp && quo.rows() > dp - 2,
398  "Invalid division-with-remainder in gsBairstow.");
399 
400  /* division-with-remainder according to
401  "Numerical Recipes in C: The Art of Scientific Computing", Chapter 5.3, page 175 */
402  int k;
403  for (k = 0; k <= dp; ++k) aux[k] = p(k, component);
404  for (k = dp - 2; k >= 0; --k){
405  quo(k, component) = aux[k + 2];
406  aux[k + 1] -= quo(k, component) * b;
407  aux[k] -= quo(k, component) * c;
408  }
409  }
410 
411  /*
412  This version of 'div' divides an arbitrary polynomial 'p', given as a coefficient-vector,
413  by a quadratic polynomial 'x^2+b*x+c'.
414  Unlike with the other version above, no quotient, but only the remainder is computed.
415  */
416  void div(const gsMatrix<C> & p, int dp, const C & b, const C & c){
417  GISMO_ASSERT( p.rows() > dp && auxLen > dp,
418  "Invalid division-with-remainder in gsBairstow.");
419 
420  /* division-with-remainder according to
421  "Numerical Recipes in C: The Art of Scientific Computing", Chapter 5.3, page 175 */
422  int k;
423  for (k = 0; k <= dp; ++k) aux[k] = p(k, component);
424  for (k = dp - 2; k >= 0; --k){
425  aux[k + 1] -= aux[k + 2] * b;
426  aux[k] -= aux[k + 2] * c;
427  }
428  }
429 }; //gsBairstowSolver
430 
431 } //namespave gismo
Main header to be included by clients using the G+Smo library.
#define short_t
Definition: gsConfig.h:35
#define GISMO_ASSERT(cond, message)
Definition: gsDebug.h:89