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/__init__.py
CHANGED
|
@@ -42,6 +42,29 @@ The scattering matrix
|
|
|
42
42
|
|
|
43
43
|
mie.phase_matrix(m, x, mu)
|
|
44
44
|
|
|
45
|
+
Submodules
|
|
46
|
+
----------
|
|
47
|
+
|
|
48
|
+
``miepython.rayleigh`` is imported with the package and needs nothing beyond NumPy.
|
|
49
|
+
It mirrors the API above using the small-sphere limit, which is handy for checking
|
|
50
|
+
the full solution as x goes to zero::
|
|
51
|
+
|
|
52
|
+
mie.rayleigh.efficiencies_mx(1.5, 0.01)
|
|
53
|
+
|
|
54
|
+
``miepython.vsh`` holds the vector spherical harmonics as a direct transcription of
|
|
55
|
+
the textbook formulas. It is a reference for development and cross-checking rather
|
|
56
|
+
than a fast path, it requires the optional ``scipy`` dependency, and so it is bound
|
|
57
|
+
only when scipy is installed::
|
|
58
|
+
|
|
59
|
+
mie.vsh.M_odd(1, k, d_sphere, r, theta, phi)
|
|
60
|
+
|
|
61
|
+
``miepython.monte_carlo`` samples scattering angles from the Mie phase function. It
|
|
62
|
+
imports this package, so importing it from here would be circular; ask for it
|
|
63
|
+
explicitly instead::
|
|
64
|
+
|
|
65
|
+
import miepython.monte_carlo as mc
|
|
66
|
+
mu, cdf = mc.mu_with_uniform_cdf(m, x, 100)
|
|
67
|
+
|
|
45
68
|
Near-field calculations are provided by the ``miepython.field`` module::
|
|
46
69
|
|
|
47
70
|
from miepython.field import e_near, h_near, eh_near
|
|
@@ -68,6 +91,8 @@ from ._backend import single_sphere, small_conducting_sphere, small_sphere
|
|
|
68
91
|
from .core import efficiencies, intensities, i_par, i_per, i_unpolarized
|
|
69
92
|
from .core import efficiencies_mx, S1_S2, phase_matrix, coefficients
|
|
70
93
|
|
|
94
|
+
from . import rayleigh
|
|
95
|
+
|
|
71
96
|
_FIELD_IMPORT_ERROR = None
|
|
72
97
|
|
|
73
98
|
|
|
@@ -93,10 +118,14 @@ def _missing_field_function(name):
|
|
|
93
118
|
|
|
94
119
|
try:
|
|
95
120
|
from . import field as _field
|
|
121
|
+
from . import vsh # noqa: F401 reachable as miepython.vsh; needs scipy, so guarded here
|
|
96
122
|
except ModuleNotFoundError as exc:
|
|
97
123
|
missing_module = str(getattr(exc, "name", "") or "")
|
|
98
124
|
if missing_module.startswith("scipy"):
|
|
99
125
|
_FIELD_IMPORT_ERROR = exc
|
|
126
|
+
# ``miepython.vsh`` is left unbound rather than stubbed out. It is a
|
|
127
|
+
# reference implementation used for development and cross-checking, not part
|
|
128
|
+
# of the public surface, and every one of its functions needs scipy.
|
|
100
129
|
e_far = _missing_field_function("e_far")
|
|
101
130
|
e_near = _missing_field_function("e_near")
|
|
102
131
|
h_near = _missing_field_function("h_near")
|
|
@@ -115,8 +144,10 @@ else:
|
|
|
115
144
|
h_near_cartesian = _field.h_near_cartesian
|
|
116
145
|
eh_near_cartesian = _field.eh_near_cartesian
|
|
117
146
|
|
|
118
|
-
#
|
|
147
|
+
# Names exposed to the user. ``vsh`` is deliberately absent: it is bound only when
|
|
148
|
+
# scipy is installed, and a star-import must not depend on that.
|
|
119
149
|
__all__ = (
|
|
150
|
+
"rayleigh",
|
|
120
151
|
"intensities",
|
|
121
152
|
"i_par",
|
|
122
153
|
"i_per",
|
|
@@ -147,7 +178,7 @@ __all__ = (
|
|
|
147
178
|
"_D_downwards",
|
|
148
179
|
)
|
|
149
180
|
|
|
150
|
-
__version__ = "3.
|
|
181
|
+
__version__ = "3.3.0"
|
|
151
182
|
__author__ = "Scott Prahl"
|
|
152
183
|
__email__ = "scott.prahl@oit.edu"
|
|
153
184
|
__copyright__ = "2017-2026, Scott Prahl"
|
miepython/core.py
CHANGED
|
@@ -47,9 +47,28 @@ __all__ = (
|
|
|
47
47
|
"S1_S2",
|
|
48
48
|
"phase_matrix",
|
|
49
49
|
"coefficients",
|
|
50
|
+
"wiscombe_terms",
|
|
51
|
+
"normalization_factor",
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
def wiscombe_terms(x):
|
|
56
|
+
"""
|
|
57
|
+
Return the number of Mie series terms from Wiscombe's criterion.
|
|
58
|
+
|
|
59
|
+
The series is truncated after this many orders so that the summed scattering
|
|
60
|
+
quantities are accurate to about 1e-6. The low-level ``an_bn`` and ``cn_dn``
|
|
61
|
+
kernels repeat this expression because numba cannot call back into Python.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
x: the size parameter of the sphere
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
number of terms, so orders 1 through the returned value
|
|
68
|
+
"""
|
|
69
|
+
return int(x + 4.05 * x**0.33333 + 2.0)
|
|
70
|
+
|
|
71
|
+
|
|
53
72
|
def coefficients(m, x, n_pole=0, internal=False):
|
|
54
73
|
"""
|
|
55
74
|
Computes the Mie coefficients for a sphere.
|
|
@@ -73,30 +92,36 @@ def coefficients(m, x, n_pole=0, internal=False):
|
|
|
73
92
|
calculate fields inside the sphere.
|
|
74
93
|
|
|
75
94
|
Returns:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
ndarray: ``[a, b]``, or ``[a, b, c, d]`` when ``internal`` is True. For
|
|
96
|
+
scalar ``m`` and ``x`` each entry is a 1-D array of coefficients, so
|
|
97
|
+
the result has shape ``(2, n_terms)`` or ``(4, n_terms)``. For array
|
|
98
|
+
input each entry gains a leading axis over the spheres, giving
|
|
99
|
+
``(2, n_spheres, n_pole)`` or ``(4, n_spheres, n_pole)``.
|
|
79
100
|
|
|
80
101
|
Notes:
|
|
81
102
|
- If the imaginary part of the refractive index is positive, it is
|
|
82
103
|
automatically corrected to its conjugate value to ensure a valid input.
|
|
104
|
+
- Every returned coefficient is a real term; the arrays carry no padding.
|
|
83
105
|
|
|
84
106
|
Examples:
|
|
85
107
|
Compute coefficients for a single sphere:
|
|
86
|
-
|
|
87
|
-
>>>
|
|
88
|
-
>>>
|
|
89
|
-
(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
3.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
(
|
|
99
|
-
|
|
108
|
+
|
|
109
|
+
>>> import miepython as mie
|
|
110
|
+
>>> a, b = mie.coefficients(1.5 - 0.1j, 0.1)
|
|
111
|
+
>>> len(a), len(b)
|
|
112
|
+
(3, 3)
|
|
113
|
+
>>> print(f"{a[0]:.6e}")
|
|
114
|
+
3.333700e-05-1.973631e-04j
|
|
115
|
+
>>> print(f"{b[0]:.6e}")
|
|
116
|
+
6.672963e-08-2.754694e-07j
|
|
117
|
+
|
|
118
|
+
Keep only the dipole term for each of several spheres:
|
|
119
|
+
|
|
120
|
+
>>> a, b = mie.coefficients([1.5 - 0.1j, 1.4 - 0.05j], [2.0, 1.8], n_pole=1)
|
|
121
|
+
>>> a.shape
|
|
122
|
+
(2, 1)
|
|
123
|
+
>>> print(f"{a[0, 0]:.6f}")
|
|
124
|
+
0.447946-0.386652j
|
|
100
125
|
"""
|
|
101
126
|
mlen = np.size(m) if np.ndim(m) > 0 else 0
|
|
102
127
|
xlen = np.size(x) if np.ndim(x) > 0 else 0
|
|
@@ -155,8 +180,11 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
155
180
|
- If 0 (default), computes contributions from all multipoles.
|
|
156
181
|
- If non-zero, computes contributions from the specified multipole order.
|
|
157
182
|
e_field (boolean):
|
|
158
|
-
|
|
159
|
-
multipole
|
|
183
|
+
When n_pole>0, selects which multipole of that order contributes:
|
|
184
|
+
True gives the electric multipole a_n, False the magnetic
|
|
185
|
+
multipole b_n. The two add up to the full order-n_pole
|
|
186
|
+
contribution for qext and qsca. Ignored when n_pole is 0.
|
|
187
|
+
|
|
160
188
|
Returns:
|
|
161
189
|
tuple:
|
|
162
190
|
qext (float or array-like):
|
|
@@ -170,7 +198,8 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
170
198
|
scattered in the exact backward direction (theta = 180 degrees).
|
|
171
199
|
g (float or array-like):
|
|
172
200
|
Asymmetry parameter, representing the average cosine of the scattering
|
|
173
|
-
angle over all angles.
|
|
201
|
+
angle over all angles. This is exactly zero when n_pole > 0, because a
|
|
202
|
+
single multipole of one parity scatters symmetrically about 90 degrees.
|
|
174
203
|
|
|
175
204
|
Notes:
|
|
176
205
|
- Ensure m and x have compatible dimensions if passed as arrays.
|
|
@@ -180,19 +209,24 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
180
209
|
non-absorbing medium.
|
|
181
210
|
|
|
182
211
|
Examples:
|
|
183
|
-
Compute efficiencies for
|
|
212
|
+
Compute efficiencies for one sphere:
|
|
184
213
|
|
|
185
214
|
>>> import miepython as mie
|
|
186
|
-
>>> mie.
|
|
187
|
-
(qext
|
|
215
|
+
>>> qext, qsca, qback, g = mie.efficiencies_mx(1.5 - 0.01j, 2.0)
|
|
216
|
+
>>> print(f"{qext:.6f} {qsca:.6f} {qback:.6f} {g:.6f}")
|
|
217
|
+
1.812597 1.724396 0.266214 0.630214
|
|
188
218
|
|
|
189
|
-
Compute efficiencies for wavelength-dependent refractive
|
|
219
|
+
Compute efficiencies for a wavelength-dependent refractive index:
|
|
190
220
|
|
|
191
|
-
>>> import
|
|
221
|
+
>>> import numpy as np
|
|
192
222
|
>>> m_array = np.array([1.5 - 0.01j, 1.45 - 0.02j])
|
|
193
223
|
>>> x_array = np.array([2.0, 2.5])
|
|
194
|
-
>>> mie.
|
|
195
|
-
(qext
|
|
224
|
+
>>> qext, qsca, qback, g = mie.efficiencies_mx(m_array, x_array)
|
|
225
|
+
>>> np.round(qext, 6)
|
|
226
|
+
array([1.812597, 2.14972 ])
|
|
227
|
+
|
|
228
|
+
Use :func:`efficiencies` instead when the diameter and wavelength are
|
|
229
|
+
known rather than the size parameter.
|
|
196
230
|
"""
|
|
197
231
|
# ensure imaginary part of refractive index is negative
|
|
198
232
|
if np.isscalar(m):
|
|
@@ -234,7 +268,12 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
234
268
|
return qext, qsca, qback, g
|
|
235
269
|
|
|
236
270
|
|
|
237
|
-
def
|
|
271
|
+
def _mie_efficiencies(m, x):
|
|
272
|
+
"""Return the full Mie efficiencies, the default source for normalization."""
|
|
273
|
+
return single_sphere(m, x, 0, True)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def normalization_factor(m, x, norm_str, efficiency_source=_mie_efficiencies):
|
|
238
277
|
"""
|
|
239
278
|
Figure out scattering function normalization.
|
|
240
279
|
|
|
@@ -242,6 +281,10 @@ def normalization_factor(m, x, norm_str):
|
|
|
242
281
|
m: complex index of refraction of sphere
|
|
243
282
|
x: dimensionless sphere size
|
|
244
283
|
norm_str: string describing type of normalization
|
|
284
|
+
efficiency_source: callable returning ``(qext, qsca, qback, g)`` for ``m``
|
|
285
|
+
and ``x``. Defaults to the full Mie solution; ``miepython.rayleigh``
|
|
286
|
+
passes its own so that the two share these rules rather than keeping
|
|
287
|
+
a second copy of them.
|
|
245
288
|
|
|
246
289
|
Returns:
|
|
247
290
|
scaling factor needed for scattering function
|
|
@@ -259,7 +302,16 @@ def normalization_factor(m, x, norm_str):
|
|
|
259
302
|
factor = x * np.sqrt(np.pi)
|
|
260
303
|
|
|
261
304
|
else:
|
|
262
|
-
qext, qsca, _, _ =
|
|
305
|
+
qext, qsca, _, _ = efficiency_source(m, x)
|
|
306
|
+
|
|
307
|
+
# every remaining choice divides by qext or qsca
|
|
308
|
+
if qext <= 0 or qsca <= 0:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
"normalization %r needs a sphere that scatters, but qext=%g and qsca=%g. "
|
|
311
|
+
"A sphere of zero size, or one whose index matches its surroundings, has "
|
|
312
|
+
"no scattering to normalize against. Use norm='wiscombe', 'bohren' or "
|
|
313
|
+
"'qsca' if an unnormalized amplitude is what you want." % (norm_str, qext, qsca)
|
|
314
|
+
)
|
|
263
315
|
|
|
264
316
|
if norm in ["a", "albedo"]:
|
|
265
317
|
factor = x * np.sqrt(np.pi * qext)
|
|
@@ -297,9 +349,15 @@ def S1_S2(m, x, mu, norm="albedo", n_pole=0):
|
|
|
297
349
|
Args:
|
|
298
350
|
m: the complex index of refraction of the sphere
|
|
299
351
|
x: the size parameter of the sphere
|
|
300
|
-
mu: the angles, cos(theta), to calculate scattering amplitudes
|
|
352
|
+
mu: the angles, cos(theta), to calculate scattering amplitudes. A scalar,
|
|
353
|
+
list, tuple or array is accepted; a scalar is treated as one angle,
|
|
354
|
+
so the returned amplitudes are still length-one arrays.
|
|
301
355
|
norm: (optional) string describing scattering function normalization
|
|
302
|
-
n_pole:
|
|
356
|
+
n_pole: isolate a single multipole order, 1=dipole, 2=quadrupole,
|
|
357
|
+
3=octupole (default=0 means include all terms). Summing over
|
|
358
|
+
every order reproduces the full series, so n_pole uses the same
|
|
359
|
+
convention as `efficiencies_mx`. Orders beyond the truncated
|
|
360
|
+
series raise a ValueError.
|
|
303
361
|
|
|
304
362
|
Returns:
|
|
305
363
|
S1, S2: the scattering amplitudes at each angle mu [sr**(-0.5)]
|
|
@@ -307,11 +365,11 @@ def S1_S2(m, x, mu, norm="albedo", n_pole=0):
|
|
|
307
365
|
if np.imag(m) > 0: # ensure imaginary part of refractive index is negative
|
|
308
366
|
m = np.conj(m)
|
|
309
367
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
368
|
+
# The numba kernel is declared for float64[:], so a list or an integer array
|
|
369
|
+
# would be rejected there while numpy accepted it. Coercing here keeps both
|
|
370
|
+
# backends callable with exactly the same arguments.
|
|
371
|
+
mu_array = np.atleast_1d(np.asarray(mu, dtype=float))
|
|
372
|
+
S1, S2 = _S1_S2(m, x, mu_array, n_pole)
|
|
315
373
|
|
|
316
374
|
normalization = normalization_factor(m, x, norm)
|
|
317
375
|
|
|
@@ -345,7 +403,9 @@ def phase_matrix(m, x, mu, norm="albedo", n_pole=0):
|
|
|
345
403
|
Args:
|
|
346
404
|
m: the complex index of refraction of the sphere
|
|
347
405
|
x: the size parameter of the sphere
|
|
348
|
-
mu: the angles, cos(theta), of the phase scattering matrix
|
|
406
|
+
mu: the angles, cos(theta), of the phase scattering matrix. Unlike the
|
|
407
|
+
other angular functions this one squeezes a single angle away, so a
|
|
408
|
+
scalar gives a bare 4x4 matrix rather than 4x4x1.
|
|
349
409
|
n_pole: return n_pole term from series (default=0 means include all terms)
|
|
350
410
|
norm: (optional) string describing scattering function normalization
|
|
351
411
|
|
|
@@ -390,7 +450,9 @@ def i_per(m, x, mu, norm="albedo", n_pole=0):
|
|
|
390
450
|
Args:
|
|
391
451
|
m (complex): Complex index of refraction of the sphere.
|
|
392
452
|
x (float): Size parameter of the sphere.
|
|
393
|
-
mu (array-like): Cosine of the scattering angle(s), cos(θ), for which
|
|
453
|
+
mu (array-like): Cosine of the scattering angle(s), cos(θ), for which
|
|
454
|
+
intensity is desired. A scalar counts as one angle and yields a
|
|
455
|
+
length-one array, not a bare number.
|
|
394
456
|
norm (str, optional): Normalization method for the scattered intensity. Default is 'albedo'.
|
|
395
457
|
n_pole (int, optional): If greater than zero, returns only the nth multipole term;
|
|
396
458
|
default is 0, which returns the sum of all terms.
|
|
@@ -419,7 +481,9 @@ def i_par(m, x, mu, norm="albedo", n_pole=0):
|
|
|
419
481
|
Args:
|
|
420
482
|
m (complex): Complex index of refraction of the sphere.
|
|
421
483
|
x (float): Size parameter of the sphere.
|
|
422
|
-
mu (array-like): Cosine of the scattering angle(s), cos(θ), for which
|
|
484
|
+
mu (array-like): Cosine of the scattering angle(s), cos(θ), for which
|
|
485
|
+
intensity is desired. A scalar counts as one angle and yields a
|
|
486
|
+
length-one array, not a bare number.
|
|
423
487
|
norm (str, optional): Normalization method for the scattered intensity. Default is 'albedo'.
|
|
424
488
|
n_pole (int, optional): If greater than zero, returns only the nth multipole term;
|
|
425
489
|
default is 0, which returns the sum of all terms.
|
|
@@ -449,7 +513,8 @@ def i_unpolarized(m, x, mu, norm="albedo", n_pole=0):
|
|
|
449
513
|
Args:
|
|
450
514
|
m: the complex index of refraction of the sphere
|
|
451
515
|
x: the size parameter
|
|
452
|
-
mu: the cos(theta) of each direction desired
|
|
516
|
+
mu: the cos(theta) of each direction desired. A scalar counts as one
|
|
517
|
+
direction and yields a length-one array, not a bare number.
|
|
453
518
|
norm: (optional) string describing scattering function normalization
|
|
454
519
|
n_pole (int, optional): If greater than zero, returns only the nth multipole term;
|
|
455
520
|
default is 0, which returns the sum of all terms.
|