"""
.. module :: MieUtils
:platform: Unix, Windows
:synopsis: Mie scattering calculation
This module contains the Mie scattering mathematics. This was separated because
many different functions were needed to model that.
The functions were adapter from `Maetzler (Maetzler, C. Matlab functions for Mie
scattering and absorption
Institute of Applied Physics, University of Bern, 2002)
<http://arrc.ou.edu/~rockee/NRA_2007_website/Mie-scattering-Matlab.pdf>`_
.. moduleauthor :: Ricardo Entz <maiko at thebigheads.net>
.. license::
PyVSim v.1
Copyright 2013 Ricardo Entz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import division
import numpy as np
import scipy.special
[docs]def Tau(pi, costheta):
"""
Returns the value of the function :math:`\\tau`, as defined by
`Maetzler (Maetzler, C. Matlab functions for Mie scattering and absorption
Institute of Applied Physics, University of Bern, 2002)
<http://arrc.ou.edu/~rockee/NRA_2007_website/Mie-scattering-Matlab.pdf>`_,
as a function of the cosine of the
scattering angle (:math:`\\theta`) and the :math:`\\Pi` function:
:math:`{\\tau}_n, cos(\\theta)) = n cos(\\theta) \\Pi(n) - (n+1) \\Pi(n-1)`
Parameters
----------
pi : numpy.array (n)
An array containing the values of the :math:`\\Pi` function
costheta : double
The co-sine of the scattering angle
Returns
-------
tau : numpy.array (n)
An array containing the values of the \Theta function
"""
tau = np.empty_like(pi)
n = np.arange(len(pi))+1
tau[0] = costheta
tau[1:] = n[1:]*costheta*pi[1:] - (n[1:] + 1)*pi[:-1]
return tau
[docs]def Pi(nmax, costheta):
"""
Returns a generator for the recursive calculation of the Pi function
as defined in "Hahn, D. W. Light scattering theory Department of
Mechanical and Aeorospace Engineering, University of Florida, 2009", which
is:
.. math::
\\Pi_n(cos \\theta) = {P_n^{(1)} (cos \\theta) \\over sin(\\theta)}
Where:
.. math::
P_n^{(1)}
is a Legendre polynomial
Or, in the recursive form, as shown in `Maetzler (Maetzler, C. Matlab
functions for Mie scattering and absorption
Institute of Applied Physics, University of Bern, 2002)
<http://arrc.ou.edu/~rockee/NRA_2007_website/Mie-scattering-Matlab.pdf>`_:
.. math::
\\Pi_n = {{2n-1} \\over {n-1}} cos(\\theta) P_{n-1} - {n \\over {n-1}} \\Pi_{n-2}
With:
.. math::
\\Pi_{0} = 0
\\Pi_{1} = 1
Parameters
----------
nmax : int
The number of coefficients to be calculated
costheta: np.array (N)
The angles (in radians) to be used as input to the \Pi function
Returns
-------
Pi : generator
A generator capable of creating an :math:`(N+1)`-sized array with the
values
of the :math:`\\Pi` function
"""
n = 2
pi_n = 1
pi_n_1 = 0
yield pi_n
while n <= nmax+1:
newpi = (((2*n - 1) / (n - 1)) * costheta * pi_n -
(n / (n - 1)) * pi_n_1)
pi_n_1 = pi_n
pi_n = newpi
n = n + 1
yield newpi
[docs]def mie_abcd(m, x):
"""
Computes a matrix of Mie coefficients:
.. math::
a_n, b_n, c_n, d_n
of orders :math:`n=1` to :math:`n_{max}`, complex refractive index
:math:`m=m^\\prime + im^{\\prime\\prime}`,
and size parameter
:math:`x=k_0\\cdot r`,
where
:math:`k_0` is the wave number in the ambient medium,
:math:`r` sphere radius;
Reference: `p. 100, 477 in Bohren and Huffman (1983) BEWI:TDD122
<http://books.google.de/books/about/Absorption_and_scattering_of_light_by_sm.html?id=S1RCZ8BjgN0C&redir_esc=y>`_
There is a limitation for the maximum allowable :math:`x` for this
function. I have not yet verified from where it comes from
(limit: :math:`x = 180`, which yields a maximum particle size of
about :math:`30\\mu m` in air)
Adapted from Matlab code (Dec 2012)
Vectorized (Apr 2013)
Parameters
----------
m : real or complex
The particle refractive index.
x : numpy.array (M)
The Mie parameter, defined as
:math:`x = k_0 r = {{2 \\pi r} \\over \\lambda}`
(where :math:`r` is the particle radius and :math:`\\lambda` is the
light wavelength)
Returns
-------
(an, bn) : numpy.array (:math:`n_{max}`, M)
The Mie :math:`a` and :math:`b` parameters calculated for :math:`n_{max}`
factors, used to
calculate an approximation of the scattered light far field.
Raises
------
ValueError
If the particle diameter which is being calculated forces the creation
of too big matrices (happens when :math:`x` is higher than approximately 180)
"""
#
# Parameter calculation
#
nmax = np.around(2 + np.max(x) + 4*(np.max(x)**(1/3)))
n = np.arange(1,nmax+1)
nu = n+0.5
z = m*x
m2 = m*m
sqx = np.sqrt(0.5*np.pi/x)
sqz = np.sqrt(0.5*np.pi/z)
#
# Bessel function calculation
#
x_block = np.tile(x,(nmax,1)).T
z_block = np.tile(z,(nmax,1)).T
n_block = np.tile(n,(len(x),1)).T
"""
Some matrix dimensions to help understanding the calculations
<--nmax-->
x_block = [[x1, x1, x1 ...], ^
[x2, x2, x2 ...], len(x)
... ]] v
<--len(x)-->
n_block = [[1, 1, 1 ...], ^
[2, 2, 2 ...], nmax(x)
... |
[n, n, n ...]] v
<--nmax-->
nu = [nu1, nu2, nu3 ...] 1
<--len(x)-->
bx, bz, yx = [[nu1.x1, nu1.x2, ... nu1.xn], ^
[nu2.x1, nu2.x2, ... nu2.xn], |
... nmax(x)
|
[nun.x1, nun.x2, ... nun.xn]] v
bx, bz, yx have the final matrix size (FMS)
"""
bx = scipy.special.jv(nu,x_block ).T * np.tile(sqx,(nmax,1))
bz = scipy.special.jv(nu,m * x_block).T * np.tile(sqz,(nmax,1))
yx = scipy.special.yv(nu,x_block ).T * np.tile(sqx,(nmax,1))
# From now on, all calculations are made with the final matrix sizes
hx = bx+1j*yx
b1x = np.vstack([np.sin(x)/x ,bx[:-1,:]])
b1z = np.vstack([np.sin(z)/z ,bz[:-1,:]])
y1x = np.vstack([-np.cos(x)/x ,yx[:-1,:]])
h1x= b1x+1j*y1x;
ax = x_block.T * b1x - n_block*bx;
az = z_block.T * b1z - n_block*bz;
ahx= x_block.T * h1x - n_block*hx;
an = (m2*bz*ax-bx*az)/(m2*bz*ahx-hx*az);
bn = (bz*ax-bx*az)/(bz*ahx-hx*az);
return (an,bn)
[docs]def mieScatteringCrossSections(refractiveIndex,
particleDiameters,
wavelength,
theta):
"""
This function calculates the Mie scattering cross section of a spherical
particle. Its optimal use is by minimizing function calls and giving
a particle size range instead.
Parameters
----------
refrativeIndex : real / complex
The refractive index of the particles. The real part of the index
is the classical (as used in Snell's law) and the complex is related
to light absorption
particleDiameters : numpy.array (N)
A list of particle diameters to be calculated
wavelength : real, meters
The wavelength of the light source
theta : numpy.array (M), radians
A list of scattering angles to be calculated
Returns
-------
(sigma1, sigma2) : tuple of numpy.array (M,N)
The differential scattering cross sections for the given particle
diameters (column number) and scattering angles (row number). Sigma1
stands for light that is polarized perpendicular to the propagation
plane, and sigma2 for light polarized parallel to the propagation plane
Polarization parameter:
1 = accounts for light polarized perpendicular to propagation plane
2 = accounts for light polarized parallel to propagation plane
None = returns average
"""
cos_theta = np.cos(theta)
# Calculation of Mie parameters
(a,b) = mie_abcd(refractiveIndex,
np.pi*particleDiameters/wavelength)
nmax = np.size(a,0)
nangles = len(cos_theta)
nd = len(particleDiameters)
pi = np.empty((nangles,nmax,nd))
tau = np.empty((nangles,nmax,nd))
header = np.tile(Header(nmax), (nd,1)).T
for (i, ctheta) in enumerate(cos_theta):
pigen = Pi(nmax, ctheta)
pitemp = np.fromiter(pigen, np.float)
tautemp = Tau(pitemp, ctheta)
pi[i,:,:] = np.tile(pitemp[:-1],(nd,1)).T
tau[i,:,:] = np.tile(tautemp[:-1], (nd,1)).T
i1 = header * (a * pi + b * tau)
i2 = header * (b * pi + a * tau)
"""
The following sum puts the coefficients in the following form:
< ndiameters >
i1,i2 = [[i1, i1, ... i1], ^
[i1, i1, ... i1], nangles
...
[i1, i1, ... i1]] v
so, each particle diameter is represented by a column
"""
i1 = np.sum(i1,1)
i2 = np.sum(i2,1)
i1 = (np.abs(i1)**2) * (1/4) * (wavelength/np.pi)**2
i2 = (np.abs(i2)**2) * (1/4) * (wavelength/np.pi)**2
return (i1, i2)
def distributedSCS(refractiveIndex,
diameters,
percentage,
wavelength,
theta = np.linspace(0,np.pi,501)):
"""
This function calculates the mean differential scattering cross section of
a particle size distribution (described by a cumulative probability
function).
Note that this result shows the behavior of the distribution, which is not
the behavior of any of the particles.
Parameters
----------
refrativeIndex : real / complex
The refractive index of the particles. The real part of the index
is the classical (as used in Snell's law) and the complex is related
to light absorption
particleDiameters : numpy.array (N)
A list of particle diameters to be calculated
percentage : numpy.array (N)
The value of the cumulative distribution function (CDF) of the particle
diameter distribution sampled at the points given in the
particleDiameter array
wavelength : real, meters
The wavelength of the light source
theta : numpy.array (M), radians
A list of scattering angles to be calculated. If not given, defaults
to a 501-element array from 0 to 180deg
Returns
-------
(sigma1, sigma2) : tuple of numpy.array (M)
The differential scattering cross sections for the given distribution.
Sigma1 stands for light that is polarized perpendicular to the
propagation plane, and sigma2 for light polarized parallel to the
propagation plane
Raises
------
ValueError
If the particle diameter which is being calculated forces the creation
of too big matrices (happens when the Mie parameter
(:math:`2\\pi r \\over \\lambda) is higher than approximately 180)
"""
scs = mieScatteringCrossSections(refractiveIndex = refractiveIndex,
particleDiameters = diameters,
wavelength = wavelength,
theta = theta)
distribution = np.tile(percentage, (len(theta),1))
return (np.sum(scs[0] * distribution, 1), np.sum(scs[1] * distribution, 1))
if __name__ == "__main__":
import Utils
tic = Utils.Tictoc()
import matplotlib.pyplot as plt
tic.tic()
theta = np.linspace(0*np.pi/180,100*np.pi/180,1001)
diam = np.arange(0.0,3.1,0.0062)
pdf = scipy.special.gammainc(13.9043,10.9078*diam)**0.2079
perc = np.diff(pdf)
diam = diam[1:]*1e-6
scs1 = distributedSCS(1.45386,
diam,
perc,
532e-9,
theta)[0]
tic.toc()
apertureAngle = 0
kernel = np.ones(apertureAngle*len(theta)/180)
kernel = kernel / len(kernel)
print "Kernel is %d elements long" % len(kernel)
#for s in scs1:
# print s
#for n,s in enumerate(perc):
# print diam[n], s
plt.figure(facecolor = [1,1,1])
plt.hold(True)
plt.plot(theta*180/np.pi, scs1, label = "Whole distribution")
for n in range(0,len(diam),int(len(diam)/5.)):
print n, diam[n]
scs2 = distributedSCS(1.45386,
np.array([diam[n]]),
1, #np.array([perc[n]]),
532e-9,
theta)[0]
if apertureAngle > 0:
# filtered
plt.plot(theta*180/np.pi,np.convolve(scs2,kernel,mode="same"),
label = "D=%s micron contribution" % (diam[n]*1e6))
else:
# unfiltered
plt.plot(theta*180/np.pi,scs2,
label = "D=%s micron contribution" % (diam[n]*1e6))
plt.xlabel("Scattering angle")
plt.ylabel("Scattering cross section (m^2)/(sr)")
plt.legend(loc=3)
plt.yscale('log')
plt.grid(True, which="both", axis="both")
plt.show()