Miepython Readthedocs Io en Stable - 1.3.3
Miepython Readthedocs Io en Stable - 1.3.3
Release 1.3.3
Scott Prahl
1 Heads up! 3
2 Using miepython 5
Index 123
i
ii
miepython, Release 1.3.3
miepython is a pure Python module to calculate light scattering by non-absorbing, partially-absorbing, or perfectly
conducting spheres. Mie theory is used, following the procedure described by Wiscombe. This code has been validated
against his results.
This code provides functions for calculating the extinction efficiency, scattering efficiency, backscattering, and scat-
tering asymmetry. Moreover, a set of angles can be given to calculate the scattering at various angles for a sphere.
CONTENTS 1
miepython, Release 1.3.3
2 CONTENTS
CHAPTER
ONE
HEADS UP!
When comparing different Mie scattering codes, make sure that you’re aware of the conventions used by each code.
miepython makes the following assumptions
• the imaginary part of the complex index of refraction for absorbing spheres is negative.
• the scattering phase function is normalized so it equals the single scattering albedo when integrated over 4
steradians.
3
miepython, Release 1.3.3
TWO
USING MIEPYTHON
2. or run this code in the cloud using Google Collaboratory by selecting the Jupyter notebook that interests you.
5
miepython, Release 1.3.3
THREE
#!/usr/bin/env python3
"""
Plot the extinction efficiency as a function of particle size
for non-absorbing and absorbing spheres
"""
import numpy as np
import matplotlib.pyplot as plt
import miepython
x = np.linspace(0.1,100,300)
7
miepython, Release 1.3.3
#!/usr/bin/env python3
"""
Plot the scattering efficiency as a function of wavelength for 4micron glass spheres
"""
import numpy as np
import matplotlib.pyplot as plt
import miepython
radius = 2 # in microns
lam = np.linspace(0.2,1.2,200) # also in microns
x = 2*np.pi*radius/lam
# from https://refractiveindex.info/?shelf=glass&book=BK7&page=SCHOTT
m2 = 1+1.03961212/(1-0.00600069867/lam**2)
m2 += 0.231792344/(1-0.0200179144/lam**2)
m2 += 1.01046945/(1-103.560653/lam**2)
m = np.sqrt(m2)
#!/usr/bin/env python3
"""
Plot the scattering cross section as a function of wavelength
for 1 micron water droplets
"""
import numpy as np
import matplotlib.pyplot as plt
import miepython
# from https://refractiveindex.info/?shelf=main&book=H2O&page=Daimon-24.0C
m2 = 1+5.666959820E-1/(1-5.084151894E-3/lam**2)
m2 += 1.731900098E-1/(1-1.818488474E-2/lam**2)
m2 += 2.095951857E-2/(1-2.625439472E-2/lam**2)
m2 += 1.125228406E-1/(1-1.073842352E1/lam**2)
m = np.sqrt(m2)
plt.plot(lam*1000, qsca)
plt.title(r"Water Droplets (1 µm diameter)")
plt.xlabel("Wavelength (nm)")
plt.ylabel(r"Scattering Cross Section (µm$^2$)")
plt.show()
#!/usr/bin/env python3
"""
Plot the scattering cross section as a function of wavelength for 100nm gold spheres
"""
import numpy as np
import matplotlib.pyplot as plt
import miepython
# from https://refractiveindex.info/?shelf=main&book=Au&page=Johnson
# wavelength in microns
ref_lam=np.array([0.1879,0.1916,0.1953,0.1993,0.2033,0.2073,0.2119,0.2164,
0.2214,0.2262,0.2313,0.2371,0.2426,0.2490,0.2551,0.2616,
0.2689,0.2761,0.2844,0.2924,0.3009,0.3107,0.3204,0.3315,
0.3425,0.3542,0.3679,0.3815,0.3974,0.4133,0.4305,0.4509,
0.4714,0.4959,0.5209,0.5486,0.5821,0.6168,0.6595,0.7045,
0.7560,0.8211,0.8920,0.9840,1.0880,1.2160,1.3930,1.6100,1.9370])
ref_n=np.array([1.28,1.32,1.34,1.33,1.33,1.30,1.30,1.30,1.30,1.31,1.30,
1.32,1.32,1.33,1.33,1.35,1.38,1.43,1.47,1.49,1.53,1.53,
1.54,1.48,1.48,1.50,1.48,1.46,1.47,1.46,1.45,1.38,1.31,
1.04,0.62,0.43,0.29,0.21,0.14,0.13,0.14,0.16,0.17,0.22,
0.27,0.35,0.43,0.56,0.92])
ref_k=np.array([1.188,1.203,1.226,1.251,1.277,1.304,1.350,1.387,1.427,
1.460,1.497,1.536,1.577,1.631,1.688,1.749,1.803,1.847,
1.869,1.878,1.889,1.893,1.898,1.883,1.871,1.866,1.895,
1.933,1.952,1.958,1.948,1.914,1.849,1.833,2.081,2.455,
2.863,3.272,3.697,4.103,4.542,5.083,5.663,6.350,7.150,
8.145,9.519,11.21,13.78])
plt.subplots(3,1,figsize=(9,9))
plt.subplot(311)
plt.plot(ref_lam*1000, ref_n, 'ob')
plt.plot(ref_lam*1000, -ref_k, 'sr')
plt.title("Gold Spheres 100nm diameter")
plt.xticks([])
plt.text(700, 1, "real refractive index", color='blue')
plt.text(1100, -6, "imaginary refractive index", color='red')
plt.subplot(312)
plt.plot(ref_lam*1000, 1000/mu_a, 'ob')
plt.ylabel("Absorption Depth [nm]")
Scott Prahl
Mar 2021, Version 5
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
When a monochromatic plane wave is incident on a sphere, it scatters and absorbs light depending on the properties
of the light and sphere. If the sphere is in a vacuum, then the complex index of refraction of the sphere is
Sign Convention
The sign of the imaginary part of the index of refraction in miepython is assumed negative (as shown above). This
convention is standard for atmospheric science and follows that of van de Hulst.
Absorption Coefficient
The imaginary part of the refractive index is a non-dimensional representation of light absorption. This can be seen by
writing out the equation for a monochromatic, planar electric field
ℰ(𝑧, 𝑡) = ℰ0 𝑒𝑗(𝑘𝑧−𝜔𝑡)
Let’s import and plot some data from the M.S. Thesis of D. Segelstein, “The Complex Refractive Index of Water”,
University of Missouri–Kansas City, (1981) to get some sense the complex index of refraction. The imaginary part
shows absorption peaks at 3 and 6 microns, as well as the broad peak starting at 10 microns.
h2o_lam = h2o[:,0]
h2o_mre = h2o[:,1]
h2o_mim = h2o[:,2]
#plot it
plt.plot(h2o_lam,h2o_mre)
plt.plot(h2o_lam,h2o_mim*3)
plt.plot((1,15),(1.333,1.333))
plt.xlim((1,15))
plt.ylim((0,1.8))
plt.xlabel('Wavelength (microns)')
plt.ylabel('Refractive Index')
plt.annotate(r'$m_\mathrm{re}$', xy=(3.4,1.5))
plt.annotate(r'$m_\mathrm{im}\,\,(3\times)$', xy=(3.4,0.5))
plt.annotate(r'$m_\mathrm{re}=1.333$', xy=(10,1.36))
plt.show()
plt.scatter(au_lam,au_mre,s=1,color='blue')
plt.scatter(au_lam,au_mim,s=1,color='red')
plt.xlim((0.2,2))
plt.xlabel('Wavelength (microns)')
plt.ylabel('Refractive Index')
plt.annotate(r'$m_\mathrm{re}$', xy=(1.0,0.5),color='blue')
plt.annotate(r'$m_\mathrm{im}$', xy=(1.0,8),color='red')
plt.show()
plt.plot(h2o_lam,mua)
plt.xlim((0.1,20))
plt.ylim((0,1.5))
plt.xlabel('Wavelength (microns)')
plt.ylabel('Absorption Coefficient (1/micron)')
plt.title('Water')
plt.show()
Size Parameters
Size Parameter 𝑥
The sphere size relative to the wavelength is called the size parameter 𝑥
𝑥 = 2𝜋𝑟/𝜆
𝑤ℎ𝑒𝑟𝑒 : 𝑚𝑎𝑡ℎ : ‘𝑟‘𝑖𝑠𝑡ℎ𝑒𝑟𝑎𝑑𝑖𝑢𝑠𝑜𝑓 𝑡ℎ𝑒𝑠𝑝ℎ𝑒𝑟𝑒.
[6]: N=500
m=1.5
x = np.linspace(0.1,20,N) # also in microns
plt.plot(x,qsca)
plt.xlabel("Sphere Size Parameter x")
plt.ylabel("Scattering Efficiency")
plt.title("index of refraction m=1.5")
plt.show()
Size Parameter 𝜌
The value 𝜌 is also sometimes used to facilitate comparisons for spheres with different indicies of refraction
𝜌 = 2𝑥(𝑚 − 1)
𝑁 𝑜𝑡𝑒𝑡ℎ𝑎𝑡𝑤ℎ𝑒𝑛 : 𝑚𝑎𝑡ℎ : ‘𝑚 = 1.5‘𝑎𝑛𝑑𝑡ℎ𝑒𝑟𝑒𝑓 𝑜𝑟𝑒 : 𝑚𝑎𝑡ℎ : ‘𝜌 = 𝑥‘.
As can be seen in the graph below, the scattering for spheres with different indicies of refraction pretty similar when
plotted against 𝜌, but no so obvious when plotted against 𝑥
[7]: N=500
m=1.5
rho = np.linspace(0.1,20,N) # also in microns
(continues on next page)
m = 1.5
x15 = rho/2/(m-1)
qext, sca15, qback, g = miepython.mie(m,x15)
m = 1.1
x11 = rho/2/(m-1)
qext, sca11, qback, g = miepython.mie(m,x11)
ax1.plot(rho,sca11,color='blue')
ax1.plot(rho,sca15,color='red')
ax1.set_xlabel(r"Size parameter $\rho$")
ax1.set_ylabel("Scattering Efficiency")
ax1.annotate('m=1.5', xy=(10,3.3), color='red')
ax1.annotate('m=1.1', xy=(8,1.5), color='blue')
ax2.plot(x11,sca11,color='blue')
ax2.plot(x15,sca15,color='red')
ax2.set_xlabel(r"Size parameter $x$")
ax2.annotate('m=1.5', xy=(5,4), color='red')
ax2.annotate('m=1.1', xy=(40,1.5), color='blue')
plt.show()
Embedded spheres
[8]: N=500
m=1.0
r=500 # nm
lambdaa = np.linspace(300,800,N) # also in nm
plt.plot(lambdaa,qsca)
plt.xlabel("Wavelength (nm)")
plt.ylabel("Scattering Efficiency")
plt.title("One micron diameter air bubbles in water")
plt.show()
Multiple scatterers
N=13
xx = 3.5 * np.random.rand(N, 1) - 1.5
yy = 5 * np.random.rand(N, 1) - 2.5
plt.scatter(xx,yy,s=40,color='red')
for i in range(N):
plt.plot(scat*np.cos(theta/180*np.pi)+xx[i],scat*np.sin(theta/180*np.pi)+yy[i],
˓→color='red')
plt.plot([-5,7],[0,0],':k')
plt.annotate('incoming\nirradiance', xy=(-4.5,-2.3),ha='left',color='blue',
˓→fontsize=14)
for i in range(6):
y0 = i -2.5
plt.annotate('',xy=(-1.5,y0),xytext=(-5,y0),arrowprops=dict(arrowstyle="->",color=
˓→'blue'))
plt.annotate('unscattered\nirradiance', xy=(3,-2.3),ha='left',color='blue',
˓→fontsize=14) (continues on next page)
#plt.annotate('scattered\nspherical\nwave', xy=(0,1.5),ha='left',color='red',
˓→fontsize=16)
#plt.annotate('',xy=(2.5,2.5),xytext=(0,0),arrowprops=dict(arrowstyle="->",color='red
˓→'))
#plt.annotate(r'$\theta$',xy=(2,0.7),color='red',fontsize=14)
#plt.annotate('',xy=(2,2),xytext=(2.7,0),arrowprops=dict(connectionstyle="arc3,rad=0.2
˓→", arrowstyle="<->",color='red'))
plt.xlim(-5,7)
plt.ylim(-3,3)
plt.axis('off')
plt.show()
Scott Prahl
Mar 2021, Version 5
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
When a monochromatic plane wave is incident on a sphere, it scatters and absorbs light depending on the properties
of the light and sphere. The sphere has radius 𝑟 and index of refraction 𝑚 = 𝑚re − 𝑗 𝑚im . The sphere size parameter
𝑥 = 2𝜋𝑥/𝜆 where 𝜆 is the wavelength of the plane wave in a vacuum.
[3]: # import the Johnson and Christy data for silver
ag = np.genfromtxt('https://refractiveindex.info/tmp/data/main/Au/Johnson.txt',
˓→delimiter='\t')
plt.scatter(ag_lam*1000,ag_mre,s=2,color='blue')
plt.scatter(ag_lam*1000,ag_mim,s=2,color='red')
plt.xlim(300,800)
plt.ylim(0,5)
plt.xlabel('Wavelength (nm)')
plt.ylabel('Refractive Index')
plt.text(350, 1.2, '$m_{re}$', color='blue', fontsize=14)
plt.text(350, 2.2, '$m_{im}$', color='red', fontsize=14)
plt.show()
Cross Sections
x = 2*np.pi*r/ag_lam;
m = ag_mre - 1.0j * ag_mim
qext, qsca, qback, g = miepython.mie(m,x)
absorb = (qext - qsca) * np.pi * r**2
scatt = qsca * np.pi * r**2
extinct = qext* np.pi * r**2
plt.plot(ag_lam*1000,absorb,color='blue')
plt.plot(ag_lam*1000,scatt,color='red')
plt.plot(ag_lam*1000,extinct,color='green')
plt.text(350, 0.35,'$\sigma_{abs}$', color='blue', fontsize=14)
plt.text(350, 0.54,'$\sigma_{sca}$', color='red', fontsize=14)
plt.text(350, 0.84,'$\sigma_{ext}$', color='green', fontsize=14)
plt.xlabel("Wavelength (nm)")
plt.ylabel("Cross Section (1/microns$^2$)")
plt.title("Cross Sections for %.1f$\mu$m Silver Spheres" % (r*2))
plt.xlim(300,800)
plt.show()
The scattering cross section may be related to the transmission of a beam through a dispersion of scatterers of equal
size. For 𝜌 particles per unit volume, the attenuation due to scattering is
𝑑𝐼
− = 𝜌𝜎sca 𝐼
𝑑𝑥
The transmission is
or
Kerker, p. 38.
For plane-wave radiation incident on a scattering object or a scattering medium, the ratio of the intensity [W/sr]
scattered in the direction toward the source to the incident irradiance [W/area].
1. So defined, the backscattering cross section has units of area per unit solid angle.
2. In common usage, synonymous with radar cross section, although this can be confusing because the radar cross
section is 4𝜋 times the backscattering cross section as defined above and has units of area.
If 𝑄𝑠𝑐𝑎 [unitless] is the backscattering efficiency then the scattering cross section 𝐶𝑠𝑐𝑎 [area]
Thus if 𝑄𝑏𝑎𝑐𝑘 [unitless] is the backscattering efficiency then the scattering cross section 𝐶𝑏𝑎𝑐𝑘 [area]
Now the phase function is normalized so that (𝑆1 (𝜃) has units of sr−0.5
|𝑆1 (−180∘ )|2 = |𝑆2 (−180∘ )|2 = |𝑆1 (180∘ )|2 = |𝑆2 (180∘ )|2
The differential scattering cross section [area/sr] in the backwards direction will be
⃒
𝑑𝐶𝑠𝑐𝑎 ⃒⃒
= 𝐶𝑠𝑐𝑎 |𝑆1 (−180∘ )|2
𝑑Ω ⃒180∘
m = 1.5
x = a * k
geometric_cross_section = np.pi * a**2
theta = np.linspace(-180,180,180)
mu = np.cos(theta/180*np.pi)
s1,s2 = miepython.mie_S1_S2(m,x,mu)
phase = (abs(s1[0])**2+abs(s2[0])**2)/2
unpolarized = 0.09847897663826231
|s1[-180]|**2 = 0.09847897663826233
|s2[-180]|**2 = 0.09847897663826233
|s1[ 180]|**2 = 0.09847897663826233
|s2[ 180]|**2 = 0.09847897663826233
Csca = 0.0011392154570612732
Cback = 0.0014098056923279039
4*pi*Csca*p(180) = 0.0014098056925207427
Efficiencies
where 𝑄𝑠𝑐𝑎 is the scattering efficiency and 𝑄𝑎𝑏𝑠 is the absorption efficiency. 𝑄𝑠𝑐𝑎 and 𝑄𝑒𝑥𝑡 are determined by the
Mie scattering program and 𝑄𝑎𝑏𝑠 is obtained by subtraction.
[6]: r = 0.3 #radius in microns
x = 2*np.pi*r/ag_lam;
m = ag_mre - 1.0j * ag_mim
qext, qsca, qback, g = miepython.mie(m,x)
plt.plot(ag_lam*1000,qext - qsca,color='blue')
plt.plot(ag_lam*1000,qsca,color='red')
plt.plot(ag_lam*1000,qext,color='green')
plt.text(350, 1.2,'$Q_{abs}$', color='blue', fontsize=14)
plt.text(350, 1.9,'$Q_{sca}$', color='red', fontsize=14)
plt.text(350, 3.0,'$Q_{ext}$', color='green', fontsize=14)
plt.xlabel("Wavelength (nm)")
plt.ylabel("Efficiency (-)")
plt.title("Mie Efficiencies for %.1f$\mu$m Silver Spheres" % (r*2))
plt.xlim(300,800)
plt.show()
Radiation Pressure
and is the momentum given to the scattering particle [van de Hulst, p. 13] in the direction of the incident wave. The
radiation pressure cross section 𝐶𝑝𝑟 is just the efficiency multiplied by the geometric cross section
The radiation pressure cross section 𝐶𝑝𝑟 can be interpreted as the area of a black wall that would receive the same
force from the same incident wave. The actual force on the particle is is
𝐶𝑝𝑟
𝐹 = 𝐸0
𝑐
where 𝐸0 is the irradiance (W/m:math:^2) on the sphere and 𝑐 is the velocity of the radiation in the medium
x = 2*np.pi*r/ag_lam;
m = ag_mre - 1.0j * ag_mim
qext, qsca, qback, g = miepython.mie(m,x)
qpr = qext - g*qsca
plt.plot(ag_lam*1000,qpr,color='blue')
plt.xlabel("Wavelength (nm)")
plt.ylabel("Efficiency $Q_{pr}$ (-)")
plt.title("Radiation Pressure Efficiency for %.1f$\mu$m Silver Spheres" % (r*2))
plt.xlim(300,800)
plt.ylim(1,2.5)
plt.show()
van de Hulst has a nice graph of backscattering efficiency that we can replicate
[8]: x = np.linspace(0.1,4,50)
m = 3.41-1.94j
qext, qsca, qback, g = miepython.mie(m,x)
plt.plot(x,qback)
plt.text(0.6,0,"m=3.41-1.94j")
m = 10000
qext, qsca, qback, g = miepython.mie(m,x)
plt.plot(x,qback)
plt.text(1.2,3.0,"m=10,000")
plt.xlabel("Size Parameter")
plt.ylabel(r"$Q_{back}$")
plt.title("van de Hulst Figure 61")
plt.grid(True)
plt.show()
[ ]:
Scott Prahl
Mar 2021, Version 7
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
Mie scattering describes the special case of the interaction of light passing through a non-absorbing medium with a
single embedded spherical object. The sphere itself can be non-absorbing, moderately absorbing, or perfectly absorb-
ing.
Geometry
Specifically, the scattering function 𝑝(𝜃𝑖 , 𝜑𝑖 , 𝜃𝑜 , 𝜑𝑜 ) describes the amount of light scattered by a particle for light
incident at an angle (𝜃𝑖 , 𝜑𝑖 ) and exiting the particle (in the far field) at an angle (𝜃𝑜 , 𝜑𝑜 ). For simplicity, the scattering
function is often assumed to be rotationally symmetric (it is, obviously, for spherical scatterers) and that the angle that
the light is scattered into only depends the 𝜃 = 𝜃𝑜 − 𝜃𝑖 . In this case, the scattering function can be written as 𝑝(𝜃).
Finally, the angle is often replaced by 𝜇 = cos 𝜃 and therefore the phase function becomes just 𝑝(𝜇).
The figure below shows the basic idea. An incoming monochromatic plane wave hits a sphere and produces in the far
field two separate monochromatic waves — a slightly attenuated unscattered planar wave and an outgoing spherical
wave.
Obviously. the scattered light will be cylindrically symmetric about the ray passing through the center of the sphere.
[3]: t = np.linspace(0,2*np.pi,100)
xx = np.cos(t)
yy = np.sin(t)
fig,ax=plt.subplots(figsize=(10,8))
plt.axes().set_aspect('equal')
plt.plot(xx,yy)
plt.plot([-5,7],[0,0],'--k')
plt.annotate('scattered\nspherical\nwave', xy=(0,1.5),ha='left',color='red',
˓→fontsize=16)
plt.annotate('',xy=(2.5,2.5),xytext=(0,0),arrowprops=dict(arrowstyle="->",color='red
˓→'))
plt.annotate(r'$\theta$',xy=(2,0.7),color='red',fontsize=14)
plt.annotate('',xy=(2,2),xytext=(2.7,0),arrowprops=dict(connectionstyle="arc3,rad=0.2
˓→", arrowstyle="<->",color='red'))
plt.xlim(-5,7)
plt.ylim(-3,3)
plt.axis('off')
plt.show()
Scattered Wave
[4]: fig,ax=plt.subplots(figsize=(10,8))
plt.axes().set_aspect('equal')
plt.scatter([0],[0],s=30)
m = 1.5
x = np.pi/3
theta = np.linspace(-180,180,180)
mu = np.cos(theta/180*np.pi)
scat = 15 * miepython.i_unpolarized(m,x,mu)
plt.plot(scat*np.cos(theta/180*np.pi),scat*np.sin(theta/180*np.pi))
for i in range(12):
ii = i*15
xx = scat[ii]*np.cos(theta[ii]/180*np.pi)
yy = scat[ii]*np.sin(theta[ii]/180*np.pi)
# print(xx,yy)
plt.annotate('',xy=(xx,yy),xytext=(0,0),arrowprops=dict(arrowstyle="->",color='red
˓→'))
plt.xlim(-5,7)
plt.ylim(-3,3)
#plt.axis('off')
plt.show()
So the scattering function or phase function has at least three reasonable normalizations that involve integrating over
all 4𝜋 steradians. Below 𝑑Ω = sin 𝜃𝑑𝜃 𝑑𝜑 is a differential solid angle
∫︁
𝑝(𝜃, 𝜑) 𝑑Ω = 1 (3.1)
4𝜋
∫︁
𝑝(𝜃, 𝜑) 𝑑Ω = 4𝜋
(3.2)
4𝜋
∫︁
𝑝(𝜃, 𝜑) 𝑑Ω = 𝑎 Used by miepython
(3.3)
4𝜋
(3.4)
For example, a circular detector with radius 𝑟𝑑 at a distance 𝑅 will subtend an angle
𝜋𝑟𝑑2
Ω=
𝑅2
(assuming 𝑟𝑑 ≪ 𝑅). Now if 𝑃0 of light is scattered by a sphere then the scattered power on the detector will be
𝑃𝑑 = 𝑃0 · Ω · 𝑝(𝜃, 𝜑)
Examples
If unpolarized light hits the sphere, then there are no polarization effects to worry about. It is pretty easy to generate a
plot to show how scattering changes with angle.
[5]: m = 1.5
x = np.pi/3
theta = np.linspace(-180,180,180)
mu = np.cos(theta/180*np.pi)
scat = miepython.i_unpolarized(m,x,mu)
fig,ax = plt.subplots(1,2,figsize=(12,5))
ax=plt.subplot(121, projection='polar')
ax.plot(theta/180*np.pi,scat)
ax.set_rticks([0.05, 0.1,0.15])
ax.set_title("m=1.5, Sphere Diameter = $\lambda$/3")
plt.subplot(122)
plt.plot(theta,scat)
plt.xlabel('Exit Angle [degrees]')
plt.ylabel('Unpolarized Scattered light [1/sr]')
plt.title('m=1.5, Sphere Diameter = $\lambda$/3')
plt.ylim(0.00,0.2)
plt.show()
[6]: m = 1.33
lambda0 = 632.8 # nm
d = 200 # nm
theta = np.linspace(-180,180,180)
mu = np.cos(theta/180*np.pi)
fig,ax = plt.subplots(1,2,figsize=(12,5))
ax=plt.subplot(121, projection='polar')
ax.plot(theta/180*np.pi,Ipar)
ax.plot(theta/180*np.pi,Iper)
plt.subplot(122)
plt.plot(theta,Ipar)
plt.plot(theta,Iper)
plt.show()
Rayleigh Scattering
Classic Rayleigh scattering treats small particles with natural (unpolarized) light.
The solid black line denotes the total scattered intensity. The red dashed line is light scattered that is polarized
perpendicular to the plane of the graph and the blue dotted line is for light parallel to the plane of the graph.
(Compare with van de Hult, Figure 10)
[7]: m = 1.3
x = 0.01
theta = np.linspace(-180,180,180)
mu = np.cos(theta/180*np.pi)
ipar = miepython.i_par(m,x,mu)/2
iper = miepython.i_per(m,x,mu)/2
iun = miepython.i_unpolarized(m,x,mu)
fig,ax = plt.subplots(1,2,figsize=(12,5))
ax=plt.subplot(121, projection='polar')
ax.plot(theta/180*np.pi,iper,'r--')
ax.plot(theta/180*np.pi,ipar,'b:')
ax.plot(theta/180*np.pi,iun,'k')
ax.set_rticks([0.05, 0.1,0.15])
plt.title('m=%.2f, Sphere Parameter = %.2f' %(m,x))
plt.subplot(122)
plt.plot(theta,iper,'r--')
plt.plot(theta,ipar,'b:')
plt.plot(theta,iun,'k')
plt.show()
Sometimes one would like the scattering function normalized so that the integral over all 4𝜋 steradians to be the
scattering cross section
𝜋𝑑2
𝜎𝑠𝑐𝑎 = 𝑄𝑠𝑐𝑎
4
The differential scattering cross section d𝜎𝑠𝑐𝑎 𝑑Ω Since the unpolarized scattering normalized so its integral is the
single scattering albedo, this means that
∫︁
𝑄𝑠𝑐𝑎
= 𝑝(𝜇) sin 𝜃 𝑑𝜃𝑑𝜑
𝑄𝑒𝑥𝑡 4𝜋
and therefore the differential scattering cross section can be obtained miepython using
d = 1700e-9 # m
x = 2 * np.pi/lambda0 * d/2
geometric_cross_section = np.pi * d**2/4 * 1e4 # cm**2
qext, qsca, qback, g = miepython.mie(m,x)
sigma_sca = geometric_cross_section * qext * miepython.i_unpolarized(m,x,mu)
plt.semilogy(theta, sigma_sca*1e-3, color='blue')
plt.text(15, sigma_sca[0]*3e-4, "%.0fnm\n(x10$^{-3}$)" % (d*1e9), color='blue')
d = 170e-9 # m
x = 2 * np.pi/lambda0 * d/2
geometric_cross_section = np.pi * d**2/4 * 1e4 # cm**2
qext, qsca, qback, g = miepython.mie(m,x)
sigma_sca = geometric_cross_section * qext * miepython.i_unpolarized(m,x,mu)
plt.semilogy(theta, sigma_sca, color='red')
plt.text(110, sigma_sca[-1]/2, "%.0fnm" % (d*1e9), color='red')
d = 17e-9 # m
x = 2 * np.pi/lambda0 * d/2
geometric_cross_section = np.pi * d**2/4 * 1e4 # cm**2
qext, qsca, qback, g = miepython.mie(m,x)
sigma_sca = geometric_cross_section * qext * miepython.i_unpolarized(m,x,mu)
plt.semilogy(theta, sigma_sca*1e6, color='green')
plt.text(130, sigma_sca[-1]*1e6, "(x10$^6$)\n%.0fnm" % (d*1e9), color='green')
Normalization revisited
Start with uniformly distributed scattering angles that are evenly spaced over the cosine of the scattered angle.
Specifically, to ensure proper normalization, the integral of the scattering function over all solid angles must be unity
∫︁ 2𝜋 ∫︁ 𝜋
𝑎= 𝑝(𝜃, 𝜑) sin 𝜃 𝑑𝜃 𝑑𝜑
0 0
or with a change of variables 𝜇 = cos 𝜃 and using the symmetry to the integral in 𝜑
∫︁ 1
𝑎 = 2𝜋 𝑝(𝜇) 𝑑𝜇
−1
This integral can be done numerically by simply summing all the rectangles
𝑁
∑︁
𝑎 = 2𝜋 𝑝(𝜇𝑖 ) ∆𝜇𝑖
𝑖=0
The total integral total= in the title should match the albedo a=.
For this non-strongly peaked scattering function, the simple integration remains close to the expected value.
[9]: m = 1.5
x = 1
mu = np.linspace(-1,1,501)
intensity = miepython.i_unpolarized(m,x,mu)
qext, qsca, qback, g = miepython.mie(m,x)
a = qsca/qext
plt.plot(mu,intensity)
plt.xlabel(r'$\cos(\theta)$')
plt.ylabel('Unpolarized Scattering Intensity [1/sr]')
plt.title('m=%.3f%+.3fj, x=%.2f, a=%.3f, total=%.3f'%(m.real,m.imag,x,a, total))
plt.show()
Aagin the total integral total= in the title should match the albedo a=.
For this non-strongly peaked scattering function, the simple integration remains close to the expected value.
plt.plot(mu,intensity)
plt.xlabel(r'$\cos(\theta)$')
plt.ylabel('Unpolarized Scattering Intensity [1/sr]')
plt.title('m=%.3f%+.3fj, x=%.2f, a=%.3f, total=%.3f'%(m.real,m.imag,x,a, total))
plt.show()
The total integral total in the title should match the albedo 𝑎.
For this non-strongly peaked scattering function, even spacing in 𝜃 improves the accuracy of the integration.
[11]: m = 1.5-1.5j
x = 1
theta = np.linspace(0,180,361)*np.pi/180
mu = np.cos(theta)
intensity = miepython.i_unpolarized(m,x,mu)
qext, qsca, qback, g = miepython.mie(m,x)
a = qsca/qext
plt.plot(mu,intensity)
plt.xlabel(r'$\cos(\theta)$')
plt.ylabel('Unpolarized Scattering Intensity [1/sr]')
plt.title('m=%.3f%+.3fj, x=%.2f, a=%.3f, total=%.3f'%(m.real,m.imag,x,a, total))
plt.show()
Wiscombe normalizes as
∫︁
𝑝(𝜃, 𝜑) 𝑑Ω = 𝜋𝑥2 𝑄𝑠𝑐𝑎
4𝜋
[12]: """
MIEV0 Test Case 14: Refractive index: real 1.500 imag -1.000E+00, Mie size
˓→parameter = 1.000
Angle Cosine S-sub-1 S-sub-2
˓→Intensity Deg of Polzn
0.00 1.000000 5.84080E-01 1.90515E-01 5.84080E-01 1.90515E-01 3.77446E-
˓→01 0.0000
30.00 0.866025 5.65702E-01 1.87200E-01 5.00161E-01 1.45611E-01 3.13213E-
˓→01 -0.1336
60.00 0.500000 5.17525E-01 1.78443E-01 2.87964E-01 4.10540E-02 1.92141E-
˓→01 -0.5597
90.00 0.000000 4.56340E-01 1.67167E-01 3.62285E-02 -6.18265E-02 1.20663E-
˓→01 -0.9574
"""
x=1.0
m=1.5-1.0j
mu=np.cos(np.linspace(0,90,4) * np.pi/180)
unpolar_miev = np.array([3.77446E-01,3.13213E-01,1.92141E-01,1.20663E-01])
unpolar_miev /= np.pi * qsca * x**2 # normalized to 1
ratio = unpolar_miev/unpolar
[13]: """
MIEV0 Test Case 10: Refractive index: real 1.330 imag -1.000E-05, Mie size
˓→parameter = 100.000
Angle Cosine S-sub-1 S-sub-2
˓→Intensity Deg of Polzn
0.00 1.000000 5.25330E+03 -1.24319E+02 5.25330E+03 -1.24319E+02 2.
˓→76126E+07 0.0000
30.00 0.866025 -5.53457E+01 -2.97188E+01 -8.46720E+01 -1.99947E+01 5.
˓→75775E+03 0.3146
60.00 0.500000 1.71049E+01 -1.52010E+01 3.31076E+01 -2.70979E+00 8.
˓→13553E+02 0.3563
90.00 0.000000 -3.65576E+00 8.76986E+00 -6.55051E+00 -4.67537E+00 7.
˓→75217E+01 -0.1645
"""
x=100.0
m=1.33-1e-5j
mu=np.cos(np.linspace(0,90,4) * np.pi/180)
unpolar_miev = np.array([2.76126E+07,5.75775E+03,8.13553E+02,7.75217E+01])
unpolar_miev /= np.pi * qsca * x**2 # normalized to 1
ratio = unpolar_miev/unpolar
[14]: """
MIEV0 Test Case 7: Refractive index: real 0.750 imag 0.000E+00, Mie size
˓→parameter = 10.000
Angle Cosine S-sub-1 S-sub-2
˓→Intensity Deg of Polzn
0.00 1.000000 5.58066E+01 -9.75810E+00 5.58066E+01 -9.75810E+00 3.
˓→20960E+03 0.0000
30.00 0.866025 -7.67288E+00 1.08732E+01 -1.09292E+01 9.62967E+00 1.
˓→94639E+02 0.0901
60.00 0.500000 3.58789E+00 -1.75618E+00 3.42741E+00 8.08269E-02 1.
˓→38554E+01 -0.1517
90.00 0.000000 -1.78590E+00 -5.23283E-02 -5.14875E-01 -7.02729E-01 1.
˓→97556E+00 -0.6158
"""
x=10.0
m=0.75
mu=np.cos(np.linspace(0,90,4) * np.pi/180)
unpolar_miev = np.array([3.20960E+03,1.94639E+02,1.38554E+01,1.97556E+00])
unpolar_miev /= np.pi * qsca * x**2 # normalized to 1
ratio = unpolar_miev/unpolar
[15]: """
BHMie Test Case 14, Refractive index = 1.5000-1.0000j, Size parameter = 1.0000
Angle Cosine S1 S2
0.00 1.0000 -8.38663e-01 -8.64763e-01 -8.38663e-01 -8.64763e-01
0.52 0.8660 -8.19225e-01 -8.61719e-01 -7.21779e-01 -7.27856e-01
1.05 0.5000 -7.68157e-01 -8.53697e-01 -4.19454e-01 -3.72965e-01
1.57 0.0000 -7.03034e-01 -8.43425e-01 -4.44461e-02 6.94424e-02
"""
x=1.0
m=1.5-1j
mu=np.cos(np.linspace(0,90,4) * np.pi/180)
s1_bh = np.empty(4,dtype=np.complex)
s1_bh[0] = -8.38663e-01 - 8.64763e-01*1j
s1_bh[1] = -8.19225e-01 - 8.61719e-01*1j
s1_bh[2] = -7.68157e-01 - 8.53697e-01*1j
s1_bh[3] = -7.03034e-01 - 8.43425e-01*1j
s2_bh = np.empty(4,dtype=np.complex)
s2_bh[0] = -8.38663e-01 - 8.64763e-01*1j
s2_bh[1] = -7.21779e-01 - 7.27856e-01*1j
s2_bh[2] = -4.19454e-01 - 3.72965e-01*1j
s2_bh[3] = -4.44461e-02 + 6.94424e-02*1j
Wiscombe's code is much more robust than Bohren's so I attribute errors all to Bohren
Tiny water droplet (0.26 microns) in clouds has pretty strong forward scattering! A graph of this is figure 4.9 in Bohren
and Huffman’s Absorption and Scattering of Light by Small Particles.
A bizarre scaling factor of 16𝜋 is needed to make the miepython results match those in the figure 4.9.
[16]: x=3
m=1.33-1e-8j
theta = np.linspace(0,180,181)
mu = np.cos(theta*np.pi/180)
scaling_factor = 16*np.pi
iper = scaling_factor*miepython.i_per(m,x,mu)
ipar = scaling_factor*miepython.i_par(m,x,mu)
P = (iper-ipar)/(iper+ipar)
plt.subplots(2,1,figsize=(8,8))
plt.subplot(2,1,1)
plt.semilogy(theta,ipar,label='$i_{par}$')
plt.semilogy(theta,iper,label='$i_{per}$')
plt.xlim(0,180)
plt.xticks(range(0,181,30))
plt.ylabel('i$_{par}$ and i$_{per}$')
plt.legend()
plt.title('Figure 4.9 from Bohren & Huffman')
plt.subplot(2,1,2)
plt.plot(theta,P)
plt.ylim(-1,1)
plt.xticks(range(0,181,30))
plt.xlim(0,180)
plt.ylabel('Polarization')
plt.plot([0,180],[0,0],':k')
(continues on next page)
This graph (see figure 29 in Light Scattering by Small Particles) was obviously constructed by hand. In this graph, van
de Hulst worked hard to get as much information as possible
[17]: x=5
m=10000
theta = np.linspace(0,180,361)
mu = np.cos(theta*np.pi/180)
fig, ax = plt.subplots(figsize=(8,8))
x=10
s1,s2 = miepython.mie_S1_S2(m,x,mu)
sone = 2.5*abs(s1)
stwo = 2.5*abs(s2)
(continues on next page)
x=5
s1,s2 = miepython.mie_S1_S2(m,x,mu)
sone = 2.5*abs(s1) + 1
stwo = 2.5*abs(s2) + 1
plt.plot(theta,sone,'b')
plt.plot(theta,stwo,'--r')
plt.annotate('x=%.1f '%x,xy=(theta[-1],sone[-1]),ha='right',va='bottom')
x=3
s1,s2 = miepython.mie_S1_S2(m,x,mu)
sone = 2.5*abs(s1) + 2
stwo = 2.5*abs(s2) + 2
plt.plot(theta,sone,'b')
plt.plot(theta,stwo,'--r')
plt.annotate('x=%.1f '%x,xy=(theta[-1],sone[-1]),ha='right',va='bottom')
x=1
s1,s2 = miepython.mie_S1_S2(m,x,mu)
sone = 2.5*abs(s1) + 3
stwo = 2.5*abs(s2) + 3
plt.plot(theta,sone,'b')
plt.plot(theta,stwo,'--r')
plt.annotate('x=%.1f '%x,xy=(theta[-1],sone[-1]),ha='right',va='bottom')
x=0.5
s1,s2 = miepython.mie_S1_S2(m,x,mu)
sone = 2.5*abs(s1) + 4
stwo = 2.5*abs(s2) + 4
plt.plot(theta,sone,'b')
plt.plot(theta,stwo,'--r')
plt.annotate('x=%.1f '%x,xy=(theta[-1],sone[-1]),ha='right',va='bottom')
plt.xlim(0,180)
plt.ylim(0,5.5)
plt.xticks(range(0,181,30))
plt.yticks(np.arange(0,5.51,0.5))
plt.title('Figure 29 from van de Hulst, Non-Absorbing Spheres')
plt.xlabel('Angle (Degrees)')
ax.set_yticklabels(['0','1/2','0','1/2','0','1/2','0','1/2','0','1/2','5',' '])
plt.grid(True)
plt.show()
Another interesting graph is figure 4.51 from *The Scattering of Light* by Kerker.
The angular gain is
4 4
𝐺1 = |𝑆1 (𝜃)|2 and 𝐺2 = |𝑆2 (𝜃)|2
𝑥2 𝑥2
x=1
m=10000
theta = np.linspace(0,180,361)
mu = np.cos(theta*np.pi/180)
fig, ax = plt.subplots(figsize=(8,8))
s1,s2 = miepython.mie_S1_S2(m,x,mu)
(continues on next page)
G1 = 4*abs(s1)**2/x**2
G2 = 4*abs(s2)**2/x**2
plt.plot(theta,G1,'b')
plt.plot(theta,G2,'--r')
plt.annotate('$G_1$',xy=(50,0.36),color='blue',fontsize=14)
plt.annotate('$G_2$',xy=(135,0.46),color='red',fontsize=14)
plt.xlim(0,180)
plt.xticks(range(0,181,30))
plt.title('Figure 4.51 from Kerker, Non-Absorbing Spheres, x=1')
plt.xlabel('Angle (Degrees)')
plt.ylabel('Angular Gain')
plt.show()
[ ]:
Scott Prahl
Mar 2021, version 3
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
Overview
So clouds are one of the big reasons that Mie scattering is useful. This notebook covers the basics of log normal
distributions and shows a few calculations using miepython.
One conclusion of this notebook is that for relatively large water droplets, the Henyey-Greenstein phase function is a
poor approximation for the forward scattered light.
Fog data
Scattering depends on the size distribution of droplets as well as the droplet density. In general, the distributions have
been modelled as log-normal or as a gamma function. This notebook focuses on the log-normal distribution.
Fog data from Podzimek, “Droplet Concentration and Size Distribution in Haze and Fog”, Studia geoph. et geod. 41
(1997).
For the first trick I’ll show that the log-normal distribution is just a plain old normal distribution but with a logarthmic
horizontal axis. Also note that the mean droplet size and the most common size (mode) differ.
shape = np.log(sigma_g)
mode = np.exp(np.log(r_g) - np.log(sigma_g)**2)
mean = np.exp(np.log(r_g) + np.log(sigma_g)**2/2)
num=100
r = np.linspace(0.1, 20, num) # values for x-axis
plt.figure(figsize=(12,4.5))
# Figure on linear scale
plt.subplot(121)
(continues on next page)
plt.plot(r, pdf)
plt.vlines(mode, 0, pdf.max(), linestyle=':', label='Mode')
plt.vlines(mean, 0, stats.lognorm.pdf(mean, shape, scale=r_g), linestyle='--', color=
˓→'green', label='Mean')
plt.ylim(ymin=0)
plt.xlabel('Radius (microns)')
plt.ylabel('Probabilty Density Function')
plt.title(fogtype)
plt.subplot(122)
plt.semilogx(r, pdf)
plt.ylim(ymin=0)
plt.xlabel('Radius (microns)')
plt.xlim(0.5,50)
plt.ylabel('Probabilty Density Function')
plt.title(fogtype)
plt.show()
So the average cosine of the scattering phase function is often called the scattering asymmetry or just the scattering
anisotropy. The value lies between -1 (completely back scattering) and +1 (total forward scattering). For these fog
values, the scattering is pretty strongly forward scattering.
plt.plot(r,g)
plt.ylim(0.5,1.0)
plt.xlabel('Radius (microns)')
plt.ylabel('Scattering Anisotropy')
plt.title(fogtype)
plt.vlines(mode, 0.85, 1, linestyle=':', label='Mode')
plt.vlines(mean, 0.7, 0.85, linestyle='--', color='green', label='Mean')
plt.annotate('mode radius = 4.0 microns', xy=(4.3,0.9))
plt.annotate('mean radius = 5.1 microns', xy=(5.5,0.75))
plt.show()
Let’s take a closer look at scattering between the mode and mean radius.
[5]: num=100 # number of angles
x = 2*np.pi*r/lambdaa
mu = np.linspace(-1,1,num)
s1,s2 = miepython.mie_S1_S2(m,x,mu)
scatter = 0.5*(abs(s1)**2+abs(s2)**2)
plt.plot(mu,scatter)
plt.yscale('log')
plt.xlim(-1.05,1.05)
#plt.ylim(ymin=0.8)
plt.xlabel(r'Exit Angle $\cos\theta$')
plt.ylabel('Unpolarized Scattering Function')
plt.title(r'Water Droplet ($\lambda=550$nm, r=4.5$\mu$m)')
plt.show()
The graph above does not really do justice to how strongly forward scattering the water droplets are! Here is a close
up of four droplet radii (1,5,10,20) microns. The most common fog size (5 micron) has a FWHM of 2.5°
[6]: num=100 # number of angles
#plt.yscale('log')
#plt.ylim(ymin=0.8)
plt.xlabel(r'Exit Angle $\theta$ (degrees)')
plt.ylabel('Normalized Scattering Function')
plt.title(r'Water Droplet ($\lambda=550$nm, r=4.5$\mu$m)')
plt.show()
How does the Mie scattering for a 5 micron droplet radius compare with Henyey-Greenstein?
First, need to make sure both scattering functions are normalized to the same overall value. If we integrate over all 4𝜋
steradians
∫︁ ∫︁ 2𝜋 ∫︁ 𝜋 ∫︁ 1
𝑆(𝜃, 𝜑) 𝑑𝜑 sin 𝜃𝑑𝜃 = 𝑆(𝜃, 𝜑) 𝑑𝜑 sin 𝜃𝑑𝜃 = 2𝜋 𝑆(𝜇) 𝑑𝜇
4𝜋 0 0 −1
x = 2*np.pi*r/lambdaa
k = 2*np.pi/lambdaa
qext, qsca, qback, g = miepython.mie(m,x)
mu = np.linspace(-1,1,num)
s1,s2 = miepython.mie_S1_S2(m,x,mu)
miescatter = 0.5*(abs(s1)**2+abs(s2)**2)
hgscatter = hg(g,mu)
delta_mu=mu[1]-mu[0]
total = 2*np.pi*delta_mu*np.sum(miescatter)
total = 2*np.pi*delta_mu*np.sum(hgscatter)
print('hg integral= ', total)
mie integral= 1.0137512825789188
hg integral= 1.0405143428517407
Now we can see how bad the approximation is when using the Henyey-Greenstein function. Here is a log plot
[8]: num=500
r=4.5
lambdaa = 0.550
m = 1.33
x = 2*np.pi*r/lambdaa
theta = np.linspace(0,180,num)
mu = np.cos(theta*np.pi/180)
s1,s2 = miepython.mie_S1_S2(m,x,mu)
miescatter = 0.5*(abs(s1)**2+abs(s2)**2)
plt.plot(theta,miescatter, color='blue')
plt.plot(theta,hg(g,mu), color='red')
plt.yscale('log')
plt.xlabel(r'Exit Angle $\theta$ (degrees)')
plt.ylabel('Normalized Scattering Function')
plt.title(r'Water Droplet ($\lambda=550$nm, r=4.5$\mu$m)')
plt.annotate('g=%.4f'%g, xy=(-150,0.9))
plt.show()
x = 2*np.pi*r/lambdaa
theta = np.linspace(0,180,num)
mu = np.cos(theta*np.pi/180)
s1,s2 = miepython.mie_S1_S2(m,x,mu)
miescatter = 0.5*(abs(s1)**2+abs(s2)**2)
hgscatter = hg(g,mu)
plt.plot(theta,miescatter/miescatter[0],color='blue')
plt.plot(-theta,miescatter/miescatter[0],color='blue')
plt.plot(theta,hg(g,mu)/hg(g,1), color='red')
plt.plot(-theta,hg(g,mu)/hg(g,1), color='red')
plt.xlabel(r'Exit Angle $\theta$ (degrees)')
plt.ylabel('Normalized Scattering Function')
plt.title(r'Water Droplet ($\lambda=550$nm, r=4.5$\mu$m)')
plt.annotate('g=%.4f'%g, xy=(-150,0.9))
plt.show()
[10]: plt.plot(theta,miescatter,color='blue')
plt.plot(-theta,miescatter,color='blue')
plt.plot(theta,hg(g,mu), color='red')
plt.plot(-theta,hg(g,mu), color='red')
plt.xlabel(r'Exit Angle $\theta$ (degrees)')
plt.ylabel('Unnormalized Scattering Function')
plt.title(r'Water Droplet ($\lambda=550$nm, r=4.5$\mu$m)')
plt.annotate('g=%.4f'%g, xy=(-150,0.9))
plt.show()
[11]: plt.plot(theta,miescatter,color='blue')
plt.plot(-theta,miescatter,color='blue')
plt.plot(theta,hg(g,mu), color='red')
plt.plot(-theta,hg(g,mu), color='red')
plt.xlabel(r'Exit Angle $\theta$ (degrees)')
plt.ylabel('Normalized Scattering Function')
(continues on next page)
[12]: num=100
plt.plot(r,cross_section*pdf)
#plt.plot(r,pdf*100)
plt.ylim(0,6)
plt.xlabel('Radius (microns)')
(continues on next page)
plt.title(fogtype)
plt.show()
Scott Prahl
Mar 2021, Version 3
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
[1]: #!pip install --user miepython
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
First thing is to define some reasonably accurate approximations for the efficiencies for large spheres. This way we
can ensure that the limiting cases behaive as they should.
These formulas used below are from Moosmüller and Sorensen Single scattering albedo of homogeneous, spherical
particles in the transition regime
[3]: def Qabs_adt(m,x):
"""
Anomalous diffraction theory approximation for absorption efficiency
(continues on next page)
if kappa == 0:
return np.zeros_like(x)
return 1+2*np.exp(-4*kappa*x)/(4*kappa*x)+2*(np.exp(-4*kappa*x)-1)/(4*kappa*x)**2
def Qext_adt(m,x):
"""
Anomalous diffraction theory approximation for extinction efficiency
"""
n = m.real
kappa = abs(m.imag)
rho = 2*x*np.abs(m-1)
beta = np.arctan2(kappa,n-1)
ex = np.exp(-rho * np.tan(beta))
qext_adt = 2
qext_adt += -4*ex*np.cos(beta)/rho*np.sin(rho-beta)
qext_adt += -4*ex*np.cos(beta)**2/rho**2*np.cos(rho-2*beta)
qext_adt += 4*np.cos(beta)**2/rho**2*np.cos(2*beta)
return qext_adt
def Qabs_madt(m,x):
"""
Modified anomalous diffraction theory approximation for absorption efficiency
"""
n = m.real
kappa = abs(m.imag)
if kappa == 0:
return np.zeros_like(x)
qabs_adt = Qabs_adt(m,x)
epsilon = 0.25 + 0.61*(1-np.exp(-8*np.pi/3*kappa))**2
c1 = 0.25*(1+np.exp(-1167*kappa))*(1-qabs_adt)
c2 = np.sqrt(2*epsilon*x/np.pi)*np.exp(0.5-epsilon*x/np.pi)*(0.7393*n-0.6069)
return (1+c1+c2)*qabs_adt
def Qext_madt(m,x):
"""
Modified anomalous diffraction theory approximation for extinction efficiency
"""
n = m.real
kappa = -np.imag(m)
qext_adt = Qext_adt(m,x)
epsilon = 0.25 + 0.61*(1-np.exp(-8*np.pi/3*kappa))**2
c2 = np.sqrt(2*epsilon*x/np.pi)*np.exp(0.5-epsilon*x/np.pi)*(0.7393*n-0.6069)
Qedge = (1-np.exp(-0.06*x))*x**(-2/3)
return (1+0.5*c2)*qext_adt+Qedge
[4]: m = 1.5
x = np.logspace(-1, 5, 50) # also in microns
qext, qsca, qback, g = miepython.mie(m,x)
qabs = qext-qsca
plt.ylabel("$Q_{abs}$")
plt.xlabel("Size Parameter")
plt.title("Absorption Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.ylabel("$Q_{ext}$")
plt.xlabel("Size Parameter")
plt.title("Extinction Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{sca}$")
plt.title("Scattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{pr}$")
plt.title("Radiation Pressure for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Anisotropy for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Backscattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
[11]: m = 1.5-0.001j
x = np.logspace(-1, 5, 50) # also in microns
qext, qsca, qback, g = miepython.mie(m,x)
qabs = qext-qsca
plt.ylabel("$Q_{abs}$")
plt.xlabel("Size Parameter")
plt.title("Absorption Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.ylabel("$Q_{ext}$")
plt.xlabel("Size Parameter")
plt.title("Extinction Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{sca}$")
plt.title("Scattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{pr}$")
plt.title("Radiation Pressure for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Anisotropy for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Backscattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
[18]: m = 1.5-0.1j
x = np.logspace(-1, 5, 50) # also in microns
qext, qsca, qback, g = miepython.mie(m,x)
qabs = qext-qsca
plt.ylabel("$Q_{abs}$")
plt.xlabel("Size Parameter")
plt.title("Absorption Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.ylabel("$Q_{ext}$")
plt.xlabel("Size Parameter")
plt.title("Extinction Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{sca}$")
plt.title("Scattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{pr}$")
plt.title("Radiation Pressure for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Anisotropy for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Backscattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
[25]: m = 1.5-1j
x = np.logspace(-1, 5, 50) # also in microns
qext, qsca, qback, g = miepython.mie(m,x)
qabs = qext-qsca
plt.ylabel("$Q_{abs}$")
plt.xlabel("Size Parameter")
plt.title("Absorption Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.ylabel("$Q_{ext}$")
plt.xlabel("Size Parameter")
plt.title("Extinction Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{sca}$")
plt.title("Scattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{pr}$")
plt.title("Radiation Pressure for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Anisotropy for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
plt.xlabel("Size Parameter")
plt.ylabel("g")
plt.title("Backscattering Efficiency for m=%.3f-%.3fi" % (m.real,abs(m.imag)))
plt.legend()
plt.grid()
plt.show()
Scott Prahl
Mar 2021, version 3
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
Args:
m: the complex index of refraction of the sphere
x: the size parameter of the sphere
Returns:
qext: the total extinction efficiency
qsca: the scattering efficiency
qback: the backscatter efficiency
g: the average cosine of the scattering phase function
"""
ratio = (m**2-1)/(m**2+2)
qsca = 8/3*x**4*abs(ratio)**2
qext = 4*x*ratio*(1+x**2/15*ratio*(m**4+27*m**2+38)/(2*m**2+3))
qext = abs(qext.imag + qsca)
qback = 4*x**4*abs(ratio)**2
g = 0
return qext, qsca, qback, g
def rayleigh_S1_S2(m,x,mu):
"""
Calculate the scattering amplitude functions for small spheres.
Args:
m: the complex index of refraction of the sphere
x: the size parameter of the sphere
mu: the angles, cos(theta), to calculate scattering amplitudes
Returns:
S1, S2: the scattering amplitudes at each angle mu [sr**(-0.5)]
"""
a1 = (2*x**3)/3 * (m**2-1)/(m**2+2)*1j
a1 += (2*x**5)/5 * (m**2-2)*(m**2-1)/(m**2+2)**2 *1j
s1 = (3/2)*a1*np.ones_like(mu)
s2 = (3/2)*a1*mu
factor = np.sqrt(np.pi*qext)*x
return s1/factor, s2/factor
def rayleigh_unpolarized(m,x,mu):
"""
Return the unpolarized scattered intensity for small spheres.
Args:
m: the complex index of refraction of the sphere
x: the size parameter
mu: the cos(theta) of each direction desired
Returns
The intensity at each angle in the array mu. Units [1/sr]
"""
s1, s2 = rayleigh_S1_S2(m,x,mu)
return (abs(s1)**2+abs(s2)**2)/2
Mie scattering describes the special case of the interaction of light passing through a non-absorbing medium with a
single embedded spherical object. The sphere itself can be non-absorbing, moderately absorbing, or perfectly absorb-
ing.
Rayleigh scattering is a simple closed-form solution for the scattering from small spheres.
Rayleigh scattering describes the elastic scattering of light by spheres that are much smaller than the wavelength of
light. The intensity 𝐼 of the scattered radiation is given by
)︂4 (︂ )︂2 (︂ )︂6
1 + cos2 𝜃 𝑛2 − 1
(︂ )︂ (︂
2𝜋 𝑑
𝐼 = 𝐼0
2𝑅2 𝜆 𝑛2 + 2 2
where 𝐼0 is the light intensity before the interaction with the particle, 𝑅 is the distance between the particle and the
observer, 𝜃 is the scattering angle, 𝑛 is the refractive index of the particle, and 𝑑 is the diameter of the particle.
𝜋𝑑 𝑅
𝑥= 𝜌=
𝜆 𝜆
and thus
)︂2
𝑛2 − 1
(︂
𝐼0
𝐼= 𝑥4 (1 + cos2 𝜃)
8𝜋 2 𝜌2 𝑛2 + 2
m = 1.5-1j
theta = np.linspace(-180,180,180)
mu = np.cos(theta*np.pi/180)
rscat = rayleigh_unpolarized(m,x,mu)
mscat = miepython.i_unpolarized(m,x,mu)
plt.plot(theta,rscat,'--b')
plt.plot(theta,mscat,'r')
plt.annotate('x=%.1f '%x,(theta[-20],mscat[-20]),ha='right',va='bottom')
plt.xlim(-180,180)
plt.xlabel('Angle [degrees]')
plt.ylabel('Scattered Light [1/sr]')
plt.title('Solid Mie, Dashed Rayleigh')
plt.show()
[5]: m = 1.5
x = 0.1
theta = np.linspace(-180,180,180)
mu = np.cos(theta/180*np.pi)
unp = rayleigh_unpolarized(m,x,mu)
s1,s2 = rayleigh_S1_S2(m,x,mu)
par = abs(s1)**2
per = abs(s2)**2
fig,ax = plt.subplots(1,2,figsize=(12,5))
ax=plt.subplot(121, projection='polar')
ax.plot(theta/180*np.pi,unp)
ax.plot(theta/180*np.pi,par)
ax.plot(theta/180*np.pi,per)
ax.set_rticks([0.05, 0.1,0.15])
plt.subplot(122)
#plt.plot(theta,scat)
plt.plot(theta,unp)
plt.plot(theta,par)
plt.plot(theta,per)
plt.ylim(0.00,0.2)
plt.xlim(0,180)
plt.show()
[6]: m = 1.5
x = 0.1
qext, qsca, qback, g = miepython.mie(m,x)
rext, rsca, rback, rg = rayleigh(m,x)
[7]: m = 1.5
x = 0.1
theta = np.linspace(-180,180,19)
mu = np.cos(np.deg2rad(theta))
s1,s2 = miepython.mie_S1_S2(m,x,mu)
rs1, rs2 = rayleigh_S1_S2(m,x,mu)
[ ]:
Scott Prahl
Mar 2021, Version 2
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
Wiscombe tests
Since the backscattering efficiency is |2𝑆1 (−180∘ )/𝑥|2 , it is easy to see that that backscattering should be the best
comparison. For example, the asymmetry factor for this test case only has three significant digits and the scattering
efficiency only has two!
A typical test result looks like this:
MIEV0 Test Case 12: Refractive index: real 1.500 imag -1.000E+00, Mie size
˓→parameter = 0.055
NUMANG = 7 angles symmetric about 90 degrees
m=complex(1.55, 0.0)
x = 2*3.1415926535*0.525/0.6328
ref = 2.92534
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
m=complex(0.0, -1000.0)
x=0.099
ref = (4.77373E-07*4.77373E-07 + 1.45416E-03*1.45416E-03)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.2f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=0.101
ref = (5.37209E-07*5.37209E-07 + 1.54399E-03*1.54399E-03)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.2f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=100
ref = (4.35251E+01*4.35251E+01 + 2.45587E+01*2.45587E+01)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.2f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=10000
ref = abs(2.91013E+03-4.06585E+03*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.2f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
print()
miepython Wiscombe
X m.real m.imag Qback Qback ratio
5.213 1.5500 0.0000 2.925341e+00 2.925340e+00 1.00000
0.099 0.0000 -1000.00 8.630007e-04 8.630064e-04 0.99999
0.101 0.0000 -1000.00 9.347779e-04 9.347732e-04 1.00001
100.000 0.0000 -1000.00 9.990254e-01 9.990256e-01 1.00000
10000.000 0.0000 -1000.00 9.999998e-01 9.999997e-01 1.00000
x=0.101
(continues on next page)
x=10.0
ref = (1.07857E+00*1.07857E+00 + 3.60881E-02*3.60881E-02)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=1000.0
ref = (1.70578E+01*1.70578E+01 + 4.84251E+02* 4.84251E+02)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
print()
miepython Wiscombe
X m.real m.imag Qback Qback ratio
0.099 0.7500 0.0000 1.108554e-05 1.108554e-05 1.00000
0.101 0.7500 0.0000 1.200381e-05 1.200382e-05 1.00000
10.000 0.7500 0.0000 4.658441e-02 4.658462e-02 1.00000
1000.000 0.7500 0.0000 9.391602e-01 9.391600e-01 1.00000
Non-absorbing spheres
m=complex(1.5, 0)
x=10
ref = abs(4.322E+00 + 4.868E+00*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.5f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=100
ref = abs(4.077E+01 + 5.175E+01*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.5f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=1000
ref = abs(5.652E+02 + 1.502E+03*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.5f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
print()
miepython Wiscombe
X m.real m.imag Qback Qback ratio
10.000 1.5000 0.00000 1.695064e+00 1.695084e+00 0.99999
100.000 1.5000 0.00000 1.736193e+00 1.736102e+00 1.00005
1000.000 1.5000 0.00000 1.030309e+01 1.030182e+01 1.00012
Water droplets
m=complex(1.33, -0.00001)
x=1
ref = (2.24362E-02*2.24362E-02 + 1.43711E-01*1.43711E-01)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.5f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=100
ref = (5.65921E+01*5.65921E+01 + 4.65097E+01*4.65097E+01)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.5f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=10000
ref = abs(-1.82119E+02 -9.51912E+02*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.5f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
print()
old
miepython Wiscombe
X m.real m.imag Qback Qback ratio
1.000 1.3300 -0.00001 8.462445e-02 8.462494e-02 0.99999
100.000 1.3300 -0.00001 2.146326e+00 2.146327e+00 1.00000
10000.000 1.3300 -0.00001 3.757191e-02 3.757215e-02 0.99999
m=complex(1.5, -1.0)
x=0.055
ref = abs(7.66140E-05 + 8.33814E-05*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=0.056
ref = (8.08721E-05*8.08721E-05 + 8.80098E-05*8.80098E-05)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=1.0
ref = (3.48844E-01*3.48844E-01 + 1.46829E-01*1.46829E-01)/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=100.0
(continues on next page)
x=10000
ref = abs(-2.18472E+02 -2.06461E+03*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
print()
miepython Wiscombe
X m.real m.imag Qback Qback ratio
0.055 1.5000 -1.0000 1.695493e-05 1.695493e-05 1.00000
0.056 1.5000 -1.0000 1.822196e-05 1.822197e-05 1.00000
1.000 1.5000 -1.0000 5.730026e-01 5.730036e-01 1.00000
100.000 1.5000 -1.0000 1.724214e-01 1.724214e-01 1.00000
10000.000 1.5000 -1.0000 1.724138e-01 1.724138e-01 1.00000
m=complex(10, -10.0)
x=1
ref = abs(4.48546E-01 + 7.91237E-01*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=100
ref = abs(-4.14538E+01 -1.82181E+01*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
x=10000
ref = abs(2.25248E+03 -3.92447E+03*1j)**2/x/x*4
qext, qsca, qback, g = miepython.mie(m,x)
print("%9.3f % 8.4f % 8.4f % 8e % 8e %8.5f" % (x,m.real,m.imag,qback,ref,qback/
˓→ref))
miepython Wiscombe
X m.real m.imag Qback Qback ratio
1.000 10.0000 -10.0000 3.308997e+00 3.308998e+00 1.00000
100.000 10.0000 -10.0000 8.201273e-01 8.201267e-01 1.00000
10000.000 10.0000 -10.0000 8.190044e-01 8.190052e-01 1.00000
For large spheres with absorption, backscattering efficiency should just be equal to the reflection for perpendicular
light on a planar surface.
kappa=1
m = 1.5 - kappa*1j
R = abs(m-1)**2/abs(m+1)**2
Qbig = R * np.ones_like(x)
kappa=0.001
m = 1.5 - kappa*1j
R = abs(m-1)**2/abs(m+1)**2
Qbig = R * np.ones_like(x)
plt.ylim(0,0.2)
plt.title("Backscattering Efficiency for m=1.5 - i $\kappa$")
plt.xlabel("Size Parameter")
plt.ylabel("$Q_{back}$")
plt.grid()
Scott Prahl
Mar 2021
Basic tests for Mie scattering in a Jupyter notebook collected in one place.
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
from miepython import * #testing private functions
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
def test_01_log_derivatives(self):
x = 62
m = 1.28 - 1.37j
nstop = 50
dn = miepython._D_calc(m,x,nstop)
self.assertAlmostEqual(dn[10].real, 0.004087, delta=0.00001)
self.assertAlmostEqual(dn[10].imag, 1.0002620, delta=0.00001)
def test_02_an_bn(self):
# Test values from Sergio Aragon's Mie Scattering in Mathematica
# imaginary parts are negative because different sign convention
m = 4.0/3.0
x = 50
a, b = miepython._mie_An_Bn(m,x)
self.assertAlmostEqual( a[0].real, 0.5311058892948411929, delta=0.00000001)
self.assertAlmostEqual(-a[0].imag,-0.4990314856310943073, delta=0.00000001)
self.assertAlmostEqual( b[0].real, 0.7919244759352004773, delta=0.00001)
self.assertAlmostEqual(-b[0].imag,-0.4059311522289938238, delta=0.00001)
m = 1.5-1j
x = 2
a, b = miepython._mie_An_Bn(m,x)
self.assertAlmostEqual( a[0].real, 0.5465202033970914511, delta=0.00000001)
self.assertAlmostEqual(-a[0].imag,-0.1523738572575972279, delta=0.00000001)
self.assertAlmostEqual( b[0].real, 0.3897147278879423235, delta=0.00001)
self.assertAlmostEqual(-b[0].imag, 0.2278960752564908264, delta=0.00001)
m = 1.1-25j
x = 2
a, b = miepython._mie_An_Bn(m,x)
self.assertAlmostEqual(a[1].real, 0.324433578437, delta=0.0001)
self.assertAlmostEqual(a[1].imag, 0.465627763266, delta=0.0001)
self.assertAlmostEqual(b[1].real, 0.060464399088, delta=0.0001)
self.assertAlmostEqual(b[1].imag,-0.236805417045, delta=0.0001)
(continues on next page)
class non_absorbing(unittest.TestCase):
def test_03_bh_dielectric(self):
m = 1.55
lambda0 = 0.6328
radius = 0.525
x = 2*np.pi*radius/lambda0
qext, qsca, qback, g = miepython.mie(m,x)
def test_05_wiscombe_non_absorbing(self):
def test_05_old_wiscombe_non_absorbing(self):
def test_04_non_dielectric(self):
m = 1.55-0.1j
lambda0 = 0.6328
radius = 0.525
x = 2*np.pi*radius/lambda0
qext, qsca, qback, g = miepython.mie(m,x)
class absorbing(unittest.TestCase):
def test_06_wiscombe_water_absorbing(self):
def test_07_wiscombe_absorbing(self):
def test_08_wiscombe_more_absorbing(self):
(continues on next page)
def test_09_single_nonmagnetic(self):
m = 1.5-0.5j
x = 2.5
qext, qsca, qback, g = miepython.mie(m,x)
class perfectly_reflecting(unittest.TestCase):
def test_11_wiscombe_perfectly_reflecting(self):
class small(unittest.TestCase):
def test_10_small_spheres(self):
# MIEV0 Test Case 5
m = 0.75
x = 0.099
qext, qsca, qback, g = miepython.mie(m,x)
self.assertAlmostEqual(qext, 0.000007, delta=1e-6)
self.assertAlmostEqual(g, 0.001448, delta=1e-6)
m = 1.5 -1j
x = 0.055
qext, qsca, qback, g = miepython.mie(m,x)
self.assertAlmostEqual(qext, 0.101491, delta=1e-6)
self.assertAlmostEqual(g, 0.000491, delta=1e-6)
x=0.056
qext, qsca, qback, g = miepython.mie(m,x)
self.assertAlmostEqual(qext, 0.103347, delta=1e-6)
self.assertAlmostEqual(g, 0.000509, delta=1e-6)
m = 1e-10 - 1e10j
x=0.099
qext, qsca, qback, g = miepython.mie(m,x)
self.assertAlmostEqual(qext, 0.000321, delta=1e-6)
self.assertAlmostEqual(g, -0.397357, delta=1e-4)
x=0.101
qext, qsca, qback, g = miepython.mie(m,x)
self.assertAlmostEqual(qext, 0.000348, delta=1e-6)
self.assertAlmostEqual(g, -0.397262, delta=1e-6)
m = 0 - 1e10j
x=0.099
qext, qsca, qback, g = miepython.mie(m,x)
self.assertAlmostEqual(qext, 0.000321, delta=1e-6)
self.assertAlmostEqual(g, -0.397357, delta=1e-4)
x=0.101
qext, qsca, qback, g = miepython.mie(m,x)
(continues on next page)
class angle_scattering(unittest.TestCase):
def test_12_scatter_function(self):
x=1.0
m=1.5-1.0j
theta = np.arange(0,181,30)
mu = np.cos(theta * np.pi/180)
----------------------------------------------------------------------
Ran 13 tests in 0.779s
OK
[3]: <unittest.main.TestProgram at 0x13c569f40>
[ ]:
Scott Prahl
March 2021, version 6
This Jupyter notebook shows the formulas used in miepython. This code is heavily influenced by Wiscomes MIEV0
code as documented in his paper on Mie scattering and his 1979 NCAR and 1996 NCAR publications.
There are a couple of things that set this code apart from other python Mie codes.
1) Instead of using the built-in special functions from SciPy, the calculation relies on the logarthmic derivative of
the Ricatti-Bessel functions. This technique is significantly more accurate.
2) The code uses special cases for small spheres. This is faster and more accurate
3) The code works when the index of refraction m.real is zero or when m.imag is very large (negative).
The code has been tested up to sizes (𝑥 = 2𝜋𝑟/𝜆 = 10000).
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
from miepython import * #testing private functions
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
This routine uses a continued fraction method to compute 𝐷𝑛 (𝑧) proposed by Lentz. Lentz uses the notation
:math:`A_n` instead of :math:`D_n`, but I prefer the notation used by Bohren and Huffman. This method eliminates
many weaknesses in previous algorithms using forward recursion.
The logarithmic derivative 𝐷𝑛 is defined as
𝑛 𝐽𝑛−1/2 (𝑧)
𝐷𝑛 = − +
𝑧 𝐽𝑛+1/2 (𝑧)
Equation (5) in Lentz’s paper can be used to obtain
𝐽𝑛−1/2 (𝑧) 2𝑛 + 1 1
= +
𝐽𝑛+1/2 (𝑧) 𝑧 2𝑛 + 3 1
− +
𝑧 2𝑛 + 5 1
+
𝑧 2𝑛 + 7
− + ···
𝑧
Now if
1
𝛼𝑖,𝑗 = [𝑎𝑖 , 𝑎𝑖−1 , . . . , 𝑎𝑗 ] = 𝑎𝑖 +
1
𝑎𝑖−1 +
1
𝑎𝑖−2 + · · ·
𝑎𝑗
we seek to create
𝛼 = 𝛼1,1 𝛼2,1 · · · 𝛼𝑗,1 𝛽 = 𝛼2,2 𝛼3,2 · · · 𝛼𝑗,2
since Lentz showed that
𝐽𝑛−1/2 (𝑧) 𝛼
≈
𝐽𝑛+1/2 (𝑧) 𝛽
The whole goal is to iterate until the 𝛼 and 𝛽 are identical to the number of digits desired. Once this is achieved, then
use equations this equation and the first equation for the logarithmic derivative to calculate 𝐷𝑛 (𝑧).
First terms
The value of 𝑎𝑗 is
2𝑛 + 2𝑗 − 1
𝑎𝑗 = (−1)𝑗+1
𝑧
The first terms for 𝛼 and 𝛽 are then
(︂ )︂
1
𝛼 = 𝑎1 𝑎2 + 𝛽 = 𝑎2
𝑎1
Later terms
Calculating 𝐷𝑛
Use formula 7 from Wiscombe’s paper to figure out if upwards or downwards recurrence should be used. Namely if
𝐷𝑛 by downwards recurrence.
Start downwards recurrence using by accurately calculating D[nstop] using the Lentz method, then find earlier
terms of the logarithmic derivative 𝐷𝑛 (𝑧) using the recurrence relation,
𝑛 1
𝐷𝑛−1 (𝑧) = −
𝑧 𝐷𝑛 (𝑧) + 𝑛/𝑧
𝐷𝑛 by upward recurrence.
Calculating the logarithmic derivative 𝐷𝑛 (𝜌) using the upward recurrence relation,
1 𝑛
𝐷𝑛 (𝑧) = −
𝑛/𝑧 − 𝐷𝑛−1 (𝑧) 𝑧
To calculate the initial value D[1] we use Wiscombe’s representation that avoids overflow errors when the usual
𝐷0 (𝑥) = 1/𝑡𝑎𝑛(𝑧) is used.
1 1 − exp(−2𝑗𝑧)
𝐷1 (𝑧) = − +
𝑧 [1 − exp(−2𝑗𝑧)]/𝑧 − 𝑗[1 + exp(−2𝑗𝑧)]
[3]: m = 1
x = 1
nstop = 10
Calculating 𝐴𝑛 and 𝐵𝑛
OK, Here we go. We need to start up the arrays. First, recall (page 128 Bohren and Huffman) that
where 𝑗𝑛 and 𝑦𝑛 are spherical Bessel functions. The first few terms may be worked out as,
sin 𝑥
𝜓0 (𝑥) = sin 𝑥 and 𝜓1 (𝑥) = − cos 𝑥
𝑥
and
[︁ cos 𝑥 ]︁
𝜉0 (𝑥) = 𝜓0 + 𝑖 cos 𝑥 and 𝜉1 (𝑥) = 𝜓1 + 𝑖 + sin 𝑥
𝑥
The main equations for 𝑎𝑛 and 𝑏𝑛 in Bohren and Huffman Equation (4.88).
[︁ ]︁
𝐷𝑛 (𝑚𝑥)/𝑚 + 𝑛/𝑥 𝜓𝑛 (𝑥) − 𝜓𝑛−1 (𝑥)
𝑎𝑛 = [︁ ]︁
𝐷𝑛 (𝑚𝑥)/𝑚 + 𝑛/𝑥 𝜉𝑛 (𝑥) − 𝜉𝑛−1 (𝑥)
and
[︁ ]︁
𝑚𝐷𝑛 (𝑚𝑥) + 𝑛/𝑥 𝜓𝑛 (𝑥) − 𝜓𝑛−1 (𝑥)
𝑏𝑛 = [︁ ]︁
𝑚𝐷𝑛 (𝑚𝑥) + 𝑛/𝑥 𝜉𝑛 (𝑥) − 𝜉𝑛−1 (𝑥)
The recurrence relations for 𝜓 and 𝜉 depend on the recursion relations for the spherical Bessel functions (page 96
equation 4.11)
2𝑛 + 1
𝑧𝑛−1 (𝑥) + 𝑧𝑛+1 (𝑥) = 𝑧𝑛 (𝑥)
𝑥
where 𝑧𝑛 might be either 𝑗𝑛 or 𝑦𝑛 . Thus
2𝑛 + 1 2𝑛 + 1
𝜓𝑛+1 (𝑥) = 𝜓𝑛 (𝑥) − 𝜓𝑛−1 (𝑥) and 𝜉𝑛+1 (𝑥) = 𝜉𝑛 (𝑥) − 𝜉𝑛−1 (𝑥)
𝑥 𝑥
If the spheres are perfectly reflecting m.real=0 then Kerker gives equations for 𝑎𝑛 and 𝑏𝑛 that do not depend on 𝐷𝑛
at all
𝑛𝜓𝑛 (𝑥)/𝑥 − 𝜓𝑛−1 (𝑥)
𝑎𝑛 =
𝑛𝜉𝑛 (𝑥)/𝑥 − 𝜉𝑛−1 (𝑥)
and
𝜓𝑛 (𝑥)
𝑏𝑛 =
𝜉𝑛 (𝑥)
Therefore D[n] will directly correspond to 𝐷𝑛 in Bohren. However, a and b will be zero based arrays and so
𝑎1 =a[0] or 𝑏𝑛 =b[n-1]
[4]: m=4/3
x=50
print("m=4/3 test, m=",m, " x=",x)
a, b = miepython._mie_An_Bn(m,x)
print("a_1=", a[0])
print("a_1= (0.531105889295-0.499031485631j) #test")
print("b_1=", b[0])
print("b_1= (0.791924475935-0.405931152229j) #test")
print()
m=3/2-1j
x=2
print("upward recurrence test, m=",m, " x=",x)
a, b = miepython._mie_An_Bn(m,x)
print("a_1=", a[0])
print("a_1= (0.546520203397-0.152373857258j) #test")
print("b_1=", b[0])
print("b_1= (0.389714727888+0.227896075256j) #test")
print()
m=11/10-25j
x=2
print("downward recurrence test, m=",m, " x=",x)
a,b=miepython._mie_An_Bn(m,x)
print("a_1=", a[0])
print("a_1= (0.322406907480-0.465063542971j) #test")
print("b_1=", b[0])
print("b_1= (0.575167279092+0.492912495262j) #test")
m=4/3 test, m= 1.3333333333333333 x= 50
a_1= (0.5311058892948325+0.4990314856310949j)
a_1= (0.531105889295-0.499031485631j) #test
b_1= (0.7919244759351927+0.40593115222899945j)
b_1= (0.791924475935-0.405931152229j) #test
Small Spheres
This calculates everything accurately for small spheres. This approximation is necessary because in the small particle
or Rayleigh limit 𝑥 → 0 the Mie formulas become ill-conditioned. The method was taken from Wiscombe’s paper
and has been tested for several complex indices of refraction.
Wiscombe uses this when
𝑥|𝑚| ≤ 0.1
and says this routine should be accurate to six places.
The formula for 𝑎
ˆ1 is
2 4𝑚2 + 5 4
𝑚2 − 1 1 − 0.1𝑥 + 1400 𝑥
𝑎
ˆ1 = 2𝑖
3 𝐷
where
8𝑚4 − 385𝑚2 + 350 4 𝑚2 − 1 3
𝐷 = 𝑚2 + 2 + (1 − 0.7𝑚2 )𝑥2 − 𝑥 + 2𝑖 𝑥 (1 − 0.1𝑥2 )
1400 3
Note that I have disabled the case when the sphere has no index of refraction. The perfectly conducting sphere
equations are
The formula for ˆ𝑏1 is
2
2 1 + 2𝑚70− 5 𝑥2
ˆ𝑏1 = 𝑖𝑥2 𝑚 − 1
45 1 − 2𝑚2 − 5 𝑥2
30
The formula for 𝑎
ˆ2 is
1 𝑥2
1 − 14
𝑚2 − 1
ˆ2 = 𝑖𝑥2
𝑎
15 2𝑚2 + 3 − 2𝑚2 − 7 𝑥2
14
The scattering and extinction efficiencies are given by
[︂ ]︂
ˆ 5
𝑄ext = 6𝑥 · ℛ⌉ 𝑎 ˆ1 + 𝑏1 + 𝑎 ˆ2
3
and
𝑄sca = 6𝑥4 𝑇
with
5
𝑇 = |ˆ𝑎1 |2 + |ˆ𝑏1 |2 + |ˆ 𝑎2 |2
3
and the anisotropy (average cosine of the phase function) is
1 [︁ ]︁
𝑔 = · ℛ⌉ 𝑎 𝑎2 + ˆ𝑏1 )*
ˆ1 (ˆ
𝑇
The backscattering efficiency 𝑄back is
|𝑆1 (−1)|2
𝑄back =
𝑥2
where 𝑆1 (𝜇) is
[︂ ]︂
𝑆1 (−1) 3 2 ˆ 5
= 𝑥 𝑎 ˆ 1 − 𝑏1 − 𝑎ˆ2
𝑥 2 3
[5]: m=1.5-0.1j
x=0.0665
print("abs(m*x)=",abs(m*x))
qext, qsca, qback, g = miepython._small_mie(m,x)
print("Qext=",qext)
print("Qsca=",qsca)
print("Qabs=",qext-qsca)
print("Qback=",qback)
print("g=",g)
print()
print('The following should be nearly the same as those above:')
print()
x=0.067
print("abs(m*x)=",abs(m*x))
qext, qsca, qback, g = miepython.mie(m,x)
print("Qext=",qext)
print("Qsca=",qsca)
print("Qabs=",qext-qsca)
print("Qback=",qback)
print("g=",g)
abs(m*x)= 0.09997142091617985
Qext= 0.013287673090500258
Qsca= 4.699313232220917e-06
Qabs= 0.013282973777268036
Qback= 7.0342827383458075e-06
g= 0.0008751930053081949
abs(m*x)= 0.1007230857350985
Qext= 0.0133881870400232
Qsca= 4.842269259059647e-06
Qabs= 0.01338334477076414
Qback= 7.248044689702778e-06
g= 0.0008883995075489132
The above equations fail when m.real=0 so use these approximations when the sphere is small and refective
[6]: m = 0 - 0.01j
x=0.099
qext, qsca, qback, g = miepython._small_conducting_mie(m,x)
print("Qext =",qext)
print("Qsca =",qsca)
print("Qabs =",qext-qsca)
print("Qback=",qback)
print("g =",g)
print()
print('The following should be nearly the same as those above:')
print()
(continues on next page)
m = 0 - 0.01j
x=0.1001
qext, qsca, qback2, g = miepython.mie(m,x)
print("Qext =",qext)
print("Qsca =",qsca)
print("Qabs =",qext-qsca)
print("Qback=",qback2)
print("g =",g)
Qext = 0.00032096740755688995
Qsca = 0.00032096740755688995
Qabs = 0.0
Qback= 0.0008630007227881904
g = -0.39735691065111844
Qext = 0.00033547238274947395
Qsca = 0.000335472382749474
Qabs = -5.421010862427522e-20
Qback= 0.0009019320002161996
g = -0.39731049260469614
From page 103 we find an expression for the scattering cross section
∞
𝜎𝑠 2 ∑︁
𝑄sca = = (2𝑛 + 1)(|𝑎𝑛 |2 + |𝑏𝑛 |2 )
𝜋𝑎2 𝑥2 𝑛=1
[8]: x=1000.0
m=1.5-0.1j
qext, qsca, qback, g = miepython.mie(m,x)
print("Qext=",qext)
print("Qsca=",qsca)
print("Qabs=",qext-qsca)
print("Qback=",qback)
print("g=",g)
Qext= 2.01970252082255
Qsca= 1.106932388925401
Qabs= 0.9127701318971491
Qback= 0.041533554644588716
g= 0.9508799127402504
[9]: x=10000.0
m=1.5-1j
qext, qsca, qback, g = miepython.mie(m,x)
print("Qext=",qext)
print("Qsca=",qsca)
print("Qabs=",qext-qsca)
print("Qback=",qback)
print("g=",g)
Qext= 2.0043677096969206
Qsca= 1.2365743120721584
Qabs= 0.7677933976247622
Qback= 0.17241380051133232
g= 0.8463099581094649
Scattering Matrix
The scattering matrix is given by Equation 4.74 in Bohren and Huffman. Namely,
∞
∑︁ 2𝑛 + 1
𝑆1 (cos 𝜃) = [𝑎𝑛 𝜋𝑛 (cos 𝜃) + 𝑏𝑛 𝜏𝑛 (cos 𝜃)]
𝑛=1
𝑛(𝑛 + 1)
and
∞
∑︁ 2𝑛 + 1
𝑆2 (cos 𝜃) = [𝑎𝑛 𝜏𝑛 (cos 𝜃) + 𝑏𝑛 𝜋𝑛 (cos 𝜃)]
𝑛=1
𝑛(𝑛 + 1)
If 𝜇 = cos 𝜃 then
∞
∑︁ 2𝑛 + 1
𝑆1 (𝜇) = [𝑎𝑛 𝜋𝑛 (𝜇) + 𝑏𝑛 𝜏𝑛 (𝜇)]
𝑛=1
𝑛(𝑛 + 1)
and
∞
∑︁ 2𝑛 + 1
𝑆2 (𝜇) = [𝑎𝑛 𝜏𝑛 (𝜇) + 𝑏𝑛 𝜋𝑛 (𝜇)]
𝑛=1
𝑛(𝑛 + 1)
This means that for each angle 𝜇 we need to know 𝜏𝑛 (𝜇) and 𝜋𝑛 (𝜇) for every 𝑎𝑛 and 𝑏𝑛 . Equation 4.47 in Bohren and
Huffman states
2𝑛 − 1 𝑛
𝜋𝑛 (𝜇) = 𝜇𝜋𝑛−1 (𝜇) − 𝜋𝑛−2 (𝜇)
𝑛−1 𝑛−1
and knowning that 𝜋0 (𝜇) = 0 and 𝜋1 (𝜇) = 1, all the rest can be found. Similarly
so the plan is to use these recurrence relations to find 𝜋𝑛 (𝜇) and 𝜏𝑛 (𝜇) during the summation process.
The only real trick is to account for 0-based arrays when the sums above are 1-based.
[10]: m=1.55-0.1j
x=5.213
mu = [0.0,0.5,1.0]
S1,S2 = miepython.mie_S1_S2(m,x,mu)
for i in range(len(mu)):
print(mu[i], S2[i].real, S2[i].imag)
0.0 0.043082703780854124 -0.05982417019769806
0.5 -0.08406917971660792 0.138950305924677
1.0 1.2438024701591524 -0.1984324112177587
[12]: num=100
m=1.1
x=np.linspace(0.01,0.21,num)
qext, qsca, qback, g = miepython.mie(m,x)
plt.plot(x,qback)
plt.plot((abs(0.1/m),abs(0.1/m)),(0,qback[num-1]))
plt.xlabel("Size Parameter (-)")
plt.ylabel("Backscattering Efficiency")
plt.show()
[ ]:
Scott Prahl
Mar 2021, Version 4
The problem is to generate random scattering angles which match a given Mie scattering profile.
This is difficult when the size parameter gets large because nearly all the light is scattered directly forward.
This notebook is an attempt to solve the scattering problem.
If RigolWFM is not installed, uncomment the following cell (i.e., delete the #) and run (shift-enter)
try:
import miepython
except ModuleNotFoundError:
print('miepython not installed. To install, uncomment and run the cell above.')
print('Once installation is successful, rerun this cell again.')
One method of generating a random number 𝜉 with a specified distribution 𝑝(𝜉) is to create a random event for the
variable 𝜉 such that the random event falls with frequency 𝑝(𝑥)𝑑𝑥 in the interval (𝜉, 𝜉 + 𝑑𝜉). This method requires the
normalization of the probability density function (PDF) over the interval (𝑎, 𝑏)
∫︁ 𝑏
𝑝(𝜉)𝑑𝜉 = 1
𝑎
This is done by choosing a random number 𝑅 uniformly distributed in the interval [0, 1] and requiring
∫︁ 𝜉
𝑅= 𝑝(𝜉 ′ )𝑑𝜉 ′
𝑎
Note that 𝑅(𝜉) represents the cumulative distribution function for 𝑝(𝜉 ′ ).
Azimuthal Angles
A normalized phase function describes the probability density function for the azimuthal and longitudinal angles for a
photon when it is scattered. If the phase function has no azimuthal dependence, then the azimuthal angle 𝜑 is uniformly
distributed between 0 and 2𝜋, and may be generated by multiplying a pseudo-random number 𝑅 uniformly distributed
over the interval [0,1] by 2𝜋
𝜑 = 2𝜋𝑅
The probability density function for the longitudinal angle 𝜃 between the current photon direction and the scattered
photon direction is found by integrating the phase function over all azimuthal angles 𝑝(cos 𝜃). For example, the
probability density function for an isotropic distribution is
1
𝑝(cos 𝜃) =
2
Substituting Equation (A1.9) into Equation (A1.2) yields the following generating function for cosine of the longitu-
dinal angle 𝜃
cos 𝜃 = 2𝑅 − 1
Unfortunately, we cannot do the integral and solve for the angle analytically for the Mie scattering case. We will need
to do it numerically.
We ignore polarization effects and are just considering total scattering of the electric field. Moreover, we assume that
the scattering function 𝑆(𝜃, 𝜑) is independent of azimuthal angle 𝜑. In that case the cumulative distribution function
(CDF) is
∫︁ 𝜋
CDF(𝜃) = 2𝜋 𝑆(𝜃) sin 𝜃 𝑑𝜃
0
if 𝜇 = cos 𝜃. Then
∫︁ 1
CDF(𝜇) = 2𝜋 𝑆(𝜇) 𝑑𝜇
−1
Consider relatively isotropic mie scattering from a smallish sphere. The idea is that we want to generate random angles
with this distribution.
x = 2*np.pi*r/lambdaa
num=50
mu = np.linspace(-1,1,num)
s1, s2 = miepython.mie_S1_S2(m,x,mu)
scat = (abs(s1)**2 + abs(s2)**2)/2
plt.scatter(mu, scat,s=1)
plt.xlabel('Cosine of Exit Angle')
plt.ylabel('Unpolarized Scattering Function')
plt.title(r'Water Droplet ($\lambda$=550nm, r=%.2f$\mu$m)'%r)
plt.ylim([-0.1,0.4])
plt.xlim([-1.1,1.1])
plt.show()
The CDF
[4]: help(miepython.mie_cdf)
Help on function mie_cdf in module miepython.miepython:
mie_cdf(m, x, num)
Create a CDF for unpolarized scattering uniformly spaced in cos(theta).
The CDF covers scattered (exit) angles ranging from 180 to 0 degrees.
(The cosines are uniformly distributed over -1 to 1.) Because the angles
are uniformly distributed in cos(theta), the scattering function is not
sampled uniformly and therefore huge array sizes are needed to adequately
sample highly anisotropic phase functions.
(continues on next page)
Args:
m: the complex index of refraction of the sphere
x: the size parameter of the sphere
num: length of desired CDF array
Returns:
mu: array of cosines of angles
cdf: array of cumulative distribution function values
x = 2*np.pi*r/lambdaa
num=50
mu, cdf = miepython.mie_cdf(m,x,num)
plt.scatter(mu,cdf,s=1)
plt.xlabel('Cosine of Exit Angle')
plt.ylabel('CDF of Unpolarized Scattering Function')
plt.title(r'Water Droplet ($\lambda$=550nm, r=%.2f$\mu$m)'%r)
plt.ylim([-0.1,1.1])
plt.xlim([-1.1,1.1])
plt.show()
Inverting
x = 2*np.pi*r/lambdaa
num=20
mu, cdf = miepython.mie_cdf(m,x,num)
plt.show()
Better Inversion
In the graph above, the horizontal spacing is not uniform. For speed in the Monte Carlo program we would like direct
look ups into the array.
Fortunately there is another function that returns a CDF with uniform spacing in CDF
[7]: help(miepython.mie_mu_with_uniform_cdf)
Help on function mie_mu_with_uniform_cdf in module miepython.miepython:
mie_mu_with_uniform_cdf(m, x, num)
Create a CDF for unpolarized scattering for uniform CDF.
The CDF covers scattered (exit) angles ranging from 180 to 0 degrees.
(continues on next page)
Args:
m: the complex index of refraction of the sphere
x: the size parameter of the sphere
num: length of desired CDF array
Returns:
mu: array of cosines of angles (irregularly spaced)
cdf: array of cumulative distribution function values
x = 2*np.pi*r/lambdaa
num=20
mid = num // 2
mu, cdf = miepython.mie_mu_with_uniform_cdf(m,x,num)
plt.show()
#print(cdf[mid],mu[mid])
And now the a random deviate along (say 0.526) will be mapped to the proper exit angle (0.715)
So now once we have calculated the magic mu array that has a CDF that is uniformly spaced, we can just do a quick
look up to get the next random deviate.
[9]: help(miepython.generate_mie_costheta)
Help on function generate_mie_costheta in module miepython.miepython:
generate_mie_costheta(mu_cdf)
Generate a new scattering angle using a cdf.
Args:
mu_cdf: a cumulative distribution function
Returns
The cosine of the scattering angle
num_angles=20
mu, cdf = miepython.mie_mu_with_uniform_cdf(m,x,num_angles)
num_bins = 20
plt.hist(angles, bins=num_bins)
plt.plot(mu,s*num_deviates/num_bins*4*np.pi)
#plt.yscale('log')
plt.title("r=%.2f$\mu$m, m=%.3f and %d bins"%(r,m.real,num_bins))
[ ]:
miepython.mie(m, x)
miepython.i_per(m, x, mu)
miepython.i_par(m, x, mu)
miepython.i_unpolarized(m, x, mu)
miepython.miepython.i_par(m, x, mu)
Return the scattered intensity in a plane parallel to the incident light.
This is the scattered intensity in a plane that is parallel to the field of the incident plane wave. The intensity is
normalized such that the integral of the unpolarized intensity over 4 steradians is equal to the single scattering
albedo.
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter
• mu – the cos(theta) of each direction desired
Returns The intensity at each angle in the array mu. Units [1/sr]
miepython.miepython.i_per(m, x, mu)
Return the scattered intensity in a plane normal to the incident light.
This is the scattered intensity in a plane that is perpendicular to the field of the incident plane wave. The intensity
is normalized such that the integral of the unpolarized intensity over 4 steradians is equal to the single scattering
albedo.
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter of the sphere
• mu – the angles, cos(theta), to calculate intensities
Returns The intensity at each angle in the array mu. Units [1/sr]
miepython.miepython.i_unpolarized(m, x, mu)
Return the unpolarized scattered intensity at specified angles.
This is the average value for randomly polarized incident light. The intensity is normalized such that the integral
of the unpolarized intensity over 4 steradians is equal to the single scattering albedo.
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter
• mu – the cos(theta) of each direction desired
Returns The intensity at each angle in the array mu. Units [1/sr]
miepython.miepython.mie(m, x)
Calculate the efficiencies for a sphere where m or x may be arrays.
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter of the sphere
Returns qext – the total extinction efficiency qsca: the scattering efficiency qback: the backscatter
efficiency g: the average cosine of the scattering phase function
miepython.miepython.mie_S1_S2(m, x, mu)
Calculate the scattering amplitude functions for spheres.
The amplitude functions have been normalized so that when integrated over all 4*pi solid angles, the integral
will be qext*pi*x**2.
The units are weird, sr**(-0.5)
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter of the sphere
• mu – the angles, cos(theta), to calculate scattering amplitudes
Returns S1, S2 – the scattering amplitudes at each angle mu [sr**(-0.5)]
miepython.miepython.mie_cdf(m, x, num)
Create a CDF for unpolarized scattering uniformly spaced in cos(theta).
The CDF covers scattered (exit) angles ranging from 180 to 0 degrees. (The cosines are uniformly distributed
over -1 to 1.) Because the angles are uniformly distributed in cos(theta), the scattering function is not sampled
uniformly and therefore huge array sizes are needed to adequately sample highly anisotropic phase functions.
Since this is a cumulative distribution function, the maximum value should be 1.
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter of the sphere
• num – length of desired CDF array
Returns mu – array of cosines of angles cdf: array of cumulative distribution function values
miepython.miepython.mie_mu_with_uniform_cdf(m, x, num)
Create a CDF for unpolarized scattering for uniform CDF.
The CDF covers scattered (exit) angles ranging from 180 to 0 degrees. (The cosines are uniformly distributed
over -1 to 1.) These angles mu correspond to uniform spacing of the cumulative distribution function for
unpolarized Mie scattering where cdf[i] = i/(num-1).
This is a brute force implementation that solves the problem by calculating the CDF at many points and then
scanning to find the specific angles that correspond to uniform interval of the CDF.
Since this is a cumulative distribution function, the maximum value should be 1.
Parameters
• m – the complex index of refraction of the sphere
• x – the size parameter of the sphere
• num – length of desired CDF array
Returns mu – array of cosines of angles (irregularly spaced) cdf: array of cumulative distribution
function values
3.4.12 Changelog
1.3.3 (03/21/21)
1.3.2 (01/13/21)
1.3.1 (03/30/20)
• improve docstrings
• use Sphinx documentation
• host docs on readthedocs.io
• use tox
1.3.0 (02/19/19)
1.2.0. (02/08/19)
• improve README.md
1.1.1. (06/25/18)
1.1.0 (03/02/2018)
• update version
• initial commit of 04_rayleigh.ipynb
• renamed doc files
• use new functions from miepython
• omit low level tests
• add __author__ and __version__
• add i_par, i_per, i_unpolarized, and hide private functions
• rename doc files
• add quantitative comparisons of angular scattering
• tweak verbiage
• ignore more
• initial commit
• more cleanup
• ignore dist files
• minor reorg of contents
• fix typos, add more refraction stuff
• Changes to match PEP8 style
• add minor comments, fix typos
1.0.0 (08/27/2017)
0.4.2 (08/26/2017)
0.4.1 (08/26/2017)
• fix typo
0.4.0 (08/26/2017)
0.3.2 (07/07/2017)
0.3.1 (07/07/2017)
• Bump version.
• Add functions to __init__.py.
0.3.0 (07/07/2017)
m
miepython.miepython, 113
121
miepython, Release 1.3.3
E
ez_intensities() (in module
miepython.miepython), 114
ez_mie() (in module miepython.miepython), 114
G
generate_mie_costheta() (in module
miepython.miepython), 114
I
i_par() (in module miepython.miepython), 115
i_per() (in module miepython.miepython), 115
i_unpolarized() (in module miepython.miepython),
115
M
mie() (in module miepython.miepython), 115
mie_cdf() (in module miepython.miepython), 116
mie_mu_with_uniform_cdf() (in module
miepython.miepython), 116
mie_S1_S2() (in module miepython.miepython), 115
miepython.miepython
module, 113
module
miepython.miepython, 113
123