Spherical harmonics

One way to represent the spherical harmonics is to consider their real and imaginary parts and assign a tone corresponding to their values at each point on the sphere. The first figure in each table shows the real part of \(Y_\ell^m\) with the jet colormap that assigns reddish colors to higher values and bluish colors to lower values. The second figure shows the result with lighting and a single color in three dimensions, with the radius proportional to the absolute value of the real part. Thanks to the identity \(\sin\varphi=\cos(\pi/2-\varphi)\), taking imaginary parts results in similar figures with rotation and symmetry. The formula for \(Y_\ell^m\) in terms of the spherical coordinates \(\theta\) and \(\varphi\) is shown to the right of the figures.

Due to the symmetry \(Y_\ell^{-m}=(-1)^m \overline{Y_\ell^{m}}\) it is only considered the case \(m\ge 0\). Adjusting the parameters in the code below one can change the ranges of \(\ell\) and \(m\). There are actually two programs: one in Octave for generating the images and another in Sagemath for automatically building this page from an HTML document template.



\(\ell=0,\qquad m=0\)
images/spha00 images/spha00_2 \[ \frac{1}{2 \, \sqrt{\pi}} \,\, \]

\(\ell=1,\qquad m=0\)
images/spha10 images/spha10_2 \[ \frac{\sqrt{3}}{2 \, \sqrt{\pi}} (\cos\theta)\, \]

\(\ell=1,\qquad m=1\)
images/spha11 images/spha11_2 \[ -\frac{\sqrt{3} \sqrt{2} \sin\theta}{4 \, \sqrt{\pi}} \,\,e^{i\varphi} \]

\(\ell=2,\qquad m=0\)
images/spha20 images/spha20_2 \[ \frac{\sqrt{5}}{4 \, \sqrt{\pi}} \big(3 \left(\cos\theta\right)^{2} - 1\big)\, \]

\(\ell=2,\qquad m=1\)
images/spha21 images/spha21_2 \[ -\frac{\sqrt{6} \sqrt{5} \sin\theta}{4 \, \sqrt{\pi}} (\cos\theta)\,e^{i\varphi} \]

\(\ell=2,\qquad m=2\)
images/spha22 images/spha22_2 \[ \frac{\sqrt{6} \sqrt{5} \left(\sin\theta\right)^{2}}{8 \, \sqrt{\pi}} \,\,e^{2i\varphi} \]

\(\ell=3,\qquad m=0\)
images/spha30 images/spha30_2 \[ \frac{\sqrt{7}}{4 \, \sqrt{\pi}} \big(5 \left(\cos\theta\right)^{3} - 3 \, \cos\theta\big)\, \]

\(\ell=3,\qquad m=1\)
images/spha31 images/spha31_2 \[ -\frac{\sqrt{7} \sqrt{3} \sin\theta}{8 \, \sqrt{\pi}} \big(5 \left(\cos\theta\right)^{2} - 1\big)\,e^{i\varphi} \]

\(\ell=3,\qquad m=2\)
images/spha32 images/spha32_2 \[ \frac{\sqrt{30} \sqrt{7} \left(\sin\theta\right)^{2}}{8 \, \sqrt{\pi}} (\cos\theta)\,e^{2i\varphi} \]

\(\ell=3,\qquad m=3\)
images/spha33 images/spha33_2 \[ -\frac{\sqrt{7} \sqrt{5} \left(\sin\theta\right)^{3}}{8 \, \sqrt{\pi}} \,\,e^{3i\varphi} \]

\(\ell=4,\qquad m=0\)
images/spha40 images/spha40_2 \[ \frac{3}{16 \, \sqrt{\pi}} \big(35 \left(\cos\theta\right)^{4} - 30 \left(\cos\theta\right)^{2} + 3\big)\, \]

\(\ell=4,\qquad m=1\)
images/spha41 images/spha41_2 \[ -\frac{3 \, \sqrt{5} \sin\theta}{8 \, \sqrt{\pi}} \big(7 \left(\cos\theta\right)^{3} - 3 \, \cos\theta\big)\,e^{i\varphi} \]

\(\ell=4,\qquad m=2\)
images/spha42 images/spha42_2 \[ \frac{3 \, \sqrt{10} \left(\sin\theta\right)^{2}}{16 \, \sqrt{\pi}} \big(7 \left(\cos\theta\right)^{2} - 1\big)\,e^{2i\varphi} \]

\(\ell=4,\qquad m=3\)
images/spha43 images/spha43_2 \[ -\frac{3 \, \sqrt{35} \left(\sin\theta\right)^{3}}{8 \, \sqrt{\pi}} (\cos\theta)\,e^{3i\varphi} \]

\(\ell=4,\qquad m=4\)
images/spha44 images/spha44_2 \[ \frac{3 \, \sqrt{70} \left(\sin\theta\right)^{4}}{32 \, \sqrt{\pi}} \,\,e^{4i\varphi} \]

\(\ell=5,\qquad m=0\)
images/spha50 images/spha50_2 \[ \frac{\sqrt{11}}{16 \, \sqrt{\pi}} \big(63 \left(\cos\theta\right)^{5} - 70 \left(\cos\theta\right)^{3} + 15 \, \cos\theta\big)\, \]

\(\ell=5,\qquad m=1\)
images/spha51 images/spha51_2 \[ -\frac{\sqrt{30} \sqrt{11} \sin\theta}{32 \, \sqrt{\pi}} \big(21 \left(\cos\theta\right)^{4} - 14 \left(\cos\theta\right)^{2} + 1\big)\,e^{i\varphi} \]

\(\ell=5,\qquad m=2\)
images/spha52 images/spha52_2 \[ \frac{\sqrt{210} \sqrt{11} \left(\sin\theta\right)^{2}}{16 \, \sqrt{\pi}} \big(3 \left(\cos\theta\right)^{3} - \cos\theta\big)\,e^{2i\varphi} \]

\(\ell=5,\qquad m=3\)
images/spha53 images/spha53_2 \[ -\frac{\sqrt{35} \sqrt{11} \left(\sin\theta\right)^{3}}{32 \, \sqrt{\pi}} \big(9 \left(\cos\theta\right)^{2} - 1\big)\,e^{3i\varphi} \]

\(\ell=5,\qquad m=4\)
images/spha54 images/spha54_2 \[ \frac{3 \, \sqrt{70} \sqrt{11} \left(\sin\theta\right)^{4}}{32 \, \sqrt{\pi}} (\cos\theta)\,e^{4i\varphi} \]

\(\ell=5,\qquad m=5\)
images/spha55 images/spha55_2 \[ -\frac{3 \, \sqrt{11} \sqrt{7} \left(\sin\theta\right)^{5}}{32 \, \sqrt{\pi}} \,\,e^{5i\varphi} \]



The code

This is the Octave code for the images:


for ll = [0:5]
  for mm = [0:ll]
    figure(1)
    % real part (surface map)
    [Xc, Yc, Zc, Z, Xc2, Yc2, Zc2, Z2] = plot_sph_harm(ll,mm);
    surf(Xc, Yc, Zc, Z, 'EdgeColor','none');
    colormap(jet);
    axis equal off;
    saveas(gcf, sprintf("./images/spha%d%d.jpg", ll, mm))
    figure(2)
    % absolute values of the real part as radius
    [Xc, Yc, Zc, Z, Xc2, Yc2, Zc2, Z2] = plot_sph_harm(ll,mm);
    surf(Xc2, Yc2, Zc2, 0*Z2, 'EdgeColor','none');
    colormap(jet);
    aa = 0.53;
    axis([-aa aa -aa aa -aa aa]);    axis equal off;
    light;
    lighting gouraud; material dull;
    saveas(gcf, sprintf("./images/spha%d%d_2.jpg", ll, mm))
  end
end

%colormaps: viridis, jet, hsv, hot, cool, and gray



It calls the following function:
% plot_sph_harm.m

function [Xc, Yc, Zc, Z, Xc2, Yc2, Zc2, Z2] = plot_sph_harm(l, m)

  [PHI, THETA] = meshgrid(linspace(0, 2*pi, 640), linspace(0, pi, 480));

  Y = sph_harm_octave(l, m, THETA, PHI);

  Z = real(Y);
  Z2 = abs(Z);
  R = 0.5+0.0*Z2;
  if l*l+m*m>0 % exlude the case l=m=0
    MM = max(Z2(:));
    mm = min(Z2(:));
    R = 0.5 * (Z2 - mm) / (MM-mm); % automatic scale
  end

  % convert to Cartesian
  Xc = sin(THETA) .* cos(PHI);
  Yc= sin(THETA) .* sin(PHI);
  Zc = cos(THETA);
  Xc2 = R .* Xc;
  Yc2 = R .* Yc;
  Zc2 = R .* Zc;
end


% This part was done by an AI
function Y = sph_harm_octave(l, m, theta, phi)
  % Computes complex spherical harmonic Y_l^m(θ,φ)
  % Uses Condon-Shortley phase (includes (-1)^m) and Schmidt normalization.
  % theta,phi may be matrices (same size)
  if m < 0
    % relation for negative m: Y_l^{-m} = (-1)^m * conj(Y_l^{m})
    Ypos = sph_harm_octave(l, -m, theta, phi);
    Y = (-1)^m * conj(Ypos);
    return;
  end

  % compute associated Legendre P_l^m(cos theta)
  x = cos(theta);
  Plm = assoc_legendre(l, m, x); % same size as theta

  % normalization factor: sqrt((2l+1)/(4π) * (l-m)!/(l+m)!)
  norm = sqrt((2*l+1)/(4*pi) * factorial(l-m)/factorial(l+m));

  Y = norm * Plm .* exp(1i * m * phi);
end

function P = assoc_legendre(l, m, x)
  % Compute associated Legendre polynomials P_l^m(x) for scalar l,m and array x
  % using recurrence. Handles 0 <= m <= l.
  x = double(x);
  sz = size(x);
  x = x(:);

  % handle m==0 separately
  if m == 0
    P0 = ones(size(x));
    if l == 0
      P = reshape(P0, sz); return;
    end
    P1 = x; % P_1^0(x) = x
    if l == 1
      P = reshape(P1, sz); return;
    end
    Pn_2 = P0; Pn_1 = P1;
    for n = 2:l
      Pn = ((2*n-1).*x.*Pn_1 - (n-1).*Pn_2)./n;
      Pn_2 = Pn_1; Pn_1 = Pn;
    end
    P = reshape(Pn, sz); return;
  end

  % m > 0: use standard initial Pmm
  % P_m^m(x) = (-1)^m * (2m-1)!! * (1 - x^2)^(m/2)
  % compute double factorial (2m-1)!! safely
  df = 1;
  for k = 1:m
    df = df * (2*k - 1);
  end
  Pmm = (-1)^m * df .* (1 - x.^2).^(m/2);

  if m == l
    P = reshape(Pmm, sz); return;
  end

  % P_{m+1}^m = x*(2m+1)*Pmm
  Pm1 = x .* (2*m + 1) .* Pmm;
  if (m+1) == l
    P = reshape(Pm1, sz); return;
  end

  % upward recurrence
  Pn_2 = Pmm; Pn_1 = Pm1;
  for n = m+2 : l
    Pn = ( (2*n - 1) .* x .* Pn_1 - (n + m - 1) .* Pn_2 ) ./ (n - m);
    Pn_2 = Pn_1; Pn_1 = Pn;
  end
  P = reshape(Pn, sz);
end


This is the Sagemath code for the web page:


# Make sphhart.html

theta = var('theta')

N = 5

# Generate Legendre polynomials

Le = [1+0*x,x]

for k in srange(1,N):
  Le.append( ( (2*k+1)/(k+1)*x*Le[k]-k/(k+1)*Le[k-1] ).expand() )


# HTML TEMPLATE FOR TABLE
###
### LLL MMM IMGRNAME IMGINAME LATXDF
templ_html = r'<table style="border-style:solid;border-width:1px;margin-left: 30px; margin-right: auto;"><thead>'
templ_html += '\n' + r'<tr><th colspan="3" style="border-style:solid;border-width:1px;">'
templ_html += '\n' + r'<span class="math inline">'
templ_html += '\n' + r'\(\ell=LLL,\qquad m=MMM\)'
templ_html += '\n' + r'</span></th></tr></thead><tbody><tr>'
templ_html += '\n' + r'<td>'
templ_html += '\n' + r'<img style="clip-path: circle(27%);width: 550px; margin: -70px -110px -75px -130px;" alt="IMGRNAME" src="./IMGRNAME.jpg">'
#templ_html += '\n' + r'<div style="overflow: hidden;width: 230px;height: 200px;">'
#templ_html += '\n' + r'<img style="margin: -120px 0px -300px -270px;" alt="IMGRNAME" src="./IMGRNAME.jpg">'
#templ_html += '\n' + r'</div>'
templ_html += '\n' + r'</td><td>'
templ_html += '\n' + r'<img style="clip-path: circle(27%);width: 550px; margin: -70px -110px -75px -130px;" alt="IMGINAME" src="./IMGINAME.jpg">'
#templ_html += '\n' + r'<img style="width: 130px;" alt="IMGINAME" src="./IMGINAME.jpg">'
templ_html += '\n' + r'</td> <td>'
templ_html += '\n' + r' <span class="math display">'
templ_html += '\n' + r'LATXDF'
templ_html += '\n' + r'</span></td></tr></tbody></table>'
templ_html += '\n' + r'<br>'


# MAIN LOOP

# k is l
table = ''
for k in srange(len(Le)):
  for m in srange(k+1):
    mpol = Le[k]
    if m!=0: mpol = diff( Le[k], x, m)
      
    co = (ZZ['x'](mpol*2^k)).content()
    mpol = 2^k/co*mpol
    c = co/2^k

      
      
    res = latex(mpol(x=cos(theta)))

    # azimuthal
    res = r'\big(' + res.strip() + r'\big)\,e^{' +str(m) + r'i\varphi}'

    # constant
    c *= (-1)^m*sqrt( (2*k+1)*factorial(k-m)/4/pi/factorial(k+m) )
    c = c.simplify()
    c = c.simplify_full()

    # sine
    if m!=0: c = c* (sin(theta))^m
    
    
    res = latex(c) + res
    
    #clean
    res = res.replace(r'\cos\left(\theta\right)^',r'\left(\cos\theta\right)^')
    res = res.replace(r'\cos\left(\theta\right)',r'\cos\theta')
    res = res.replace(r'\, \left(\cos',r'\left(\cos')
    res = res.replace(r'\big(1\big)',r'\,')
    res = res.replace(r'\sin\left(\theta\right)^',r'\left(\sin\theta\right)^')
    res = res.replace(r'\sin\left(\theta\right)',r'\sin\theta')  
    res = res.replace(r'{1i\varphi}',r'{i\varphi}')
    res = res.replace(r'e^{0i\varphi}',r'')  
    res = res.replace(r'\big(\cos\theta\big)',r'(\cos\theta)')  
    
    

    
    res = r'\[' + '\n' + r' ' + res + '\n' + r'\]'

    it_table = templ_html
    it_table = it_table.replace('LLL', str(k))
    it_table = it_table.replace('MMM', str(m))
    it_table = it_table.replace('IMGRNAME', r'images/spha' + str(k) +str(m))
    it_table = it_table.replace('IMGINAME', r'images/spha' + str(k) +str(m)+'_2')
    it_table = it_table.replace('LATXDF', res)
    table += it_table +'\n'
  
#print(table)



def f_save( text ):
  try:
    with open( 'sphhar.html' , 'w') as sali:
      sali.write( text )
  except IOError as e:
    sys.exit('Error writing on sphhar.html')


try:
  with open('sphhart.html', encoding='UTF-8') as entr:
#  with open('sphhart.html', encoding='iso-8859-15') as entr:
    text_file = entr.read()
    L = text_file.split('<!--REPLACE-->',1)
    text_file = L[0] + table + L[1]
    f_save( text_file )

except IOError as e:
  sys.exit('sphhart.html not found')