Skip to content

Commit 445b2d1

Browse files
committed
changed calc_cross_sections() to return dimensionless quantities
Returned cross-sections are now multiplied by k^2 so that unit checking is not needed.
1 parent 463f936 commit 445b2d1

File tree

3 files changed

+127
-96
lines changed

3 files changed

+127
-96
lines changed

pymie/mie.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -119,29 +119,31 @@ def calc_ang_scat(m, x, angles, mie = True, check = False):
119119

120120
return np.array([ipar, iperp])
121121

122-
@ureg.check(None, None, '[length]', None, None)
123-
def calc_cross_sections(m, x, wavelen_media, eps1 = DEFAULT_EPS1,
122+
def calc_cross_sections(m, x, eps1 = DEFAULT_EPS1,
124123
eps2 = DEFAULT_EPS2):
125124
"""
126-
Calculate (dimensional) scattering, absorption, and extinction cross
125+
Calculate dimensionaless scattering, absorption, and extinction cross
127126
sections, and asymmetry parameter for spherically symmetric scatterers.
128127
129128
Parameters
130129
----------
131-
m: complex relative refractive index
132-
x: size parameter
133-
wavelen_media: structcol.Quantity [length]
134-
wavelength of incident light *in media* (usually this would be the
135-
wavelength in the effective index of the particle-matrix composite)
130+
m : array-like
131+
complex relative refractive index
132+
x : array-like
133+
size parameter
136134
137135
Returns
138136
-------
139137
cross_sections : tuple (5)
140-
Dimensional scattering, absorption, extinction, and backscattering
138+
Dimensionless scattering, absorption, extinction, and backscattering
141139
cross sections, and <cos theta> (asymmetry parameter g)
142140
143141
Notes
144142
-----
143+
To recover the dimensional cross-sections, multiply the returned
144+
cross-sections by 1/k^2, where k is the wavevector in *media*. The
145+
asymmetry parameter is dimensionless and needs no scaling.
146+
145147
The backscattering cross-section is 1/(4*pi) times the radar backscattering
146148
cross-section; that is, it corresponds to the differential scattering
147149
cross-section in the backscattering direction. See B&H 4.6.
@@ -155,18 +157,19 @@ def calc_cross_sections(m, x, wavelen_media, eps1 = DEFAULT_EPS1,
155157
156158
where I_0 is the incident intensity. See van de Hulst, p. 14.
157159
"""
158-
# This is adapted from mie.py in holopy
159-
160160
lmax = _nstop(np.array(x).max())
161161
albl = _scatcoeffs(m, x, lmax, eps1=eps1, eps2=eps2)
162162

163-
cscat, cext, cback = tuple(np.abs(wavelen_media)**2 * c/2/np.pi for c in
163+
cscat, cext, cback = tuple(c*2*np.pi for c in
164164
_cross_sections(albl[0], albl[1]))
165165

166166
cabs = cext - cscat # conservation of energy
167167

168-
asym = np.abs(wavelen_media)**2 / np.pi / cscat * \
169-
_asymmetry_parameter(albl[0], albl[1])
168+
# _asymmetry_parameter returns g*Q_sca*x^2/4. cscat is k^2 times the
169+
# dimensional cross-section. So asymmetry parameter is
170+
# 4*pi/(k^2 Csca) * sum from _asymmetry_parameter, where Csca is the
171+
# dimensional cross section
172+
asym = 4*np.pi / cscat * _asymmetry_parameter(albl[0], albl[1])
170173

171174
return cscat, cext, cabs, cback, asym
172175

@@ -317,10 +320,10 @@ def calc_dwell_time(radius, n_medium, n_particle, wavelen,
317320
c = Quantity(1.0, 'speed_of_light').to('m/s')
318321

319322
# calculate total cross section
323+
k = 2*np.pi/wavelen_media
320324
if np.imag(x)>0:
321325
angles = Quantity(np.linspace(min_angle, np.pi, num_angles), 'rad')
322326
distance = radius.max()
323-
k = 2*np.pi/wavelen_media
324327
kd = (k*distance).to("").magnitude
325328
(diff_cscat_par,
326329
diff_cscat_perp) = diff_scat_intensity_complex_medium(m, x, angles,
@@ -331,8 +334,8 @@ def calc_dwell_time(radius, n_medium, n_particle, wavelen,
331334
distance,
332335
angles, k)[0]
333336
else:
334-
cscat = calc_cross_sections(m, x, wavelen_media,
335-
eps1 = eps1, eps2 = eps2)[0]
337+
cscat = calc_cross_sections(m, x, eps1 = eps1, eps2 = eps2)[0]
338+
cscat = cscat * 1/k**2
336339

337340
# calculate dwell time
338341
dwell_time = W/(cscat*c)
@@ -697,11 +700,10 @@ def _W0(radius, n_medium):
697700
return W0
698701

699702
def _nstop(x):
700-
# takes size parameter, outputs order to compute to according to
701-
# Wiscombe, Applied Optics 19, 1505 (1980).
702-
# 7/7/08: generalize to apply same criterion when x is complex
703+
# Takes size parameter, outputs order to compute. Previously used criterion
704+
# from Wiscombe, Applied Optics 19, 1505 (1980):
703705
#return (np.round(np.absolute(x+4.05*x**(1./3.)+2))).astype('int')
704-
706+
# now modified to use:
705707
# Criterion for calculating near-field properties with exact Mie solutions
706708
# (J. R. Allardice and E. C. Le Ru, Applied Optics, Vol. 53, No. 31 (2014).
707709
return (np.round(np.absolute(x+11*x**(1./3.)+1))).squeeze().astype('int')

pymie/tests/test_mie.py

Lines changed: 84 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from .. import Quantity, index_ratio, size_parameter, np, mie
2424
from numpy.testing import (assert_almost_equal, assert_array_almost_equal,
2525
assert_approx_equal, assert_allclose)
26-
from pint.errors import DimensionalityError
2726
import pytest
2827

2928
def test_cross_sections():
@@ -50,18 +49,17 @@ def test_cross_sections():
5049
cscat = qscat * np.pi * radius**2
5150
cext = qext * np.pi * radius**2
5251
cback = qback * np.pi * radius**2
53-
cscat2, cext2, _, cback2, g2 = mie.calc_cross_sections(m, x, wavelen/n_matrix)
52+
cscat2, cext2, _, cback2, g2 = mie.calc_cross_sections(m, x)
53+
# calculate dimensional cross-sections
54+
k = 2*np.pi*n_matrix/wavelen
55+
cscat2 = cscat2/k**2
56+
cext2 = cext2/k**2
57+
cback2 = cback2/k**2
5458
assert_almost_equal(cscat.to('m^2').magnitude, cscat2.to('m^2').magnitude)
5559
assert_almost_equal(cext.to('m^2').magnitude, cext2.to('m^2').magnitude)
5660
assert_almost_equal(cback.to('m^2').magnitude, cback2.to('m^2').magnitude)
57-
assert_almost_equal(g, g2.magnitude)
61+
assert_almost_equal(g, g2)
5862

59-
# test that calc_cross_sections throws an exception when given an argument
60-
# with the wrong dimensions
61-
pytest.raises(DimensionalityError, mie.calc_cross_sections,
62-
m, x, Quantity('0.25 J'))
63-
pytest.raises(DimensionalityError, mie.calc_cross_sections,
64-
m, x, Quantity('0.25'))
6563

6664
def test_form_factor():
6765
wavelen = Quantity('658.0 nm')
@@ -187,7 +185,7 @@ def test_multilayer_spheres():
187185
x = size_parameter(wavelen, n_sample, radius)
188186

189187
f_parperp = mie.calc_ang_scat(m, x, angles)
190-
cscat, cext, cabs, cback, asym = mie.calc_cross_sections(m, x, wavelen)
188+
cscat, cext, cabs, cback, asym = mie.calc_cross_sections(m, x)
191189

192190
# form factor and cross section for a multilayer particle with a core that
193191
# is the same as the non-multilayer and a shell thickness of zero
@@ -196,14 +194,16 @@ def test_multilayer_spheres():
196194
xarray = size_parameter(wavelen, n_sample, multi_radius)
197195

198196
f_parperp_multi = mie.calc_ang_scat(marray, xarray, angles)
199-
cscat_multi, cext_multi, cabs_multi, cback_multi, asym_multi = mie.calc_cross_sections(marray, xarray, wavelen)
197+
cscat_multi, cext_multi, cabs_multi, cback_multi, asym_multi = \
198+
mie.calc_cross_sections(marray, xarray)
200199

200+
# note that these are the nondimensional cross-sections (C * k^2)
201201
assert_array_almost_equal(f_parperp, f_parperp_multi)
202-
assert_array_almost_equal(cscat.to('um^2').magnitude, cscat_multi.to('um^2').magnitude)
203-
assert_array_almost_equal(cext.to('um^2').magnitude, cext_multi.to('um^2').magnitude)
204-
assert_array_almost_equal(cabs.to('um^2').magnitude, cabs_multi.to('um^2').magnitude)
205-
assert_array_almost_equal(cback.to('um^2').magnitude, cback_multi.to('um^2').magnitude)
206-
assert_array_almost_equal(asym.magnitude, asym_multi.magnitude)
202+
assert_array_almost_equal(cscat, cscat_multi)
203+
assert_array_almost_equal(cext, cext_multi)
204+
assert_array_almost_equal(cabs, cabs_multi)
205+
assert_array_almost_equal(cback, cback_multi)
206+
assert_array_almost_equal(asym, asym_multi)
207207

208208
# form factor and cross section for a multilayer particle with a core that
209209
# is the same as the non-multilayer and a shell index matched with the
@@ -213,14 +213,16 @@ def test_multilayer_spheres():
213213
xarray2 = size_parameter(wavelen, n_sample, multi_radius2)
214214

215215
f_parperp_multi2 = mie.calc_ang_scat(marray2, xarray2, angles)
216-
cscat_multi2, cext_multi2, cabs_multi2, cback_multi2, asym_multi2 = mie.calc_cross_sections(marray2, xarray2, wavelen)
216+
cscat_multi2, cext_multi2, cabs_multi2, cback_multi2, asym_multi2 = \
217+
mie.calc_cross_sections(marray2, xarray2)
217218

219+
# again, we compare the nondimensional cross sections
218220
assert_array_almost_equal(f_parperp, f_parperp_multi2)
219-
assert_array_almost_equal(cscat.to('um^2').magnitude, cscat_multi2.to('um^2').magnitude)
220-
assert_array_almost_equal(cext.to('um^2').magnitude, cext_multi2.to('um^2').magnitude)
221-
assert_array_almost_equal(cabs.to('um^2').magnitude, cabs_multi2.to('um^2').magnitude)
222-
assert_array_almost_equal(cback.to('um^2').magnitude, cback_multi2.to('um^2').magnitude)
223-
assert_array_almost_equal(asym.magnitude, asym_multi2.magnitude)
221+
assert_array_almost_equal(cscat, cscat_multi2)
222+
assert_array_almost_equal(cext, cext_multi2)
223+
assert_array_almost_equal(cabs, cabs_multi2)
224+
assert_array_almost_equal(cback, cback_multi2)
225+
assert_array_almost_equal(asym, asym_multi2)
224226

225227
# form factor and cross section for a 3-layer-particle with a core that
226228
# is the same as the non-multilayer and shell thicknesses of zero
@@ -229,14 +231,16 @@ def test_multilayer_spheres():
229231
xarray3 = size_parameter(wavelen, n_sample, multi_radius3)
230232

231233
f_parperp_multi3 = mie.calc_ang_scat(marray3, xarray3, angles)
232-
cscat_multi3, cext_multi3, cabs_multi3, cback_multi3, asym_multi3 = mie.calc_cross_sections(marray3, xarray3, wavelen)
234+
cscat_multi3, cext_multi3, cabs_multi3, cback_multi3, asym_multi3 = \
235+
mie.calc_cross_sections(marray3, xarray3)
233236

237+
# compare the nondimensional cross-sections
234238
assert_array_almost_equal(f_parperp, f_parperp_multi3)
235-
assert_array_almost_equal(cscat.to('um^2').magnitude, cscat_multi3.to('um^2').magnitude)
236-
assert_array_almost_equal(cext.to('um^2').magnitude, cext_multi3.to('um^2').magnitude)
237-
assert_array_almost_equal(cabs.to('um^2').magnitude, cabs_multi3.to('um^2').magnitude)
238-
assert_array_almost_equal(cback.to('um^2').magnitude, cback_multi3.to('um^2').magnitude)
239-
assert_array_almost_equal(asym.magnitude, asym_multi3.magnitude)
239+
assert_array_almost_equal(cscat, cscat_multi3)
240+
assert_array_almost_equal(cext, cext_multi3)
241+
assert_array_almost_equal(cabs, cabs_multi3)
242+
assert_array_almost_equal(cback, cback_multi3)
243+
assert_array_almost_equal(asym, asym_multi3)
240244

241245
# form factor and cross section for a 3-layer-particle with a core that
242246
# is the same as the non-multilayer and a shell index matched with the
@@ -246,14 +250,16 @@ def test_multilayer_spheres():
246250
xarray4 = size_parameter(wavelen, n_sample, multi_radius4)
247251

248252
f_parperp_multi4 = mie.calc_ang_scat(marray4, xarray4, angles)
249-
cscat_multi4, cext_multi4, cabs_multi4, cback_multi4, asym_multi4 = mie.calc_cross_sections(marray4, xarray4, wavelen)
253+
cscat_multi4, cext_multi4, cabs_multi4, cback_multi4, asym_multi4 = \
254+
mie.calc_cross_sections(marray4, xarray4)
250255

256+
# compare the nondimensional cross sections
251257
assert_array_almost_equal(f_parperp, f_parperp_multi4)
252-
assert_array_almost_equal(cscat.to('um^2').magnitude, cscat_multi4.to('um^2').magnitude)
253-
assert_array_almost_equal(cext.to('um^2').magnitude, cext_multi4.to('um^2').magnitude)
254-
assert_array_almost_equal(cabs.to('um^2').magnitude, cabs_multi4.to('um^2').magnitude)
255-
assert_array_almost_equal(cback.to('um^2').magnitude, cback_multi4.to('um^2').magnitude)
256-
assert_array_almost_equal(asym.magnitude, asym_multi4.magnitude)
258+
assert_array_almost_equal(cscat, cscat_multi4)
259+
assert_array_almost_equal(cext, cext_multi4)
260+
assert_array_almost_equal(cabs, cabs_multi4)
261+
assert_array_almost_equal(cback, cback_multi4)
262+
assert_array_almost_equal(asym, asym_multi4)
257263

258264
def test_multilayer_absorbing_spheres():
259265
# test that the form factor and cross sections are the same for a real
@@ -269,15 +275,21 @@ def test_multilayer_absorbing_spheres():
269275
f_parperp_multi_real = mie.calc_ang_scat(marray_real, xarray, angles)
270276
f_parperp_multi_imag = mie.calc_ang_scat(marray_imag, xarray, angles)
271277

272-
cross_sections_multi_real = mie.calc_cross_sections(marray_real, xarray, wavelen)
273-
cross_sections_multi_imag = mie.calc_cross_sections(marray_imag, xarray, wavelen)
278+
cross_sections_multi_real = mie.calc_cross_sections(marray_real, xarray)
279+
cross_sections_multi_imag = mie.calc_cross_sections(marray_imag, xarray)
274280

281+
# compare nondimensional cross sections
275282
assert_array_almost_equal(f_parperp_multi_real, f_parperp_multi_imag)
276-
assert_array_almost_equal(cross_sections_multi_real[0].to('um^2').magnitude, cross_sections_multi_imag[0].to('um^2').magnitude)
277-
assert_array_almost_equal(cross_sections_multi_real[1].to('um^2').magnitude, cross_sections_multi_imag[1].to('um^2').magnitude)
278-
assert_array_almost_equal(cross_sections_multi_real[2].to('um^2').magnitude, cross_sections_multi_imag[2].to('um^2').magnitude)
279-
assert_array_almost_equal(cross_sections_multi_real[3].to('um^2').magnitude, cross_sections_multi_imag[3].to('um^2').magnitude)
280-
assert_array_almost_equal(cross_sections_multi_real[4].magnitude, cross_sections_multi_imag[4].magnitude)
283+
assert_array_almost_equal(cross_sections_multi_real[0],
284+
cross_sections_multi_imag[0])
285+
assert_array_almost_equal(cross_sections_multi_real[1],
286+
cross_sections_multi_imag[1])
287+
assert_array_almost_equal(cross_sections_multi_real[2],
288+
cross_sections_multi_imag[2])
289+
assert_array_almost_equal(cross_sections_multi_real[3],
290+
cross_sections_multi_imag[3])
291+
assert_array_almost_equal(cross_sections_multi_real[4],
292+
cross_sections_multi_imag[4])
281293

282294
def test_cross_section_Fu():
283295
# Test that the cross sections match the Mie cross sections when there is
@@ -290,7 +302,11 @@ def test_cross_section_Fu():
290302
n_matrix1 = 1.33
291303
m1 = index_ratio(n_particle, n_matrix1)
292304
x1 = size_parameter(wavelen, n_matrix1, radius)
293-
cscat1, cext1, cabs1, _, _ = mie.calc_cross_sections(m1, x1, wavelen/n_matrix1)
305+
cscat1, cext1, cabs1, _, _ = mie.calc_cross_sections(m1, x1)
306+
k1 = 2*np.pi*n_matrix1/wavelen
307+
cscat1 = cscat1/k1**2
308+
cext1 = cext1/k1**2
309+
cabs1 = cabs1/k1**2
294310

295311
# Fu cross sections
296312
n_matrix2 = 1.33
@@ -320,7 +336,11 @@ def test_cross_section_Fu():
320336
n_matrix1 = 1.33
321337
m1 = index_ratio(n_particle2, n_matrix1)
322338
x1 = size_parameter(wavelen, n_matrix1, radius)
323-
cscat3, cext3, cabs3, _, _ = mie.calc_cross_sections(m1, x1, wavelen/n_matrix1)
339+
cscat3, cext3, cabs3, _, _ = mie.calc_cross_sections(m1, x1)
340+
k1 = 2*np.pi* n_matrix1/wavelen
341+
cscat3 = cscat3/k1**2
342+
cext3 = cext3/k1**2
343+
cabs3 = cabs3/k1**2
324344

325345
# Fu cross sections
326346
n_matrix2 = 1.33
@@ -353,7 +373,11 @@ def test_cross_section_Sudiarta():
353373
n_matrix1 = 1.33
354374
m1 = index_ratio(n_particle, n_matrix1)
355375
x1 = size_parameter(wavelen, n_matrix1, radius)
356-
cscat1, cext1, cabs1, _, _ = mie.calc_cross_sections(m1, x1, wavelen/n_matrix1)
376+
cscat1, cext1, cabs1, _, _ = mie.calc_cross_sections(m1, x1)
377+
k1 = 2*np.pi* n_matrix1/wavelen
378+
cscat1 = cscat1/k1**2
379+
cext1 = cext1/k1**2
380+
cabs1 = cabs1/k1**2
357381

358382
# Sudiarta cross sections
359383
n_matrix2 = 1.33
@@ -378,9 +402,13 @@ def test_cross_section_Sudiarta():
378402
n_matrix1 = 1.33
379403
m1 = index_ratio(n_particle2, n_matrix1)
380404
x1 = size_parameter(wavelen, n_matrix1, radius)
381-
cscat3, cext3, cabs3, _, _ = mie.calc_cross_sections(m1, x1, wavelen/n_matrix1)
405+
cscat3, cext3, cabs3, _, _ = mie.calc_cross_sections(m1, x1)
406+
k1 = 2*np.pi* n_matrix1/wavelen
407+
cscat3 = cscat3/k1**2
408+
cext3 = cext3/k1**2
409+
cabs3 = cabs3/k1**2
382410

383-
# Fu cross sections
411+
# Sudiarta cross sections
384412
n_matrix2 = 1.33
385413
m2 = index_ratio(n_particle2, n_matrix2)
386414
x2 = size_parameter(wavelen, n_matrix2, radius)
@@ -504,7 +532,9 @@ def test_cross_section_complex_medium():
504532
coeffs = mie._scatcoeffs(m, x, nstop)
505533

506534
# With far-field Mie solutions
507-
cscat_mie = mie.calc_cross_sections(m, x, wavelen/n_matrix)[0]
535+
cscat_mie = mie.calc_cross_sections(m, x)[0]
536+
k = 2*np.pi*n_matrix/wavelen
537+
cscat_mie = cscat_mie/k**2
508538

509539
# With Sudiarta
510540
cscat_sudiarta = mie._cross_sections_complex_medium_sudiarta(coeffs[0],
@@ -586,7 +616,8 @@ def test_cross_section_complex_medium():
586616
theta, k)[0]
587617

588618
# With far-field Mie solutions
589-
cscat_mie3 = mie.calc_cross_sections(m, x, wavelen/n_matrix)[0]
619+
cscat_mie3 = mie.calc_cross_sections(m, x)[0]
620+
cscat_mie3 = cscat_mie3/k**2
590621

591622
# check that intensity equations without the asymptotic form of the spherical
592623
# Hankel equations (because they simplify when the fields are multiplied by
@@ -611,7 +642,8 @@ def test_multilayer_complex_medium():
611642
kd = (k*distance).to("").magnitude
612643

613644
# With far-field Mie solutions
614-
cscat_real = mie.calc_cross_sections(marray, xarray, wavelen/n_sample)[0]
645+
cscat_real = mie.calc_cross_sections(marray, xarray)[0]
646+
cscat_real = cscat_real/k**2
615647

616648
# with imag solutions
617649
I_parperp = mie.diff_scat_intensity_complex_medium(marray, xarray, angles,
@@ -855,7 +887,9 @@ def test_dwell_time_and_energy():
855887

856888
cscat_reported = 3.9*np.pi*radius**2
857889
cscat_reported = cscat_reported.to('um^2')
858-
cscat_calc = mie.calc_cross_sections(m, x, wavelen_media)[0]
890+
cscat_calc = mie.calc_cross_sections(m, x)[0]
891+
k = 2*np.pi/wavelen_media
892+
cscat_calc = cscat_calc/k**2
859893
cscat_calc = cscat_calc.to('um^2')
860894

861895
distance_reported = Quantity('190.0 um')

0 commit comments

Comments
 (0)