#ifndef ZERNIKE_C #define ZERNIKE_C /** @author Richard J. Mathar @since 2007-03-23 Richard J. Mathar homepage. */ #include #include #include #include #include #include "Zernike.h" using namespace std ; /** Ctor. * @param[in] nodaln the nodal quantum number * @since 2007-03-23 */ Zernike::Zernike(const int nodaln) : n(nodaln) { /* coefficients are only non-zero for even n-m. So we loop only * over these. */ for(int m= n %2 ; m <=n ; m += 2) { const int nplusmhalf = (n+m)/2 ; const int nminusmhalf = (n-m)/2 ; /* start with the coefficient at s=(n-m)/2, corresponding to r^m and x^m. * This is the coefficient in front of \f$r^{n-2s}\f$ in eq (2) of Noll's paper. * (n-s)! = [(n+m)/2]!. s!=[(n-m)/2]!. [(n+m)/2-s]!=m!. [(n-m)/2-s]!=1 */ double c = ( nminusmhalf % 2) ? -1. : 1. ; c *= binom(nplusmhalf,m) ; /* so far, c is the polynomial coefficient for \f$r\f$. Move over to the * coefficient for \f$x=r/2\f$. */ c *= pow(2.,m) ; /* We are interested not in Noll's \f$R_n^m\f$ but in the normalized \f$2\surd()R_n^m\f$ */ c *= 2.*sqrt((double)(2*n+2)) ; vector Rm ; /* we arrange for the lowest power x^m coming first, the power x^n coming last. */ for(int s= nminusmhalf ; s >= 0 ; s--) { Rm.push_back(c) ; /* Update: (-)^s brings a minus sign. Transition (n-s)!->(n-s+1)! implied by s->s-1 brings multiplication * by (n-s+1). Transition 1/s!->1/(s-1)! brings multiplication by s. Transition * 1/[(n+m)/2-s]! -> 1/[..-s+1]! brings division through (n+m)/2-s+1. Transition * 1/[(n-m)/2-s]! -> 1/[..-s+1]! brings division through (n-m)/2-s+1. The modification * to the x powers means (2x)^(n-2s)-> (2x)^(n-2s+2) brings a factor of 4. */ c *= -4*(n-s+1)*s/(nplusmhalf-s+1.)/(nminusmhalf-s+1.) ; } /* debugging * cout << __FILE__ << " " << __LINE__ << " Z(n,m) " << nodaln << " " << m << endl ; */ coef.push_back(Rm); } } /** The value at some distance from the origin. * @param[in] x the distance from the origin in the range 0 to 1/2. * @param[in] m the azimuthal quantum number between 0 and the nodal number. * @return the value at that distance in the unit normalization. * @since 2007-03-23 */ double Zernike::at(const double x, const int m) const { /* debugging * cout << __FILE__ << " " << __LINE__ << " Z(n,m) query " << n << " " << m << endl ; */ /* request outside the valid distance from the origin or with invalid azimuthal number * are silently answered with a value of zero. */ if ( x>=0. && x <=0.5 && m>= 0 && m <=n && (n-m) %2 ==0) { double resul =0. ; /* sum the coefficients times x^t, t=m..n in steps of 2, coefficients in coef[m/2][(t-m)/2]. * Use some Horner scheme to do the multiplications. * resul = coef[][0]*x^m+coef[][1]*x^(m+2)+...+coef[][(n-m)/2]*x^n. * = x^m { [][0]+coef[][1]*x^2+...+coef[][(n-m)/2]*x^(n-m) } */ const int mhalf = m/2 ; const double x2 = x*x ; for(int nminmhalf=(n-m)/2; nminmhalf>=0 ; nminmhalf--) { resul *= x2 ; resul += coef[mhalf][nminmhalf] ; } /* the residual x^m is accumulated also */ return resul* pow(x,m) ; } else return 0. ; } #if 0 /** The value at some distance from the origin. * @param[in] x the distance from the origin in the range 0 to 1/2. * @param[in] n the nodal quantum number * @param[in] m the azimuthal quantum number between 0 and the nodal number. * @return the value at that distance in the unit normalization. * The functionality is the same as with a Zernike::Zernike(n) followed by Zernike::at(x,m) . * @since 2007-03-23 */ double Zernike::at(const double x, const int n, const int m) { /* request outside the valid distance from the origin or with invalid azimuthal number * are silently answered with a value of zero. */ if ( x>=0. && x <=0.5 && m>= 0 && m <=n && (n-m) %2 ==0) { const double r = 2.*x ; const double r2 = r*r ; const int nplusmhalf = (n+m)/2 ; const int nminusmhalf = (n-m)/2 ; /* start with the coefficient at s=0, corresponding to r^n. * This is the coefficient in front of \f$r^{n-2s}\f$ in eq (2) of Noll's paper. * (n-s)! = n!. s!=1. [(n+m)/2-s]!=[(n+m)/2]!. [(n-m)/2-s]!=[(n-m)/2]! */ double c = binom(n,nminusmhalf) ; double resul =0. ; /* sum the coefficients times r^s, s=0..(n-m)/2 in steps of 2. * Use some Horner scheme to do the multiplications. */ for(int s=0 ; s <= nminusmhalf ; s++) { resul *= r2 ; resul += c ; /* Update: (-)^s brings a minus sign. Transition (n-s)!->(n-s-1)! implied by s->s+1 brings division * through (n-s). Transition 1/s!->1/(s+1)! brings division through s. Transition * 1/[(n+m)/2-s]! -> 1/[..-s-1]! brings multiplication by (n+m)/2-s. Transition * 1/[(n-m)/2-s]! -> 1/[..-s-1]! brings multiplication by (n-m)/2-s. */ c *= -(nplusmhalf-s)*(nminusmhalf-s)/((double)(n-s)*s) ; } /* the residual r^m is accumulated also. * We are interested not in Noll's \f$R_n^m\f$ but in the normalized \f$2\surd()R_n^m\f$ */ return resul*2.*M_SQRT2*sqrt((double)(n+1))*pow(r,m) ; } else return 0. ; } #endif /* 0 */ /** The coefficient of a specified power of x. * @param[in] m the azimuthal quantum number between 0 and the nodal number. * @param[in] i the power of \f$x\f$ * @return the coefficient in front of \f$x^i\f$. * @since 2007-03-23 */ double Zernike::powcoef(const int m, const int i) const { /* debugging * cout << __FILE__ << " " << __LINE__ << " Z(n,m) query " << n << " " << m << " x^" << i << endl ; */ /* request outside the valid distance from the origin or with invalid azimuthal number * are silently answered with a value of zero. */ if ( m>= 0 && m <=n && (n-m) %2 ==0 && i>=m && i<=n && (i-m) %2 == 0) { /* the coefficients in front of x^i, i=m..n in steps of 2, are in coef[m/2][(i-m)/2]. */ return coef[m/2][(i-m)/2] ; } else return 0. ; } /** The integral from xlo to xhi * @param[in] m the azimuthal quantum number between 0 and the nodal number. * @param[in] xlo the lower limit * @param[in] xhi the upper limit. Actually this will also return the correct * value of the upper limit is smaller than the lower one. * @return the \f$\int_x Z dx\f$. Note that this does not involve any additional * power of \f$x\f$. * @warn there is no check built in here that the two limits are in the interval * between 0 and 1/2. * @since 2007-03-28 */ double Zernike::intgrl(const int m, const double xlo, const double xhi) const { /* request outside the valid distance from the origin or with invalid azimuthal number * are silently answered with a value of zero. */ if ( m>= 0 && m <=n && (n-m) %2 ==0 ) { double resul = 0. ; /* the coefficients in front of x^i, i=m..n in steps of 2, are in coef[m/2][(i-m)/2]. */ for(int i=m; i <= n ; i+= 2) resul += powcoef(m,i)*( pow(xhi,i+1)-pow(xlo,i+1))/(i+1) ; return resul ; } else return 0. ; } #if 0 /** The coefficient of a specified power of x. * @param[in] n the nodal quantum number * @param[in] m the azimuthal quantum number between 0 and n * @param[in] i the power of \f$x\f$ * @return the coefficient in front of \f$x^i\f$. * @since 2007-03-23 * The functionality is the same as with a Zernike::Zernike(n) followed by Zernike::powcoef(m,i) . */ double Zernike::powcoef(const int n, const int m, const int i) { /* request outside the valid distance from the origin or with invalid azimuthal number * are silently answered with a value of zero. */ if ( m>= 0 && m <=n && (n-m) %2 ==0 && i>=m && i<=n && (i-m) %2 == 0) { const int s = (n-i)/2 ; const int nplusmhalf = (n+m)/2 ; const int nminusmhalf = (n-m)/2 ; /* Compute first coefficient for R_n^m(r=2x). * Noll's equation (2). Start with factor (-)^s */ double resul = ( s % 2) ? -1. : 1. ; /* rewrite (n-s)!/s! as binomial(n-s,s)*(n-2s)!. Then rewrite * (n-2s)!/ (nplusmhalf-s)! / (nminusmhalf-s)! as binomial(n-2s,nminusmhalf-s). * Note that this could also be nplusmhalf-s, but since there is a loop over the second * parameter in KarhLoeve::binom(), we try to minimize it. Finally use that n-2s=i. */ resul *= binom(n-s,s) * binom(i,nminusmhalf-s) ; /* The transition from r=2x to x induces another 2^i */ resul *= pow(2.,i) ; /* * We are interested not in Noll's \f$R_n^m\f$ but in the normalized \f$2\surd()R_n^m\f$ */ return resul*2.*M_SQRT2*sqrt((double)(n+1)) ; } else return 0. ; } #endif /* 0 */ /** A overlap integral with another Zernike radial function. * @param[in] m the azimuthal quantum number between 0 and n * @param[in] oth the other radial function * @param[in] othm the azimuthal quantum number of the other radial function * @return the integral \f$\int_0^{1/2} Z(x)* othZ(x) dx\f$. * @since 2007-03-23 * @warn this is not the ordinary overlap which would have a factor \f$x\f$ extra * from the Jacobian of the polar coordinates and which would be loosely related * to the usual orthogonalization properties. */ double Zernike::Overl(const int m, const Zernike & oth, const int othm) const { double resul =0. ; for (int i= m; i<= n ; i += 2) for (int j= othm;j <= oth.n; j += 2) /* we use that the integral of x^i*x^j from 0 to 1/2 is * x^(i+j+1)/(i+j+1). */ resul += powcoef(m,i)* oth.powcoef(othm,j)/ ((i+j+1)*pow(2.,i+j+1)) ; return resul ; } /** The overlap integral of the derivative with another radial function. * @param[in] m the azimuthal quantum number between 0 and n * @param[in] oth the other radial function in the overlap integral * @param[in] othm the azimuthal quantum number of the other radial function * @return the value of \f$\int_0^{1/2}dx x [\partial_x this (x)] other(x)\f$. * @author Richard J. Mathar * @since 2007-03-23 * @warning unlike Overl(), this function here is not commutative in the two functions. */ double Zernike::gradOverl(const int m, const Zernike & oth, const int othm) const { double resul =0. ; for (int i= m; i<= n ; i += 2) for (int j= othm;j <= oth.n; j += 2) /* we use that the integral of x d_x(x^i)*x^j from 0 to 1/2 is * i*x^(i+j+1)/(i+j+1). The only difference to Overl() is the * additional factor of i from the derivative of the first factor. */ resul += i*powcoef(m,i)* oth.powcoef(othm,j)/ ((i+j+1)*pow(2.,i+j+1)) ; return resul ; } /** Ctor. * There are no parameters and we grow the internal set of basis functions as needed, * which means as requested through one of the query functions. * @since 2007-03-23 */ ZernikeBase::ZernikeBase() { } vector ZernikeBase::Z ; /** Enlarge the basis set on request. * @param[in] n the minimum nodal quantum number * @since 2007-03-23 */ void ZernikeBase::growto(const int n) { /* if n is larger than the components created so far, enlarge the basis set */ while( n >= Z.size() ) { /* debugging * cout << __FILE__ << " " << __LINE__ << " Zernike::growto " << n << endl ; */ Zernike Zn(Z.size()) ; Z.push_back(Zn) ; } } /** The value at some distance from the origin. * @param[in] x the distance from the origin in the range 0 to 1/2. * @param[in] n the nodal quantum number * @param[in] m the azimuthal quantum number between 0 and the nodal number. * @return the value at that distance in the unit normalization. * The functionality is the same as with a Zernike::Zernike(n) followed by Zernike::at(x,m) . * @since 2007-03-23 */ double ZernikeBase::at(const double x, const int n, const int m) { /* debugging * cout << __FILE__ << " " << __LINE__ << " ZBas(n,m) query " << n << " " << m << endl ; */ /* if n is larger than the components created so far, enlarge the basis set */ growto(n) ; /* the static version which doesn't use the pre-computed values. * return Z[n].at(x,n,m) ; */ /* debugging * cout << __FILE__ << " " << __LINE__ << " ZBas(n,m) query " << x << " " << n << " " << m << " " << Z[n].at(x,m) << endl ; */ return Z[n].at(x,m) ; } /** The value at some distance from the origin. * @param[in] x the distance from the origin in the range 0 to 1/2. * @param[in] c the expansion coefficients in the condensed format. * The condensed format is the one with all coefficiens of zero value omitted. * @param[in] m the azimuthal quantum number between 0 and the nodal number. * @return the value at that distance in the unit normalization. * @since 2007-03-23 */ double ZernikeBase::at(const double x, const vector & coe, const int m) { double resul=0. ; for(int c=0 ; c < coe.size() ; c++) { /* debugging * cout << __FILE__ << " " << __LINE__ << " " << c << " " << coe[c] << " " << at(x,m+2*c,m) << endl ; */ resul += coe[c]* at(x,m+2*c,m) ; } return resul ; } /** The coefficient of a specified power of x. * @param[in] n the nodal quantum number * @param[in] m the azimuthal quantum number between 0 and n * @param[in] i the power of \f$x\f$ * @return the coefficient in front of \f$x^i\f$. * @since 2007-03-23 * The functionality is the same as with a Zernike::Zernike(n) followed by Zernike::powcoef(m,i) . */ double ZernikeBase::powcoef(const int n, const int m, const int i) { /* if n is larger than the components created so far, enlarge the basis set */ growto(n) ; /* the static version which doesn't use the pre-computed values. * return Z[n].powcoef(n,m,i) ; */ return Z[n].powcoef(m,i) ; } /** The coefficient of a specified power of x. * @param[in] n the nodal quantum number * @param[in] m the azimuthal quantum number between 0 and n * @param[in] xlo the lower limit of the integral * @param[in] xhi the upper limit of the integral * @return the \f$\int Z(n,m)\f$ between the two limits. * @since 2007-03-28 * The functionality is the same as with a Zernike::Zernike(n) followed by Zernike::intgrl(m,xlo,xhi) . */ double ZernikeBase::intgrl(const int n, const int m, const double xlo, const double xhi) { /* if n is larger than the components created so far, enlarge the basis set */ growto(n) ; return Z[n].intgrl(m,xlo,xhi) ; } /** A overlap integral between two Zernike radial functions. * @param[in] n the nodal parameter of the first function * @param[in] m the azimuthal quantum number of the first function * @param[in] nprime the nodal parameter of the 2nd function * @param[in] mprime the azimuthal quantum number of the 2nd function * @return the integral \f$\int_0^{1/2} Z(x)* othZ(x) dx\f$. * @since 2007-03-23 * @warn this is not the ordinary overlap which would have a factor \f$x\f$ extra * from the Jacobian of the polar coordinates and which would be loosely related * to the usual orthogonalization properties. */ double ZernikeBase::Overl(const int n, const int m, const int nprime, const int mprime) { /* if n is larger than the components created so far, enlarge the basis set */ growto(n) ; growto(nprime) ; return Z[n].Overl(m, Z[nprime], mprime) ; } /** A overlap integral between two Zernike radial functions. * @param[in] m the azimuthal quantum number of the first function * @param[in] coe the expansion coefficients of the first function * @param[in] mprime the azimuthal quantum number of the 2nd function * @param[in] coeprime the expansion coefficients of the first function * @return the integral \f$\int_0^{1/2} Z(x)* othZ(x) dx\f$. * @since 2007-03-23 * @warn this is not the ordinary overlap which would have a factor \f$x\f$ extra * from the Jacobian of the polar coordinates and which would be loosely related * to the usual orthogonalization properties. * @todo there is room for speedup since the the Overl() is a symmetric function of the * two essential parameter pairs. Combining the coe and coeprime instead of running * the loop over all independent pairs might save a factor of 2. * */ double ZernikeBase::Overl(const int m, const vector & coe, const int mprime, const vector &coeprime) { double resul =0. ; for(int i = 0 ; i < coe.size(); i++) for(int j = 0 ; j < coeprime.size(); j++) resul += coe[i]*coeprime[j]*Overl(m+2*i,m,mprime+2*j,mprime) ; return resul ; } /** A overlap integral between two Zernike radial functions. * @param[in] n the nodal parameter of the first function * @param[in] m the azimuthal quantum number of the first function * @param[in] nprime the nodal parameter of the 2nd function * @param[in] mprime the azimuthal quantum number of the 2nd function * @return the integral \f$\int_0^{1/2} x [grad_x Z(x)]* othZ(x) dx\f$. * @since 2007-03-23 */ double ZernikeBase::gradOverl(const int n, const int m, const int nprime, const int mprime) { /* if n is larger than the components created so far, enlarge the basis set */ growto(n) ; growto(nprime) ; return Z[n].gradOverl(m, Z[nprime], mprime) ; } /** A overlap integral between two Zernike radial functions. * @param[in] m the azimuthal quantum number of the first function * @param[in] coe the expansion coefficients of the first function * @param[in] mprime the azimuthal quantum number of the 2nd function * @param[in] coeprime the expansion coefficients of the first function * @return the integral \f$\int_0^{1/2} x [grad_x Z(x)]* othZ(x) dx\f$. * @since 2007-03-23 */ double ZernikeBase::gradOverl(const int m, const vector & coe, const int mprime, const vector &coeprime) { double resul =0. ; for(int i = 0 ; i < coe.size(); i++) for(int j = 0 ; j < coeprime.size(); j++) resul += coe[i]*coeprime[j]*gradOverl(m+2*i,m,mprime+2*j,mprime) ; return resul ; } /** Binomial coefficients. * @param[in] n the ``numerator'' of the coefficient. * @param[in] m the ``denominator'' of the coefficient. * This is is limited to the range 0,1,2,... * @return \f$x \choose l\f$ * @since 2007-03-23 */ double Zernike::binom(const int n, int m) { double resul =1. ; /* optimize with the formula C(n,m)=C(n,n-m) to minimize the loop count. */ if ( n < 2*m) m = n-m ; for(int c=1; c <=m ; c++) resul *= (n+1.-c)/c ; return resul ; } #endif /* ZERNIKE_C */