Any scalar pattern on a sphere \(T(\theta,\phi)\) can be expressed as a series of spherical harmonic multipoles, \begin{equation} T(\theta,\phi) = \sum_{l=0}^{\infty} \sum_{m=-l}^{+l} a_{l,m} Y_{l,m}(\theta,\phi) \end{equation} where the \(a_{l,m}\) are complex as are the \(Y_{l,m}(\theta,\phi)\). The latter are the spherical harmonics which are defined as, \begin{equation} Y_{l,m}(\theta,\phi) = \sqrt{\frac{1}{2\pi}} P_{l,m}(\cos\theta) \, e^{im\phi} \end{equation} where the \(P_{l,m}\) are the (real) associated Legendre polynomials (in Matlab "fully normalized" form).
(Note that the use of \(\cos(\theta)\) here dictates that \(\theta=0\) is perp to the plane of \(\phi\) - the usual physics convention for spherical polar coords - different to the astronomy/geography convention where Dec=0 is in the plane of Right Ascension.)
Going to Matlab let's generate the \(Y_{l,m}(\theta,\phi)\) patterns on a rectangular grid of spherical polar theta/phi coordinates:
% number of phi values and angular increment nphi=90; da=(2*pi)/nphi; % calc theta/phi values theta=da/2:da:pi; phi=da/2:da:2*pi; % get normalized associated Legendre polynomials l=4; P_lm=legendre(l,cos(theta),'norm'); % look at the output plot(theta,P_lm); legend({'0','1','2','3','4'}); % select desired poly and multiply by complex exponential in phi % note use of the single quote symbol to "flip" to column vector and % that column times row = matrix m=3; Y_lm=sqrt(1/(2*pi))*P_lm(m+1,:)'*exp(i*m*phi); % plot on a rectangular grid imagesc(phi,theta,real(Y_lm)); % plot on Mollweid projection clf; axesm('MapProjection','mollweid'); pcolorm([-90,90],[-180,180],real(Y_lm)) % plot on sphere clf; axesm('MapProjection','globe'); pcolorm([-90,90],[-180,180],real(Y_lm)); view(30,30) axis off
One of the best features of Matlab is that one can extend the language very easily by writing new functions. If we make a file called multipole.m and place the following code in it:
function [Y_lm,theta,phi]=multipole(l,m,nphi) % % Calculate spherical harmonic multipole % angular increment da=(2*pi)/nphi; % calc theta/phi values theta=da/2:da:pi; phi=da/2:da:2*pi; % normalized associated Legendre polynomials P_lm=legendre(l,cos(theta),'norm'); % select desired poly and multiply by complex exponential in phi Y_lm=sqrt(1/(2*pi))*P_lm(abs(m)+1,:)'*exp(i*m*phi); returnthen magically a new command line function "multipole" is available:
% plot the l=4, m=+3 pattern [Y_lm,theta,phi]=multipole(4,3,90); subplot(2,1,1); imagesc(phi,theta,real(Y_lm)); subplot(2,1,2); imagesc(phi,theta,imag(Y_lm)); % the l=4, m=-3 pattern just flips the sign of imag part [Y_lm,theta,phi]=multipole(4,-3,90); subplot(2,1,1); imagesc(phi,theta,real(Y_lm)); subplot(2,1,2); imagesc(phi,theta,imag(Y_lm));Note that the \(Y_{l,m}(\theta,\phi)\) is really two patterns in one container - the real and imaginary parts.
% get the data load topo % plot on a rectangular grid imagesc(topo); axis xy % look at the distribution - why is this a bit wrong? hist(topo(:),100) % see how big the grid is whos topo % make the multipole on the same size grid [Y_lm,theta,phi]=multipole(4,3,size(topo,2)); % find the pixel area [phig,thetag]=meshgrid(phi,theta); da=theta(2)-theta(1); pixarea=da^2*sin(thetag); % integrate product of map and multipole over theta and phi - % this gives the value of a_{4,3} x=topo.*conj(Y_lm).*pixarea; a_lm=sum(x(:)) % look at the weighted map imagesc(real(x)); axis xy % integrate product of multipole and its conjugate - % this checks the normalization - should be unity x=Y_lm.*conj(Y_lm).*pixarea; sum(x(:))
function [a_lm,l,m]=decompose_direct(map,lmax) % % Decompose a pattern on the sphere to yield the a_lm % coefficients by generating each Y_lm pattern, multiplying % and integrating % make non-existant a_lm's be NaN a_lm=complex(NaN,NaN)*zeros(lmax+1,2*lmax+1); for l=0:lmax for m=-l:l [l m] % get the multipole pattern [Y_lm,theta,phi]=multipole(l,m,size(map,2)); % find the pixel area [phig,thetag]=meshgrid(phi,theta); da=theta(2)-theta(1); pixarea=da^2*sin(thetag); % take the integral of the product - note conjugation x=map.*conj(Y_lm).*pixarea; a_lm(l+1,lmax+m+1)=sum(x(:)); end end % for plotting convenience l=0:lmax; m=-lmax:lmax; returnUsing this look at the \(a_{l,m}\)'s.
load topo % get the a_lm's [a_lm,l,m]=decompose_direct(topo,4); % look at a_lm's as number % note imaginary part is zero for m=0 and +/-m are conjugates % of one another a_lm % visualize subplot(2,1,1); imagesc(m,l,real(a_lm)); colorbar subplot(2,1,2); imagesc(m,l,imag(a_lm)); colorbar
% get the a_lm's up to a higher l [a_lm,l,m]=decompose_direct(topo,30); % watch it reconstruct topor=zeros(size(topo)); lmax=size(a_lm,1)-1; for l=0:lmax for m=-l:l % get the multipole [Y_lm,theta,phi]=multipole(l,m,size(topo,2)); topor=topor+Y_lm*a_lm(l+1,lmax+m+1); end clf; axesm('MapProjection','globe'); pcolorm([-90,90],[-180,180],real(topor)); view(-160,30); colormap jet; gridm; axis off title(l) drawnow end % check normalization subplot(2,1,1); imagesc(topo); axis xy; colorbar subplot(2,1,2); imagesc(real(topor)); axis xy; colorbar % check imaginary part small subplot(2,1,2); imagesc(imag(topor)); axis xy; colorbar
The rectangular grid of spherical polar coordinates used above is clumsy - there are lots of very small pixels around the poles. A better option developed specifically for CMB data analysis is Healpix - also see the Healpix paper. Three main features are: i) equal area pixels ii) pixels arranged along equal latitude rings iii) resolution scales by splitting each quadrilateral cell into 4 starting from basic 12 cells.
Healpix was originally Fortran, and was then recoded as C/C++. There is a library of functions and then a few top-level programs built from these - pricipally anafast which goes from \(a_{l,m}\) to map and synfast which goes the other way. There is an incomplete IDL library. There is now a complete library called healpy which can be used interactively with ipython - this is probably the best bet for new users. Unfortunately there is no proper Matlab port. But let's play with a port of a simple low-level function pix2ang_ring.m which allows us to investigate the pixel layout on the sphere:
% resolution specified by "nside" which is power of 2 [theta,phi]=pix2ang_ring(1); % plot flat plot(phi,theta,'.'); % plot on sphere clf; axesm('MapProjection','mollweid','angleunits','radians'); gridm [theta,phi]=pix2ang_ring(1); plotm(pi/2-theta,phi,'b.'); [theta,phi]=pix2ang_ring(2); plotm(pi/2-theta,phi,'r.'); [theta,phi]=pix2ang_ring(4); plotm(pi/2-theta,phi,'g.'); % values of theta along rings unique(theta) % number of rings = nside*4-1 length(unique(theta)) % theta spacing of rings - clearly can't be equally spaced... diff(unique(theta)) % number of phi points around each ring u=unique(theta); for i=1:length(u) n(i)=sum(theta==u(i)); end clf; plot(n,'.-') % so we have polar caps with varying number of pixels around rings % and equatorial block with constant 4*nside around each ring