Skip to content

Commit 661eeca

Browse files
committed
made kd optional for integrate_intensity() and diff_scat_intensity()
Now these functions can handle non-absorbing media. When kd is not specified, far field solutions are given. These functions are now renamed from integrate_intensity_complex_medium() and diff_scat_intensity_complex_medium() to integrate_intensity() and diff_scat_intensity()
1 parent c7498e9 commit 661eeca

File tree

3 files changed

+104
-113
lines changed

3 files changed

+104
-113
lines changed

pymie/mie.py

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,8 @@ def calc_ang_scat(m, x, thetas, kd=None, phis=None, incident_vector=None,
180180
& Huffman ch. 3 for details.
181181
"""
182182
if (kd is not None) or (phis is not None):
183-
return diff_scat_intensity_complex_medium(m, x, thetas, kd=kd,
184-
phis=phis,
185-
incident_vector =
186-
incident_vector)
183+
return diff_scat_intensity(m, x, thetas, kd=kd, phis=phis,
184+
incident_vector=incident_vector)
187185

188186
# Mie scattering preliminaries
189187
nstop = _nstop(x.max())
@@ -458,8 +456,8 @@ def calc_dwell_time(radius, n_medium, n_particle, wavelen,
458456
angles = np.linspace(min_angle, np.pi, num_angles)
459457
distance = radius.max()
460458
kd = (k*distance).to("").magnitude
461-
diff_cscat = diff_scat_intensity_complex_medium(m, x, angles, kd)
462-
cscat = integrate_intensity_complex_medium(diff_cscat, angles, kd)[0]
459+
diff_cscat = diff_scat_intensity(m, x, angles, kd)
460+
cscat = integrate_intensity(diff_cscat, angles, kd)[0]
463461
else:
464462
cscat = calc_cross_sections(m, x, eps1 = eps1, eps2 = eps2)[0]
465463
cscat = cscat * 1/k**2
@@ -492,10 +490,8 @@ def calc_reflectance(radius, n_medium, n_particle, wavelen,
492490
distance = rmax
493491
k = np.atleast_1d(2*np.pi/wavelen_media)
494492
kd = (k*distance).to("").magnitude
495-
diff_cscat = diff_scat_intensity_complex_medium(m, x, thetas,
496-
kd)
497-
refl_cscat = integrate_intensity_complex_medium(diff_cscat, thetas,
498-
kd)[0]
493+
diff_cscat = diff_scat_intensity(m, x, thetas, kd)
494+
refl_cscat = integrate_intensity(diff_cscat, thetas, kd)[0]
499495
refl_cscat = refl_cscat/k**2
500496
else:
501497
refl_cscat = calc_integrated_cross_section(m, x, thetas)
@@ -1172,20 +1168,21 @@ def _scat_fields_complex_medium(m, x, thetas, kd, near_field=False):
11721168
# note that these solutions are not currently used anywhere in mie.py.
11731169
# When the fields are multiplied to calculate the intensity, the
11741170
# exponential terms reduce down to a term that depends on kd (see
1175-
# diff_scat_intensity_complex_medium(). So these equations lead to
1171+
# diff_scat_intensity(). So these equations lead to
11761172
# intensities that are the same as those calculated with the scattering
1177-
# matrix in diff_scat_intensity_complex_medium().
1173+
# matrix in diff_scat_intensity().
11781174
# We leave the expressions here in case users ever have a need to know
11791175
# the actual fields, rather than the intensities.
11801176

11811177
return Es_theta, Es_phi, Hs_theta, Hs_phi
11821178

11831179

1184-
def diff_scat_intensity_complex_medium(m, x, thetas, kd, phis=None,
1185-
near_field=False,
1186-
incident_vector=None):
1180+
def diff_scat_intensity(m, x, thetas, kd=None, phis=None,
1181+
near_field=False,
1182+
incident_vector=None):
11871183
"""
1188-
Calculates the differential scattered intensity in an absorbing medium.
1184+
Calculates the differential scattered intensity for both absorbing and
1185+
non-absorbing medium.
11891186
User can choose whether to include near fields.
11901187
11911188
When phis is None:
@@ -1223,11 +1220,11 @@ def diff_scat_intensity_complex_medium(m, x, thetas, kd, phis=None,
12231220
size parameter, x = ka = 2*pi*n_med/lambda * a (sphere radius a)
12241221
thetas : array-like
12251222
Scattering angles. Must be in radians.
1226-
kd : float
1223+
kd : float (default None)
12271224
k * distance, where k = 2*np.pi*n_matrix/wavelen, and distance is the
12281225
distance away from the center of the particle. The standard far-field
12291226
solutions are obtained when distance >> radius in a non-absorbing
1230-
medium.
1227+
medium. If kd is None, far-field solutions are used.
12311228
phis : None or ndarray
12321229
Azimuthal angles for which to calculate the diff scat intensity. If not
12331230
provided, scattering calculations will be carried out in the
@@ -1295,25 +1292,29 @@ def diff_scat_intensity_complex_medium(m, x, thetas, kd, phis=None,
12951292
in an absorbing medium". Applied Optics, 40, 9 (2001).
12961293
12971294
"""
1298-
# ensure that broadcasting will work correctly by adding an axis
1299-
# corresponding to theta
1300-
kd = np.atleast_1d(kd)[..., np.newaxis]
1301-
if phis is not None:
1302-
kd = kd[..., np.newaxis]
1295+
if kd is not None:
1296+
# ensure that broadcasting will work correctly by adding an axis
1297+
# corresponding to theta
1298+
kd = np.atleast_1d(kd)[..., np.newaxis]
1299+
if phis is not None:
1300+
kd = kd[..., np.newaxis]
13031301

13041302
if near_field:
1303+
if kd is None:
1304+
raise ValueError("Near field calculation requires nondimensional "
1305+
"distance kd to be specified")
13051306
if phis is None:
13061307
# calculate scattered fields in scattering plane coordinate system
13071308
Es_theta, Es_phi, Hs_theta, Hs_phi = _scat_fields_complex_medium(m,
13081309
x,thetas, kd, near_field=near_field)
1309-
I_1 = Es_theta * np.conj(Hs_phi) # I_par
1310-
I_2 = -Es_phi * np.conj(Hs_theta) # I_perp
1310+
# calculate intensity and multiply by |kd|^2 so that resulting
1311+
# values can be dimensionalized by multiplying by 1/|k|^2
1312+
I_1 = Es_theta * np.conj(Hs_phi) * np.abs(kd)**2 # I_par
1313+
I_2 = -Es_phi * np.conj(Hs_theta) * np.abs(kd)**2 # I_perp
13111314
else:
13121315
raise ValueError("Near fields have not been implemented for the "
13131316
"Cartesian coordinate system. Set near_field "
13141317
"to False to calculate scattered intensity")
1315-
1316-
13171318
else:
13181319
# calculate vector scattering amplitude
13191320
vec_scat_amp_1, vec_scat_amp_2 = vector_scattering_amplitude(m, x,
@@ -1331,36 +1332,38 @@ def diff_scat_intensity_complex_medium(m, x, thetas, kd, phis=None,
13311332
# down to 1/(kd)^2 when k is real, which is the factor usually used to
13321333
# get the final intensity in a non-absorbing medium (p. 113 of Bohren
13331334
# and Huffman).
1334-
factor = np.exp(-2*kd.imag) / ((kd.real)**2 + (kd.imag)**2)
1335+
if kd is not None:
1336+
# The factor is normally e^(-2k_I)/|k|^2 but we simplify to
1337+
# e^(2k_I) so that the resulting non-dimensional cross-sections can
1338+
# be dimensionalized by multiplying by 1/|k|^2 (just as with
1339+
# cross-sections returned by other functions). For far-field, the
1340+
# simplified factor is unity
1341+
factor = np.exp(-2*kd.imag)
1342+
else:
1343+
factor = 1
1344+
13351345
I_1 = (np.abs(vec_scat_amp_1)**2)*factor # par or x
13361346
I_2 = (np.abs(vec_scat_amp_2)**2)*factor # perp or y
13371347

1338-
# the intensities should be real. We multiply by |kd|^2 so that the
1339-
# resulting nondimensional cross-sections can be dimensionalized by
1340-
# multiplying by 1/|k|^2 (just as with cross-sections returned by other
1341-
# functions)
1342-
result = np.stack([I_1.real, I_2.real], axis=-1)
1343-
return result * np.abs(kd)[..., np.newaxis]**2
1348+
# the intensities should be real
1349+
return np.stack([I_1.real, I_2.real], axis=-1)
13441350

13451351

1346-
def integrate_intensity_complex_medium(dscat, thetas, kd,
1347-
phi_min=0.0,
1348-
phi_max=2*np.pi,
1349-
phis=None):
1352+
def integrate_intensity(dscat, thetas, kd=None,
1353+
phi_min=0.0, phi_max=2*np.pi, phis=None):
13501354
"""
13511355
Calculates the scattering cross section by integrating the differential
1352-
scattered intensity at a distance of our choice in an absorbing medium.
1356+
scattered intensity. If the medium is absorbing, kd should be specified.
13531357
Choosing the right distance is essential in an absorbing medium because the
13541358
differential scattering intensities decrease with increasing distance.
13551359
The integration is done over scattering angles theta and azimuthal angles
13561360
phi using the trapezoid rule.
13571361
13581362
Parameters
13591363
----------
1360-
dscat : array-like with shape (2, ..., num_thetas, [num_phis])
1364+
dscat : array-like with shape (..., num_thetas, [num_phis], 2)
13611365
differential scattered intensities for both polarizations. Can be
1362-
functions of theta or of theta and phi. If a function of theta and phi,
1363-
the theta dimension MUST come first
1366+
functions of theta or of theta and phi.
13641367
thetas : array-like, shape ([angle_leading_dims], num_thetas)
13651368
scattering angles
13661369
kd : array-like, shape (...)
@@ -1392,7 +1395,6 @@ def integrate_intensity_complex_medium(dscat, thetas, kd,
13921395
"""
13931396
# add axis for polarization dimension
13941397
thetas = thetas[..., np.newaxis]
1395-
kd = np.atleast_1d(kd)[..., np.newaxis]
13961398

13971399
# do phi integral first because it's the next-to-last dimension in dscat
13981400
# when specified (thus phis will broadcast with dscat)
@@ -1407,9 +1409,9 @@ def integrate_intensity_complex_medium(dscat, thetas, kd,
14071409
# This factor is needed to account for polarization, which introduces
14081410
# factors of cos(phi) and sin(phi) for the electric fields.
14091411
factor = np.array([(phi_max/2 + np.sin(2*phi_max)/4
1410-
- phi_min/2 - np.sin(2*phi_min)/4),
1412+
- phi_min/2 - np.sin(2*phi_min)/4),
14111413
(phi_max/2 - np.sin(2*phi_max)/4
1412-
- phi_min/2 + np.sin(2*phi_min)/4)])
1414+
- phi_min/2 + np.sin(2*phi_min)/4)])
14131415
integrand = integrand * factor
14141416

14151417
# integrate diff. cross-sections over theta using Jacobian
@@ -1421,20 +1423,27 @@ def integrate_intensity_complex_medium(dscat, thetas, kd,
14211423
# (see Sudiarta and Chylek (2001), eq 10).
14221424
# if the imaginary part of k is close to 0 (because the medium index is
14231425
# close to 0), then use the limit value of factor for the calculations
1424-
exponent = np.exp(2*kd.imag)
1425-
factor_limit = 2
1426-
# ignore division by zero in case k.imag=0; we'll replace the nans with the
1427-
# limit value of factor anyway
1428-
with np.errstate(divide='ignore', invalid='ignore'):
1429-
factor = np.where(kd.imag <= 1e-6, factor_limit,
1430-
1 / (exponent / (2*kd.imag)
1431-
+ (1 - exponent) / (2*kd.imag)**2))
1426+
if kd is not None:
1427+
# add axis corresponding to polarization
1428+
kd = np.atleast_1d(kd)[..., np.newaxis]
1429+
1430+
exponent = np.exp(2*kd.imag)
1431+
factor_limit = 2
1432+
# ignore division by zero in case k.imag=0; we'll replace the nans
1433+
# with the limit value of factor anyway
1434+
with np.errstate(divide='ignore', invalid='ignore'):
1435+
factor = np.where(kd.imag <= 1e-6, factor_limit,
1436+
1 / (exponent / (2*kd.imag)
1437+
+ (1 - exponent) / (2*kd.imag)**2))
1438+
else:
1439+
# far field result
1440+
factor = 2
14321441

14331442
# calculate the averaged sigma (polarization axis is last)
14341443
sigma = sigma * factor
14351444
sigma_avg = sigma.sum(axis=-1)/2
14361445

1437-
return(sigma_avg, sigma[..., 0], sigma[..., 1])
1446+
return (sigma_avg, sigma[..., 0], sigma[..., 1])
14381447

14391448

14401449
def diff_abs_intensity_complex_medium(m, x, thetas, ktd):

pymie/tests/test_mie.py

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def test_pis_taus():
473473

474474
def test_differential_cross_section():
475475
"""
476-
Tests that the differential cross-sections from diff_scat_complex_medium()
476+
Tests that the differential cross-sections from diff_scat_intensity()
477477
and calc_ang_scat() are the same for a non-absorbing medium.
478478
"""
479479
# set parameters
@@ -494,16 +494,16 @@ def test_differential_cross_section():
494494
# With Mie solutions at surface of particle (but neglecting near-fields)
495495
kd = (k*distance).to("").magnitude
496496
incident_vector = [1, 1]
497-
I_parperp = mie.diff_scat_intensity_complex_medium(m, x, theta, kd,
498-
incident_vector =
499-
incident_vector)
497+
I_parperp = mie.diff_scat_intensity(m, x, theta, kd,
498+
incident_vector =
499+
incident_vector)
500500

501501
# since both of these functions rely on the same routine to calculate the
502502
# amplitude scattering matrix, they should give results to within
503503
# floating-point precision
504504
assert_allclose(I_parperp, I_parperp_cad, rtol=1e-14)
505505

506-
# Now check that calc_ang_dist() calls diff_scat_intensity_complex_medium
506+
# Now check that calc_ang_dist() calls diff_scat_intensity
507507
# when kd is specified
508508
I_parperp_cad_kd = mie.calc_ang_scat(m, x, theta, kd=kd)
509509

@@ -552,8 +552,7 @@ def test_cross_section_complex_medium():
552552
# With Mie solutions in absorbing medium
553553
rho_scat = (k*distance).to("").magnitude
554554
I_parperp = mie.calc_ang_scat(m, x, theta, kd=rho_scat)
555-
cscat_exact = mie.integrate_intensity_complex_medium(I_parperp, theta,
556-
rho_scat)[0]
555+
cscat_exact = mie.integrate_intensity(I_parperp, theta, rho_scat)[0]
557556
cscat_exact_dimensional = (cscat_exact/np.abs(k)**2).to("um^2")
558557

559558
# check that intensity equations without the asymptotic form of the spherical
@@ -597,10 +596,8 @@ def test_cross_section_complex_medium():
597596
wavelen)[0]
598597
# With full Mie solutions that include the near fields
599598
rho_scat = (k*distance).to("").magnitude
600-
I_parperp = mie.diff_scat_intensity_complex_medium(m, x, theta, rho_scat,
601-
near_field=True)
602-
cscat_exact2 = mie.integrate_intensity_complex_medium(I_parperp, theta,
603-
rho_scat)[0]
599+
I_parperp = mie.diff_scat_intensity(m, x, theta, rho_scat, near_field=True)
600+
cscat_exact2 = mie.integrate_intensity(I_parperp, theta, rho_scat)[0]
604601
cscat_exact2_dimensional = (cscat_exact2/np.abs(k)**2).to("um^2")
605602

606603
assert_allclose(cscat_exact2_dimensional.magnitude,
@@ -619,8 +616,7 @@ def test_cross_section_complex_medium():
619616
# With full Mie solutions
620617
I_parperp = mie.calc_ang_scat(m, x, theta, kd=rho_scat)
621618

622-
cscat_exact3 = mie.integrate_intensity_complex_medium(I_parperp, theta,
623-
rho_scat)[0]
619+
cscat_exact3 = mie.integrate_intensity(I_parperp, theta, rho_scat)[0]
624620
cscat_exact3_dimensional = (cscat_exact3/np.abs(k)**2).to("um^2")
625621

626622
# With far-field Mie solutions
@@ -654,8 +650,7 @@ def test_multilayer_complex_medium():
654650

655651
# with imag solutions
656652
I_parperp = mie.calc_ang_scat(marray, xarray, angles, kd=kd)
657-
cscat_imag = mie.integrate_intensity_complex_medium(I_parperp, angles,
658-
kd)[0]
653+
cscat_imag = mie.integrate_intensity(I_parperp, angles, kd)[0]
659654

660655
cscat_imag_dimensional = (cscat_imag/np.abs(k)**2).to("nm^2")
661656

@@ -717,7 +712,7 @@ def test_vector_scattering_amplitude_2d_theta_cartesian():
717712
assert_allclose(as_vec_y0, as_vec_y)
718713

719714

720-
def test_diff_scat_intensity_complex_medium_cartesian():
715+
def test_diff_scat_intensity_cartesian():
721716
'''
722717
Test that the magnitude of the differential scattered intensity is the
723718
same in the xy basis as it is in the parallel, perpendicular basis, as
@@ -740,18 +735,17 @@ def test_diff_scat_intensity_complex_medium_cartesian():
740735
kd = (2*np.pi*n_matrix/wavelen*Quantity(10000.0, "nm")).to("").magnitude
741736

742737
# calculate differential scattered intensity in par/perp basis
743-
I_parperp = mie.diff_scat_intensity_complex_medium(m, x, thetas, kd,
744-
near_field=False)
738+
I_parperp = mie.diff_scat_intensity(m, x, thetas, kd, near_field=False)
745739
# broadcast over the phi dimension, allowing us to compare to cartesian
746740
# calculation
747741
I_parperp = np.repeat(I_parperp[..., np.newaxis, :], phis.shape, axis=2)
748742

749743
# calculate differential scattered intensity in xy basis
750744
# if incident vector is unpolarized (1,1), then the resulting differential
751745
# scattered intensity should be the same as I_par, I_perp
752-
I_xy = mie.diff_scat_intensity_complex_medium(m, x, thetas, kd,
753-
phis = phis, near_field=False,
754-
incident_vector = (1, 1))
746+
I_xy = mie.diff_scat_intensity(m, x, thetas, kd, phis=phis,
747+
near_field=False,
748+
incident_vector = (1, 1))
755749

756750
# calculate magnitudes (polarization axis is last)
757751
I_xy_mag = np.sqrt((I_xy**2).sum(axis=-1))
@@ -760,7 +754,7 @@ def test_diff_scat_intensity_complex_medium_cartesian():
760754
# check that the magnitudes are equal
761755
assert_allclose(I_xy_mag, I_par_perp_mag, rtol=1e-15)
762756

763-
def test_integrate_intensity_complex_medium_cartesian():
757+
def test_integrate_intensity_cartesian():
764758
'''
765759
Test that when integrated over all theta and phi angles, the intensities
766760
calculated in the par/perp basis match those calculated in the x/y basis
@@ -785,8 +779,7 @@ def test_integrate_intensity_complex_medium_cartesian():
785779
I_parperp = mie.calc_ang_scat(m, x, thetas, kd=kd)
786780

787781
# integrate the differential scattered intensities
788-
cscat_xy = mie.integrate_intensity_complex_medium(I_xy, thetas, kd,
789-
phis=phis)[0]
782+
cscat_xy = mie.integrate_intensity(I_xy, thetas, kd, phis=phis)[0]
790783
cscat_xy_dimensional = (cscat_xy/np.abs(k)**2).to("nm^2")
791784

792785
# check that intensity equations without the asymptotic form of the spherical
@@ -795,8 +788,7 @@ def test_integrate_intensity_complex_medium_cartesian():
795788
cscat_xy_old = Quantity(6010696.7108612377, "nm^2")
796789
assert_allclose(cscat_xy_dimensional.magnitude, cscat_xy_old.magnitude)
797790

798-
cscat_parperp = mie.integrate_intensity_complex_medium(I_parperp, thetas,
799-
kd)[0]
791+
cscat_parperp = mie.integrate_intensity(I_parperp, thetas, kd=kd)[0]
800792

801793
# check that the integrated cross sections are equal
802794
assert_allclose(cscat_xy, cscat_parperp, rtol=1e-15)
@@ -823,16 +815,13 @@ def test_value_errors():
823815

824816
with pytest.raises(ValueError):
825817
# try to calculate near field in cartesian
826-
_ = mie.diff_scat_intensity_complex_medium(m, x, thetas, kd,
827-
phis=phis,
828-
near_field=True)
829-
# calculate the differential scattered intensities
830-
I_xy = mie.diff_scat_intensity_complex_medium(m, x, thetas, kd,
831-
phis=phis,
832-
near_field=False)
818+
_ = mie.diff_scat_intensity(m, x, thetas, kd, phis=phis,
819+
near_field=True)
820+
# calculate the differential scattered intensities
821+
I_xy = mie.diff_scat_intensity(m, x, thetas, kd, phis=phis,
822+
near_field=False)
833823

834-
_ = mie.diff_scat_intensity_complex_medium(m, x, thetas, kd,
835-
near_field=True)
824+
_ = mie.diff_scat_intensity(m, x, thetas, kd, near_field=True)
836825

837826

838827
def test_dwell_time_and_energy():

0 commit comments

Comments
 (0)