G+Smo  25.01.0
Geometry + Simulation Modules
 
Loading...
Searching...
No Matches
gsBairstowSolver.h
1#pragma once
2
3#include <gismo.h>
4
5#include "gsMonomialBasis.h"
6#include "gsMonomialPoly.h"
7
8namespace gismo
9{
10
11template<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
24public:
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
187private:
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
A matrix with arbitrary coefficient type and fixed or dynamic size.
Definition gsMatrix.h:41
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
The G+Smo namespace, containing all definitions for the library.