Initial Commit
This commit is contained in:
parent
c46f17029d
commit
374fc24f04
11 changed files with 674 additions and 0 deletions
46
Examples/P_q_vs_a_p.py
Normal file
46
Examples/P_q_vs_a_p.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
Plots Mie and Rayleigh form factors at varying particle radii.
|
||||
"""
|
||||
|
||||
from scattering import *
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Default Scattering Parameters
|
||||
n_p = 2.0 # particle refractive index
|
||||
n_s = 1.332 # medium refractive index (water)
|
||||
lambda_vac = 685e-9 # wavelength of light in vacuum [meters]
|
||||
phi = 0.03 # particle volume fraction
|
||||
n_ang = 100 # number of sampled angles
|
||||
|
||||
a_p_range = [50e-9, 100e-9, 250e-9, 500e-9, 1000e-9] # particle radii [meters]
|
||||
p_q = np.empty(shape=(n_ang, np.size(a_p_range), 2)) # initialize result array
|
||||
|
||||
# Collect Scattering Data
|
||||
for i in range(np.size(a_p_range)):
|
||||
_, _, _, _, [q, p_q[:, i, 0], _, _, _, _, _, _] = \
|
||||
mie_scattering(n_p, n_s, a_p_range[i], lambda_vac, phi, n_ang=n_ang, struct='PY')
|
||||
_, _, _, _, [_, p_q[:, i, 1], _, _] = \
|
||||
rayleigh_scattering(n_p, n_s, a_p_range[i], lambda_vac, phi, n_ang=n_ang)
|
||||
|
||||
# Generate Colors
|
||||
colormap = plt.get_cmap('plasma')
|
||||
c = np.empty(shape=(np.size(a_p_range), 3))
|
||||
for i in range(np.size(a_p_range)):
|
||||
c[i, :] = colormap.colors[round(256 * i / np.size(a_p_range))]
|
||||
|
||||
# Plot
|
||||
fig, ax1 = plt.subplots()
|
||||
for i in range(np.size(a_p_range)):
|
||||
if i == 0:
|
||||
ax1.plot(q, p_q[:, i, 0] / p_q[0, i, 0], label=r'Mie, $a_p$ = %i nm' % (a_p_range[i] * 1e9), c=c[i])
|
||||
ax1.plot(q, p_q[:, i, 1] / p_q[0, i, 1], linestyle='--',
|
||||
label=r'Rayleigh, $a_p$ = %i nm' % (a_p_range[i] * 1e9), c=c[i])
|
||||
else:
|
||||
ax1.plot(q, p_q[:, i, 0] / p_q[0, i, 0], label=r'$a_p$ = %i nm' % (a_p_range[i] * 1e9), c=c[i])
|
||||
ax1.plot(q, p_q[:, i, 1] / p_q[0, i, 1], linestyle='--', c=c[i])
|
||||
ax1.set(xlabel='q', ylabel='Normalized P(q)', title=r'$n_p$ = %.3f, $n_s$ = %.3f, $\lambda$ = %i nm, $\phi$ = %.2f' % (n_p, n_s, lambda_vac*1e9, phi))
|
||||
ax1.legend(loc='upper right')
|
||||
ax1.set(ylim=[-0.05, 1.1])
|
||||
plt.show()
|
||||
|
||||
BIN
Examples/Results/Intensity Plot.png
Normal file
BIN
Examples/Results/Intensity Plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
BIN
Examples/Results/Mie vs Rayleigh l_star.png
Normal file
BIN
Examples/Results/Mie vs Rayleigh l_star.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
Examples/Results/P(q) vs a_p.png
Normal file
BIN
Examples/Results/P(q) vs a_p.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
Examples/Results/S(q) Models.png
Normal file
BIN
Examples/Results/S(q) Models.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
Examples/Results/Silica Fit.png
Normal file
BIN
Examples/Results/Silica Fit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
38
Examples/S_q_models.py
Normal file
38
Examples/S_q_models.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""
|
||||
Plots various structure factor models.
|
||||
"""
|
||||
|
||||
from scattering import *
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Default Scattering Parameters
|
||||
a_p = 200e-9 # meters
|
||||
n_p = 1.5 # particle refractive index
|
||||
n_s = 1.332 # medium refractive index (water)
|
||||
lambda_vac = 685e-9 # wavelength of light in vacuum [meters]
|
||||
phi = 0.03 # particle volume fraction
|
||||
n_ang = 100 # number of sampled angles
|
||||
|
||||
s_q = np.empty(shape=(n_ang, 3)) # initialize result array
|
||||
|
||||
# Collect Scattering Data
|
||||
_, _, _, _, [q, _, s_q[:, 0], _, _, _, _, _] = mie_scattering(n_p, n_s, a_p, lambda_vac, phi, n_ang=n_ang,
|
||||
struct='PY')
|
||||
_, _, _, _, [_, _, s_q[:, 1], _, _, _, _, _] = mie_scattering(n_p, n_s, a_p, lambda_vac, phi, n_ang=n_ang,
|
||||
struct='PYAnnulus', optional_params=[1.2*a_p])
|
||||
_, _, _, _, [_, _, s_q[:, 2], _, _, _, _, _] = mie_scattering(n_p, n_s, a_p, lambda_vac, phi, n_ang=n_ang,
|
||||
struct='SHS', optional_params=[0.2, 0.05])
|
||||
|
||||
# Plot
|
||||
fig, ax1 = plt.subplots()
|
||||
ax1.plot(q, np.ones_like(s_q[:, 0]), 'k', label='No Interaction', linewidth=2)
|
||||
ax1.plot(q, s_q[:, 0], 'k-.', label='Hard Sphere', linewidth=2)
|
||||
ax1.plot(q, s_q[:, 1], 'k:', label='1.2$a_p$ Excluded Annulus', linewidth=2)
|
||||
ax1.plot(q, s_q[:, 2], 'k--', label='Sticky Hard Sphere', linewidth=2)
|
||||
ax1.set(xlabel=r'$q$', ylabel='S(q)',
|
||||
title=r'$n_p$ = %.3f, $n_s$ = %.3f, $a_p$ = %i nm, $\lambda$ = %i nm, $\phi$ = %.2f'
|
||||
% (n_p, n_s, a_p*1e9, lambda_vac*1e9, phi))
|
||||
ax1.legend()
|
||||
|
||||
plt.show()
|
||||
56
Examples/intensity_plot.py
Normal file
56
Examples/intensity_plot.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
Plots Mie intensities and form factor versus scattering angle.
|
||||
"""
|
||||
|
||||
from scattering import *
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Default Scattering Parameters
|
||||
a_p = 500e-9 # # particle radius [meters]
|
||||
n_p = 1.5 # particle refractive index
|
||||
n_s = 1.0 # medium refractive index
|
||||
lambda_vac = 685e-9 # wavelength of light in vacuum [meters]
|
||||
phi = 0.03 # particle volume fraction
|
||||
n_ang = 100 # number of sampled angles
|
||||
|
||||
# Collect Data
|
||||
theta, i1, i2, _, _ \
|
||||
= mie_scattering(n_p, n_s, a_p, lambda_vac, phi)
|
||||
_, i1r, i2r, _, _ \
|
||||
= rayleigh_scattering(n_p, n_s, a_p, lambda_vac, phi)
|
||||
theta = np.append(theta, np.pi + theta)
|
||||
i1 = np.append(i1, i1[::-1])
|
||||
i2 = np.append(i2, i2[::-1])
|
||||
i1r = np.append(i1r, i1r[::-1])
|
||||
i2r = np.append(i2r, i2r[::-1])
|
||||
|
||||
# Generate Colors
|
||||
n_colors = 3
|
||||
colormap = plt.get_cmap('plasma')
|
||||
c = np.empty(shape=(n_colors, 3))
|
||||
for i in range(n_colors):
|
||||
c[i, :] = colormap.colors[round(256 * i / n_colors)]
|
||||
|
||||
# Plot
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2, subplot_kw=dict(projection='polar'))
|
||||
ax1.plot(theta, i1, label='Perpendicular Mie', c=c[0])
|
||||
ax1.plot(theta, i2, label='Parallel Mie', c=c[2])
|
||||
ax1.plot(theta, 0.5*(i1+i2), label='Mie P(q)', c=c[1])
|
||||
# ax1.plot(theta, i1r, label='Perpendicular Rayleigh', c=c[0], linestyle='--')
|
||||
# ax1.plot(theta, i2r, label='Parallel Rayleigh', c=c[2], linestyle='--')
|
||||
# ax1.plot(theta, 0.5*(i1r+i2r), label='Rayleigh P(q)', c=c[1], linestyle='--')
|
||||
ax1.set_title("Intensity")
|
||||
ax1.legend()
|
||||
|
||||
ax2.plot(theta, np.log10(i1 + 1), label='Perpendicular Mie', c=c[0])
|
||||
ax2.plot(theta, np.log10(i2 + 1), label='Parallel Mie', c=c[2])
|
||||
ax2.plot(theta, np.log10(0.5*(i1+i2) + 1), label='Mie P(q)', c=c[1])
|
||||
# ax2.plot(theta, np.log10(i1r + 1), label='Perpendicular Rayleigh', c=c[0], linestyle='--')
|
||||
# ax2.plot(theta, np.log10(i2r + 1), label='Parallel Rayleigh', c=c[2], linestyle='--')
|
||||
# ax2.plot(theta, np.log10(0.5*(i1r+i2r) + 1), label='Rayleigh P(q)', c=c[1], linestyle='--')
|
||||
ax2.set_title("Log Intensity")
|
||||
ax2.legend()
|
||||
|
||||
plt.suptitle(r'$n_p$ = %.3f, $n_s$ = %.3f, $a_p$ = %i nm, $\lambda$ = %i nm, $\phi$ = %.2f'
|
||||
% (n_p, n_s, a_p*1e9, lambda_vac*1e9, phi))
|
||||
plt.show()
|
||||
58
Examples/mie_vs_rayleigh.py
Normal file
58
Examples/mie_vs_rayleigh.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
Plots l* for varying input parameters for both Mie and Rayleigh scattering.
|
||||
"""
|
||||
|
||||
from scattering import *
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Default Scattering Parameters
|
||||
n_p = 1.8 # particle refractive index
|
||||
n_s = 1.332 # medium refractive index (water)
|
||||
a_p = 250e-9 # particle radii [meters]
|
||||
lambda_vac = 685e-9 # wavelength of light in vacuum [meters]
|
||||
phi = 0.03 # particle volume fraction
|
||||
|
||||
# Generate Parameter Ranges
|
||||
n_range = 100 # number of data points between values
|
||||
phi_range = np.linspace(0.01, 0.15, n_range)
|
||||
a_p_range = np.linspace(150e-9/2, 1000e-9/2, n_range)
|
||||
n_p_range = np.linspace(1.6, 2, n_range)
|
||||
|
||||
# Initialize Result Array
|
||||
l_star = np.empty(shape=(n_range, 3, 2))
|
||||
|
||||
# Collect Scattering Data
|
||||
for i in range(n_range):
|
||||
_, _, _, l_star[i, 0, 0], _ \
|
||||
= mie_scattering(n_p, n_s, a_p, lambda_vac, phi_range[i])
|
||||
_, _, _, l_star[i, 1, 0], _ \
|
||||
= mie_scattering(n_p, n_s, a_p_range[i], lambda_vac, phi)
|
||||
_, _, _, l_star[i, 2, 0], _ \
|
||||
= mie_scattering(n_p_range[i], n_s, a_p, lambda_vac, phi)
|
||||
|
||||
_, _, _, l_star[i, 0, 1], _ \
|
||||
= rayleigh_scattering(n_p, n_s, a_p, lambda_vac, phi_range[i])
|
||||
_, _, _, l_star[i, 1, 1], _ \
|
||||
= rayleigh_scattering(n_p, n_s, a_p_range[i], lambda_vac, phi)
|
||||
_, _, _, l_star[i, 2, 1], _ \
|
||||
= rayleigh_scattering(n_p_range[i], n_s, a_p, lambda_vac, phi)
|
||||
|
||||
# Plot Data
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
|
||||
ax1.plot(phi_range, l_star[:, 0, 0]*1e6, 'k', label='Mie', linewidth=2)
|
||||
ax1.plot(phi_range, l_star[:, 0, 1]*1e6, 'k--', label='Rayleigh', linewidth=2)
|
||||
ax1.legend()
|
||||
ax1.set(xlabel=r'$\phi$', ylabel='l* [µm]')
|
||||
|
||||
ax2.plot(a_p_range*1e9, l_star[:, 1, 0]*1e6, 'k', label='Mie', linewidth=2)
|
||||
ax2.plot(a_p_range*1e9, l_star[:, 1, 1]*1e6, 'k--', label='Rayleigh', linewidth=2)
|
||||
ax2.set(xlabel=r'$a_p$ [nm]')
|
||||
|
||||
ax3.plot(n_p_range, l_star[:, 2, 0]*1e6, 'k', label='Mie', linewidth=2)
|
||||
ax3.plot(n_p_range, l_star[:, 2, 1]*1e6, 'k--', label='Rayleigh', linewidth=2)
|
||||
ax3.set(xlabel=r'$n_p$')
|
||||
|
||||
# title with default parameters listed
|
||||
plt.suptitle(r'$n_p$ = %.3f, $n_s$ = %.3f, $a_p$ = %i nm, $\lambda$ = %i nm, $\phi$ = %.2f' % (n_p, n_s, a_p*1e9, lambda_vac*1e9, phi))
|
||||
plt.show()
|
||||
55
Examples/silica_data_fit.py
Normal file
55
Examples/silica_data_fit.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
Generates l* values versus particle volume concentration for silica particles with varying form factor models.
|
||||
Plots model versus experimental data (courtesy of Qi Li).
|
||||
"""
|
||||
|
||||
from scattering import *
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Scattering Parameters
|
||||
n_p = 1.446 # particle refractive index (silica)
|
||||
n_s = 1.332 # medium refractive index (water)
|
||||
a_p = 345e-9/2 # particle radii [meters]
|
||||
lambda_vac = 685e-9 # wavelength of light in vacuum [meters]
|
||||
|
||||
# Generate Parameter Range
|
||||
n_range = 100 # number of data points between values
|
||||
phi_range = np.linspace(0.03, 0.15, n_range)
|
||||
|
||||
# Initialize Result Array
|
||||
l_star_phi = np.empty(shape=(n_range, 4))
|
||||
|
||||
# Silica Data (courtesy of Qi Li)
|
||||
silica_data = np.array([[0.065, 0.067, 0.069, 0.072, 0.075, 0.135],
|
||||
[0.000230, 0.000225, 0.000216, 0.000215, 0.000205, 0.000130]])
|
||||
|
||||
# Collect Scattering Data
|
||||
for i in range(n_range):
|
||||
_, _, _, l_star_phi[i, 0], _ \
|
||||
= mie_scattering(n_p, n_s, a_p, lambda_vac, phi_range[i])
|
||||
_, _, _, l_star_phi[i, 1], _ \
|
||||
= mie_scattering(n_p, n_s, a_p, lambda_vac, phi_range[i], struct='PY')
|
||||
_, _, _, l_star_phi[i, 2], _ \
|
||||
= mie_scattering(n_p, n_s, a_p, lambda_vac, phi_range[i], struct='SHS', optional_params=[0.2, 0.05])
|
||||
_, _, _, l_star_phi[i, 3], _ \
|
||||
= mie_scattering(n_p, n_s, a_p, lambda_vac, phi_range[i], struct='PYAnnulus', optional_params=[1.2*a_p])
|
||||
|
||||
# Generate Colors
|
||||
n_colors = 4
|
||||
colormap = plt.get_cmap('plasma')
|
||||
c = np.empty(shape=(n_colors, 3))
|
||||
for i in range(n_colors):
|
||||
c[i, :] = colormap.colors[round(256 * i / n_colors)]
|
||||
|
||||
# Plot Data
|
||||
fig, ax1 = plt.subplots()
|
||||
ax1.plot(phi_range, l_star_phi[:, 0]*1e6, label='No Interactions', linewidth=2, c=c[0])
|
||||
ax1.plot(phi_range, l_star_phi[:, 1]*1e6, linestyle='--', label='Hard Sphere', linewidth=2, c=c[1])
|
||||
ax1.plot(phi_range, l_star_phi[:, 2]*1e6, linestyle=':', label='Sticky Hard Sphere', linewidth=2, c=c[2])
|
||||
ax1.plot(phi_range, l_star_phi[:, 3]*1e6, linestyle='-.', label='1.2$a_p$ Excluded Annulus', linewidth=2, c=c[3])
|
||||
ax1.plot(silica_data[0, :], silica_data[1, :]*1e6, 'k.', label='Silica Data', markersize=10)
|
||||
ax1.legend()
|
||||
ax1.set(xlabel=r'$\phi$', ylabel='l* [µm]')
|
||||
|
||||
plt.show()
|
||||
421
scattering.py
Normal file
421
scattering.py
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
"""
|
||||
Function package for generating theoretical light scattering models.
|
||||
|
||||
Originally composed by Nicholas Sbalbi (nsbalbi@umass.edu) during the 2021 CHARM REU Program for the Furst Group in the
|
||||
University of Delaware Department of Chemical and Biomolecular Engineering. Last updated 08/13/2021
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import scipy.special as sps
|
||||
import sys
|
||||
from scattering_test import *
|
||||
|
||||
|
||||
def mie_pi_tau(theta, alpha):
|
||||
"""
|
||||
Calculate the functions pi and tau which appear in the Mie scattering calculation.
|
||||
|
||||
These functions match those described by van de Hulst in Light Scattering by Small Particles, 1981, and are
|
||||
calculated using the method outlined by Kerker in The Scattering of Light and Other Electromagnetic Radiation, 1969.
|
||||
|
||||
Args:
|
||||
theta: the desired scattering angles
|
||||
alpha: the size parameter of the spheres (2 * pi * radius / wavelength)
|
||||
|
||||
Returns:
|
||||
pi: the first function
|
||||
tau: the second function
|
||||
"""
|
||||
# Constants
|
||||
cos_theta = np.cos(theta) # pre-calculated for efficiency
|
||||
sin_theta = np.sin(theta) # "
|
||||
n_ang = np.size(theta) # number of scattering angles
|
||||
n_terms = np.rint(alpha + 4.05*alpha**0.3333 + 2).astype(int) # number of terms in series for coeff calculation
|
||||
|
||||
# Initialize arrays
|
||||
pi = np.empty(shape=(n_terms, n_ang))
|
||||
pi_prime = np.empty(shape=(n_terms, n_ang))
|
||||
tau = np.empty(shape=(n_terms, n_ang))
|
||||
|
||||
# Caluclation of pi and tau via Kerker's method
|
||||
for i in range(n_terms): # loop over rows
|
||||
pi[i, :] = -sps.lpmv(1, i + 1, cos_theta) / sin_theta # generates 1st order Legendre polynomials
|
||||
if i == 0:
|
||||
pi_prime[i, :] = np.zeros(shape=n_ang)
|
||||
tau[i, :] = cos_theta * pi[i, :]
|
||||
elif i == 1:
|
||||
pi_prime[i, :] = 3 * np.ones(shape=n_ang)
|
||||
tau[i, :] = cos_theta * pi[i, :] - 3 * sin_theta ** 2
|
||||
else:
|
||||
for j in range(n_ang): # loop over columns
|
||||
pi_prime[i, j] = (2 * i + 1) * pi[i - 1, j] + pi_prime[i - 2, j]
|
||||
tau[i, :] = cos_theta * pi[i, :] - sin_theta ** 2 * pi_prime[i, :]
|
||||
|
||||
return pi, tau
|
||||
|
||||
|
||||
def mie_a_b(alpha, m):
|
||||
"""
|
||||
Calculate the Mie coefficients for Mie scattering by spheres.
|
||||
|
||||
The size of the arrays is based on the recommended by Barber and Hill in Light Scattering by Particles:
|
||||
Computational Methods, 1990. The coefficients are calculated using the method outlined by Kerker in
|
||||
The Scattering of Light and Other Electromagnetic Radiation, 1969.
|
||||
|
||||
Args:
|
||||
alpha: the size parameter of the spheres (2 * pi * radius / wavelength)
|
||||
m: the ratio between the refractive indices of the spheres and the medium
|
||||
|
||||
Returns:
|
||||
a: first set of Mie coefficients
|
||||
b: second set of Mie coefficients
|
||||
"""
|
||||
# Constants
|
||||
beta = m * alpha
|
||||
n_terms = np.rint(alpha + 4.05*alpha**0.3333 + 2).astype(int) # number of terms in series for coeff calculation
|
||||
x = np.arange(n_terms + 2) + 0.5 # two extra values for low and high bounds
|
||||
|
||||
# Calculation of coefficients via Kerker's method
|
||||
psi = np.empty(shape=(2, n_terms + 2)) # add two extra terms for left and right bounds
|
||||
chi = np.empty(shape=(2, n_terms + 2))
|
||||
psi[0, :] = (np.pi * alpha / 2) ** 0.5 * sps.jv(x, alpha) # generates Bessel function values of the first kind
|
||||
psi[1, :] = (np.pi * beta / 2) ** 0.5 * sps.jv(x, beta) # generates Bessel function values of the second kind
|
||||
chi[0, :] = -(np.pi * alpha / 2) ** 0.5 * sps.yv(x, alpha)
|
||||
chi[1, :] = -(np.pi * beta / 2) ** 0.5 * sps.yv(x, beta)
|
||||
|
||||
zeta = psi + 1j * chi # conversion to complex numbers
|
||||
|
||||
psi_prime = np.empty(shape=(2, n_terms))
|
||||
zeta_prime = np.empty(shape=(2, n_terms), dtype=np.complex64)
|
||||
psi_prime[0, :] = 0.5 * psi[0, :-2] - 0.5 * psi[0, 2:] + 0.5 / alpha * psi[0, 1:-1]
|
||||
psi_prime[1, :] = 0.5 * psi[1, :-2] - 0.5 * psi[1, 2:] + 0.5 / beta * psi[1, 1:-1]
|
||||
zeta_prime[0, :] = 0.5 * zeta[0, :-2] - 0.5 * zeta[0, 2:] + 0.5 / alpha * zeta[0, 1:-1]
|
||||
zeta_prime[1, :] = 0.5 * zeta[1, :-2] - 0.5 * zeta[1, 2:] + 0.5 / beta * zeta[1, 1:-1]
|
||||
|
||||
a = (psi_prime[1, :] * psi[0, 1:-1] - m * psi_prime[0, :] * psi[1, 1:-1]) / \
|
||||
(psi_prime[1, :] * zeta[0, 1:-1] - m * zeta_prime[0, :] * psi[1, 1:-1])
|
||||
b = (m * psi_prime[1, :] * psi[0, 1:-1] - psi_prime[0, :] * psi[1, 1:-1]) / \
|
||||
(m * psi_prime[1, :] * zeta[0, 1:-1] - zeta_prime[0, :] * psi[1, 1:-1])
|
||||
|
||||
return a, b
|
||||
|
||||
|
||||
def mie_s1_s2(theta, alpha, m):
|
||||
"""
|
||||
Calculate the scattering amplitude functions for Mie scattering by spheres.
|
||||
|
||||
Args:
|
||||
theta: the angles at which scattering amplitudes are calculated
|
||||
alpha: the size parameter of the spheres (2 * pi * radius / wavelength)
|
||||
m: the ratio between the refractive indices of the spheres and the medium
|
||||
|
||||
Returns:
|
||||
s1: perpendicular amplitude function at corresponding theta values
|
||||
s2: parallel amplitude function at corresponding theta values
|
||||
a: first set of Mie coefficients
|
||||
b: second set of Mie coefficients
|
||||
"""
|
||||
# Constants
|
||||
n_ang = np.size(theta)
|
||||
n_terms = np.rint(alpha + 4.05*alpha**0.3333 + 2).astype(int) # number of terms in series for coeff calculation
|
||||
|
||||
# Calculation of coefficients using other functions
|
||||
pi, tau = mie_pi_tau(theta, alpha)
|
||||
a, b = mie_a_b(alpha, m)
|
||||
|
||||
# Calculation of scattering amplitude functions
|
||||
s1 = np.reshape(np.tile(a, n_ang), (n_terms, n_ang), order='F') * pi \
|
||||
+ np.reshape(np.tile(b, n_ang), (n_terms, n_ang), order='F') * tau # perpendicular amplitude function
|
||||
s2 = np.reshape(np.tile(a, n_ang), (n_terms, n_ang), order='F') * tau \
|
||||
+ np.reshape(np.tile(b, n_ang), (n_terms, n_ang), order='F') * pi # parallel amplitude function
|
||||
temp = np.arange(n_terms) + 1 # factor multiplied by each term (row)
|
||||
temp = (2 * temp + 1) / (temp ** 2 + temp)
|
||||
s1 = s1 * np.reshape(np.tile(temp, n_ang), (n_terms, n_ang), order='F')
|
||||
s2 = s2 * np.reshape(np.tile(temp, n_ang), (n_terms, n_ang), order='F')
|
||||
s1 = np.sum(s1, axis=0) # sum along angle (column)
|
||||
s2 = np.sum(s2, axis=0)
|
||||
|
||||
return s1, s2, a, b
|
||||
|
||||
|
||||
def percus_yevick(qa, phi):
|
||||
"""
|
||||
Calculate the structure factor for hard spheres using the Percus-Yevick
|
||||
closure of the Ornstein-Zernicke equation.
|
||||
|
||||
Args:
|
||||
qa: the scattering vector scaled by the sphere radius at the desired scattering angles
|
||||
phi: volume fraction of spheres in solution
|
||||
|
||||
Returns:
|
||||
s_q: the structure factor at the corresponding scattering angles
|
||||
"""
|
||||
qa2 = 2 * qa # twice qa, for efficiency/conciseness
|
||||
lambda_1 = (1 + 2 * phi) ** 2 / (1 - phi) ** 4
|
||||
lambda_2 = -(1 + phi / 2) ** 2 / (1 - phi) ** 4
|
||||
c_1 = lambda_1 / qa2 ** 3 * (np.sin(qa2) - qa2 * np.cos(qa2))
|
||||
c_2 = -6 * phi * lambda_2 / qa2 ** 4 * (qa2 ** 2 * np.cos(qa2) - 2 * qa2 * np.sin(qa2) - 2 * np.cos(qa2) + 2)
|
||||
c_3 = -phi * lambda_1 / 2 / qa2 ** 6 * \
|
||||
(qa2 ** 4 * np.cos(qa2) - 4 * qa2 ** 3 * np.sin(qa2) - 12 * qa2 ** 2 * np.cos(qa2)
|
||||
+ 24 * qa2 * np.sin(qa2) + 24 * np.cos(qa2) - 24)
|
||||
|
||||
s_q = np.divide(1, 1 + 24 * phi * (c_1 + c_2 + c_3))
|
||||
return s_q
|
||||
|
||||
|
||||
def percus_yevick_annulus(q, a_p, a_e, phi):
|
||||
"""
|
||||
Calculate the structure factor for hard spheres with an exluded annulus
|
||||
using the Percus-Yevick closure of the Ornstein-Zernicke equation.
|
||||
|
||||
Args:
|
||||
q: scattering vector at desired scattering angles
|
||||
a_p: sphere radius (excluding annulus)
|
||||
a_e: effective sphere radius (including annulus)
|
||||
phi: volume fraction of spheres in solution
|
||||
|
||||
Returns:
|
||||
s_q: the structure factor at the corresponding scattering angles
|
||||
"""
|
||||
phi_e = phi * (a_e / a_p) ** 3 # effective volume fraction
|
||||
|
||||
qa2 = 2 * q * a_e # twice qa, for efficiency/conciseness
|
||||
lambda_1 = (1 + 2 * phi_e) ** 2 / (1 - phi_e) ** 4
|
||||
lambda_2 = -(1 + phi_e / 2) ** 2 / (1 - phi_e) ** 4
|
||||
c_1 = lambda_1 / qa2 ** 3 * (np.sin(qa2) - qa2 * np.cos(qa2))
|
||||
c_2 = -6 * phi_e * lambda_2 / qa2 ** 4 * (qa2 ** 2 * np.cos(qa2) - 2 * qa2 * np.sin(qa2) - 2 * np.cos(qa2) + 2)
|
||||
c_3 = -phi_e * lambda_1 / 2 / qa2 ** 6 * \
|
||||
(qa2 ** 4 * np.cos(qa2) - 4 * qa2 ** 3 * np.sin(qa2) - 12 * qa2 ** 2 * np.cos(qa2)
|
||||
+ 24 * qa2 * np.sin(qa2) + 24 * np.cos(qa2) - 24)
|
||||
|
||||
s_q = np.divide(1, 1 + 24 * phi_e * (c_1 + c_2 + c_3))
|
||||
return s_q
|
||||
|
||||
|
||||
def sticky_hard_sphere(q, a_p, phi, tau=0.2, epsilon=0.05):
|
||||
"""
|
||||
Calculate the structure factor for hard spheres with square-well interparticle
|
||||
potential. Based on the method of Rao et. al. in "A new interpretation of the
|
||||
sticky hard sphere model", J. Chem. Phys. 95(12), 9186-9190 (1991).
|
||||
|
||||
Args:
|
||||
q: scattering vector at desired scattering angles
|
||||
a_p: sphere radius (excluding annulus)
|
||||
phi: volume fraction of spheres in solution
|
||||
tau: float, "stickiness" parameter within {0, 1}
|
||||
epsilon: float, perturbation parameter within {0, 0.1}
|
||||
|
||||
Returns:
|
||||
s_q: the structure factor at the corresponding scattering angles
|
||||
"""
|
||||
eta = phi / (1 - epsilon) ** 3
|
||||
eta1 = 1 - eta
|
||||
sigma = 2 * a_p
|
||||
a = sigma / (1 - epsilon)
|
||||
kappa = q * a
|
||||
|
||||
# Quadratic for Lambda
|
||||
a_q = eta/12
|
||||
b_q = -tau - eta / eta1
|
||||
c_q = (1 + eta / 2) / eta1 ** 2
|
||||
|
||||
disc = b_q ** 2 - 4 * a_q * c_q # discriminant
|
||||
if disc < 0: # error if no real roots
|
||||
print('Error: No Real Roots within SHS Model')
|
||||
return -1*np.ones_like(q)
|
||||
lamb = (-b_q + np.sqrt(disc)) / (2 * a_q)
|
||||
lamb2 = (-b_q - np.sqrt(disc)) / (2 * a_q)
|
||||
if lamb2 < lamb:
|
||||
lamb = lamb2 # take smaller root, larger is unphysical
|
||||
|
||||
# Alpha and Beta
|
||||
mu = lamb * eta * eta1
|
||||
if mu > 1 + 2 * eta: # model condition set in paper
|
||||
print('Error: Unphysical SHS Model Result')
|
||||
return -1*np.ones_like(q)
|
||||
alpha = (1 + 2 * eta - mu) / eta1 ** 2
|
||||
beta = (-3 * eta + mu) / 2 / eta1 ** 2
|
||||
|
||||
# S_q
|
||||
k2 = kappa ** 2
|
||||
k3 = kappa ** 3
|
||||
ksin = np.sin(kappa)
|
||||
kcos = np.cos(kappa)
|
||||
A = 1 + 12 * eta * (alpha * (ksin - kappa * kcos) / k3 + beta * (1 - kcos) / k2 - lamb / 12 * ksin / kappa)
|
||||
B = 12 * eta * (alpha * (1 / 2 / kappa - ksin / k2 + (1 - kcos) / k3)
|
||||
+ beta * (1 / kappa - ksin / k2) - lamb / 12 * (1 - kcos) / kappa)
|
||||
s_q = 1 / (A ** 2 + B ** 2)
|
||||
|
||||
return s_q
|
||||
|
||||
|
||||
def mie_scattering(n_p, n_s, a_p, lambda_vac, phi, n_ang=100, struct='none', optional_params=None):
|
||||
"""
|
||||
Calculate intensities and statistics for Mie scattering by spheres.
|
||||
|
||||
Methods and algorithms are those outlined by Kerker in
|
||||
The Scattering of Light and Other Electromagnetic Radiation, 1969
|
||||
|
||||
Args:
|
||||
n_p: float, refractive index of the spheres
|
||||
n_s: float, refractive index of the medium
|
||||
a_p: float, sphere radius [m]
|
||||
lambda_vac: float, wavelength of light in vacuum [m]
|
||||
phi: float, volume fraction of particles in solution
|
||||
n_ang: int, number of angles calculated (Default = 100)
|
||||
struct: str, choice of equation used for structure factor
|
||||
- 'none': S(q) = 1
|
||||
- 'PY': S(q) is calculated using the Percus-Yevick approximation
|
||||
- 'PYAnnulus': S(q) is calculated using the Percus-Yevick approximation with an excluded annulus
|
||||
- 'SHS': S(q) is calculated via a perturbative sticky hard sphere solution of the Percus-Yevick closure
|
||||
optional_params: list of optional parameters required for some structure factor calculations
|
||||
- 'PYAnnulus': [a_e]
|
||||
- a_e: float, effective sphere radius (including excluded annulus)
|
||||
- 'SHS': [tau, epsilon]
|
||||
- tau: float, "stickiness" parameter within {0, 1}
|
||||
- epsilon: float, perturbation parameter within {0, 0.1}
|
||||
|
||||
Returns:
|
||||
theta: the angles at which scattering intensities are calculated
|
||||
i1: perpendicular scattering intensity at corresponding scattering angles
|
||||
i2: parallel scattering intensity
|
||||
l_star: photon mean free path
|
||||
stats: list of other output scattering statistics
|
||||
- q: array of scattering vectors corresponding with theta
|
||||
- p_q: form factor at corresponding theta/q values
|
||||
- s_q: structure factor at corresponding theta/q values
|
||||
- l: scattering mean free path
|
||||
- q_scat: scattering efficiency factor
|
||||
- q_ext: extinction efficiency factor
|
||||
- c_scat: scattering cross section
|
||||
- c_ext: extinction cross section
|
||||
"""
|
||||
# Initial Constants
|
||||
m = n_p / n_s # refractive index ratio
|
||||
lambda_med = lambda_vac / n_s # wavelength in medium
|
||||
k_0 = 2 * np.pi / lambda_med # scattering wavevector
|
||||
alpha = k_0 * a_p
|
||||
rho = phi / 4 * 3 / np.pi / a_p ** 3 # number density of scatterers
|
||||
n_terms = np.rint(alpha + 4.05*alpha**0.3333 + 2).astype(int) # number of terms in series for coeff calculation
|
||||
|
||||
theta = np.linspace(0.001, 0.999 * np.pi, n_ang) # angles (ends are not absolute to prevent zeroing out)
|
||||
qa = a_p * 2 * k_0 * np.sin(theta/2) # radius times scattering vector
|
||||
|
||||
# Intensity Calculation
|
||||
s1, s2, a, b = mie_s1_s2(theta, alpha, m)
|
||||
|
||||
i1 = np.real(s1 * np.conjugate(s1)) # perpendicular intensity
|
||||
i2 = np.real(s2 * np.conjugate(s2)) # parallel intensity
|
||||
|
||||
# Scattering Statistics Calculation
|
||||
temp = 2 * np.arange(n_terms) + 3 # factor multiplied by each term (row)
|
||||
# scattering efficiency factor
|
||||
q_scat = 2 / alpha ** 2 * np.sum(temp * np.real(a * np.conjugate(a) + b * np.conjugate(b)))
|
||||
q_ext = 2 / alpha ** 2 * np.sum(temp * np.real(a + b)) # extinction efficiency factor
|
||||
c_scat = q_scat * 3.14158 * a_p ** 2 # scattering cross section
|
||||
c_ext = q_ext * 3.14158 * a_p ** 2 # extinction cross section
|
||||
|
||||
# Structure and Form Factor Calculation
|
||||
# structure factor
|
||||
if struct == 'none':
|
||||
s_q = np.ones_like(theta)
|
||||
elif struct == 'PY':
|
||||
s_q = percus_yevick(qa, phi)
|
||||
elif struct == 'PYAnnulus':
|
||||
s_q = percus_yevick_annulus(qa / a_p, a_p, optional_params[0], phi)
|
||||
elif struct == 'SHS':
|
||||
s_q = sticky_hard_sphere(qa / a_p, a_p, phi, optional_params[0], optional_params[1])
|
||||
else:
|
||||
sys.exit('Error: Unexpected Input for Choice of Structure Factor Equation')
|
||||
p_q = (i1 + i2) / 2 # form factor (average intensity)
|
||||
|
||||
# l Calculation
|
||||
integrand = qa * p_q * s_q
|
||||
l = (k_0 ** 4 * a_p ** 2) / \
|
||||
(2 * np.pi * rho * 0.5 * np.sum((integrand[:-1] + integrand[1:]) * (qa[1:] - qa[:-1])))
|
||||
|
||||
# l* Calculation
|
||||
integrand = qa ** 3 * p_q * s_q
|
||||
l_star = (k_0 ** 6 * a_p ** 4) / \
|
||||
(np.pi * rho * 0.5 * np.sum((integrand[:-1] + integrand[1:]) * (qa[1:] - qa[:-1])))
|
||||
|
||||
# Gather Misc Statistics
|
||||
q = 2 * k_0 * np.sin(theta / 2) # scattering vector
|
||||
stats = [q, p_q, s_q, l, q_scat, q_ext, c_scat, c_ext]
|
||||
|
||||
return theta, i1, i2, l_star, stats
|
||||
|
||||
|
||||
def rayleigh_scattering(n_p, n_s, a_p, lambda_vac, phi, n_ang=100, struct='none', optional_params=None):
|
||||
"""
|
||||
Calculate intensities and statistics for Rayleigh scattering by spheres.
|
||||
|
||||
Args:
|
||||
n_p: float, refractive index of the spheres
|
||||
n_s: float, refractive index of the medium
|
||||
a_p: float, sphere radius [m]
|
||||
lambda_vac: float, wavelength of light in vacuum [m]
|
||||
phi: float, volume fraction of particles in solution
|
||||
n_ang: int, number of angles calculated (Default = 100)
|
||||
struct: str, choice of equation used for structure factor
|
||||
- 'none': S(q) = 1
|
||||
- 'PY': S(q) is calculated using the Percus-Yevick approximation
|
||||
- 'PYAnnulus': S(q) is calculated using the Percus-Yevick approximation with an excluded annulus
|
||||
optional_params: list of optional parameters required for some structure factor calculations
|
||||
- 'PYAnnulus': [a_e]
|
||||
- a_e: float, effective sphere radius (including excluded annulus)
|
||||
|
||||
Returns:
|
||||
theta: the angles at which scattering intensities are calculated
|
||||
i1: perpendicular scattering intensity at corresponding scattering angles
|
||||
i2: parallel scattering intensity
|
||||
l_star: photon mean free path
|
||||
stats: list of other output scattering statistics
|
||||
- q: array of scattering vectors corresponding with theta
|
||||
- p_q: form factor at corresponding theta/q values
|
||||
- s_q: structure factor at corresponding theta/q values
|
||||
- l: scattering mean free path
|
||||
"""
|
||||
# Initial Constants
|
||||
m = n_p / n_s # refractive index ratio
|
||||
lambda_med = lambda_vac / n_s # wavelength in medium
|
||||
k_0 = 2 * np.pi / lambda_med # scattering wavevector
|
||||
rho = phi / 4 * 3 / np.pi / a_p ** 3 # number density of scatterers
|
||||
v = 4 / 3 * np.pi * a_p ** 3 # sphere volume
|
||||
|
||||
theta = np.linspace(0.001, 0.999 * np.pi, n_ang) # angles (ends are not absolute to prevent zeroing out)
|
||||
cos_theta = np.cos(theta)
|
||||
qa = a_p * 2 * k_0 * np.sin(theta/2) # radius times scattering vector
|
||||
|
||||
# Intensity Calculation
|
||||
i1 = k_0 ** 6 * v ** 2 * ((m - 1) / 2 / np.pi) ** 2 \
|
||||
* np.divide(3 * (np.sin(qa) - qa * np.cos(qa)), qa ** 3) ** 2 # perpendicular Rayleigh intensity
|
||||
i2 = i1 * cos_theta ** 2 # parallel Rayleigh intensity
|
||||
|
||||
# Form and Structure Factor Calculation
|
||||
# structure factor
|
||||
if struct == 'none':
|
||||
s_q = np.ones_like(theta)
|
||||
elif struct == 'PY':
|
||||
s_q = percus_yevick(qa, phi)
|
||||
elif struct == 'PYAnnulus':
|
||||
s_q = percus_yevick_annulus(qa / a_p, a_p, optional_params[0], phi)
|
||||
else:
|
||||
sys.exit('Error: Unexpected Input for Choice of Structure Factor Equation')
|
||||
p_q = (i1 + i2) / 2 # form factor (average intensity)
|
||||
|
||||
# l Calculation
|
||||
integrand = qa * p_q * s_q
|
||||
l = (k_0 ** 4 * a_p ** 2) / \
|
||||
(2 * np.pi * rho * 0.5 * np.sum((integrand[:-1] + integrand[1:]) * (qa[1:] - qa[:-1])))
|
||||
|
||||
# l* Calculation
|
||||
integrand = qa ** 3 * p_q * s_q
|
||||
l_star = (k_0 ** 6 * a_p ** 4) / \
|
||||
(np.pi * rho * 0.5 * np.sum((integrand[:-1] + integrand[1:]) * (qa[1:] - qa[:-1])))
|
||||
|
||||
# Gather Misc Statistics
|
||||
q = 2 * k_0 * np.sin(theta / 2) # scattering vector
|
||||
stats = [q, p_q, s_q, l]
|
||||
|
||||
return theta, i1, i2, l_star, stats
|
||||
Loading…
Add table
Add a link
Reference in a new issue