TheAlgorithms/C++ 1.0.0
All the algorithms implemented in C++
Loading...
Searching...
No Matches
durand_kerner_roots.cpp File Reference

Compute all possible approximate roots of any given polynomial using Durand Kerner algorithm More...

#include <algorithm>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <iostream>
#include <valarray>
Include dependency graph for durand_kerner_roots.cpp:

Go to the source code of this file.

Macros

#define ACCURACY   1e-10
 
#define MAX_BUFF_SIZE   50
 

Functions

std::complex< double > poly_function (const std::valarray< double > &coeffs, std::complex< double > x)
 
const char * complex_str (const std::complex< double > &x)
 
bool check_termination (long double delta)
 
std::pair< uint32_t, double > durand_kerner_algo (const std::valarray< double > &coeffs, std::valarray< std::complex< double > > *roots, bool write_log=false)
 
void test1 ()
 
void test2 ()
 
int main (int argc, char **argv)
 

Detailed Description

Compute all possible approximate roots of any given polynomial using Durand Kerner algorithm

Author
Krishna Vedala

Test the algorithm online: https://gist.github.com/kvedala/27f1b0b6502af935f6917673ec43bcd7

Try the highly unstable Wilkinson's polynomial:

./numerical_methods/durand_kerner_roots 1 -210 20615 -1256850 53327946
-1672280820 40171771630 -756111184500 11310276995381 -135585182899530
1307535010540395 -10142299865511450 63030812099294896 -311333643161390640
1206647803780373360 -3599979517947607200 8037811822645051776
-12870931245150988800 13803759753640704000 -8752948036761600000
2432902008176640000

Sample implementation results to compute approximate roots of the equation \(x^4-1=0\):
Error evolution during root approximations computed every
   iteration. Roots evolution - shows the initial approximation of the
   roots and their convergence to a final approximation along with the iterative
   approximations

Definition in file durand_kerner_roots.cpp.

Macro Definition Documentation

◆ ACCURACY

#define ACCURACY   1e-10

maximum accuracy limit

Definition at line 46 of file durand_kerner_roots.cpp.

Function Documentation

◆ check_termination()

bool check_termination ( long double delta)

check for termination condition

Parameters
[in]deltapoint at which to evaluate the polynomial
Returns
false if termination not reached
true if termination reached

Definition at line 92 of file durand_kerner_roots.cpp.

92 {
93 static long double past_delta = INFINITY;
94 if (std::abs(past_delta - delta) <= ACCURACY || delta < ACCURACY)
95 return true;
96 past_delta = delta;
97 return false;
98}
#define ACCURACY

◆ complex_str()

const char * complex_str ( const std::complex< double > & x)

create a textual form of complex number

Parameters
[in]xpoint at which to evaluate the polynomial
Returns
pointer to converted string

Definition at line 77 of file durand_kerner_roots.cpp.

77 {
78#define MAX_BUFF_SIZE 50
79 static char msg[MAX_BUFF_SIZE];
80
81 std::snprintf(msg, MAX_BUFF_SIZE, "% 7.04g%+7.04gj", x.real(), x.imag());
82
83 return msg;
84}

◆ durand_kerner_algo()

std::pair< uint32_t, double > durand_kerner_algo ( const std::valarray< double > & coeffs,
std::valarray< std::complex< double > > * roots,
bool write_log = false )

Implements Durand Kerner iterative algorithm to compute all roots of a polynomial.

Parameters
[in]coeffscoefficients of the polynomial
[out]rootsthe computed roots of the polynomial
[in]write_logflag whether to save the log file (default = false)
Returns
pair of values - number of iterations taken and final accuracy achieved

Definition at line 110 of file durand_kerner_roots.cpp.

112 {
113 long double tol_condition = 1;
114 uint32_t iter = 0;
115 int n;
116 std::ofstream log_file;
117
118 if (write_log) {
119 /*
120 * store intermediate values to a CSV file
121 */
122 log_file.open("durand_kerner.log.csv");
123 if (!log_file.is_open()) {
124 perror("Unable to create a storage log file!");
125 std::exit(EXIT_FAILURE);
126 }
127 log_file << "iter#,";
128
129 for (n = 0; n < roots->size(); n++) log_file << "root_" << n << ",";
130
131 log_file << "avg. correction";
132 log_file << "\n0,";
133 for (n = 0; n < roots->size(); n++)
134 log_file << complex_str((*roots)[n]) << ",";
135 }
136
137 bool break_loop = false;
138 while (!check_termination(tol_condition) && iter < INT16_MAX &&
139 !break_loop) {
140 tol_condition = 0;
141 iter++;
142 break_loop = false;
143
144 if (log_file.is_open())
145 log_file << "\n" << iter << ",";
146
147#ifdef _OPENMP
148#pragma omp parallel for shared(break_loop, tol_condition)
149#endif
150 for (n = 0; n < roots->size(); n++) {
151 if (break_loop)
152 continue;
153
154 std::complex<double> numerator, denominator;
155 numerator = poly_function(coeffs, (*roots)[n]);
156 denominator = 1.0;
157 for (int i = 0; i < roots->size(); i++)
158 if (i != n)
159 denominator *= (*roots)[n] - (*roots)[i];
160
161 std::complex<long double> delta = numerator / denominator;
162
163 if (std::isnan(std::abs(delta)) || std::isinf(std::abs(delta))) {
164 std::cerr << "\n\nOverflow/underrun error - got value = "
165 << std::abs(delta) << "\n";
166 // return std::pair<uint32_t, double>(iter, tol_condition);
167 break_loop = true;
168 }
169
170 (*roots)[n] -= delta;
171
172#ifdef _OPENMP
173#pragma omp critical
174#endif
175 tol_condition = std::max(tol_condition, std::abs(std::abs(delta)));
176 }
177 // tol_condition /= (degree - 1);
178
179 if (break_loop)
180 break;
181
182 if (log_file.is_open()) {
183 for (n = 0; n < roots->size(); n++)
184 log_file << complex_str((*roots)[n]) << ",";
185 }
186
187#if defined(DEBUG) || !defined(NDEBUG)
188 if (iter % 500 == 0) {
189 std::cout << "Iter: " << iter << "\t";
190 for (n = 0; n < roots->size(); n++)
191 std::cout << "\t" << complex_str((*roots)[n]);
192 std::cout << "\t\tabsolute average change: " << tol_condition
193 << "\n";
194 }
195#endif
196
197 if (log_file.is_open())
198 log_file << tol_condition;
199 }
200
201 return std::pair<uint32_t, long double>(iter, tol_condition);
202}
bool check_termination(long double delta)
const char * complex_str(const std::complex< double > &x)
std::complex< double > poly_function(const std::valarray< double > &coeffs, std::complex< double > x)

◆ main()

int main ( int argc,
char ** argv )

Definition at line 284 of file durand_kerner_roots.cpp.

284 {
285 /* initialize random seed: */
286 std::srand(std::time(nullptr));
287
288 if (argc < 2) {
289 test1(); // run tests when no input is provided
290 test2(); // and skip tests when input polynomial is provided
291 std::cout << "Please pass the coefficients of the polynomial as "
292 "commandline "
293 "arguments.\n";
294 return 0;
295 }
296
297 int n, degree = argc - 1; // detected polynomial degree
298 std::valarray<double> coeffs(degree); // create coefficiencts array
299
300 // number of roots = degree - 1
301 std::valarray<std::complex<double>> s0(degree - 1);
302
303 std::cout << "Computing the roots for:\n\t";
304 for (n = 0; n < degree; n++) {
305 coeffs[n] = strtod(argv[n + 1], nullptr);
306 if (n < degree - 1 && coeffs[n] != 0)
307 std::cout << "(" << coeffs[n] << ") x^" << degree - n - 1 << " + ";
308 else if (coeffs[n] != 0)
309 std::cout << "(" << coeffs[n] << ") x^" << degree - n - 1
310 << " = 0\n";
311
312 /* initialize root approximations with random values */
313 if (n < degree - 1) {
314 s0[n] = std::complex<double>(std::rand() % 100, std::rand() % 100);
315 s0[n] -= 50.f;
316 s0[n] /= 50.f;
317 }
318 }
319
320 // numerical errors less when the first coefficient is "1"
321 // hence, we normalize the first coefficient
322 {
323 double tmp = coeffs[0];
324 coeffs /= tmp;
325 }
326
327 clock_t end_time, start_time = clock();
328 auto result = durand_kerner_algo(coeffs, &s0, true);
329 end_time = clock();
330
331 std::cout << "\nIterations: " << result.first << "\n";
332 for (n = 0; n < degree - 1; n++)
333 std::cout << "\t" << complex_str(s0[n]) << "\n";
334 std::cout << "absolute average change: " << result.second << "\n";
335 std::cout << "Time taken: "
336 << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC
337 << " sec\n";
338
339 return 0;
340}
void test2()
void test1()
std::pair< uint32_t, double > durand_kerner_algo(const std::valarray< double > &coeffs, std::valarray< std::complex< double > > *roots, bool write_log=false)
uint64_t result(uint64_t n)

◆ poly_function()

std::complex< double > poly_function ( const std::valarray< double > & coeffs,
std::complex< double > x )

Evaluate the value of a polynomial with given coefficients

Parameters
[in]coeffscoefficients of the polynomial
[in]xpoint at which to evaluate the polynomial
Returns
\(f(x)\)

Definition at line 54 of file durand_kerner_roots.cpp.

55 {
56 double real = 0.f, imag = 0.f;
57 int n;
58
59 // #ifdef _OPENMP
60 // #pragma omp target teams distribute reduction(+ : real, imag)
61 // #endif
62 for (n = 0; n < coeffs.size(); n++) {
63 std::complex<double> tmp =
64 coeffs[n] * std::pow(x, coeffs.size() - n - 1);
65 real += tmp.real();
66 imag += tmp.imag();
67 }
68
69 return std::complex<double>(real, imag);
70}

◆ test1()

void test1 ( )

Self test the algorithm by checking the roots for \(x^2+4=0\) to which the roots are \(0 \pm 2i\)

Definition at line 208 of file durand_kerner_roots.cpp.

208 {
209 const std::valarray<double> coeffs = {1, 0, 4}; // x^2 - 2 = 0
210 std::valarray<std::complex<double>> roots(2);
211 std::valarray<std::complex<double>> expected = {
212 std::complex<double>(0., 2.),
213 std::complex<double>(0., -2.) // known expected roots
214 };
215
216 /* initialize root approximations with random values */
217 for (int n = 0; n < roots.size(); n++) {
218 roots[n] = std::complex<double>(std::rand() % 100, std::rand() % 100);
219 roots[n] -= 50.f;
220 roots[n] /= 25.f;
221 }
222
223 auto result = durand_kerner_algo(coeffs, &roots, false);
224
225 for (int i = 0; i < roots.size(); i++) {
226 // check if approximations are have < 0.1% error with one of the
227 // expected roots
228 bool err1 = false;
229 for (int j = 0; j < roots.size(); j++)
230 err1 |= std::abs(std::abs(roots[i] - expected[j])) < 1e-3;
231 assert(err1);
232 }
233
234 std::cout << "Test 1 passed! - " << result.first << " iterations, "
235 << result.second << " accuracy"
236 << "\n";
237}

◆ test2()

void test2 ( )

Self test the algorithm by checking the roots for \(0.015625x^3-1=0\) to which the roots are \((4+0i),\,(-2\pm3.464i)\)

Definition at line 243 of file durand_kerner_roots.cpp.

243 {
244 const std::valarray<double> coeffs = {// 0.015625 x^3 - 1 = 0
245 1. / 64., 0., 0., -1.};
246 std::valarray<std::complex<double>> roots(3);
247 const std::valarray<std::complex<double>> expected = {
248 std::complex<double>(4., 0.), std::complex<double>(-2., 3.46410162),
249 std::complex<double>(-2., -3.46410162) // known expected roots
250 };
251
252 /* initialize root approximations with random values */
253 for (int n = 0; n < roots.size(); n++) {
254 roots[n] = std::complex<double>(std::rand() % 100, std::rand() % 100);
255 roots[n] -= 50.f;
256 roots[n] /= 25.f;
257 }
258
259 auto result = durand_kerner_algo(coeffs, &roots, false);
260
261 for (int i = 0; i < roots.size(); i++) {
262 // check if approximations are have < 0.1% error with one of the
263 // expected roots
264 bool err1 = false;
265 for (int j = 0; j < roots.size(); j++)
266 err1 |= std::abs(std::abs(roots[i] - expected[j])) < 1e-3;
267 assert(err1);
268 }
269
270 std::cout << "Test 2 passed! - " << result.first << " iterations, "
271 << result.second << " accuracy"
272 << "\n";
273}