/* Mike Clark - 25th May 2005 alg_remez.C AlgRemez is an implementation of the Remez algorithm, which in this case is used for generating the optimal nth root rational approximation. Note this class requires the gnu multiprecision (GNU MP) library. */ #include #include #include #include #include #include #include #include // Constructor AlgRemez::AlgRemez(double lower, double upper, long precision) { prec = precision; bigfloat::setDefaultPrecision(prec); apstrt = lower; apend = upper; apwidt = apend - apstrt; std::cout<<"Approximation bounds are ["< tolerance) { //iterate until convergance if (iter++%100==0) std::cout<<"Iteration " <= tolerance); search(step); } int sign; double error = (double)getErr(mm[0],&sign); std::cout<<"Converged at "<= xx1) { // Cannot skip over adjacent boundaries q = -q; xn = xm; yn = ym; ensign = emsign; } else { yn = getErr(xn,&ensign); if (yn < ym) { q = -q; xn = xm; yn = ym; ensign = emsign; } } while(yn >= ym) { // March until error becomes smaller. if (++steps > 10) break; ym = yn; xm = xn; emsign = ensign; a = xm + q; if (a == xm || a <= xx0 || a >= xx1) break;// Must not skip over the zeros either side. xn = a; yn = getErr(xn,&ensign); } mm[i] = xm; // Position of maximum yy[i] = ym; // Value of maximum if (eclose > ym) eclose = ym; if (farther < ym) farther = ym; xx0 = xx1; // Walk to next zero. } // end of search loop q = (farther - eclose); // Decrease step size if error spread increased if (eclose != 0.0) q /= eclose; // Relative error spread if (q >= spread) delta *= 0.5; // Spread is increasing; decrease step size spread = q; for (i = 0; i < neq; i++) { q = yy[i+1]; if (q != 0.0) q = yy[i] / q - (bigfloat)1l; else q = 0.0625; if (q > (bigfloat)0.25) q = 0.25; q *= mm[i+1] - mm[i]; step[i] = q * delta; } step[neq] = step[neq-1]; for (i = 0; i < neq; i++) { // Insert new locations for the zeros. xm = xx[i] - step[i]; if (xm <= apstrt) continue; if (xm >= apend) continue; if (xm <= mm[i]) xm = (bigfloat)0.5 * (mm[i] + xx[i]); if (xm >= mm[i+1]) xm = (bigfloat)0.5 * (mm[i+1] + xx[i]); xx[i] = xm; } delete [] yy; } // Solve the equations void AlgRemez::equations(void) { bigfloat x, y, z; int i, j, ip; bigfloat *aa; bigfloat *AA = new bigfloat[(neq)*(neq)]; bigfloat *BB = new bigfloat[neq]; for (i = 0; i < neq; i++) { // set up the equations for solution by simq() ip = neq * i; // offset to 1st element of this row of matrix x = xx[i]; // the guess for this row y = func(x); // right-hand-side vector z = (bigfloat)1l; aa = AA+ip; for (j = 0; j <= n; j++) { *aa++ = z; z *= x; } z = (bigfloat)1l; for (j = 0; j < d; j++) { *aa++ = -y * z; z *= x; } BB[i] = y * z; // Right hand side vector } // Solve the simultaneous linear equations. if (simq(AA, BB, param, neq)) { std::cout<<"simq failed\n"; exit(0); } delete [] AA; delete [] BB; } // Evaluate the rational form P(x)/Q(x) using coefficients // from the solution vector param bigfloat AlgRemez::approx(const bigfloat x) { bigfloat yn, yd; int i; // Work backwards toward the constant term. yn = param[n]; // Highest order numerator coefficient for (i = n-1; i >= 0; i--) yn = x * yn + param[i]; yd = x + param[n+d]; // Highest degree coefficient = 1.0 for (i = n+d-1; i > n; i--) yd = x * yd + param[i]; return(yn/yd); } // Compute size and sign of the approximation error at x bigfloat AlgRemez::getErr(bigfloat x, int *sign) { bigfloat e, f; f = func(x); e = approx(x) - f; if (f != 0) e /= f; if (e < (bigfloat)0.0) { *sign = -1; e = -e; } else *sign = 1; return(e); } // Calculate function required for the approximation. bigfloat AlgRemez::func(const bigfloat x) { bigfloat z = (bigfloat)power_num / (bigfloat)power_den; bigfloat y; if (x == (bigfloat)1.0) y = (bigfloat)1.0; else y = pow_bf(x,z); if (a_length > 0) { bigfloat sum = 0l; for (int j=0; j big) { big = size; idxpiv = i; } } if (big == (bigfloat)0l) { std::cout<<"simq big=0\n"; delete [] IPS; return(2); } if (idxpiv != k) { j = IPS[k]; IPS[k] = IPS[idxpiv]; IPS[idxpiv] = j; } kp = IPS[k]; kpk = n*kp + k; pivot = A[kpk]; kp1 = k+1; for (i = kp1; i < n; i++) { ip = IPS[i]; ipk = n*ip + k; em = -A[ipk] / pivot; A[ipk] = -em; nip = n*ip; nkp = n*kp; aa = A+nkp+kp1; for (j = kp1; j < n; j++) { ipj = nip + j; A[ipj] = A[ipj] + em * *aa++; } } } kpn = n * IPS[n-1] + n - 1; // last element of IPS[n] th row if (A[kpn] == (bigfloat)0l) { std::cout<<"simq A[kpn]=0\n"; delete [] IPS; return(3); } ip = IPS[0]; X[0] = B[ip]; for (i = 1; i < n; i++) { ip = IPS[i]; ipj = n * ip; sum = 0.0; for (j = 0; j < i; j++) { sum += A[ipj] * X[j]; ++ipj; } X[i] = B[ip] - sum; } ipn = n * IPS[n-1] + n - 1; X[n-1] = X[n-1] / A[ipn]; for (iback = 1; iback < n; iback++) { //i goes (n-1),...,1 i = nm1 - iback; ip = IPS[i]; nip = n*ip; sum = 0.0; aa = A+nip+i+1; for (j= i + 1; j < n; j++) sum += *aa++ * X[j]; X[i] = (X[i] - sum) / A[nip+i]; } delete [] IPS; return(0); } // Calculate the roots of the approximation int AlgRemez::root() { long i,j; bigfloat x,dx=0.05; bigfloat upper=1, lower=-100000; bigfloat tol = 1e-20; bigfloat *poly = new bigfloat[neq+1]; // First find the numerator roots for (i=0; i<=n; i++) poly[i] = param[i]; for (i=n-1; i>=0; i--) { roots[i] = rtnewt(poly,i+1,lower,upper,tol); if (roots[i] == 0.0) { std::cout<<"Failure to converge on root "<=0; i--) { poles[i]=rtnewt(poly,i+1,lower,upper,tol); if (poles[i] == 0.0) { std::cout<<"Failure to converge on pole "<=0; i--) f = f*x + poly[i]; return f; } // Evaluate the differential of the polynomial bigfloat AlgRemez::polyDiff(bigfloat x, bigfloat *poly, long size) { bigfloat df = (bigfloat)size*poly[size]; for (int i=size-1; i>0; i--) df = df*x + (bigfloat)i*poly[i]; return df; } // Newton's method to calculate roots bigfloat AlgRemez::rtnewt(bigfloat *poly, long i, bigfloat x1, bigfloat x2, bigfloat xacc) { int j; bigfloat df, dx, f, rtn; rtn=(bigfloat)0.5*(x1+x2); for (j=1; j<=JMAX;j++) { f = polyEval(rtn, poly, i); df = polyDiff(rtn, poly, i); dx = f/df; rtn -= dx; if (abs_bf(dx) < xacc) return rtn; } std::cout<<"Maximum number of iterations exceeded in rtnewt\n"; return 0.0; } // Evaluate the partial fraction expansion of the rational function // with res roots and poles poles. Result is overwritten on input // arrays. void AlgRemez::pfe(bigfloat *res, bigfloat *poles, bigfloat norm) { int i,j,small; bigfloat temp; bigfloat *numerator = new bigfloat[n]; bigfloat *denominator = new bigfloat[d]; // Construct the polynomials explicitly for (i=1; i=0; i--) { numerator[i] *= -res[j]; denominator[i] *= -poles[j]; if (i>0) { numerator[i] += numerator[i-1]; denominator[i] += denominator[i-1]; } } } // Convert to proper fraction form. // Fraction is now in the form 1 + n/d, where O(n)+1=O(d) for (i=0; i=0; j--) { res[i] = poles[i]*res[i]+numerator[j]; } for (j=n-1; j>=0; j--) { if (i!=j) res[i] /= poles[i]-poles[j]; } res[i] *= norm; } // res now holds the residues j = 0; for (i=0; i