miepython 3.2.0__py3-none-any.whl → 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- miepython/__init__.py +33 -2
- miepython/core.py +105 -40
- miepython/field.py +238 -293
- miepython/mie_jit.py +202 -97
- miepython/mie_nojit.py +166 -86
- miepython/monte_carlo.py +57 -8
- miepython/rayleigh.py +7 -33
- miepython/util.py +25 -5
- miepython/vsh.py +16 -7
- {miepython-3.2.0.dist-info → miepython-3.3.0.dist-info}/METADATA +6 -5
- miepython-3.3.0.dist-info/RECORD +18 -0
- {miepython-3.2.0.dist-info → miepython-3.3.0.dist-info}/WHEEL +1 -1
- miepython-3.2.0.dist-info/RECORD +0 -18
- {miepython-3.2.0.dist-info → miepython-3.3.0.dist-info}/licenses/LICENSE.txt +0 -0
- {miepython-3.2.0.dist-info → miepython-3.3.0.dist-info}/top_level.txt +0 -0
miepython/mie_jit.py
CHANGED
|
@@ -82,10 +82,79 @@ def _D_upwards(z, N, D):
|
|
|
82
82
|
D[n] = 1 / (n / z - D[n - 1]) - n / z
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
@njit((complex128, int64), cache=True)
|
|
86
|
+
def _psi_downwards_nb(z, nstop):
|
|
87
|
+
"""
|
|
88
|
+
Compute the Riccati-Bessel functions psi_0(z)..psi_nstop(z).
|
|
89
|
+
|
|
90
|
+
The three-term upwards recurrence
|
|
91
|
+
``psi_{n+1} = (2n+1)/z psi_n - psi_{n-1}`` is contaminated by the growing
|
|
92
|
+
chi_n solution once n exceeds ``|z|``, which is the normal case for psi_n(mx)
|
|
93
|
+
whenever the relative index is below one. Miller's algorithm is used
|
|
94
|
+
instead: seed the recurrence well above ``|z|`` where psi_n is the decaying
|
|
95
|
+
solution, run it downwards, then fix the arbitrary scale against whichever
|
|
96
|
+
of psi_0 or psi_1 is larger. sin(z) and cos(z) cannot both be small, so one
|
|
97
|
+
of those two seeds is always well conditioned.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
z: function argument (the refractive index times the size parameter)
|
|
101
|
+
nstop: highest order needed
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Array of psi_k(z) for k=0 to nstop
|
|
105
|
+
"""
|
|
106
|
+
abs_z = abs(z)
|
|
107
|
+
n_start = int(max(float(nstop), abs_z)) + 25 + int(1.5 * np.sqrt(abs_z))
|
|
108
|
+
|
|
109
|
+
psi = np.zeros(n_start + 2, dtype=np.complex128)
|
|
110
|
+
psi[n_start] = 1e-50
|
|
111
|
+
for n in range(n_start, 0, -1):
|
|
112
|
+
psi[n - 1] = (2 * n + 1) / z * psi[n] - psi[n + 1]
|
|
113
|
+
if abs(psi[n - 1]) > 1e200: # keep the growing tail in range
|
|
114
|
+
for k in range(n - 1, n_start + 2):
|
|
115
|
+
psi[k] /= 1e200
|
|
116
|
+
|
|
117
|
+
sin_z = np.sin(z)
|
|
118
|
+
psi_0 = sin_z
|
|
119
|
+
psi_1 = sin_z / z - np.cos(z)
|
|
120
|
+
if abs(psi_1) > abs(psi_0):
|
|
121
|
+
scale = psi_1 / psi[1]
|
|
122
|
+
else:
|
|
123
|
+
scale = psi_0 / psi[0]
|
|
124
|
+
for k in range(n_start + 2):
|
|
125
|
+
psi[k] *= scale
|
|
126
|
+
|
|
127
|
+
return psi[: nstop + 1]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@njit((complex128, int64), cache=True)
|
|
131
|
+
def _D_calc_down_nb(z, N):
|
|
132
|
+
"""
|
|
133
|
+
Compute D_1(z)..D_N(z) using only the downwards recurrence.
|
|
134
|
+
|
|
135
|
+
``_D_calc_nb`` picks between the upwards and downwards recurrences with
|
|
136
|
+
Wiscombe's criterion, which considers the refractive index but not the number
|
|
137
|
+
of terms. The upwards recurrence loses accuracy once N exceeds ``|z|`` (about
|
|
138
|
+
4 digits for a small sphere), and the resulting noise differs between the
|
|
139
|
+
numba and pure-python backends. The internal-field coefficients are not on
|
|
140
|
+
the hot path, so they always take the stable route.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
z: function argument (already multiplied by the refractive index)
|
|
144
|
+
N: highest order needed
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Array of logarithmic derivatives D_k(z) for k=1 to N
|
|
148
|
+
"""
|
|
149
|
+
D = np.zeros(N + 1, dtype=np.complex128)
|
|
150
|
+
_D_downwards(z, N, D)
|
|
151
|
+
return D[1:]
|
|
152
|
+
|
|
153
|
+
|
|
85
154
|
@njit((complex128, float64, int64), cache=True)
|
|
86
155
|
def _D_calc_nb(m, x, N):
|
|
87
156
|
"""
|
|
88
|
-
Compute the logarithmic derivative of ψ_n(z) using the
|
|
157
|
+
Compute the logarithmic derivative of ψ_n(z) using the downwards recurrence.
|
|
89
158
|
|
|
90
159
|
D_n(z) = d[log ψ_n(z)] = ψ_n'(z)/ψ_n(z)
|
|
91
160
|
|
|
@@ -94,6 +163,14 @@ def _D_calc_nb(m, x, N):
|
|
|
94
163
|
|
|
95
164
|
The zero-based array, D[:], is shifted so that D[0] = D₁(z) = ψ₁'(z)/ψ₁(z)
|
|
96
165
|
|
|
166
|
+
Wiscombe's criterion used to pick between the upwards and downwards
|
|
167
|
+
recurrences from the refractive index alone. The upwards recurrence is
|
|
168
|
+
contaminated once N passes ``|mx|``, which is the case for every small sphere,
|
|
169
|
+
and it left the Mie coefficients wrong by as much as 1% near m=1. The
|
|
170
|
+
downwards recurrence is stable everywhere, and the criterion already chose it
|
|
171
|
+
for most large spheres, so always taking it costs little. ``_D_upwards`` is
|
|
172
|
+
kept for comparison and testing.
|
|
173
|
+
|
|
97
174
|
Args:
|
|
98
175
|
m: the np.complex128 index of refraction of the sphere
|
|
99
176
|
x: the size parameter of the sphere
|
|
@@ -102,20 +179,15 @@ def _D_calc_nb(m, x, N):
|
|
|
102
179
|
Returns:
|
|
103
180
|
Array of logarithmic derivatives D_k(z) for k=1 to N-1.
|
|
104
181
|
"""
|
|
105
|
-
|
|
106
|
-
kappa = np.abs(m.imag)
|
|
107
|
-
D = np.zeros(N + 1, dtype=np.complex128)
|
|
108
|
-
mx = np.complex128(m * x) # ensure complex
|
|
109
|
-
|
|
110
|
-
if n < 1 or n > 10 or kappa > 10 or x * kappa >= 3.9 - 10.8 * n + 13.78 * n**2:
|
|
111
|
-
_D_downwards(mx, N, D)
|
|
112
|
-
else:
|
|
113
|
-
_D_upwards(mx, N, D)
|
|
114
|
-
return D[1:]
|
|
182
|
+
return _D_calc_down_nb(np.complex128(m * x), N)
|
|
115
183
|
|
|
116
184
|
|
|
117
|
-
|
|
118
|
-
|
|
185
|
+
# Lazily compiled, unlike the kernels around it, so that n_pole can carry the same
|
|
186
|
+
# default as ``_an_bn_py``: numba rejects an omitted argument against an eager
|
|
187
|
+
# signature, which used to make ``an_bn(m, x)`` work with the JIT off and raise
|
|
188
|
+
# TypeError with it on.
|
|
189
|
+
@njit(cache=True)
|
|
190
|
+
def _an_bn_nb(m, x, n_pole=0):
|
|
119
191
|
"""
|
|
120
192
|
Compute arrays of Mie coefficients a_n and b_n for a sphere.
|
|
121
193
|
|
|
@@ -123,8 +195,8 @@ def _an_bn_nb(m, x, n_pole):
|
|
|
123
195
|
formula. The length of the arrays is chosen so that the error when the series
|
|
124
196
|
is summed is around 1e-6.
|
|
125
197
|
|
|
126
|
-
If n_pole>0, then the
|
|
127
|
-
trying to isolate the behavior of a particular multipole.
|
|
198
|
+
If n_pole>0, then the arrays hold exactly n_pole terms, orders 1 to n_pole.
|
|
199
|
+
This is useful when trying to isolate the behavior of a particular multipole.
|
|
128
200
|
|
|
129
201
|
To support resonance calculations, one can specify the number of terms
|
|
130
202
|
to be calculated. In general, using too few or too many terms increases the
|
|
@@ -137,7 +209,8 @@ def _an_bn_nb(m, x, n_pole):
|
|
|
137
209
|
n_pole: the number of An and Bn terms (0 does autosizing)
|
|
138
210
|
|
|
139
211
|
Returns:
|
|
140
|
-
a, b: arrays of Mie coefficents An and Bn
|
|
212
|
+
a, b: arrays of Mie coefficents An and Bn, one entry per order,
|
|
213
|
+
with no padding. ``_cn_dn`` returns the same number of terms.
|
|
141
214
|
"""
|
|
142
215
|
# make sure sign of imaginary part is negative
|
|
143
216
|
m_re = m.real
|
|
@@ -146,55 +219,70 @@ def _an_bn_nb(m, x, n_pole):
|
|
|
146
219
|
m_im = -m_im
|
|
147
220
|
m = complex(m_re, m_im)
|
|
148
221
|
|
|
149
|
-
#
|
|
222
|
+
# Wiscombe's truncation: the series is summed through order n_terms
|
|
150
223
|
if n_pole == 0:
|
|
151
|
-
|
|
224
|
+
n_terms = int(x + 4.05 * x**0.33333 + 2.0)
|
|
152
225
|
else:
|
|
153
|
-
|
|
226
|
+
n_terms = n_pole
|
|
154
227
|
|
|
155
|
-
a = np.zeros(
|
|
156
|
-
b = np.zeros(
|
|
228
|
+
a = np.zeros(n_terms, dtype=np.complex128)
|
|
229
|
+
b = np.zeros(n_terms, dtype=np.complex128)
|
|
157
230
|
if x <= 0:
|
|
158
231
|
return a, b
|
|
159
232
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
233
|
+
# group the arithmetic exactly as _an_bn_py does: multiplying by a precomputed
|
|
234
|
+
# 1/x here and dividing by x there differed by one ulp, which the recurrences
|
|
235
|
+
# amplified into 2.6e-7 between the two backends.
|
|
236
|
+
inv_x = 1.0 / x
|
|
237
|
+
sin_x = np.sin(x)
|
|
238
|
+
cos_x = np.cos(x)
|
|
239
|
+
|
|
240
|
+
# psi comes from Miller's downwards recurrence. The upwards one is fine while
|
|
241
|
+
# n stays below x, which is all Wiscombe's truncation needs, but its relative
|
|
242
|
+
# accuracy is already down to about 1e-6 at n = n_terms and it collapses
|
|
243
|
+
# completely past that, so a caller asking for extra orders used to get noise.
|
|
244
|
+
psi = _psi_downwards_nb(np.complex128(x), n_terms).real
|
|
245
|
+
|
|
246
|
+
# chi is the growing solution, so its upwards recurrence is the stable one.
|
|
247
|
+
# Building xi as psi + i*chi keeps xi exactly consistent with the psi above,
|
|
248
|
+
# which the lossless identity Re(a_n) == |a_n|**2 depends on.
|
|
249
|
+
chi_nm1 = cos_x
|
|
250
|
+
chi_n = cos_x * inv_x + sin_x
|
|
164
251
|
|
|
165
252
|
if m.real > 0.0:
|
|
166
|
-
D = _D_calc_nb(m, x,
|
|
167
|
-
|
|
168
|
-
for n in range(1,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
253
|
+
D = _D_calc_nb(m, x, n_terms + 2)
|
|
254
|
+
|
|
255
|
+
for n in range(1, n_terms + 1):
|
|
256
|
+
n_over_x = n * inv_x
|
|
257
|
+
xi_nm1 = psi[n - 1] + 1j * chi_nm1
|
|
258
|
+
xi_n = psi[n] + 1j * chi_n
|
|
259
|
+
temp = D[n - 1] / m + n_over_x
|
|
260
|
+
a[n - 1] = (temp * psi[n] - psi[n - 1]) / (temp * xi_n - xi_nm1)
|
|
261
|
+
temp = D[n - 1] * m + n_over_x
|
|
262
|
+
b[n - 1] = (temp * psi[n] - psi[n - 1]) / (temp * xi_n - xi_nm1)
|
|
263
|
+
chi = (2 * n + 1) * inv_x * chi_n - chi_nm1
|
|
264
|
+
chi_nm1 = chi_n
|
|
265
|
+
chi_n = chi
|
|
179
266
|
|
|
180
267
|
else:
|
|
181
|
-
for n in range(1,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if n_pole != 0:
|
|
191
|
-
a = a[:-1]
|
|
192
|
-
b = b[:-1]
|
|
268
|
+
for n in range(1, n_terms + 1):
|
|
269
|
+
n_over_x = n * inv_x
|
|
270
|
+
xi_nm1 = psi[n - 1] + 1j * chi_nm1
|
|
271
|
+
xi_n = psi[n] + 1j * chi_n
|
|
272
|
+
a[n - 1] = (n_over_x * psi[n] - psi[n - 1]) / (n_over_x * xi_n - xi_nm1)
|
|
273
|
+
b[n - 1] = psi[n] / xi_n
|
|
274
|
+
chi = (2 * n + 1) * inv_x * chi_n - chi_nm1
|
|
275
|
+
chi_nm1 = chi_n
|
|
276
|
+
chi_n = chi
|
|
193
277
|
|
|
194
278
|
return np.conjugate(a), np.conjugate(b)
|
|
195
279
|
|
|
196
280
|
|
|
197
|
-
|
|
281
|
+
# No kernel in this module uses fastmath. It implies LLVM's nnan and ninf, which
|
|
282
|
+
# folded the np.isinf guard below to False and let an infinite index divide by
|
|
283
|
+
# zero, and its reassociation also broke the exact Re(a_n) == |a_n|**2 identity
|
|
284
|
+
# that a lossless sphere has to satisfy. Do not add it back.
|
|
285
|
+
@njit((complex128, float64, int64))
|
|
198
286
|
def _cn_dn_nb(m, x, n_pole):
|
|
199
287
|
"""
|
|
200
288
|
Calculate Mie coefficients c_n and d_n for the internal field of a sphere.
|
|
@@ -215,51 +303,48 @@ def _cn_dn_nb(m, x, n_pole):
|
|
|
215
303
|
m = complex(m_re, m_im)
|
|
216
304
|
mx = m * x
|
|
217
305
|
|
|
306
|
+
# same truncation as _an_bn_nb so the internal and external series match
|
|
218
307
|
if n_pole == 0:
|
|
219
|
-
|
|
308
|
+
n_terms = int(x + 4.05 * x**0.33333 + 2.0)
|
|
220
309
|
else:
|
|
221
|
-
|
|
310
|
+
n_terms = n_pole
|
|
222
311
|
|
|
223
|
-
c = np.zeros(
|
|
224
|
-
d = np.zeros(
|
|
312
|
+
c = np.zeros(n_terms, dtype=np.complex128)
|
|
313
|
+
d = np.zeros(n_terms, dtype=np.complex128)
|
|
225
314
|
if x <= 0:
|
|
226
315
|
return c, d
|
|
227
316
|
|
|
228
317
|
# no need to calculate anything when sphere is perfectly conducting
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
xi = (2 * n + 1) * xi_n / x - xi_nm1
|
|
318
|
+
# (m.real <= 0 or an infinite index): there is no internal field, so c and d
|
|
319
|
+
# stay zero. The `and` chain must not be mixed with `or` here, otherwise the
|
|
320
|
+
# test is true for every finite index and m=0 divides by zero below.
|
|
321
|
+
if m.real > 0.0 and not np.isinf(m.real) and not np.isinf(m.imag):
|
|
322
|
+
# same arithmetic grouping as _cn_dn_py, so the two backends agree to the
|
|
323
|
+
# last bits rather than only to about 1e-13
|
|
324
|
+
inv_x = 1.0 / x
|
|
325
|
+
sin_x = np.sin(x)
|
|
326
|
+
cos_x = np.cos(x)
|
|
327
|
+
|
|
328
|
+
# xi is the growing solution, so seeding it and running upwards is stable
|
|
329
|
+
xi_nm1 = np.complex128(sin_x + 1j * cos_x)
|
|
330
|
+
xi_n = np.complex128((sin_x * inv_x - cos_x) + 1j * (cos_x * inv_x + sin_x))
|
|
331
|
+
|
|
332
|
+
psi_x = _psi_downwards_nb(np.complex128(x), n_terms + 1)
|
|
333
|
+
psi_mx = _psi_downwards_nb(np.complex128(mx), n_terms + 1)
|
|
334
|
+
Dmx = _D_calc_down_nb(np.complex128(mx), n_terms + 2)
|
|
335
|
+
Dx = _D_calc_down_nb(np.complex128(x), n_terms + 2)
|
|
336
|
+
|
|
337
|
+
for n in range(1, n_terms + 1):
|
|
338
|
+
n_over_x = n * inv_x
|
|
339
|
+
common = (psi_x[n] / psi_mx[n]) * ((Dx[n - 1] + n_over_x) * xi_n - xi_nm1)
|
|
340
|
+
|
|
341
|
+
c[n - 1] = m * common / ((m * Dmx[n - 1] + n_over_x) * xi_n - xi_nm1)
|
|
342
|
+
d[n - 1] = common / ((Dmx[n - 1] / m + n_over_x) * xi_n - xi_nm1)
|
|
343
|
+
|
|
344
|
+
xi = (2 * n + 1) * inv_x * xi_n - xi_nm1
|
|
257
345
|
xi_nm1 = xi_n
|
|
258
346
|
xi_n = xi
|
|
259
347
|
|
|
260
|
-
if n_pole != 0:
|
|
261
|
-
c = c[:-1]
|
|
262
|
-
d = d[:-1]
|
|
263
348
|
return np.conjugate(c), np.conjugate(d)
|
|
264
349
|
|
|
265
350
|
|
|
@@ -297,6 +382,9 @@ def _pi_tau_nb(mu, pi, tau):
|
|
|
297
382
|
pi[n] = ((2 * n + 1) * mu * temp - (n + 1) * pi_nm2) / n
|
|
298
383
|
pi_nm2 = temp
|
|
299
384
|
|
|
385
|
+
# each pass sets tau one order behind pi, so the highest order is still due
|
|
386
|
+
tau[n_terms - 1] = n_terms * mu * pi[n_terms - 1] - (n_terms + 1) * pi_nm2
|
|
387
|
+
|
|
300
388
|
|
|
301
389
|
@njit((complex128, float64, float64[:], int64), cache=True)
|
|
302
390
|
def _S1_S2_nb(m, x, mu, n_pole):
|
|
@@ -319,6 +407,9 @@ def _S1_S2_nb(m, x, mu, n_pole):
|
|
|
319
407
|
"""
|
|
320
408
|
a, b = _an_bn_nb(m, x, 0)
|
|
321
409
|
N = len(a)
|
|
410
|
+
if n_pole < 0 or n_pole > N:
|
|
411
|
+
raise ValueError("n_pole must be 0 (all terms) or a multipole order in 1.." + str(N))
|
|
412
|
+
|
|
322
413
|
pi = np.zeros(N)
|
|
323
414
|
tau = np.zeros(N)
|
|
324
415
|
n = np.arange(1, N + 1, dtype=np.float64)
|
|
@@ -328,6 +419,9 @@ def _S1_S2_nb(m, x, mu, n_pole):
|
|
|
328
419
|
S1 = np.zeros(nangles, dtype=np.complex128)
|
|
329
420
|
S2 = np.zeros(nangles, dtype=np.complex128)
|
|
330
421
|
|
|
422
|
+
# zero-based arrays: multipole order n lives at index n-1
|
|
423
|
+
j = n_pole - 1
|
|
424
|
+
|
|
331
425
|
for k in range(nangles):
|
|
332
426
|
_pi_tau_nb(mu[k], pi, tau)
|
|
333
427
|
if n_pole == 0:
|
|
@@ -340,13 +434,13 @@ def _S1_S2_nb(m, x, mu, n_pole):
|
|
|
340
434
|
S1[k] = s1
|
|
341
435
|
S2[k] = s2
|
|
342
436
|
else:
|
|
343
|
-
S1[k] = scale[
|
|
344
|
-
S2[k] = scale[
|
|
437
|
+
S1[k] = scale[j] * (pi[j] * a[j] + tau[j] * b[j])
|
|
438
|
+
S2[k] = scale[j] * (tau[j] * a[j] + pi[j] * b[j])
|
|
345
439
|
|
|
346
440
|
return np.conjugate(S1), np.conjugate(S2)
|
|
347
441
|
|
|
348
442
|
|
|
349
|
-
@njit((complex128, float64), cache=True
|
|
443
|
+
@njit((complex128, float64), cache=True)
|
|
350
444
|
def _small_conducting_sphere_nb(_m, x):
|
|
351
445
|
"""
|
|
352
446
|
Calculate the efficiencies for a small conducting spheres.
|
|
@@ -384,7 +478,7 @@ def _small_conducting_sphere_nb(_m, x):
|
|
|
384
478
|
return qext, qsca, qback, g
|
|
385
479
|
|
|
386
480
|
|
|
387
|
-
@njit((complex128, float64), cache=True
|
|
481
|
+
@njit((complex128, float64), cache=True)
|
|
388
482
|
def _small_sphere_nb(m, x):
|
|
389
483
|
"""
|
|
390
484
|
Calculate the efficiencies for a small sphere.
|
|
@@ -432,7 +526,7 @@ def _small_sphere_nb(m, x):
|
|
|
432
526
|
return qext, qsca, qback, g
|
|
433
527
|
|
|
434
528
|
|
|
435
|
-
@njit((complex128, float64, int64, boolean), cache=True
|
|
529
|
+
@njit((complex128, float64, int64, boolean), cache=True)
|
|
436
530
|
def _single_sphere_nb(m, x, n_pole, e_field):
|
|
437
531
|
"""
|
|
438
532
|
Calculate the efficiencies for a single sphere (both m and x are scalars).
|
|
@@ -441,7 +535,9 @@ def _single_sphere_nb(m, x, n_pole, e_field):
|
|
|
441
535
|
m: the complex index of refraction of the sphere
|
|
442
536
|
x: the size parameter of the sphere
|
|
443
537
|
n_pole: a non-zero value returns the contribution by the n_pole multipole
|
|
444
|
-
e_field:
|
|
538
|
+
e_field: selects which multipole of order n_pole contributes, the
|
|
539
|
+
electric one a_n (True) or the magnetic one b_n (False).
|
|
540
|
+
Ignored when n_pole == 0.
|
|
445
541
|
|
|
446
542
|
Returns:
|
|
447
543
|
qext: the total extinction efficiency
|
|
@@ -449,7 +545,10 @@ def _single_sphere_nb(m, x, n_pole, e_field):
|
|
|
449
545
|
qback: the backscatter efficiency
|
|
450
546
|
g: the average cosine of the scattering phase function
|
|
451
547
|
"""
|
|
452
|
-
|
|
548
|
+
# a sphere of zero size scatters nothing; every efficiency tends to zero,
|
|
549
|
+
# including qback, whose small-sphere form is 0/0 at x=0
|
|
550
|
+
if x <= 0:
|
|
551
|
+
return 0.0, 0.0, 0.0, 0.0
|
|
453
552
|
|
|
454
553
|
# case when sphere matches its environment
|
|
455
554
|
if abs(m.real - 1) <= 1e-8 and abs(m.imag) < 1e-8:
|
|
@@ -515,12 +614,18 @@ def _single_sphere_nb(m, x, n_pole, e_field):
|
|
|
515
614
|
g = 4.0 * g_acc / qsca / x2
|
|
516
615
|
|
|
517
616
|
else:
|
|
617
|
+
# isolate one multipole: the electric term a_n or the magnetic term b_n
|
|
518
618
|
cn = 2.0 * n_pole + 1
|
|
519
|
-
|
|
520
|
-
|
|
619
|
+
coeff = a[-1] if e_field else b[-1]
|
|
620
|
+
qext = 2.0 * cn * coeff.real / x2
|
|
621
|
+
# the (-1)**n_pole and the sign of b_n drop out of the modulus
|
|
622
|
+
qback = np.abs(cn * coeff) ** 2 / x2
|
|
521
623
|
qsca = qext
|
|
522
624
|
if m.imag < 0:
|
|
523
|
-
qsca = 2.0 * cn *
|
|
524
|
-
|
|
625
|
+
qsca = 2.0 * cn * np.abs(coeff) ** 2 / x2
|
|
626
|
+
|
|
627
|
+
# pi_n**2 and tau_n**2 are both even in mu, so an isolated multipole of
|
|
628
|
+
# one parity scatters symmetrically about 90 degrees and <cos(theta)>=0
|
|
629
|
+
g = 0.0
|
|
525
630
|
|
|
526
631
|
return qext, qsca, qback, g
|