#ifndef KARHLOE_C
#define KARHLOE_C
/**
@author Richard J. Mathar
@since 2005-12-06
Richard J. Mathar homepage.
*/
#include
#include
#include
#include
#include
#include "KarhLoe.h"
extern "C" {
/** The clapack header file \c clapack.h.
* This is obtained from www.netlib.org/clapack.
*/
#include
#ifdef HAVE_GSL
#include
#endif
}
using namespace std ;
/** Print the Gamma coupling matrix for pupil resizes, if defined.
*/
#define KARHLOE_SHOW_RESIZE 2
/** Type 1 without lambda in the Gamma definition , Type 2 with
*/
#define KARHLOE_SHOW_TYPE 1
/** Macro to transpose matrix entries from the F77 to the C domain.
* The parameters are a row index (in the F77 program, but 0-based), a column index
* (in the F77 program, but 0-based), and the number of rows/columns of the square matrix.
* @since 2007-03-09
*/
#define KARHLOE_F77_FLAT(f77row,f77col,matdim) ((matdim)*(f77col)+(f77row))
/** \f$ 3.44=6.88/2 = (24/5*\Gamma(6/5))^{5/6}\f$.
* \latexonly
* See \cite[p 1372]{FriedJOSA56_1372}
* \endlatexonly
*/
#define FRIED_CONSTANT 3.441938591
/** The square root of Pi, \f$\sqrt\pi\f$.
*/
#define M_SQRT_PI 1.77245385090551602729816748
/** Two to the power of 11/6, \f$2^{11/6}\f$.
*/
#define M_2_POW_11_6 3.56359487256135721896090482236
/** Ctor.
* @param maxthetord
*/
KarhLoeve::KarhLoeve(const int maxthetord) : ord(maxthetord)
{
}
#ifdef KARHLOE_RADIAL_POLYN
/** Solve the KL radial equation to generate the eigenvalues and eigenfunctions.
* The symmetrized eigenvectors \f$\tilde K_p^q(x)\equiv \sqrt{x}K_p^q(x)\f$
* are used for the intermediate representation, represented by the variable \c Kpqsymm .
* The support mesh contains N basis functions \f$x^0\f$ up to \f$x^{N-1}\f$.
* The symmetrized kernel is \f$R_q(x,x')\equiv x'\tilde R_q(x,x')\f$.
* The KL equation is \f$ \int_{x'=0}^{1/2}\sqrt{xx'}\tilde R(x,x') \tilde K_p^q(x')dx'=\lambda_{pq}^2\tilde K_p^q(x)\f$.
* @param[in] q the azimuthal quantum number of the integral kernel
* @param[out] K the list of \c N eigenvectors (radial functions).
* \f$\int_{x=0}^{1/2}xK^2(x)dx=1=\sum_0^{N-1}x K^2(N)/(2N)\f$
* @param[out] lambda the list of \c N squared eigenvalues \f$\lambda_{pq}^2\f$
* @param[in] N the number of points in the interval \f$[0,1/2]\f$ on which the
* integral kernel and eigenfunctions are discretized.
*/
void KarhLoeve::Kpq(const int q, double *K, double *lambda, const int N, ZernikeBase & zbase)
{
/** matr contains the N by N matrix of the integral kernel.
*/
double *matr = new double[N*N] ;
for(int i=0; i< N; i++)
{
for(int j=0; j<=i ; j++)
{
/** The values of \f$\tilde R_q(x,x')\f$ are collected by a
* call to S(). The implicit representation is x^(1/2+q) times a polynomial of order N.
*/
matr[i+j*N]=matr[j+N*i] = -FRIED_CONSTANT*2*M_PI*binom(q-11./6.,q)*S(i,j,q,zbase) ;
}
}
printf("Matrix q= %d N=%d\n",q,N) ;
for(int i=0; i< N; i++)
{
for(int j=0; j<=i ; j++)
printf("%.4e ",matr[i+j*N]) ;
printf("\n") ;
}
/** Some intermediate variables are set up since we will be using the
* standard LAPACK Fortran interface.
*/
char jobz[2] = "V" ;
char uplo[2] = "U" ;
integer lda = N ;
integer lwork = 3*N ;
integer info ;
double *work = new double[lwork] ;
/** The finite element version of the integral equation with symmetric matrix
* is solved by a call to LAPACK's dsyev().
*/
dsyev_(jobz,uplo,&lda,matr,&lda,lambda,work,&lwork,&info) ;
if ( info)
cerr << __LINE__ << " dsyev info " << info << endl ;
else
{
/* the result represents tilde K, but since the sqrt(x) was only
* implicit, silently dropping this sqrt(x) does not initiate action.
* The eigenvectors represent the prefactors of x^(q+i), i=0,1,2..N-1
* and are normalized to a sum of squares equal unity, and we want
* to have an integral over x*K^2=1 (x=0..1/2) =
* int x * sum_j,s k_j k_s x^{j+q}x^{s+q} = sum_j,s.
*/
#if 0
// re-normalize such that they are positive at x=1/2 : TODO
for(int e=0; e < N ;e++)
{
if( matr[N-1+N*e] < 0.) // last value of the eigenvector
for(int j=0; j < N ; j++)
matr[j+N*e] *= -1 ;
}
#endif
/* save the eigenvectors into return area
* They are now normalized to sum_{0..N} ( x*K^2* Delta(x)=1 where
* Delta(x)=0.5/N.
*/
memcpy(K,matr,N*N*sizeof(double)) ;
}
delete work ;
delete matr ;
}
#else /* KARHLOE_RADIAL_POLYN */
/** Solve the KL radial equation to generate the eigenvalues and eigenfunctions.
* The symmetrized eigenvectors \f$\tilde K_p^q(x)\equiv \sqrt{x}K_p^q(x)\f$
* are used for the intermediate representation, represented by the variable \c Kpqsymm .
* The support mesh contains N points \f$x=(i+1/2)/(2N)\f$, \f$i=0\ldots N-1\f$, \f$0\le x\le 1/2\f$.
* The symmetrized kernel is \f$R_q(x,x')\equiv x'\tilde R_q(x,x')\f$.
* The KL equation is \f$ \int_{x'=0}^{1/2}\sqrt{xx'}\tilde R(x,x') \tilde K_p^q(x')dx'=\lambda_{pq}^2\tilde K_p^q(x)\f$.
* @param[in] q the azimuthal quantum number of the integral kernel
* @param[out] K the list of \c N eigenvectors (radial functions).
* \f$\int_{x=0}^{1/2}xK^2(x)dx=1=\sum_0^{N-1}x K^2(N)/(2N)\f$
* @param[out] lambda the list of \c N squared eigenvalues \f$\lambda_{pq}^2\f$
* @param[in] N the number of points in the interval \f$[0,1/2]\f$ on which the
* integral kernel and eigenfunctions are discretized.
*/
void KarhLoeve::Kpq(const int q, double *K, double *lambda, const int N)
{
/** matr contains the N by N matrix of the integral kernel.
*/
double *matr = new double[N*N] ;
for(int i=0; i< N; i++)
{
/** The abscissa points on the radial axis are N equidistant points
* between 0 and 1/2.
*/
const double x=(i+0.5)/(2.*N) ;
for(int j=0; j<=i ; j++)
{
double xprime=(j+0.5)/(2.*N) ;
/** The values of \f$\tilde R_q(x,x')\f$ are collected by a
* call to Rsymm(). A factor \f$1/(2N)\f$ is introduced;
* in this finite element representation it represents the stride on the
* abscissa in the Riemann sense of integration.
*/
matr[i+j*N]=matr[j+N*i] = sqrt(x*xprime)*Rsymm(x,xprime,q)/(2.*N) ;
}
}
/** Some intermediate variables are set up since we will be using the
* standard LAPACK Fortran interface.
*/
char jobz[2] = "V" ;
char uplo[2] = "U" ;
integer lda = N ;
integer lwork = 3*N ;
integer info ;
double *work = new double[lwork] ;
/** The finite element version of the integral equation with symmetric matrix
* is solved by a call to LAPACK's dsyev().
*/
dsyev_(jobz,uplo,&lda,matr,&lda,lambda,work,&lwork,&info) ;
if ( info)
cerr << __LINE__ << " dsyev info " << info << endl ;
else
{
#if 0
/* The piston mode never appears here... */
for(int i=N-1 ; i ; i--)
{
if( lambda[i] >= 0.)
{
cout << "# p=" << N-1-i << " q=" << q << " lam^2= "<< lambda[i] << endl ;
}
else
cerr << "# " << __LINE__ << " lambda " << i << " " << lambda[i] << endl ;
}
#endif
// re-normalize such that they are positive at x=1/2
for(int e=0; e < N ;e++)
{
if( matr[N-1+N*e] < 0.) // last value of the eigenvector
for(int j=0; j < N ; j++)
matr[j+N*e] *= -1 ;
}
// the dsyev normalization: the sum of these squares is 1 now, still incorporating the symmetrizer,
// equivalent to sum(j=0..N) x*Kpq(x)^2=1.
// symmetrize such that integral(x=0..1/2)x*K^2= 1 according to eq 43), = sum(j=0..N-1) x*Kpq(x)^2/(2N)
// since the step width is 1/(2N) in the Riemann sense for an upper limit of 1/2.
const double nor = 2.*N ; // this means multiply K^2 by 2N
// and implies the angular functions are cos(q*theta)/sqrt(pi),
// sin(q*theta)/sqrt(pi) for q>=1, or 1/sqrt(2pi) for q=0.
/* remove the symmetrizer factor, and multiply with the the normalization to have the correct 2N or N sum
* They are now normalized to sum_{0..N} ( x*K^2* Delta(x)=1 where
* Delta(x)=0.5/N.
*/
for(int e=0; e < N ;e++)
for(int j=0; j < N ; j++)
{
const double x=(j+0.5)/nor ;
matr[j+N*e] /= sqrt(x/nor) ;
}
#if 0
/* debugging */
cout << "q=" << q << " f matrix lambda^2 " << endl ;
for(int e=0; e < N ;e++)
{
if ( lambda[e] > 1.e-3 )
{
cout << "# lambda^2 " << lambda[e] << endl ;
for(int j=0; j < N ; j++)
{
cout << e << " " << (j+0.5)/nor << " " << matr[j+N*e] << endl ;
}
cout << endl << endl ;
}
}
#endif
#ifdef KARHLOE_RADIAL_PFIT
double *matrXp = new double[N*N] ;
#if 0
for(int e=0; e < N ;e++)
for(int j=0; j < N ; j++)
{
const double x=(j+0.5)/nor ;
//matrXp[j+N*e] = pow(x,0.5*q+2*e) ;
//matrXp[j+N*e] = pow(x,q+2*e) ;
/* Zernike decomposition */
matrXp[j+N*e] = Kq::zbase.at(x,q+2*e,q) ;
}
/* debugging */
cout << "q=" << q << " x power matrix " << endl ;
for(int e=0; e < N ;e++)
{
for(int j=0; j < N ; j++)
{
cout << "x_" << j << " ^ " << q+2*e << " " << matrXp[j+N*e] << endl ;
}
}
#endif /* 0 */
#if 0
integer *ipiv = new integer[N] ;
/** The system of linear equations
* is solved by a call to LAPACK's dgesv().
*/
dgesv_(&lda,&lda,matrXp,&lda,ipiv,matr,&lda,&info) ;
if ( info)
cerr << __LINE__ << " dgesv info " << info << endl ;
delete ipiv ;
/* save the eigenvectors into return area
*/
memcpy(K,matr,N*N*sizeof(double)) ;
#else /* 0 */
/* for the radial solutions enumebered by e, expand them individually
* into the lowest N applicable Zernike polynomials.
*/
for(int e=0; e < N ;e++)
{
/* just as a check of numerical fitness, accumulate the squared sum of
* the expansion coefficients, which ought add to unity because the
* Zernike polynomials as well as the KL eigenfunctions habe been normoalized
* to unite with the weight x.
*/
double rintg2=0. ;
for(int zc = 0 ; zc < N ; zc++)
{
double rintg =0.;
for(int j=0; j < N ; j++)
{
const double x=(j+0.5)/nor ;
/* a strictly Rieman version that ignores the wiggles in the
* Zernike polynomials and is therefore worse when we compare the sum of the
* squared expansion coefficients below with the precise 1.
* rintg += x* Kq::zbase.at(x,q+2*zc,q) *matr[j+N*e]/nor ;
*/
/* Use a step function for x*KL(x), which is the accurate representation
* of the normalization used for the discrete solution, but within this
* step use a more accurate integrated version of the Zernike polynomials
*/
rintg += x* Kq::zbase.intgrl(q+2*zc,q,j/nor,(j+1)/nor)*matr[j+N*e] ;
}
/* debugging
if ( lambda[e] > 1.e-4 )
cout << "store q=" << q << " lambda^2 " << lambda[e] << " coef " << zc << " " << rintg << endl ;
*/
rintg2 += rintg*rintg ;
matrXp[zc+N*e] = rintg ;
}
/* delete strong wiggles and bad fits */
if ( rintg2 > 4.5)
{
cout << "q=" << q << " lambda^2 " << lambda[e] << " norm " << rintg2 << endl ;
for(int zc =0 ; zc < N ;zc++)
matrXp[zc+N*e] = 0. ;
}
/* debugging
* cout << "q=" << q << " lambda^2 " << lambda[e] << " norm " << rintg2 << endl ;
*/
}
#if 0
/* debugging */
cout << "q=" << q << " reprod " << endl ;
for(int e=0; e < N ;e++)
{
if ( lambda[e] > 1.e-3 )
{
cout << "# lambda^2 " << lambda[e] << endl ;
for(int j=0; j < N ; j++)
{
const double x=(j+0.5)/nor ;
double fat =0. ;
for(int i=0; i < N ;i++)
fat += matrXp[i+N*e]*Kq::zbase.at(x,q+2*i,q) ;
cout << x << " " << fat << endl ;
}
cout << endl << endl ;
}
}
#endif
/* save the eigenvectors into return area
*/
memcpy(K,matrXp,N*N*sizeof(double)) ;
#endif /* 0 */
#if 0
/* debugging */
cout << "Zern coeff exp q=" << q << endl ;
for(int e=0; e < N ;e++)
{
cout << "radial # " << e << " lambda^2 " << lambda[e] << endl ;
double fat =0. ; // check sumsquared
for(int j=0; j < N ; j++)
{
cout << "x^" << q+2*j << " " << matr[j+N*e] << endl ;
fat += pow(matr[j+N*e],2) ;
}
cout << "radial # " << e << " coef^2 " << fat << endl ;
/*
for(int j=0; j < N ; j++)
{
const double x=(j+0.5)/nor ;
double fat =0. ;
for(int i=0; i < N ;i++)
fat += matr[i+N*e]*Kq::zbase.at(x,q+2*i,q) ;
cout << x << " " << fat << endl ;
}
*/
}
#endif /* 0 */
delete matrXp ;
#else /* KARHLOE_RADIAL_PFIT */
/* save the eigenvectors into return area
*/
memcpy(K,matr,N*N*sizeof(double)) ;
#endif /* KARHLOE_RADIAL_PFIT */
}
delete work ;
delete matr ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifndef KARHLOE_RADIAL_POLYN
/*
B_0, B_1 and B_2 of equation (40)-(42).
Digits := 26 ;
assume(x>0) ;
ikern := (x^2+x2p^2-2*x*x2p*cost)^(5/6) ;
las := taylor(ikern,cost=0,20) ;
las := convert(las,polynom) ;
cost := cos(t) ;
las := int(las,t=0..2*Pi) ;
b := las ;
las := las*x2p ;
las := int(las,x2p=0..1/2) ;
las := 3.44*4*las/Pi ;
las := expand(las) ;
las := simplify(las,power) ;
las := simplify(las,radical) ;
evalf(las) ;
# instead from x=0..1/2 and x2p=0..1/2 only up to diagonal..
b := 2*b*x*x2p ;
b := int(b,x=0..x2p) ;
b := int(b,x2p=0..1/2) ;
evalf(b*3.44*4/Pi*8) ;
accuracy about 0.002 for ord=8
accuracy about 0.0003 for ord=20
*/
double KarhLoeve::B(const int n, const double x)
{
switch(n)
{
double resul, aux ;
case 0:
return FRIED_CONSTANT*pow(x,5./3.) ;
break ;
case 1:
/* int(x''=0..1/2) dx'' (x^2+x''^2)^(5/6) int(0..2pi) dtehta [1-2xx''/()]^(5/6)
=int(x''=0..1/2) dx'' (x^2+x''^2)^(5/6) int(0..2pi) dtehta sum(l=0,2,4,..)(5/6 chose l)[-2xx''/()]^l Int(..)
=sum(l=0,2,4,...) (5/6 chose l) (-2x)^l int(x''=0..1/2) x''^(l+1)/(x^2+x''^2)^(l-5/6) cosInt(l)
where with substitution x''^2=y
int(x''=0..1/2) x''^(l+1)/(x^2+x''^2)^(l-5/6)dx''=(1/2)int(y=0..1/4) x''^l/(x^2+y)^(l-5/6)
= (1/2)int(y=0..1/4) y^(l/2)/(x^2+y)^(l-5/6) and with substit z=x^2+y
= (1/2)int(z=x^2..x^2+1/4) (z-x^2)^(l/2)/z^(l-5/6) and another binomial series
= (1/2)int(z=x^2..x^2+1/4) sum(m=0..l/2) (l/2 chose m) z^(m-l+5/6) (-x^2)^(l/2-m)
= (1/2) sum(m=0..l/2) (l/2 chose m) (-x^2)^(l/2-m) [z^(m-l+11/6)/(m-l+11/6)]between x^2+0.25 and x^2
*/
resul=0. ;
for(int l=0; l <= ord ; l += 2)
{
double msum=0. ;
for(int m=0; m <= l/2; m++)
msum += binom(0.5*l,m)*pow(-x*x,l/2-m)
*(pow(x*x+0.25,m-l+11./6.)-pow(x*x,m-l+11./6.))/(m-l+11./6.) ;
resul += 0.5*msum*binom(5./6.,l)*pow(-2.*x,l)*cosInt(l);
}
return FRIED_CONSTANT*4./M_PI*resul ;
break ;
case 2:
return B2() ;
break ;
default:
cerr << __LINE__ << " n " << n << " not supported\n" ;
exit(EXIT_FAILURE) ;
}
}
#endif /* KARHLOE_RADIAL_POLYN */
/** Binomial coefficients.
* @param[in] x the ``numerator'' of the coefficient.
* This may be an arbitrary, positive or negative real number.
* @param[in] l the ``denominator'' of the coefficient.
* This is is limited to the range 0,1,2,...
* @return \f$x \choose l\f$
*/
double KarhLoeve::binom(double x, int l)
{
double resul =1. ;
for(int c=1; c <=l ; c++)
resul *= (x+1.-c)/c ;
return resul ;
}
#ifndef KARHLOE_RADIAL_POLYN
/** the integral over \f$\cos^l x\f$ over one period.
* @param[in] l the integer power of the integrand
* @return \f$\int_0^{2\pi}\cos^l xdx\f$.
*/
double KarhLoeve::cosInt(int l)
{
/** The value is zero for odd \f$l\f$.
*/
if( l %2 ) /* zero if l is odd=1,3,5,-1,-3,-5,.. */
return 0 ;
double resul ;
switch(l)
{
case 0:
/** The value is \f$2\pi\f$ for \f$l=0\f$.
*/
resul = 2.*M_PI ; // int(t=0..2*pi,1)=2*pi
break ;
default :
resul=M_PI ;
l /= 2 ;
/** In the general case of even \f$l\f$ we use
* \latexonly
* \cite[2.512.2]{GR}.
* \endlatexonly
*/
// (oldl-1)!!/2^newl/newl!*2*pi; no loop for oldl=2, newl=1
for(int c=2 ; c <= l; c++)
resul *= 0.5*(2.*c-1.)/(double)c ;
}
return resul ;
}
/** the integral over \f$\cos^l x\cos(qx)\f$ over one period.
* @param[in] l the integer power of the integrand
* @param[in] q the second integer of the integrand
* @return \f$\int_0^{2\pi}\cos^l x\cos (qx)dx\f$.
*/
double KarhLoeve::cosInt(const int l, int q)
{
q = abs(q) ;
switch(q)
{
double resul ;
case 0 :
/** the cases \f$q=0,1\f$ are forwarded to cosInt(const int l) .
*/
return cosInt(l) ;
break ;
case 1 :
return cosInt(l+1) ;
break ;
default :
/*
int (t=0..2*pi, cos^l t *cos(qt)); q=+-1, +-2,..GR 2.512.2.
with cos^lt cos(qt)=cos^(l-2)t cos^2t cos(qt)
= (1/2)cos^(l-2)t [1+cos(2t)] cos(qt)
= (1/2)cos^(l-2)t cos(qt) + (1/2) cos^(l-2)t cos(2t)cos(qt)
= (1/2)cos^(l-2)t cos(qt) + (1/4) cos^(l-2)t {cos[(2+q)t]+cos[(2-q)t]}
or via binomial expansion of cos^l t = [(e^i*t+e^-i*t/2]^l=sum(k=0..l)binom(l,k)..
*/
switch(l)
{
case 0 :
return 0. ;
break ;
case 1:
// cos(t)*cos(qt)=(1/2)[cos(1+q)t+cos(1-q)t] only result if 1+q=0 or 1-q=0: (1/2)*2*pi
return ( q == 1 ) ? M_PI : 0. ;
break ;
default:
resul=0. ;
// if ( l-q >=0 && l-q <= 2*l) superfluous the l-q<=2*l since -q<=l always for q>=0
if ( l-q >=0 )
{
if( (l-q) %2 == 0 ) // would meet 2k
resul += binom( (double)l, (l-q)/2 ) ;
}
//if ( l+q >=0 && l+q <= 2*l) superfluous the l+q>=0 since q>0 and l>0
if ( l+q <= 2*l)
{
if( (l+q) %2 == 0 ) // would meet 2k
resul += binom( (double)l, (l+q)/2 ) ;
}
return resul*M_PI/pow(2.,l) ;
}
}
}
#endif /* KARHLOE_RADIAL_POLYN */
#if 0
/**
* @param[in] j the common integer of the powers of the integral, non-negative integer.
* @param[in] q the azimuthal quantum number of the mode, non-negative
* @param[in] o half of the effective order of the hypergeometric function, non-negative
* @return the integral of \f$\sin^{j+1+2(q+o)}\alpha/\cos^{j+14/3}\alpha\f$
* integrated from \f$\alpha=0\f$ to \f$\alpha=\pi/4.\f$
* @since 2007-03-19
*/
double KarhLoeve::I2(const int j, const int q, const int o)
{
if ( j % 2 )
{
/* case of odd j, used with recursion
*/
if ( o > 0 )
{
double resul=0. ;
/* naive, non-recursive implementation of the binomial ..
*/
for(int l=0 ; l <= o ; l++)
if ( l % 2 )
resul -= binom(o,l)*I2(j,q+o+l,0) ;
else
resul += binom(o,l)*I2(j,q+o+l,0) ;
/* debugging
*/
cout << __FILE__ << " " << __LINE__ << " " << j << " " << q << " " << o << " I2 " << resul << endl ;
return pow(4.,o)*resul ;
}
else
{
if ( q > 0 )
{
return ((j+2*q)*I2(j,q-1,0)-pow(2.,11./6.-q))/(2*q-11./3.) ;
}
else
{
if ( j == 1)
/* here qo=0, int(0..pi/4) sin^2 alpha / cos^17/3 alpha
*/
return 0.492200573702716302791048473793 ;
else
return (M_2_POW_11_6-j*I2(j-2,0,0))/(j+11./3.) ;
}
}
}
else
{
#ifdef HAVE_GSL
/* 2F1(-j/2-q-o, -j/2-11/6+o;-5/6-j/2+o;x^2) at the upper limit x=1
* using
* @latexonly
* \cite[15.1.20]{AS}
* @endlatexonly
*/
/* debugging
* cout << __FILE__ << " " << __LINE__ << " gamma " << -5./6.-0.5*j+o << " "
* << 1.+0.5*j+q+o << " " << -5./6.+q+2*o << endl ;
*/
const double ulim = gsl_sf_gamma(-5./6.-0.5*j+o) *gsl_sf_gamma(1.+0.5*j+q+o) /gsl_sf_gamma(-5./6.+q+2*o);
/* debugging
* cout << __FILE__ << " " << __LINE__ << " ulim " << ulim << endl ;
* cout << __FILE__ << " " << __LINE__ << " 2f1 " << -0.5*j-q-o << " "
* << -11./6.-0.5*j+o << " " << -5./6.-0.5*j+o << endl ;
*/
/* 2F1(-j/2-q-o, -j/2-11/6+o;-5/6-j/2+o;x^2) at the lower limit x=1/sqrt(2).
* If the first parameter is zero, the GSL implementation returns an error, which we
* avoid by knowing that this case means 2F1()=1.
* If the first parameter is a negative integer smaller than -10 (larger in absolute terms than 10),
* the gsl interface will also report an error. If this is a negative integer >= -9, it will
* use hyperg_2F1_series(). See gsl_sf_hyperg_2F1_e() which is declared static in the C source and not
* available through the library interface.
*/
const int jhalf = j/2 ;
double bin = 1.0;
/* debuging
* double llim = (0.5*j+q+o > 0.2) ? gsl_sf_hyperg_2F1(-0.5*j-q-o,-11./6.-0.5*j+o,-5./6.-0.5*j+o,0.5) : 1.0 ;
*/
double llim = 1.0 ;
/* upper limit is jhalf+q+o but this contributes with a term of zero. So using < instead <= is sufficient
*/
for( int sigm = 1 ; sigm < jhalf+q+o; sigm++)
{
/* update the binomial coefficient, including the (-)^sigm
*/
bin *= (jhalf+q+o-sigm+1)/(-11./6.-jhalf+o+sigm) ;
llim += bin ;
}
llim /= pow(2.,-11./6.+q+2*o) ;
cout << __FILE__ << " " << __LINE__ << " llim " << llim << endl ;
cout << __FILE__ << " " << __LINE__ << " j " << j << " q " << q << " " << o << " I2 " << pow(4.,o)*(ulim-llim)/(-j-11./3.+2*o) << endl ;
return pow(4.,o)*(ulim-llim)/(-j-11./3.+2*o) ;
#else /* HAVE_GSL */
/* case of even j gives a finite sum
*/
const int jhalf = j/2 ;
/* binomial(j/2+q+o,sigm), initialized at sigm=0
*/
double bin = 1.0;
/* 1/2^(sigm+o-j/2-11/6), initialized at sigm=0
*/
double pow2 = pow(2.,jhalf-o+11./6.) ;
int sigm =0 ;
/* the resulting value of the integral, initialized at sigm=0
*/
double resul= (1.-pow2)/(2*o-j-11./3.) ;
for( sigm = 1 ; sigm <= jhalf+q+o; sigm++)
{
/* update the component 1/2^sigm in the power of 2
*/
pow2 /= 2. ;
/* update the binomial coefficient, including the (-)^sigm
*/
bin *= -(double)(jhalf+q+o+1-sigm)/sigm ;
resul += bin*(1.-pow2)/(2*(sigm+o) -j-11./3.) ;
}
/* debugging
* cout << __FILE__ << " " << __LINE__ << " j " << j << " q " << q << " " << o << " I2 " << resul << endl ;
*/
return pow(4.,o)*resul ;
#endif /* HAVE_GSL */
}
}
#endif /* 0 */
#ifdef KARHLOE_RADIAL_POLYN
/**
* @param[in] j power of the basis of the bra
* @param[in] s power of the basis of the ket
* @param[in] q azimuthal parameter of the KL mode
* @return the integral of \f$\int_0^{2\pi)d\theta \int_0^{\pi/4}d\alpha \sin^{1+q}(2\alpha)
* [1-\sin(2\alpha)\cos\theta]^{5/6}cos(q\theta)/(1+2q+j+s+14/3)/(2\cos\alpha)^{1+j+q+2q+14/3}\f$
* @since 2007-03-26
* @warning the factor -3.44 or 2pi or (-5/6)_q/q! is not yet in here.
* @warning the overlap integral is for the symmetrized kernel.
* @warning the overlap integral is incomplete for q=0.
* @warning the index offset of the power basis is already worked into the response.
* The parameter j might already have been reduced by q.
*/
double KarhLoeve::Ialpha(int j, int s, const int q)
{
if ( j < s )
{
const int tmp=j ; j=s ; s=tmp ;
}
/* store[q][j][s] */
static vector< vector< vector > > store ;
static int oldq = -1 ;
static vector f21store ;
if ( oldq != q )
f21store.clear() ;
/* debuging
* cout << __FILE__ << " " << __LINE__ << " " << j << " " << s << " " << q << endl ;
*/
/* supposed we need the value of q=2, stored in store[2], we fill store[0] and store[1] if needed,
* at the end of which we'll have store.size()=q=2.
*/
while ( q >= store.size())
{
vector< vector > tmp ;
store.push_back(tmp) ;
}
while ( j >= store[q].size() )
{
vector tmp ;
store[q].push_back(tmp) ;
}
/* Two possibilities here, s = store[q][j].size() or s < store[q][j].size().
* Add/compute store[q][j][s] if not yet done.
*/
while ( s >= store[q][j].size() )
{
const int sloc = store[q][j].size() ;
double resul=0. ;
/* alp=0 to pi/4 of
* [sin^j(alp)/cos^(s+14/3)(alp)+(j<->s)] sin^(1+2q)(alp) 2F1[q/2-5/12,q/2+1/12;q+1;sin^2(2alp)]
*/
int N= 4000 ;
#if 1
/* Simpson */
for(int i=0 ; i <= N ; i++)
{
/* weights 1,4,2,4,2,...,4,1 */
int wei = (i==0 || i==N)? 1 : ( (i %2)?4 :2 ) ;
const double alp = i*M_PI_4/(double)N ;
const double salp = sin(alp) ;
const double calp = cos(alp) ;
/* avoid domain error with sin(2*alpha)=1 at 2*alpha=pi/2 or alpha=pi/4.
*/
double f21 ;
if ( oldq == q)
f21 = f21store[i] ;
else
{
#if 0
f21 = ( i==N) ?
0.897686900876088227830096388406*pow(2.,q)* gsl_sf_fact(q) / gsl_sf_gamma(q+11./6.)
: gsl_sf_hyperg_2F1(0.5*q-5./12.,0.5*q+1./12.,q+1.,pow(2.*salp*calp,2.)) ;
#else
if ( i == N )
f21 = 0.897686900876088227830096388406*pow(2.,q)* gsl_sf_fact(q) / gsl_sf_gamma(q+11./6.) ;
else
{
const double a= 0.5*q-5./12. ;
const double b= 0.5*q+5./12. ;
const double c=q+1. ;
double z= 2.*salp*calp ; /* my z, argument is z^2 */
/* AS 15.3.20 */
//f21 = pow(1+z,-2.*a)*gsl_sf_hyperg_2F1(2.*a,c-1./2.,2.*c-1.,2.*z/(1.+z)) ;
/* AS 15.3.21 */
//f21 = pow(1-z*z,-a)*gsl_sf_hyperg_2F1(2.*a,2.*(c-a)-1.,c,0.5*(sqrt(1.-z*z)-1.)/sqrt(1.-z*z)) ;
/* AS 15.3.19 */
z = sqrt(1.-z*z) ;
f21 = pow(0.5+0.5*z,-2.*a)*gsl_sf_hyperg_2F1(2.*a,2.*a-c+1.,c,(1.-z)/(1.+z)) ;
}
#endif
f21store.push_back(f21) ;
}
resul += wei*( pow(salp,j)/pow(calp,j+14./3.)+pow(salp,sloc)/pow(calp,sloc+14./3.) )
*pow(salp,1+2*q) *f21 ;
}
/* multiply by Simpson weight, which is a third of the step width \f$\pi(4N)\f$ */
resul *= M_PI_4/(3*N) ;
#else
/* Simpson */
for(int i=0 ; i <= N ; i++)
{
/* weights 1,4,2,4,2,...,4,1 */
int wei = (i==0 || i==N)? 1 : ( (i %2)?4 :2 ) ;
const double x = M_SQRT1_2+i*(1.-M_SQRT1_2)/(double)N ;
const double salp = sqrt(1.-x*x) ;
const double calp = x;
/* avoid domain error with sin(2*alpha)=1 at 2*alpha=pi/2 or alpha=pi/4.
*/
double f21 ;
if ( oldq == q)
f21 = f21store[i] ;
else
{
f21 = ( i==0) ?
0.897686900876088227830096388406*pow(2.,q)* gsl_sf_fact(q) / gsl_sf_gamma(q+11./6.)
: gsl_sf_hyperg_2F1(0.5*q-5./12.,0.5*q+1./12.,q+1.,pow(2.*salp*calp,2.)) ;
f21store.push_back(f21) ;
}
resul += wei*( pow(salp,j)/pow(calp,j+14./3.)+pow(salp,sloc)/pow(calp,sloc+14./3.) )
*pow(salp,2*q) *f21 ;
}
/* multiply by Simpson weight, which is a third of the step width \f$\pi(4N)\f$ */
resul *= (1.-M_SQRT1_2)/(3*N) ;
#endif /* 0 */
const double qjs17 = 2*q+j+sloc+17./3. ;
store[q][j].push_back( resul/( qjs17* pow(2.,qjs17) ) ) ;
}
oldq =q ;
return store[q][j][s] ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#if 0
/**
* @param[in] j power of the basis of the bra
* @param[in] s power of the basis of the ket
* @param[in] q azimuthal parameter of the KL mode
* @return the integral of \f$\int_0^{2\pi)d\theta \int_0^{\pi/4}d\alpha \sin^{1+q}(2\alpha)
* [1-\sin(2\alpha)\cos\theta]^{5/6}cos(q\theta)/(1+2q+j+s+14/3)/(2\cos\alpha)^{1+j+q+2q+14/3}\f$
* @since 2007-03-19
* @warning the factor -3.44 or 2pi or (-5/6)_q/q! is not yet in here.
* @warning the overlap integral is for the symmetrized kernel.
* @warning the overlap integral is incomplete for q=0.
* @warning the index offset of the power basis is already worked into the response.
* The parameter j might already have been reduced by q.
*/
double KarhLoeve::I1(const int j, const int s, const int q, const int o)
{
const double qjs17 = 2*q+j+s+17./3. ;
return ( I2(j,q,o)+I2(s,q,o) )/( qjs17* pow(2.,qjs17) ) ;
}
#endif /* 0 */
#ifdef KARHLOE_RADIAL_POLYN
/**
* @param[in] j power of the basis of the bra
* @param[in] s power of the basis of the ket
* @param[in] q azimuthal parameter of the KL mode
* @return the integral of \f$\int_0^{2\pi)d\theta \int_0^{\pi/4}d\alpha \sin^{1+q}(2\alpha)
* [1-\sin(2\alpha)\cos\theta]^{5/6}cos(q\theta)/(1+2q+j+s+14/3)/(2\cos\alpha)^{1+j+q+2q+14/3}\f$
* @since 2007-03-19
* @warning the factor -3.44 or 2pi or (-5/6)_q/q! is not yet in here.
* @warning the overlap integral is for the symmetrized kernel.
* @warning the overlap integral is incomplete for q=0.
* @warning the index offset of the power basis is already worked into the response.
* The parameter j might already have been reduced by q.
*/
double KarhLoeve::S(const int j, const int s, const int q)
{
/* write the theta integral
* sum(o=0..infinity) binomial(5/6,o) (-z)^o int_0^2pi cos^o theta cos(q theta)d theta
*/
#if 1
double resul= Ialpha(j,s,q) ;
if ( q == 0)
{
/* contribution from G1, dropping the 2pi, the 3.44 and
* switching the sign
*/
resul -= Ialpha(0,s,q)/((2+j)*pow(2.,j)) +Ialpha(j,0,q)/((2+s)*pow(2.,s)) ;
}
#else
double bin= 1. ;
double resul= 0. ;
double oresul= 0. ;
/* todo: use member parameter ord to limit the loop
*/
for( int o=0; o<10 || fabs(resul-oresul) > 1.e-5*fabs(resul) ; o++)
{
oresul = resul ;
resul += bin * I1(j,s,q,o) ;
if ( q == 0)
{
/* contribution from G1, dropping the 2pi, the 3.44 and
* switching the sign.
*/
resul -= I1(0,s,0,o)/(2+j)/pow(2.,(double)j) ;
resul -= I1(j,0,0,o)/(2+s)/pow(2.,(double)s) ;
}
/* the sign is from the term (-sin(2*alpha)*cos theta)^o
* in the binomial expansion of the theta-integral.
* Update (q/2-5/12)_o (q/2+1/12)_0 / (q+1)_o/o! to the next o.
* (q/2-5/12+o-1)-> (q/2-5/12+o)
* (q/2+1/12+o-1)-> (q/2+1/12+o)
* (q+1+o-1)-> (q+o+1)
* o-> (o+1)
*/
bin *= -(0.5*q+o-5./12.)*(0.5*q+o+1./12.)/((q+o+1)*(o+1)) ;
}
#endif
if ( q == 0)
{
/* contribution from G2, dropping the 2pi, the 3.44 and
* the negative sign.
*/
resul += 0.29995353261705446/((2+j)*(2+s)*pow(2.,4+j+s)) ;
}
/* optionally include the important factors left out up to here
* resul *= -FRIED_PARAMETER*2.0*M_PI*Pochhammer(-5/6,q)/factorial(q)
*/
return resul ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifdef KARHLOE_RADIAL_POLYN
/**
* @param[in] j index to the Zernike polynomials, 0-based
* @param[in] s index to the Zernike polynomials, 0-based
* @param[in] q azimuthal parameter of the KL mode
* @param[in] z the generic global radial Zernike functions
* @return the integral of \f$\int_0^{2\pi)d\theta \int_0^{\pi/4}d\alpha \sin^{1+q}(2\alpha)
* [1-\sin(2\alpha)\cos\theta]^{5/6}cos(q\theta)/(1+2q+j+s+14/3)/(2\cos\alpha)^{1+j+q+2q+14/3}\f$
* @since 2007-03-19
* @warning the factor -3.44 or 2pi or (-5/6)_q/q! is not yet in here.
* @warning the overlap integral is for the symmetrized kernel.
* @warning the overlap integral is incomplete for q=0.
* @warning the index offset of the power basis is already worked into the response.
* The parameter j might already have been reduced by q.
*/
double KarhLoeve::S(const int j, const int s, const int q, ZernikeBase & z)
{
double resul= 0. ;
/* The values of Noll's n parameter are starting at q and increasing to
* whatever we need to have j (s) independent degrees of freedom
*/
const int nj = q+2*j ;
const int ns = q+2*s ;
/* the j and s terms are polynomials in z.Z[j] and z.Z[s], implicitly those
* where the azimuthal parameter is q. This function here is called always in a context of
* fixed q, so we may assume that the indices j and s are 0 if the nodal parameter is n=q,
* that they are 1 if it is n=q+2 etc. The powers of the x^i are q(=m)<=i <= n, and we
* build the distributed product over the two polynomials.
*/
for(int powj = q ; powj <= nj ; powj +=2)
for(int pows = q ; pows <= ns ; pows +=2)
{
/* The interface to Zernike::powcoef() demands the Noll parameter n,
* the Noll parameter m, and the nominal exponent of the radial power.
* The interface to the reduces KarhLoeve::S() uses the powers that
* are left over after subtracting the m parameter.
*/
resul += z.powcoef(nj,q,powj)* z.powcoef(ns,q,pows)* S(powj-q,pows-q,q) ;
}
/* optionally include the important factors left out up to here
* resul *= -FRIED_PARAMETER*2.0*M_PI*Pochhammer(-5/6,q)/factorial(q)
*/
return resul ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifndef KARHLOE_RADIAL_POLYN
/** The auxiliary integral
* \latexonly
* \cite[(42)]{WangJOSA68}
* \endlatexonly
* @return \f$\int_0^{1/2}xdx\int_0^{1/2}x'dx' \int_0^{2\pi}d\theta (x^2+x'^2-2xx'\cos\theta)^{5/6}\f$.
*/
double KarhLoeve::B2()
{
double resul =0. ;
/*
int(x=0..1/,x''=0..1/2) x x'' int(theta=0..2pi) [x^2+x'^2-2xx''cos(theta)]^(5/6)
the binomial expansion after breaking (x^2+x''^2) away from the square bracket
=int(x,x'') x x'' (x^2+x''^2)^(5/6) sum(l=0..infity,here:ord) (5/6 choose l) [-2xx''/(x^2+x''^2)]^l int(theta)cos^l(theta)
=int(x,x'') x x'' (x^2+x''^2)^(5/6) sum(l=0..infity,here:ord) (5/6 choose l) [-2xx''/(x^2+x''^2)]^l cosInt(l)
=sum(l=0..infity,here:ord) (5/6 choose l) B2int(l) cosInt(l)
*/
/** The numerics uses a binomial expansion up to order \c ord . The number
* of terms used is only half as large because only terms of even index contribute to the sum.
*/
for(int l=0; l <= ord ; l += 2)
resul += B2int(l)*cosInt(l)*binom(5./6.,l) ;
/** The result is correctly multiplied with the constants of
* \latexonly
* \cite[(41)]{WangJOSA68} and \cite[(42)]{WangJOSA68}.
* \endlatexonly
*/
return resul*FRIED_CONSTANT*4/M_PI*8. ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifndef KARHLOE_RADIAL_POLYN
/*****************************
case q=0:
Digits := 26 ;
assume(x>0) ;
ikern := (x^2+x2p^2-2*x*x2p*cost)^(5/6) ;
las := taylor(ikern,cost=0,8) ;
las := convert(las,polynom) ;
cost := cos(t) ;
las := int(las,t=0..2*Pi) ;
las := 3.44*las ;
las := expand(las) ;
las := simplify(las,power) ;
las := simplify(las,radical) ;
evalf(las) ;
Rsymm return R(x,x')/x' to support the symmetrized version
******************************/
double KarhLoeve::Rsymm(double x, double xprime, const int q)
{
double resul;
const double xoverxprime = -2.*x*xprime/(x*x+xprime*xprime) ;
switch(q)
{
case 0:
if( x==0. || xprime==0.)
resul = FRIED_CONSTANT*2.*M_PI*pow(x*x+xprime*xprime,5./6.) ;
else // 3.44*int(0..2*pi, [x^2+x'^2-2*x*x'*cos(t)]^(5/6), binomial up to 'ord'
// 3.44*(x^2+x'^2)^(5/6) int(0..2*pi( 1-xoverprime*cos(t))^5/6)
// =3.44*(x^2+x'^2)^(5/6) sum(l=0..ord) (-xoverprime^l binom(5/6,l)* int(t=0..2pi,cos^l(t))
{
resul = cosInt(0) ; // term l=0
//for(int l=1; l <= ord ; l++) // skip the l=1,3,5,7.. because then the cosInt()=0.
for(int l=2; l <= ord ; l += 2) // skip the l=1,3,5,7.. because then the cosInt()=0.
resul += pow(xoverxprime,l)*cosInt(l)*binom(5./6.,l) ;
resul *= FRIED_CONSTANT*pow(x*x+xprime*xprime,5./6.) ;
}
return -resul+2.*M_PI*(B(1,x)+B(1,xprime)-B(2,x)) ;
break ;
default:
if( x==0. || xprime==0.)
return 0. ; // then the integral over t=0..2*pi of cos(q*t) is zero.
else // 3.44*int(0..2*pi, [x^2+x'^2-2*x*x'*cos(t)]^(5/6)*cos(q*t), binomial up to 'ord'
// 3.44*(x^2+x'^2)^(5/6) int(0..2*pi( 1-xoverprime*cos(t))^5/6)
// =3.44*(x^2+x'^2)^(5/6) sum(l=0..ord) (-xoverprime^l binom(5/6,l)* int(t=0..2pi,cos^l(t)*cos(q*t))
{
resul = cosInt(0,q) ; // term l=0
for(int l=1; l <= ord ; l++)
resul += pow(xoverxprime,l)*cosInt(l,q)*binom(5./6.,l) ;
resul *= FRIED_CONSTANT*pow(x*x+xprime*xprime,5./6.) ;
}
return -resul ;
break ;
}
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifndef KARHLOE_RADIAL_POLYN
/******************
I := int(x=0..1/2,x''=0..1/2) x x'' (x^2+x''^2)^(5/6)[-2xx''/(x^2+x''^2)]^l
Substitute x=r cos(phi), x''=r sin(phi); because integrand is symmetric, integrate only
up to phi=0..Pi/4, and r=0..1/(2 cos phi) and multiply by 2. Jacobian is r.
I := 2*(-2)^l int(phi=0..Pi/4,r=0..1/(2 cos phi)) r* x^(l+1) x''^(l+1) (x^2+x''^2)^(5/6)[1/(x^2+x''^2)]^l
= 2*(-2)^l int(phi=0..Pi/4,r=0..1/(2 cos phi)) r* x^(l+1) x''^(l+1) (x^2+x''^2)^(5/6-l)
= 2*(-2)^l int(phi=0..Pi/4,r=0..1/(2 cos phi)) r^(2l+3) (x^2+x''^2)^(5/6-l) cos^(l+1)phi sin^(l+1)phi
= 2*(-2)^l int(phi=0..Pi/4,r=0..1/(2 cos phi)) r^(2l+3) r^(5/3-2l) cos^(l+1)phi sin^(l+1)phi
= 2*(-2)^l int(phi=0..Pi/4,r=0..1/(2 cos phi)) r^(4+2/3) cos^(l+1)phi sin^(l+1)phi
underivative r^(5+2/3)/(17/3)=r^(17/3)/(17/3)
= 2*(-2)^l/2^(17/3)*3/17 int(phi=0..Pi/4) cos^(l+1)phi sin^(l+1)phi/cos^(17/3)phi
subsittute x=cos phi, dx =- dphi sin phi
= 3/17*(-2)^l/2^(14/3) int(x=1..1/sqrt(2)) (-) x^(l+1) sin(phi)^l /x^(17/3)
= 3/17*(-2)^l/2^(14/3) int(x=1/sqrt(2)..1) x^(l-14/3) sin(phi)^l
= 3/17*(-2)^l/2^(14/3) int(x=1/sqrt(2)..1) x^(l-14/3) [1-x^2]^(l/2) (only needed for even l)
= 3/17*(-)^l 2^(l-14/3) int(x=1/sqrt(2)..1) x^(l-14/3) sum(m=0..l/2) (l/2 choose m) (-x^2)^m
= 3/17*(-)^l 2^(l-14/3) sum(m=0..l/2) (l/2 choose m) (-)^m int(x=1/sqrt(2)..1) x^(2m+l-14/3)
= 3/17*(-)^l 2^(l-14/3) sum(m=0..l/2) (l/2 choose m) (-)^m x^(2m+l-11/3)/(2m+l-11/3) at 1 minus 1/sqrt(2)
*************************/
double KarhLoeve::B2int(const int l)
{
if ( l < 0 || l % 2)
{
cerr << __LINE__ << " invalid choice l " << l << endl ;
exit(EXIT_FAILURE) ;
}
double resul =0. ;
for(int m=0; m <= l/2 ; m++)
if ( m % 2)
resul -= binom(0.5*l,m)*(B2intaux(2*m+l,1.)-B2intaux(2*m+l,M_SQRT1_2)) ;
else
resul += binom(0.5*l,m)*(B2intaux(2*m+l,1.)-B2intaux(2*m+l,M_SQRT1_2)) ;
// (-)^l always +1 since l even
return 3./17.*resul*pow(2.,l-14./3.) ;
}
double KarhLoeve::B2intaux(const int twoml, const double x)
{
return pow(x,twoml-11./3.)/(twoml-11./3.) ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#undef M_2_POW_11_6
#undef FRIED_CONSTANT
/** Default ctor.
* The internal parameters are instantiated with invalid values.
* This only serves to have a default ctor for use in vector initializations.
*/
Kq::Kq() : q(99), p(-1), lambdasq(-1.), see(1)
{
}
/** Explicit ctor.
* @param[in] qval the azimuthal quantum number \f$q\f$
* @param[in] lambda the square of the KH eigenvalue
* @param[in] K a vector of the values on the radial mesh, with \f$N\f$ elements
* @param[in] N the length of the vector \c K
* @param[in] pval an initializer to indicate the index \c p
* @param[in] rseed an explicit seed value for the random number generator
*/
Kq::Kq(const int qval, const double lambda, double * K , const int N, const int pval, const int rseed )
: q(qval), lambdasq(lambda), p(pval), see(rseed)
{
for(int e=0; e< N; e++)
kvect.push_back(K[e]) ;
}
/** Calculate the modal eigenfunction at a point on the screen given in terms of 2 polar coordinates.
* @param[in] usecos flags whether the azimuthal functions \f$\cos q\theta\f$ are called (\c true) or \f$\sin q\theta\f$
* @param[in] r the radial coordinate in the interval \f$[0,1/2]\f$.
* @param[in] theta the azimuthal coordinate [rad] .
* @return the value of the normalized mode, which is the product of the radial and azimuthal functions
*/
double Kq::K(const bool usecos, const double r, const double theta) const
{
/** Outside the circular pupil return zero.
*/
if( r > 0.5)
return 0. ;
/** The normalized azimuthal functions are \f$1/\sqrt{2\pi}\f$ (\f$q=0\f$),
* \f$\cos (q\theta)/\sqrt\pi\f$ and
* \f$\sin (q\theta)/\sqrt\pi\f$ (\f$q\neq 0\f$).
*/
double resul = q ? ( usecos ? cos(q*theta)/M_SQRT_PI : sin(q*theta)/M_SQRT_PI ) : 1.0/(M_SQRT_PI*M_SQRT2) ;
#if defined KARHLOE_RADIAL_POLYN || defined KARHLOE_RADIAL_PFIT
resul *= zbase.at(r,kvect,q) ;
/* debugging
* cout << __FILE__ << " " << __LINE__ << " " << r << " " << q << " " << resul << endl ;
*/
#else
// linear interpolation radial mesh
// was r=(rindx+0.5)/(2*N), N=kvect.size(), in the middle
int rindx = 2.*kvect.size()*r ; // round to zero
//resul *= kvect[rindx]+ (2.*kvect.size()*r-rindx)*(kvect[rindx+1]-kvect[rindx]) ;
/* no attempt is made to interpolate; just a rounding to the nearest pixel.
* The somewhat complicated call of Kq::at() ensures that the case of scaling with
* lambdasq (enabled by the cpp macro KARHLOE_RETURNWI_LAMBDA) works in all cases.
*/
resul *= at(rindx) ;
#endif
return resul ;
}
/** Calculate the modal eigenfunction at a point on the screen given in terms of 2 Cartesian coordinates.
* @param[in] usecos flags whether the azimuthal functions \f$\cos q\theta\f$ are called (\c true) or \f$\sin q\theta\f$
* (\c false)
* @param[in] x the cartesian coordinates on the screen, each in \f$[-1/2,1/2]\f$
* @return the value of the mode
*/
double Kq::K(const bool usecos, double x[2]) const
{
/** The implementation decomposes the cartesian coordinates in polar/circular coordinates
* and forwards the request to K(bool,const double,const double).
*/
const double r=hypot(x[0],x[1]) ;
const double thet = atan2(x[1],x[0]) ;
return K(usecos,r,thet) ;
}
#ifndef KARHLOE_RADIAL_POLYN
/** Calculate the modal eigenfunction at a point on the screen given in terms of the abscissa index.
* @param[in] idx the index in the range of 0 to N() .
* @return the value of the mode
* @author Richard J. Mathar
* @since 2006-11-29
*/
double Kq::at(const int idx) const
{
#ifdef DEBUG
cout << __LINE__ << " " << idx << " " << kvect.size() << " " << kvect[idx] << endl ;
#endif
if ( idx>=0 && idx < kvect.size())
#ifdef KARHLOE_RETURNWI_LAMBDA
if ( lambdasq > 0.)
return kvect[idx]*sqrt(lambdasq) ;
else
return 0. ;
#else
return kvect[idx] ;
#endif
else
return 0. ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifndef KARHLOE_RADIAL_POLYN
/** Calculate the Hankel transform (that is the FT of the basis function).
* The factor of the eigenvalue is not incorporated.
* @param[in] sigma the wavenumber
* @return the value of the radial integral, skipping the factor i^q.
* @author Richard J. Mathar
* @since 2007-04-29
*/
double Kq::fft(const double sigma) const
{
double resul = 0. ;
const double h = 0.5/N() ;
for(int i=0; i < kvect.size(); i++)
{
const double x = h*(i+0.5) ;
#ifdef KARHLOE_RETURNWI_LAMBDA
resul += x*kvect[i]* jn(q,2.*M_PI*sigma*x) ;
#else
resul += x*at(i)* jn(q,2.*M_PI*sigma*x) ;
#endif
}
/** The approximation is the simple Riemann sum with interval lengths \f$\Delta x=0.5/N()\f$,
* which yields a division through 2*N(). Another factor \f$ 2\pi \f$ comes from the integration over
* the azimuths.
*/
return 2.*M_PI*h*resul ;
}
#endif /* KARHLOE_RADIAL_POLYN */
#ifndef KARHLOE_RADIAL_POLYN
/** Calculate the derivative of the modal eigenfunction at a point on the screen given in terms of the abscissa index.
* @param[in] idx the index in the range of 0 to N() .
* @return the value of the derivative \f$\partial_xK_p^q(x)\f$
* @author Richard J. Mathar
* @since 2006-11-29
*/
inline double Kq::gradat(const int idx) const
{
if ( idx< 0 || idx >= kvect.size())
return 0. ;
else
{
/** Step size \c h is the total length of the radial interval, 1/2, divided
* by the number of abscissa points.
*/
const double h = 0.5/N() ;
/** At the end points we use the two point approximation
* \latexonly
* \cite[25.3.21]{AS}
* \endlatexonly
* else the three-point approximation
* \latexonly
* \cite[25.3.4]{AS}
* \endlatexonly
*/
if ( idx == 0 )
return ( at(1)-at(0) )/h ;
else if ( idx == kvect.size()-1 )
return ( at(idx)-at(idx-1) )/h ;
else
return 0.5*( at(idx+1)-at(idx-1) )/h ;
}
}
#endif /* KARHLOE_RADIAL_POLYN */
/** The overlap integral with another radial function.
* @param[in] oth the other radial function in the overlap integral
* @return the value of \f$\int_0^{1/2}dx this (x) other(x)\f$.
* @warning Note that this is not the ordinary overlap which would involve a factor \f$x\f$,
* the Jacobian in the polar coordinate system.
* @author Richard J. Mathar
* @since 2006-11-29
*/
double Kq::Overl(const Kq & oth) const
{
#if defined KARHLOE_RADIAL_POLYN || defined KARHLOE_RADIAL_PFIT
return zbase.Overl(q,kvect,oth.q,oth.kvect) ;
#else
double resul=0. ;
for(int i=0; i < kvect.size(); i++)
resul += at(i)*oth.at(i) ;
/** The approximation is the simple Riemann sum with interval lengths \f$\Delta x=0.5/N()\f$.
*/
return resul/(2.*N()) ;
#endif
}
/** The overlap integral of the derivative with another radial function.
* @param[in] oth the other radial function in the overlap integral
* @return the value of \f$\int_0^{1/2}dx x [\partial_x this (x)] other(x)\f$.
* @author Richard J. Mathar
* @since 2006-11-29
* @warning unlike Overl(), this function here is not commutative in the two functions.
*/
double Kq::gradOverl(const Kq & oth) const
{
#if defined KARHLOE_RADIAL_POLYN || defined KARHLOE_RADIAL_PFIT
return zbase.gradOverl(q,kvect,oth.q,oth.kvect) ;
#else /* KARHLOE_RADIAL_POLYN */
double resul=0. ;
for(int i=0; i < kvect.size(); i++)
{
/* \f$x\f$ is actually (i+1/2)/(2N). The missing factor is recovered at the return statement
* One could of course become more efficient by eliminating a factor \c h in gradat() which
* cancels against one factor here.
*/
resul += (i+0.5)*gradat(i)*oth.at(i) ;
/* debugging
* cout << __LINE__ << " " << i << " " << gradat(i) << " " << oth.at(i) << endl ;
*/
}
/** The approximation is the simple Riemann sum with interval lengths \f$\Delta x=0.5/N()\f$.
*/
return resul/(4*N()*N()) ;
#endif /* KARHLOE_RADIAL_POLYN */
}
/** Size of the representation vector.
* @return number of elements (abscissa points)
*/
int Kq::N() const
{
return kvect.size() ;
}
/** Neumann factor associated with the azimuthal quantum number
* @return 1 if \f$q=0\f$, 2 else.
*/
int Kq::Neumann() const
{
return (q ? 2 : 1) ;
}
/** The azimuthal overlap integral.
* @param[in] m1 azimuthal quantum number of first factor
* @param[in] m2 azimuthal quantum number of second factor
* @param[in] isCos flags this as \f$\int d\theta \cos m_1\theta\cos m_2\theta\f$ if \c true,
* else as \f$\int_0^{2\pi} d\theta \sin m_1\theta\sin m_2\theta\f$ if \c false
* @return the angular integral,
*/
int Kq::azOverl(const int m1, const int m2, const bool isCos)
{
/* The overlap between two azimuthal functions of the cosine type is 1
* if the absolute quantum numbers are the same; for the sine case,
* a different sign in the arguments may toggle the sine in the result.
*/
if(isCos)
if ( abs(m1)==abs(m2) )
return 1 ;
else
return 0 ;
else
if ( abs(m1)==abs(m2) )
return (m1*m2 >0)? 1 : -1 ;
else
return 0 ;
}
#if 0
/** The azimuthal overlap integral.
* @param[in] oth
* @param[in] isCos flags this as \f$\int d\theta \cos m_1\theta\cos m_2\theta\f$ if \c true,
* else as \f$\int_0^{2\pi} d\theta \sin m_1\theta\sin m_2\theta\f$ if \c false
* @return the angular integral
*/
int Kq::azOverl(const Kq & oth, const bool isCos) const
{
return azOverl(q,oth.q,isCos) ;
}
#endif
#if defined KARHLOE_RADIAL_POLYN || defined KARHLOE_RADIAL_PFIT
ZernikeBase Kq::zbase ;
#endif /* KARHLOE_RADIAL_POLYN, KARHLOE_RADIAL_PFIT */
/** Comparison operator.
* The radial functions are sorted with largest (dominant) eigenvalue first.
* @param[in] left the Kq left from the less sign
* @param[in] right the Kq right from the less sign
* @return true of \c left is smaller (has larger eigenvalue) than \c right
* @author Richard J. Mathar
* @since 2005-12-06
*/
bool operator< (const Kq & left, const Kq & right)
{
return ( left.lambdasq > right.lambdasq) ;
}
/** Overloaded output operator.
* A simple ASCII representation of a single radial function
* @param[in,out] os the output stream
* @param[in] some the Kq instance to be printed
* @author Richard J. Mathar
* @since 2005-12-06
*/
ostream & operator<<(ostream &os, const Kq & some)
{
// the values of Wang-Markey's Table III are obtained by dividing lambda^2 through pi
// the values of Dai's Table 1 "Variance" are obtained by multiplying this in addition with 4
os << "# lambda^2 " << some.lambdasq << " lambda^2/pi " << some.lambdasq/M_PI << " 4xlambda^2 "
<< 4.*some.lambdasq << endl ;
os << "# p " << some.p << " q +-" << some.q << endl ;
const int N= some.kvect.size() ;
for(int j=0;j< N; j++)
{
#if defined KARHLOE_RADIAL_POLYN || defined KARHLOE_RADIAL_PFIT
/* debugging
* os << "# Zern mode " << j << " coef " << some.kvect[j] << endl ;
*/
const double r = (j+0.5)/(2.*N) ;
cout << r << " " << some.K(true,r,0.) << endl ;
#else
os << (j+0.5)/(2.*N) << " " << some.kvect[j] << endl ;
#endif
}
os << endl << endl ;
return os ;
}
/** Ctor
* @param[in] see a seed value for the Gaussian random numbers of the eigenfunctions
* @param[in] qmax the maximum number of phase screen basis functions.
* This puts a constraint on the degrees of freedom admitted in the
* speckles.
* @param[in] N the number of points on the radial mesh used to generate
* the radial eigenfunctions.
* @param[in] ord a cut-off parameter for the number of terms in the binomial
* expansions for some kernel integrals,
* \latexonly
* \cite[(27)]{FriedJOSA68} and
* \cite[(39)--(42)]{WangJOSA68}.
* \endlatexonly
*/
KqBasis::KqBasis(const int see, const int qmax, const int N, const int ord) : Ksort(), scrs()
{
initseed(see) ;
KarhLoeve KL(ord) ;
double * lambda = new double[N] ;
double * K = new double[N*N] ;
for(int q=0; q < qmax ; q++)
{
#if defined KARHLOE_RADIAL_POLYN
KL.Kpq(q,K,lambda,N,zbase) ;
#else
KL.Kpq(q,K,lambda,N) ;
#endif
// scan all the newly generated radial functions; insert them
// into the vector Ksort and optionally remove at the end with the too small
// eigenvalues one, if the maximum number of requested modes is reached, so only
// those with the largest eigenvalues survive.
for(int e=0; e < N ; e++)
{
const int efrom0 = N-1-e ;
Kq tmp(q,lambda[efrom0],&K[N*efrom0],N,e+1) ;
vector::iterator i= Ksort.begin();
for( ; i != Ksort.end() ; i++)
if( tmp < *i)
{
Ksort.insert(i,tmp) ;
break ;
}
if( i == Ksort.end() )
Ksort.push_back(tmp) ;
Ksort.resize(qmax) ;
}
#ifdef KARHLOE_SHOW_RESIZE
printf("# "__FILE__" %d q=%d lambda^2= %.6f %.6f %.6f %.6f\n",__LINE__,q,lambda[N-1],lambda[N-2],lambda[N-3],lambda[N-4]) ;
double *DDbar = Dresize(K,N,2,lambda) ;
delete [] DDbar ;
#endif
}
delete [] K ;
delete [] lambda ;
}
/**
* @param[in] K the radial expansion of the KL eigenmodes of a constant q
* @param[in] shrink a small pupil resizing factor, a divisors of N
* @since 2007-03-29
*/
double * KqBasis::Dresize(const double *K, const int N, const int shrink, const double *lambda) const
{
#if KARHLOE_SHOW_RESIZE == 1
integer lda = N ;
integer nrhs = N/shrink ;
#else
integer lda = N/shrink ;
integer nrhs = N ;
#endif
double *Dcoup = new double[lda*nrhs] ;
/* need a temproray storage because dgesv_() overwrites its A matrix
*/
double *A = new double[lda*lda] ;
/* rather simple solution that doesnot reverse the order (still smallest eigenvalues first
* memcpy(A,K,N*N*sizeof(double)) ;
*/
#if KARHLOE_SHOW_RESIZE == 1
/* the pixels vary along the columns (changing row index), and this order is maintained,
* the reversal of the eigenvectors (to largest eigenvalues first) means reversal of the column indices
*/
for(int row = 0 ; row < lda ; row++)
for(int col =0 ; col < lda ; col++)
#if KARHLOE_SHOW_TYPE == 1
// A[row+col*lda] = K[row+(lda-1-col)*lda] ;
A[KARHLOE_F77_FLAT(row,col,lda)] = K[row+(N-1-col)*N] ;
#else
/* perhaps solve for the gamma matrix defined to couple the radial values multiplied
* by the lambda (eigenvalues square roots)
*/
if ( lambda[lda-1-col] > 0. )
A[KARHLOE_F77_FLAT(row,col,lda)] = sqrt(lambda[lda-1-col])*K[row+(lda-1-col)*lda] ;
else
A[KARHLOE_F77_FLAT(row,col,lda)] = 0. ;
#endif
for(int row = 0 ; row < lda ; row++)
for(int col =0 ; col < nrhs ; col++)
// Dcoup[row+col*lda] = A[shrink*row+col*lda] ;
Dcoup[KARHLOE_F77_FLAT(row,col,lda)] = A[KARHLOE_F77_FLAT(shrink*row,col,lda)] ;
#else /* KARHLOE_SHOW_RESIZE */
/* the pixels vary along the columns (changing row index), and this order is maintained,
* the reversal of the eigenvectors (to largest eigenvalues first) means reversal of the column indices
*/
for(int row = 0 ; row < lda ; row++)
for(int col =0 ; col < lda ; col++)
#if KARHLOE_SHOW_TYPE == 1
A[KARHLOE_F77_FLAT(row,col,lda)] = K[shrink*row+(N-1-col)*N] ;
#else
/* perhaps solve for the gamma matrix defined to couple the radial values multiplied
* by the lambda (eigenvalues square roots)
*/
if ( lambda[N-1-col] > 0. )
A[KARHLOE_F77_FLAT(row,col,lda)] = sqrt(lambda[N-1-col])*K[shrink*row+(N-1-col)*N] ;
else
A[KARHLOE_F77_FLAT(row,col,lda)] = 0. ;
#endif
for(int row = 0 ; row < lda ; row++)
for(int col =0 ; col < nrhs ; col++)
#if KARHLOE_SHOW_TYPE == 1
Dcoup[KARHLOE_F77_FLAT(row,col,lda)] = K[row+(N-1-col)*N] ;
#else
if ( lambda[N-1-col] >0.)
Dcoup[KARHLOE_F77_FLAT(row,col,lda)] = sqrt(lambda[N-1-col])*K[row+(N-1-col)*N] ;
else
Dcoup[KARHLOE_F77_FLAT(row,col,lda)] = 0. ;
#endif
#endif /* KARHLOE_SHOW_RESIZE */
#if 0
/* debugging input
*/
cout << "# solving A" << endl ;
for(int row = 0 ; row < lda && row < 10; row++)
{
for(int col =0 ; col < lda && col < 10; col++)
printf("%.4f ",A[KARHLOE_F77_FLAT(row,col,lda)]) ;
printf("\n") ;
}
cout << "# solving B" << endl ;
for(int row = 0 ; row < nrhs && row < 10 ; row++)
{
for(int col =0 ; col < lda && col < 10 ; col++)
printf("%.4f ",Dcoup[KARHLOE_F77_FLAT(row,col,lda)]) ;
printf("\n") ;
}
#endif
integer info ;
integer *ipiv = new integer[N] ;
/* solve the system of linear equations K(x=r/shrink)= K(r)*Dcoup by a call to LAPACK's dgesv().
* A of the dgesv is represented by K(r), and B by K(x=r/shrink), and X by Dcoup (on return stored in B).
*/
dgesv_(&lda,&nrhs,A,&lda,ipiv,Dcoup,&lda,&info) ;
if ( info)
cerr << __LINE__ << " dgesv info " << info << endl ;
delete ipiv ;
delete A ;
#if 0
/* debugging ASCII output
*/
for(int row = 0 ; row < lda ; row++)
{
for(int col =0 ; col < nrhs ; col++)
{
printf("%.4f ",Dcoup[KARHLOE_F77_FLAT(row,col,lda)]) ;
}
if ( row == nrhs)
printf("\n------") ;
printf("\n") ;
}
#endif
#if 1
/* debugging gnuplot output. lambda still contains the square here, which we convert
* to the values themselves for display
*/
printf("\n\n") ;
for(int col =0 ; col < nrhs ; col++)
{
for(int row = 0 ; row < lda ; row++)
{
/* lambda still contains the square here, which we would convert to lambda for display
*/
//printf("%.1f %d %e %e\n",row-0.5,col,fabs(sqrt(lambda[lda-1-row])*Dcoup[row+col*lda]),fabs(Dcoup[row+col*lda])) ;
printf("%.1f %d %e\n",row-0.5,col,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
printf("%.1f %d %e\n",row+0.5,col,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
}
printf("\n") ;
for(int row = 0 ; row < lda ; row++)
{
printf("%.1f %.1f %e\n",row-0.5,col+0.5,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
printf("%.1f %.1f %e\n",row+0.5,col+0.5,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
}
printf("\n") ;
}
#endif
#if 0
/* debugging Maple output
*/
printf("dta :=[\n\n") ;
for(int col =0 ; col < nrhs ; col++)
{
if( col != 0 )
printf(",\n") ;
printf("[") ;
for(int row = 0 ; row < lda ; row++)
{
if( row != 0 )
printf(",\n") ;
printf("[%d,%f,%e],",col,row-0.5,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
printf("[%d,%f,%e]",col,row+0.5,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
}
printf("],") ;
#if 0
if( col != 0 )
printf(",\n") ;
#endif
printf("[") ;
for(int row = 0 ; row < lda ; row++)
{
if( row != 0 )
printf(",\n") ;
printf("[%f,%f,%e],",ccol+0.5,crow-0.5,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
printf("[%f,%f,%e]",ccol+0.5,crow+0.5,fabs(Dcoup[KARHLOE_F77_FLAT(row,col,lda)])) ;
}
printf("]") ;
}
printf("] ;\n") ;
printf("interface(plotdevice=\"ps\",plotoutput=\"tmp.ps\",plotoptions=\"noborder,portrait\");\n") ;
printf("plots[surfdata](dta,axes=FRAMED,labels=[p,p,\"\"],orientation=[-148,54]);\n") ;
#endif
/* save the eigenvectors into return area
*/
return Dcoup ;
}
#if 0
/** Ctor
* Not needed and incompatible with the other ctor because that one would
* generate the resizing matrix.
* @param[in] src
* @param[in] see
*/
KqBasis::KqBasis( vector & src, const int see) : Ksort(src), scrs()
{
initseed(see) ;
}
#endif
#if defined KARHLOE_RADIAL_POLYN || defined KARHLOE_RADIAL_PFIT
ZernikeBase KqBasis::zbase ;
#endif /* KARHLOE_RADIAL_POLYN */
/** Dtor
*/
KqBasis::~KqBasis()
{
/* debugging
* cout << "# dtor KqBasis " << __FILE__ << " " << __LINE__ << endl ;
*/
/** The memory allocated in makeScreen() to keep the vector of the Taylor screens
* is de-allocated.
*/
for(int s=0; s < scrs.size() ; s++)
delete [] scrs[s] ;
}
/** Produce a gnuplot type of output for the FT of the radial functions
* @since 2007-04-29
* @author Richard J. Mathar
*/
void KqBasis::showFT() const
{
const int Nradial = Ksort[0].N() ;
for(int no=0; no < Ksort.size() ; no++)
{
cout << "# q= " << Ksort[no].q << " lambda^2=" << Ksort[no].lambdasq << " index " << no << endl ;
/* the x have been tabulated along integers i=0,1,2... so the natural resolution
* on the sigma axis is j=0,1,2,... for 2*pi*sigma*x = 2*pi*j*i/Nradial
*/
for (int s=0 ; s < 2*Nradial ; s++)
{
const double sigm = 0.05*s ;
cout << sigm << " " << Ksort[no].fft(sigm) << endl ;
}
cout << endl << endl ;
}
}
/** produce another single screen and add it to the scrs[]
* @param[in] Dr0 the ratio \f$D/r_0\f$ of pupil diameter and Fried parameter
* @param[in] withtipt a flag indicating whether the tip-tilt component ought remain
* in the screen generated (\c true) or be removed (\c false)
*/
void KqBasis::makeScreen(double Dr0, const bool withtipt)
{
if ( withtipt)
/* only piston removed */
makeScreen(Dr0,0) ;
else
/* piston and tip-tilt removed */
makeScreen(Dr0,2) ;
}
/** produce another single screen and add it to the scrs[]
* @param[in] Dr0 the ratio \f$D/r_0\f$ of pupil diameter and Fried parameter
* @param[in] aoOrd the number of low-order modes skipped. 3 means only tip-tilt removed.
*/
void KqBasis::makeScreen(double Dr0, const int aoOrd)
{
/** A cartesian coordinate pair in the pupil square (which has the side
* length \f$D\f$), normalized to \f$0\le x[]\le 1/2\f$.
*/
double x[2] ={0.,0.} ;
/** The number of radial absissa points in the numerical representation
* of the radial eigenfunctions, or the number of different Zernike polynomials
* if this has been chosen as the representation..
*/
const int Nradial = Ksort[0].N() ;
/** The distance between two abscissa points on the radial mesh. This
* is equivalent to the value used in the Riemann sense of integration over
* the interval [0,1/2].
*/
const double xstep = 0.5/Nradial ;
/** Further down we only need the fluctuation amplitude
* \f$ (D/r_0)^{5/6}\f$. The factor \f$D^2\f$ of Fried's equation (23) is already
* implicitly contained in the fact that our basis functions are normalized to be
* 1 if integrated from x=0 to x=1/2, which needs to become an integral over the
* r=0 to r=D/2 and implicitly divided thru \f$D^2\f$ to match the equation.
*/
Dr0 = pow(Dr0,5./6.) ;
/** The flac \c idist indicates to the \c dlarnv function of LAPACK that we need Gaussian distributed
* random variables.
*/
integer idist = 3;
/** The number of independent eigenfunctions is kept in \c iN
*/
integer iN = modes() ;
/** Each mode is modelled for each instance of the phase screen with an
* individual randomized amplitude with Gaussian distribution. All these
* are kept for one single screen in the vector \c rstd .
*/
double * rstd = new double[iN] ;
/** A call to dlarnv() of the LAPACK library produces \c iN Gaussian random numbers
* with mean 0 and standard deviation 1.
*/
dlarnv_(&idist,lapsee,&iN,rstd) ;
/** A square grid of width and height equal to the diameter needs two times
* as much grid points along \f$x\f$ and two times as much grid points along \f$y\f$
* as there were abscissa values in the radial meshes, if one wants to match
* the resolution in the KL eigenfunctions sensibly. For each point we allocate
* one value (that is the real-valued phase at that point in the phase screen).
*/
double *scr = new double[4*Nradial*Nradial] ;
#if 0
/* debugging. Explicitly document the modes and their eigenvalues that will
* be skipped with this level of AO correction. The actual skipping is done
* later below in the loop.
*/
for(int no=0; no < aoOrd ; no++)
{
double lambdasq ;
K(no,x,lambdasq) ;
cout << "# skipping " << no << " lambda^2 " << lambdasq << endl ;
}
#endif
/** The square grid is filled by a double loop over the two orthogonal axes.
*/
for(int xindx=0 ; xindx < 2*Nradial ; xindx++)
{
x[0] = -0.5+(0.5+xindx)*xstep ;
for(int yindx=0 ; yindx < 2*Nradial ; yindx++)
{
x[1] = -0.5+(0.5+yindx)*xstep ;
double resul=0. ;
/** The value on the grid is a sum over all eigenmodes (which are basis functions)
* scaled to the physically relevant units.
*/
for(int no=0; no< iN;no++)
{
double lambdasq ;
/** For each point in the grid we call K() to recover the
* normalized value of the eigenmode and to query its eigenvalue.
* On return, K() has not yet multiplied these with the square root
* of \f$\lambda^2\f$.
*/
double val = K(no,x,lambdasq) ; // scaling with lambda not in here
/** If this particular mode is the tip-tilt mode, we skip it. This
* can be sensed by comparison of the squared eigenvalue with the threshold
* value of 0.0178 for the next higher mode, in comparison to the value
* \f$ 0.3521=0.1121\pi\f$ of the tip-tilt mode(s).
* if ( !withtipt && lambdasq > 0.03) // tip-tilt has lambdasquared = 0.3521=pi*0.1121
*continue ;
*/
if ( no < aoOrd)
continue ;
if( lambdasq > 0.)
{
#ifndef KARHLOE_RETURNWI_LAMBDA
/* with the normalization of the basis function over x=0..1/2 and
* theta =0 .. 2*pi equal to 1, we need a variance of the new random
* number of (D/r0)^5/3*lambdasq. We upscale the values of rstd[] from
* a standard deviation of 1 to this value by multiplying the entire set of
* random numbers with the square root of the std dev.
*/
val *= sqrt(lambdasq) ;
#endif
val *= rstd[no] ;
val *= Dr0; // already powered above with power 5/6
resul += val ;
}
}
/** The same over all these properly scaled eigenmodes is kept
* in the linear vector \c scr . There is no sense of using the language
* of ``row-order'' or ``column-order'' here; the stride is set by the side length
* of the phase screen square.
*/
scr[xindx+2*Nradial*yindx] = resul ;
}
}
/** As a clean-up procedure, the set of random numbers allocated above is de-allocated.
*/
delete [] rstd ;
/** A pointer to this particular phase screen (but not the data themselves)
* is stored in \c scr for future use.
*/
scrs.push_back(scr) ; // store the pointer to the data (to be released by the dtor)
scrsIsBasis=false ;
}
/** produce the basis function no and add it to the scrs[]
* @param[in] Dr0 the ratio \f$D/r_0\f$ of pupil diameter and Fried parameter
* @param[in] no the number of the basis function, starting at 0
*/
void KqBasis::makeBasis(double Dr0, const int no)
{
const int iN = modes() ;
if ( no >= iN)
{
cerr << __FILE__ << " " << __LINE__ << " no=" << no << ">=" << iN << endl ;
return ;
}
/** A cartesian coordinate pair in the pupil square (which has the side
* length \f$D\f$), normalized to \f$0\le x[]\le 1/2\f$.
*/
double x[2] ={0.,0.} ;
/** The number of radial absissa points in the numerical representation
* of the radial eigenfunctions.
*/
const int Nradial = Ksort[0].N() ;
/** The distance between two abscissa points on the radial mesh. This
* is equivalent to the value used in the Riemann sense of integration over
* the interval [0,1/2].
*/
const double xstep = 0.5/Nradial ;
/** Further down we only need the fluctuation amplitude
* \f$ (D/r_0)^{5/6}\f$. The factor \f$D^2\f$ of Fried's equation (23) is already
* implicitly contained in the fact that our basis functions are normalized to be
* 1 if integrated from x=0 to x=1/2, which needs to become an integral over the
* r=0 to r=D/2 and implicitly divided thru \f$D^2\f$ to match the equation.
*/
Dr0 = pow(Dr0,5./6.) ;
/** A square grid of width and height equal to the diameter needs two times
* as much grid points along \f$x\f$ and two times as much grid points along \f$y\f$
* as there were abscissa values in the radial meshes, if one wants to match
* the resolution in the KL eigenfunctions sensibly. For each point we allocate
* one value (that is the real-valued phase at that point in the phase screen).
*/
double *scr = new double[4*Nradial*Nradial] ;
/** The square grid is filled by a double loop over the two orthogonal axes.
*/
for(int xindx=0 ; xindx < 2*Nradial ; xindx++)
{
x[0] = -0.5+(0.5+xindx)*xstep ;
for(int yindx=0 ; yindx < 2*Nradial ; yindx++)
{
x[1] = -0.5+(0.5+yindx)*xstep ;
double resul=0. ;
/** The value on the grid is the basis function
* scaled to the physically relevant units.
*/
double lambdasq ;
/** For each point in the grid we call K() to recover the
* normalized value of the eigenmode and to query its eigenvalue.
* On return, K() has not yet multiplied these with the square root
* of \f$\lambda^2\f$.
*/
double val = K(no,x,lambdasq) ; // scaling with lambda not in here
if( lambdasq > 0.)
{
#ifndef KARHLOE_RETURNWI_LAMBDA
/* with the normalization of the basis function over x=0..1/2 and
* theta =0 .. 2*pi equal to 1, we need a variance of the new random
* number of (D/r0)^5/3*lambdasq. We upscale the values of rstd[] from
* a standard deviation of 1 to this value by multiplying the entire set of
* random numbers with the square root of the std dev.
*/
val *= sqrt(lambdasq) ;
#endif
val *= Dr0; // already powered above with power 5/6
resul = val ;
}
/** The same over all these properly scaled eigenmodes is kept
* in the linear vector \c scr . There is no sense of using the language
* of ``row-order'' or ``column-order'' here; the stride is set by the side length
* of the phase screen square.
*/
scr[xindx+2*Nradial*yindx] = resul ;
}
}
/** A pointer to this particular phase screen (but not the data themselves)
* is stored in \c scr for future use.
*/
scrs.push_back(scr) ; // store the pointer to the data (to be released by the dtor)
scrsIsBasis=true ;
}
/** produce all basis functions and add it to the scrs[]
* @param[in] Dr0 the ratio \f$D/r_0\f$ of pupil diameter and Fried parameter
*/
void KqBasis::makeBasis(double Dr0)
{
for(int no=0; no < modes() ; no++)
makeBasis(Dr0,no) ;
}
/** Read one value of a previously generated phase screen pixel.
* @param[in] screenNo the index of the phase screen, 0 -based.
* @param[in] xindx the integer coordinate of the \f$x\f$ value
* @param[in] yindx the integer coordinate of the \f$y\f$ value
* @param[in] scrV a pointer to a vector of screens. If the parameter is NULL,
* the collection of screens in KqBasis::scrs is used.
* @return the value that had been generated with the \c screenNo 'th call to screen() before.
* @todo include a check for out-of-bound indices
*/
double KqBasis::inScreen(const int screenNo, const int xindx, const int yindx,const vector *scrV) const
{
/** This reads immediately, with no protection against index over/underflow,
* one value of the screen set.
*/
if ( scrV)
return (*scrV)[screenNo][xindx+2*Ksort[0].N()*yindx] ;
else
return scrs[screenNo][xindx+2*Ksort[0].N()*yindx] ;
}
/** Read one value of a previously generated phase screen pixel.
* @param[in] screenNo the index of the phase screen, 0 -based.
* @param[in] r the radial distance to the pupil center between 0 and 0.5
* @param[in] theta the azimuthal coordinate between 0 to \f$2\pi\f$
* @return the value that had been generated with the \c screenNo 'th call to screen() before.
* Points outside the circular aperture are returned as zero.
*/
double KqBasis::inScreen(const int screenNo, const double r, const double theta) const
{
/* in a Cartesian system, the values -1/2<=x<=1/2 and -1/2<=y<=1/2 are mapped
* onto 0<=xindx < 2*N() and 0<=yindx < 2*N().
* We invert this transformation to
* get xindx and yindx: (2*x+1)/N() = 0.5+xindx; (2*y+1)/N()=0.5+yindx
*/
double x[2] ;
x[0] = r*cos(theta) ;
x[1] = r*sin(theta) ;
/** The number of radial absissa points in the numerical representation
* of the radial eigenfunctions.
*/
const int Nradial = Ksort[0].N() ;
#if 0
/* this is a quasi reversed but unnecessarily complicated implementation of
* the formula used before.
*/
const int xindx = round((2.*x[0]+1.)*Nradial-0.5) ;
const int yindx = round((2.*x[1]+1.)*Nradial-0.5) ;
#else
const int xindx = (2.*x[0]+1.)*Nradial ;
const int yindx = (2.*x[1]+1.)*Nradial ;
#endif
if ( xindx >=0 && xindx < 2*Nradial && yindx >=0 && yindx < 2*Nradial)
return inScreen(screenNo,xindx,yindx) ;
else
return 0. ;
}
/** Reproduce the phase screens previously generated with makeScreen() in various output formats.
* @param[in] Dr0 the value of \f$D/r_0\f$. This value is not used again to scale the actual
* phases, but only used to generate a FITS header line.
* @param[in] D the value of \f$D\f$. This value is not used again to scale the actual
* phases, but only used to generate a FITS header line.
* @param[in] slrnseed a seed value for the random number generator. This value is not actually used
* to compute again any phase value, but only used to generate a FITS header line.
* @param[in] withtipt This parameter recalls whether tip-tilt is still in the phase screen (\c true)
* or removed (\c false). Only used to generate a parameter in the FITS header.
* @param[in] mapletype flag to request that the ASCII output is suitable for display with Maple .
* @param[in] clip if \c true demands that the phase screens are confined to the circular
* central piece of the pupil. This is the region to which the phase statistics has been
* adapted.
* @param[in] fits if \c cfitsio support had been compiled in, and if this is not a \c NULL pointer,
* the output is a FITS file, not an ASCII report to stdout .
* @param[in] scrV a pointer to a vector of screens. If the parameter is NULL,
* the collection of screens in KqBasis::scrs is used.
* Since \c cfitsio does not declare the variables \c const where possible, we avoid using this here.
*/
void KqBasis::out(double D, double Dr0, int slrnseed, bool withtipt, bool mapletype, bool clip, char *fits,
const vector * scrV) const
{
double x[2] ;
const int Nradial = Ksort[0].N() ;
const double xstep = 0.5/Nradial ;
const int scrCount = (scrV == 0)? scrs.size() : scrV->size() ;
/* debugging
* cerr << __FILE__ << " " << __LINE__ << " " << fits << " " << Nradial << " " << scrCount << endl ;
*/
if( fits == 0 )
{
for(int screenNo=0; screenNo < scrCount ;screenNo++)
{
if ( scrV == NULL)
{
bool usecos ;
int j=mod2rad(screenNo,usecos) ;
if ( usecos)
cout << "# q=+" ;
else
cout << "# q=-" ;
cout << Ksort[j].q << " p=" << Ksort[j].p << " lambdasq=" << Ksort[j].lambdasq << endl ;
}
if( mapletype)
cout << "dat" << screenNo << " := [" ;
bool firstx = true ;
for(int xindx=0 ; xindx < 2*Nradial ; xindx++)
{
x[0] = -0.5+(0.5+xindx)*xstep ;
if( mapletype)
if(firstx)
cout << "[" ;
else
cout << ",[" ;
bool firsty = true ;
for(int yindx=0 ; yindx < 2*Nradial ; yindx++)
{
x[1] = -0.5+(0.5+yindx)*xstep ;
if ( clip && hypot(x[0],x[1]) > 0.5)
continue ;
const double resul= inScreen(screenNo,xindx,yindx,scrV) ;
if( mapletype)
{
if ( !firsty)
cout << "," ;
cout << "[" << x[0] << "," << x[1] << "," << resul << "]\n" ;
}
else
cout << x[0] << " " << x[1] << " " << resul << endl ;
firsty= false ;
}
if( mapletype)
cout << "]" << endl ;
cout << endl ;
firstx = false ;
}
if( mapletype)
cout << "]:" << endl ;
cout << endl ;
}
if ( mapletype)
{
cout << "interface(plotdevice=postscript) :\n" ;
cout << "interface(plotoptions=`portrait,noborder`) :" << endl ;
for(int screenNo=0; screenNo < scrCount ;screenNo++)
{
cout << "interface(plotoutput=`tmp" << screenNo << ".ps`) :" << endl ;
bool usecos ;
int j=mod2rad(screenNo,usecos) ;
cout << "plots[surfdata](dat" << screenNo ;
if ( scrV)
{
cout << ",title=\"" << screenNo << "\"" ;
}
else if ( scrsIsBasis)
{
cout << ",title=\"q=" ;
if (usecos)
cout << Ksort[j].q ;
else
cout << -Ksort[j].q ;
cout << " p=" << Ksort[j].p << "\"" ;
}
else
{
// TODO: generate time labels
cout << ",title=\"" << screenNo << "\"" ;
}
//cout << ",axes=NORMAL,labels=[x,y,\"\"],tickmarks=[1,1,1],orientation=[60,45],scaling=CONSTRAINED) ;\n" ;
cout << ",axes=NORMAL,labels=[x,y,\"\"],tickmarks=[1,1,1],orientation=[60,45]) ;\n" ;
}
}
}
#ifdef HAVE_CFITSIO
else
{
fitsfile * fptr;
int status=0 ;
/** Use fits_create_file() of \c cfitsio to create the file. This implies that we'll return
* with an error condition if a file of the same name already exists.
*/
fits_create_file(&fptr,fits,&status) ;
if( status)
fits_report_error(stderr,status) ;
else
{
/** This becomes a simple image cube, one phase screen per images.
*/
long naxes[3] ;
naxes[0]=naxes[1]=2*Nradial ; naxes[2] = scrCount ;
long fpixel[3] ;
fpixel[0]=fpixel[1]=fpixel[2] = 1 ;
char * srcfile = __FILE__ ;
fits_create_img(fptr,FLOAT_IMG,3,naxes,&status) ;
/** All the screens generated with makeScreen() are copied to the FITS file in the
* order of creation.
*/
for(int screenNo=0; screenNo < scrCount ;screenNo++)
{
fpixel[2] = screenNo+1 ;
if ( scrV )
fits_write_pix(fptr,TDOUBLE,fpixel,4*Nradial*Nradial,(*scrV)[screenNo],&status) ;
else
fits_write_pix(fptr,TDOUBLE,fpixel,4*Nradial*Nradial,scrs[screenNo],&status) ;
}
fits_movabs_hdu(fptr,1,0,&status) ;
/** We install primary header keywords \c CREATOR, \c DR0, \c SEED and \C TIPTILT
* to indicate the parameters of creation.
*/
fits_write_key(fptr,TSTRING,"CREATOR",srcfile,"Richard J. Mathar",&status) ;
fits_write_key(fptr,TDOUBLE,"D",&D,"[m] pupil diameter",&status) ;
fits_write_key(fptr,TDOUBLE,"DR0",&Dr0,"D over r0",&status) ;
fits_write_key(fptr,TINT,"SEED",&slrnseed,"seed of random number generator",&status) ;
int ttilt = withtipt ;
fits_write_key(fptr,TLOGICAL,"TIPTILT",&ttilt,"false if tip-tilt removed",&status) ;
fits_write_date(fptr,&status) ;
fits_close_file(fptr,&status) ;
if( status)
fits_report_error(stderr,status) ;
}
}
#endif
}
/** Return the number of available modes.
* @return The number of modes, counting those with nonzero azimuthal quantum number
* with multiplicity (twice).
*/
int KqBasis::modes() const
{
int iN=0 ;
for(int i=0 ; i < Ksort.size() ; i++)
iN += Ksort[i].q ? 2 : 1 ;
return iN ;
}
/** Return the radial mode for each of the available pupil modes.
* @param[out] iscos \c true if this is a cosine in azimuth, \c false if this is a sine
* @return 0,1,1,2,2,3,3 etc if \c m = 0,1,2,3,...
* @author Richard J. Mathar
* @since 2006-11-29
*/
int KqBasis::mod2rad(int m, bool & iscos) const
{
/* The basis assumption is that we did not remove any modes when the basis
* functions were created, only the q=0 piston mode.
*/
int j=0 ;
iscos= true ;
while(m--)
{
if ( j >= Ksort.size() )
{
cerr << __FILE__ << " " << __LINE__ << " intrn error " << Ksort.size() << endl ;
exit(EXIT_FAILURE) ;
}
if ( Ksort[j].q) // have cos and sine components
{
if ( !iscos)
// previous was a sine (2nd call) so move on to the next radial function
j++ ;
iscos= !iscos ; // toggle the value
}
else
{
j++ ;
iscos=true ; // always report iscos=true for the q=0 components
}
}
return j ;
}
/**
* @param[in] i the index of the basis function to be used, starting at 0 for the
* one with the largest eigenvalue (tip-tilt).
* @param[in] x the 2 Cartesian coordinates of the point on the screen. Both in \f$[0,1/2]\f$.
* @param[out] lambdasq the normalized square of the KH eigenvalue
* @return the value of the eigenmode.
* This value is not yet multiplied by the eigenvalue \f$\lambda\f$, nor by any
* weight derived from the Gaussian statistics.
*/
double KqBasis::K(const int i, double x[2], double & lambdasq) const
{
/** The point on the radial mesh is given by the Pythagoran distance
* from the pupil center.
*/
const double r=hypot(x[0],x[1]) ;
/** Initialize the result to 0. This will be the value returned if the
* point selected by \c x falls outside the circular center of the pupil.
*/
double resul=0. ;
if( r > 0.5 || i>= modes() )
return resul ;
/** expansions \f$\propto \cos q\theta\f$ or \f$\propto \sin q\theta\f$.
*/
bool usecos ;
/** \f$i=0\f$ selects the first eigenvector, \f$i=1,2\f$ use the \f$\cos \theta\f$
* and \f$\sin\theta\f$ azimuthal modes, \f$i=3,4\f$ the \f$\cos 2\theta\f$ and
* \f$\sin 2\theta\f$ azimuthal modes, etc.
*/
const int j= mod2rad(i,usecos) ;
/** If the value of \c i has been larger than the number of eigenmodes, we return 0.
* Otherwise the value is computed by a call to Kq::K() .
*/
if( j < Ksort.size())
{
resul = Ksort[j].K(usecos,x) ;
lambdasq = Ksort[j].lambdasq ;
/* already included or not included in the call to Kq::K(), depending on
* the cpp macro KARHLOE_RETURNWI_LAMBDA.
* resul *= sqrt(Ksort[j].lambdasq) ;
*/
}
return resul ;
}
/** Feed a new seed into the random number generator of the screens.
* @param[in] see the new seed
*/
void KqBasis::initseed( const int see)
{
lapsee[0]=lapsee[1]=lapsee[2]=0 ;
lapsee[3] = (see & 4095) | 1 ;
}
/**
* A simple ASCII representation of all radial functions
* @param[in,out] os the output stream
* @param[in] some the KqBasis instance to be documented
* @author Richard J. Mathar
* @since 2005-12-06
*/
ostream & operator<<(ostream &os, const KqBasis & some)
{
#ifndef KARHLOE_SHOW_RESIZE
for(int q=0 ; q < some.Ksort.size() ; q++)
{
os << "# index " << q << endl ;
os << some.Ksort[q] << endl ;
}
#endif
return os ;
}
#undef KARHLOE_F77_FLAT
#endif /* KARHLOE_C */