/* polynom.cpp POLYNOM 0.11, complex polynomial factoring and expansion library implementation. See API specification in headerfile polynom.hpp. Latest version available at: https://ecomaan.nl/cpp/polynom Copyright (c) 2006, 2020 - Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define kPOLYNOM_DEBUG (0) // Make this constant 0 to surpress logging of // function factor(). Make 1 for debugging. #if (kPOLYNOM_DEBUG) #include #endif #include #include using namespace std; #include "polynom.hpp" // Header needs std namespace prior to inclusion. #define kEPSILON (1e-6) // Remainders after synthetic division below this // value are regarded zero (maximum error allowed). /* Function laguerre() tries to find one single root. Took Fortran code from http://www.netlib.org/kincaid-cheney/laguerre.f as a starting point: c Numerical Analysis: The Mathematics of Scientific Computing c D.R. Kincaid & E.W. Cheney. Brooks/Cole Publ., 1990. c Section 3.5 Example of Laguerre's Method c implicit complex (a-h,o-z) parameter (n=4, M=7) dimension a(0:n) data a/(-72.,68.),(1140.,636.),(-216.,190.),(-10.,-26.),(1.,0.)/ data z/(1.,1.)/ c sn = real(n) do 3 k=1,m alpha = a(n) beta = (0.0,0.0) gamma = (0.0,0.0) c do 2 j=n-1,0,-1 gamma = z*gamma + beta beta = z*beta + alpha alpha = z*alpha + a(j) 2 continue c Pieter: you will notice: ca = -beta/alpha DIV BY ZERO POSSIBLE HERE cb = ca*ca - 2.0*gamma/alpha AND HERE (converged & done?) c1 = sqrt((sn-1.0)*(sn*cb - ca*ca)) cc1 = (ca + c1)/sn cc2 = (ca - c1)/sn cc = cc2 if (abs(cc1) .gt. abs(cc2)) then cc = cc1 c2 = 1.0/cc z = z + c2 d = (ca - cc)/(sn - 1.0) rho1 = sqrt(sn)*c2 rho2 = sqrt(sn/(cc*cc + (sn-1)*d*d)) print *,'values of z, c2, rho1, and rho2:' print *,z,c2,rho1,rho2 3 continue c */ static int laguerre(int degree, // Number of coefficients-1. const complex* a, // Array of coefficients. complex* zp) // Starting and end point. { #if (kPOLYNOM_DEBUG) ofstream t("POLYNOM.DEBUG"); // Some stream for debugging. #endif // cout, cerr, '/dev/null', etc. static const double f[8] = { 1.0, 0.5, 0.25, 0.75, 0.13, 0.38, 0.62, 0.88 }; complex z = *zp; // Copy for faster access. double sn(degree); // degree = sn = number of coefficients - 1. double snm(degree-1); long k; for (k = 0; k < 10000; k++) // Not more than ten thousand iterations. { complex ca, caca, cb, c1, dz, cc, cp, cm, zz; complex alpha(a[degree]); complex beta (0.0, 0.0); complex gamma(0.0, 0.0); for (int j = degree - 1; j >= 0; j--) // Horner's scheme. { gamma = (z * gamma) + beta; beta = (z * beta) + alpha; alpha = (z * alpha) + a[j]; } if (alpha == 0.0) { *zp = z; #if (kPOLYNOM_DEBUG) t << "Laguerre: (alpha=0) converged after " << k << " iterations.\n"; #endif return 0; } ca = -beta / alpha; caca = ca * ca; cb = caca - (2.0 * gamma / alpha); c1 = sqrt(snm * ((sn * cb) - caca)); cp = ca + c1; cm = ca - c1; if (norm(cm) > norm(cp)) // norm() is faster than abs(). cp = cm; if (cp == 0.0) // Re-init trick from 'Numerical Recipes'. dz = (1.0 + abs(z)) * polar(1.0, static_cast(k)); else dz = sn / cp; short kk = k % 9; // Once every 9 iterations. if (!kk) { dz *= f[(k / 9) & 7]; // Cycle breaking trick (but f[0]=1.0). } z += dz; if (kk && (norm(dz) < 1e-31)) // OK, converged: almost no change. { // THIS 1e-31 IS RELATED TO kEPSILON ! *zp = z; #if (kPOLYNOM_DEBUG) t << "Laguerre: (dz=0) converged after " << k << " iterations.\n"; #endif return 0; } if (k == 2000) { z *= -1.0; } // Kick z around when it else if (k == 4000) { z = conj(z); } // is taking too long. else if (k == 6000) { z = -conj(z); } else if (k == 8000) { z *= complex(0.0, 1.0); } } #if (kPOLYNOM_DEBUG) t << "Laguerre: failed to converge within " << k << " iterations.\n"; #endif return 1; } /* In most cases, the number of roots will equal the number of input coeffi- cients minus one. Only if the last (or latter) coefficient(s) is (are) zero, the number of resulting roots may be less than expected (coeffs.size() - 1). Coefficients expected in ascending order of degree. */ int factor(const vector >& coeffs, vector >& roots) { int attempt = 0; const int n_attempts = 4; static const complex guess[n_attempts] = { complex( 8.0), complex(-6.0), complex( 0.0, 6.0), complex( 0.0, -8.0) }; complex *cc = NULL, // Tmp copy of coefficients. *c = NULL; // Itarator within it. vector >::const_iterator coeff = coeffs.begin(); int degree = coeffs.size() - 1; int num_roots = 0; int e = 0; // Find the ACTUAL degree of the polynomial. This guarentees last coeff is non-zero. while ((degree > 0) && (coeff[degree] == 0.0)) // Use kEPSILON instead of 0.0? degree--; if (degree < 1) // There must at least be 2 coefficients, of which return 1; // only the first may be zero. if (roots.size() != static_cast(degree)) // Prepare output vector. roots.resize(degree); // Factor-out roots at the origin. while ((degree > 0) && (coeff[0] == 0.0)) // Use kEPSILON instead of 0.0? { roots[num_roots++] = 0.0; // Insert root at 0. coeff++; // Never reuse this coeff (INC ITERATOR). degree--; } int degreeH = degree; // Remember for retries. // From here on, first and last coeff are non-zero. if (degree > 0) // Copy input array so we can write in it. { // (Only necessary for Laguerre, actually). cc = c = new complex[degree + 1]; // MAY THROW AN EXCEPTION. for (int n = 0; n <= degree; n++) c[n] = coeff[n]; } while (degree > 2) { // It is perhaps a bit stupid to investigate this EACH time: the chance that // such a simple form appears in between Laguerre iterations is almost nil(?). int n; // See whether all coeffs in between are zero. for (n = 1; (n < degree) && (c[n] == 0.0); n++) // Use kEPSILON instead of 0.0? ; // Then we have a complex 88 if (n == degree) // binomial in this form: x = -a0 / a88 { // Now we have 88 solutions all at once: complex p = -c[0] / c[degree]; double magnitude = pow(norm(p), 0.5 / static_cast(degree)); double angle = arg(p) / static_cast(degree); double angle_inc = 8.0 * atan(1.0) / static_cast(degree); do { roots[num_roots++] = polar(magnitude, angle); angle += angle_inc; } while (--degree); // degree = 0 returns immediately. } else { // Solve numerically. complex z = guess[attempt]; // Select a starting point. if (laguerre(degree, // Number of coefficients - 1. c, // Ptr to array of complex coefficients. &z)) // Starting point and found root. e = 2; // 2 means Laguerre did not converge. else { // Synthetic division (and check if z is really a root). for (n = degree - 1; n >= 0; n--) // c[degree] stays the same. c[n] += c[n+1] * z; if (abs(c[0]) < kEPSILON) // Must be (almost) zero. { degree--; c++; // Remove first coefficient. roots[num_roots++] = z; // Store the divisor. e = 0; } else e = 3; // 3 means synthetic division left non-zero remainder. } if (e) { if (++attempt < n_attempts) // Try all over again. { num_roots -= (degreeH - degree); // Remove only the non- degree = degreeH; // zero roots found. c = cc; // Restore coeffs. for (n = 0; n <= degree; n++) c[n] = coeff[n]; } else degree = 0; /* Exit the loop, e=2 or e=3. */ } } } if (degree == 2) // Solve quadratic equation analytically: { complex a2 = c[2] * 2.0; // -------- complex D = sqrt(c[1]*c[1] // / 2 -4.0 * c[2]*c[0]); // D = \/ b - 4ac roots[num_roots++] = (-c[1] + D) / a2; // x1 = (-b+D)/2a roots[num_roots++] = (-c[1] - D) / a2; // x2 = (-b-D)/2a } else if (degree == 1) // Solve linear equation analytically: { // a0 + a1 x = 0 <=> x = -a0 / a1. roots[num_roots++] = -c[0]/c[1]; // Division by zero can never occur. } // else (if (degree == 0)) we're done. delete[] cc; // Release temporary workspace. return e; // 0 = Ok. } /* Same as factor() except that coefficients are expected in descending order of degree (and this function works slightly slower :-). */ int factor_d(const vector >& coeffs, vector >& roots) { int n; int num_coeffs = coeffs.size(); // MAY THROW. vector > c(num_coeffs); // Work with temporary copy of the given coefficients in reverse order. for (n = 0; n < num_coeffs; n++) c[n] = coeffs[num_coeffs - n - 1]; n = factor(c, roots); return n; } /* Expand the polynome. Recursive multiplication of its' roots. */ int expand(const vector >& roots, // Input array. vector >& coeffs) // Output, ascending degree. { int i; int num_roots = roots.size(); if (num_roots < 1) // At least one root should be there. return 1; if (coeffs.size() != static_cast(num_roots + 1)) coeffs.resize(num_roots + 1); // Prepare output vector. for (i = 0; i < num_roots; i++) // Initialise coefficient vector. coeffs[i] = 0.0; coeffs[i] = 1.0; // Last coeff = 1.0. for (i = num_roots; i > 0; i--) // Recursive multiplication. { coeffs[i - 1] = -roots[i - 1] * coeffs[i]; for (int j = i; j < num_roots; j++) coeffs[j] -= roots[i - 1] * coeffs[j + 1]; } return 0; // 0 = Ok. } /* Same as expand() except that coefficients are delivered in descending order of degree. */ int expand_d(const vector >& roots, // Input array. vector >& coeffs) // Output, descending degree. { int i; int num_roots = roots.size(); if (num_roots < 1) // At least one root should be there. return 1; if (coeffs.size() != static_cast(num_roots + 1)) coeffs.resize(num_roots + 1); // Prepare output vector. coeffs[0] = 1.0; // Init coefficient vector. for (i = 1; i <= num_roots; i++) coeffs[i] = 0.0; for (i = 0; i < num_roots; i++) // Recursive multiplication. { coeffs[i + 1] = -roots[i] * coeffs[i]; for (int j = i; j > 0; j--) coeffs[j] -= roots[i] * coeffs[j - 1]; } return 0; // 0 = Ok. }