miepython 3.1.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 +85 -42
- miepython/_backend.py +49 -0
- miepython/core.py +106 -42
- miepython/field.py +241 -295
- 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 +17 -8
- {miepython-3.1.0.dist-info → miepython-3.3.0.dist-info}/METADATA +16 -13
- miepython-3.3.0.dist-info/RECORD +18 -0
- {miepython-3.1.0.dist-info → miepython-3.3.0.dist-info}/WHEEL +1 -1
- miepython-3.1.0.dist-info/RECORD +0 -17
- {miepython-3.1.0.dist-info → miepython-3.3.0.dist-info}/licenses/LICENSE.txt +0 -0
- {miepython-3.1.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
|
|
@@ -62,50 +85,69 @@ Example near-field usage::
|
|
|
62
85
|
|
|
63
86
|
"""
|
|
64
87
|
|
|
65
|
-
import
|
|
66
|
-
|
|
67
|
-
USE_JIT = os.environ.get("MIEPYTHON_USE_JIT", "0") == "1"
|
|
68
|
-
|
|
69
|
-
if USE_JIT:
|
|
70
|
-
# pull in the jit‐compiled backends under the "private" names core.py expects
|
|
71
|
-
from .mie_jit import _an_bn_nb as an_bn
|
|
72
|
-
from .mie_jit import _cn_dn_nb as cn_dn
|
|
73
|
-
from .mie_jit import _single_sphere_nb as single_sphere
|
|
74
|
-
from .mie_jit import _small_sphere_nb as small_sphere
|
|
75
|
-
from .mie_jit import _small_conducting_sphere_nb as small_conducting_sphere
|
|
76
|
-
from .mie_jit import _pi_tau_nb as _pi_tau
|
|
77
|
-
from .mie_jit import _D_calc_nb as _D_calc
|
|
78
|
-
from .mie_jit import _S1_S2_nb as _S1_S2
|
|
79
|
-
from .mie_jit import _Lentz_Dn
|
|
80
|
-
from .mie_jit import _D_upwards
|
|
81
|
-
from .mie_jit import _D_downwards
|
|
82
|
-
else:
|
|
83
|
-
from .mie_nojit import _an_bn_py as an_bn
|
|
84
|
-
from .mie_nojit import _cn_dn_py as cn_dn
|
|
85
|
-
from .mie_nojit import _single_sphere_py as single_sphere
|
|
86
|
-
from .mie_nojit import _small_sphere_py as small_sphere
|
|
87
|
-
from .mie_nojit import _small_conducting_sphere_py as small_conducting_sphere
|
|
88
|
-
from .mie_nojit import _pi_tau_py as _pi_tau
|
|
89
|
-
from .mie_nojit import _D_calc_py as _D_calc
|
|
90
|
-
from .mie_nojit import _S1_S2_py as _S1_S2
|
|
91
|
-
from .mie_nojit import _Lentz_Dn
|
|
92
|
-
from .mie_nojit import _D_upwards
|
|
93
|
-
from .mie_nojit import _D_downwards
|
|
88
|
+
from ._backend import D_calc, USE_JIT, _D_downwards, _D_upwards, _Lentz_Dn, _S1_S2, an_bn, cn_dn, pi_tau
|
|
89
|
+
from ._backend import single_sphere, small_conducting_sphere, small_sphere
|
|
94
90
|
|
|
95
91
|
from .core import efficiencies, intensities, i_par, i_per, i_unpolarized
|
|
96
92
|
from .core import efficiencies_mx, S1_S2, phase_matrix, coefficients
|
|
97
|
-
from .field import (
|
|
98
|
-
e_far,
|
|
99
|
-
e_near,
|
|
100
|
-
h_near,
|
|
101
|
-
eh_near,
|
|
102
|
-
e_near_cartesian,
|
|
103
|
-
h_near_cartesian,
|
|
104
|
-
eh_near_cartesian,
|
|
105
|
-
)
|
|
106
93
|
|
|
107
|
-
|
|
94
|
+
from . import rayleigh
|
|
95
|
+
|
|
96
|
+
_FIELD_IMPORT_ERROR = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _raise_field_import_error():
|
|
100
|
+
"""Raise a consistent error when optional near-field dependencies are missing."""
|
|
101
|
+
raise ModuleNotFoundError(
|
|
102
|
+
"Near-field functions require the optional dependency 'scipy'. "
|
|
103
|
+
"Install miepython with SciPy support to use e_far/e_near/h_near/eh_near."
|
|
104
|
+
) from _FIELD_IMPORT_ERROR
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _missing_field_function(name):
|
|
108
|
+
"""Create a placeholder near-field function when SciPy is unavailable."""
|
|
109
|
+
|
|
110
|
+
def _missing(*_args, **_kwargs):
|
|
111
|
+
_raise_field_import_error()
|
|
112
|
+
|
|
113
|
+
_missing.__name__ = name
|
|
114
|
+
_missing.__qualname__ = name
|
|
115
|
+
_missing.__doc__ = f"{name} requires the optional dependency 'scipy'."
|
|
116
|
+
return _missing
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
from . import field as _field
|
|
121
|
+
from . import vsh # noqa: F401 reachable as miepython.vsh; needs scipy, so guarded here
|
|
122
|
+
except ModuleNotFoundError as exc:
|
|
123
|
+
missing_module = str(getattr(exc, "name", "") or "")
|
|
124
|
+
if missing_module.startswith("scipy"):
|
|
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.
|
|
129
|
+
e_far = _missing_field_function("e_far")
|
|
130
|
+
e_near = _missing_field_function("e_near")
|
|
131
|
+
h_near = _missing_field_function("h_near")
|
|
132
|
+
eh_near = _missing_field_function("eh_near")
|
|
133
|
+
e_near_cartesian = _missing_field_function("e_near_cartesian")
|
|
134
|
+
h_near_cartesian = _missing_field_function("h_near_cartesian")
|
|
135
|
+
eh_near_cartesian = _missing_field_function("eh_near_cartesian")
|
|
136
|
+
else:
|
|
137
|
+
raise
|
|
138
|
+
else:
|
|
139
|
+
e_far = _field.e_far
|
|
140
|
+
e_near = _field.e_near
|
|
141
|
+
h_near = _field.h_near
|
|
142
|
+
eh_near = _field.eh_near
|
|
143
|
+
e_near_cartesian = _field.e_near_cartesian
|
|
144
|
+
h_near_cartesian = _field.h_near_cartesian
|
|
145
|
+
eh_near_cartesian = _field.eh_near_cartesian
|
|
146
|
+
|
|
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.
|
|
108
149
|
__all__ = (
|
|
150
|
+
"rayleigh",
|
|
109
151
|
"intensities",
|
|
110
152
|
"i_par",
|
|
111
153
|
"i_per",
|
|
@@ -127,15 +169,16 @@ __all__ = (
|
|
|
127
169
|
"single_sphere",
|
|
128
170
|
"small_sphere",
|
|
129
171
|
"small_conducting_sphere",
|
|
172
|
+
"USE_JIT",
|
|
173
|
+
"D_calc",
|
|
174
|
+
"pi_tau",
|
|
130
175
|
"_S1_S2",
|
|
131
|
-
"_D_calc",
|
|
132
|
-
"_pi_tau",
|
|
133
176
|
"_Lentz_Dn",
|
|
134
177
|
"_D_upwards",
|
|
135
178
|
"_D_downwards",
|
|
136
179
|
)
|
|
137
180
|
|
|
138
|
-
__version__ = "3.
|
|
181
|
+
__version__ = "3.3.0"
|
|
139
182
|
__author__ = "Scott Prahl"
|
|
140
183
|
__email__ = "scott.prahl@oit.edu"
|
|
141
184
|
__copyright__ = "2017-2026, Scott Prahl"
|
miepython/_backend.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Select and expose the active low-level backend implementation.
|
|
2
|
+
|
|
3
|
+
This module centralizes JIT vs no-JIT backend selection so other modules can
|
|
4
|
+
import backend callables without importing ``miepython`` package root.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
USE_JIT = os.environ.get("MIEPYTHON_USE_JIT", "0") == "1"
|
|
10
|
+
|
|
11
|
+
if USE_JIT:
|
|
12
|
+
from .mie_jit import _D_calc_nb as D_calc
|
|
13
|
+
from .mie_jit import _D_downwards
|
|
14
|
+
from .mie_jit import _D_upwards
|
|
15
|
+
from .mie_jit import _Lentz_Dn
|
|
16
|
+
from .mie_jit import _S1_S2_nb as _S1_S2
|
|
17
|
+
from .mie_jit import _an_bn_nb as an_bn
|
|
18
|
+
from .mie_jit import _cn_dn_nb as cn_dn
|
|
19
|
+
from .mie_jit import _pi_tau_nb as pi_tau
|
|
20
|
+
from .mie_jit import _single_sphere_nb as single_sphere
|
|
21
|
+
from .mie_jit import _small_conducting_sphere_nb as small_conducting_sphere
|
|
22
|
+
from .mie_jit import _small_sphere_nb as small_sphere
|
|
23
|
+
else:
|
|
24
|
+
from .mie_nojit import _D_calc_py as D_calc
|
|
25
|
+
from .mie_nojit import _D_downwards
|
|
26
|
+
from .mie_nojit import _D_upwards
|
|
27
|
+
from .mie_nojit import _Lentz_Dn
|
|
28
|
+
from .mie_nojit import _S1_S2_py as _S1_S2
|
|
29
|
+
from .mie_nojit import _an_bn_py as an_bn
|
|
30
|
+
from .mie_nojit import _cn_dn_py as cn_dn
|
|
31
|
+
from .mie_nojit import _pi_tau_py as pi_tau
|
|
32
|
+
from .mie_nojit import _single_sphere_py as single_sphere
|
|
33
|
+
from .mie_nojit import _small_conducting_sphere_py as small_conducting_sphere
|
|
34
|
+
from .mie_nojit import _small_sphere_py as small_sphere
|
|
35
|
+
|
|
36
|
+
__all__ = (
|
|
37
|
+
"USE_JIT",
|
|
38
|
+
"D_calc",
|
|
39
|
+
"an_bn",
|
|
40
|
+
"cn_dn",
|
|
41
|
+
"pi_tau",
|
|
42
|
+
"single_sphere",
|
|
43
|
+
"small_sphere",
|
|
44
|
+
"small_conducting_sphere",
|
|
45
|
+
"_S1_S2",
|
|
46
|
+
"_Lentz_Dn",
|
|
47
|
+
"_D_upwards",
|
|
48
|
+
"_D_downwards",
|
|
49
|
+
)
|
miepython/core.py
CHANGED
|
@@ -34,8 +34,7 @@ Normalized Mie scattering intensities for angles mu=cos(theta)::
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
import numpy as np
|
|
37
|
-
from
|
|
38
|
-
|
|
37
|
+
from ._backend import _S1_S2, an_bn, cn_dn, single_sphere
|
|
39
38
|
|
|
40
39
|
# not really needed but included for clarity
|
|
41
40
|
__all__ = (
|
|
@@ -48,9 +47,28 @@ __all__ = (
|
|
|
48
47
|
"S1_S2",
|
|
49
48
|
"phase_matrix",
|
|
50
49
|
"coefficients",
|
|
50
|
+
"wiscombe_terms",
|
|
51
|
+
"normalization_factor",
|
|
51
52
|
)
|
|
52
53
|
|
|
53
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
|
+
|
|
54
72
|
def coefficients(m, x, n_pole=0, internal=False):
|
|
55
73
|
"""
|
|
56
74
|
Computes the Mie coefficients for a sphere.
|
|
@@ -74,30 +92,36 @@ def coefficients(m, x, n_pole=0, internal=False):
|
|
|
74
92
|
calculate fields inside the sphere.
|
|
75
93
|
|
|
76
94
|
Returns:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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)``.
|
|
80
100
|
|
|
81
101
|
Notes:
|
|
82
102
|
- If the imaginary part of the refractive index is positive, it is
|
|
83
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.
|
|
84
105
|
|
|
85
106
|
Examples:
|
|
86
107
|
Compute coefficients for a single sphere:
|
|
87
|
-
|
|
88
|
-
>>>
|
|
89
|
-
>>>
|
|
90
|
-
(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
3.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
(
|
|
100
|
-
|
|
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
|
|
101
125
|
"""
|
|
102
126
|
mlen = np.size(m) if np.ndim(m) > 0 else 0
|
|
103
127
|
xlen = np.size(x) if np.ndim(x) > 0 else 0
|
|
@@ -156,8 +180,11 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
156
180
|
- If 0 (default), computes contributions from all multipoles.
|
|
157
181
|
- If non-zero, computes contributions from the specified multipole order.
|
|
158
182
|
e_field (boolean):
|
|
159
|
-
|
|
160
|
-
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
|
+
|
|
161
188
|
Returns:
|
|
162
189
|
tuple:
|
|
163
190
|
qext (float or array-like):
|
|
@@ -171,7 +198,8 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
171
198
|
scattered in the exact backward direction (theta = 180 degrees).
|
|
172
199
|
g (float or array-like):
|
|
173
200
|
Asymmetry parameter, representing the average cosine of the scattering
|
|
174
|
-
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.
|
|
175
203
|
|
|
176
204
|
Notes:
|
|
177
205
|
- Ensure m and x have compatible dimensions if passed as arrays.
|
|
@@ -181,19 +209,24 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
181
209
|
non-absorbing medium.
|
|
182
210
|
|
|
183
211
|
Examples:
|
|
184
|
-
Compute efficiencies for
|
|
212
|
+
Compute efficiencies for one sphere:
|
|
185
213
|
|
|
186
214
|
>>> import miepython as mie
|
|
187
|
-
>>> mie.
|
|
188
|
-
(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
|
|
189
218
|
|
|
190
|
-
Compute efficiencies for wavelength-dependent refractive
|
|
219
|
+
Compute efficiencies for a wavelength-dependent refractive index:
|
|
191
220
|
|
|
192
|
-
>>> import
|
|
221
|
+
>>> import numpy as np
|
|
193
222
|
>>> m_array = np.array([1.5 - 0.01j, 1.45 - 0.02j])
|
|
194
223
|
>>> x_array = np.array([2.0, 2.5])
|
|
195
|
-
>>> mie.
|
|
196
|
-
(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.
|
|
197
230
|
"""
|
|
198
231
|
# ensure imaginary part of refractive index is negative
|
|
199
232
|
if np.isscalar(m):
|
|
@@ -235,7 +268,12 @@ def efficiencies_mx(m, x, n_pole=0, e_field=True):
|
|
|
235
268
|
return qext, qsca, qback, g
|
|
236
269
|
|
|
237
270
|
|
|
238
|
-
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):
|
|
239
277
|
"""
|
|
240
278
|
Figure out scattering function normalization.
|
|
241
279
|
|
|
@@ -243,6 +281,10 @@ def normalization_factor(m, x, norm_str):
|
|
|
243
281
|
m: complex index of refraction of sphere
|
|
244
282
|
x: dimensionless sphere size
|
|
245
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.
|
|
246
288
|
|
|
247
289
|
Returns:
|
|
248
290
|
scaling factor needed for scattering function
|
|
@@ -260,7 +302,16 @@ def normalization_factor(m, x, norm_str):
|
|
|
260
302
|
factor = x * np.sqrt(np.pi)
|
|
261
303
|
|
|
262
304
|
else:
|
|
263
|
-
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
|
+
)
|
|
264
315
|
|
|
265
316
|
if norm in ["a", "albedo"]:
|
|
266
317
|
factor = x * np.sqrt(np.pi * qext)
|
|
@@ -298,9 +349,15 @@ def S1_S2(m, x, mu, norm="albedo", n_pole=0):
|
|
|
298
349
|
Args:
|
|
299
350
|
m: the complex index of refraction of the sphere
|
|
300
351
|
x: the size parameter of the sphere
|
|
301
|
-
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.
|
|
302
355
|
norm: (optional) string describing scattering function normalization
|
|
303
|
-
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.
|
|
304
361
|
|
|
305
362
|
Returns:
|
|
306
363
|
S1, S2: the scattering amplitudes at each angle mu [sr**(-0.5)]
|
|
@@ -308,11 +365,11 @@ def S1_S2(m, x, mu, norm="albedo", n_pole=0):
|
|
|
308
365
|
if np.imag(m) > 0: # ensure imaginary part of refractive index is negative
|
|
309
366
|
m = np.conj(m)
|
|
310
367
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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)
|
|
316
373
|
|
|
317
374
|
normalization = normalization_factor(m, x, norm)
|
|
318
375
|
|
|
@@ -346,7 +403,9 @@ def phase_matrix(m, x, mu, norm="albedo", n_pole=0):
|
|
|
346
403
|
Args:
|
|
347
404
|
m: the complex index of refraction of the sphere
|
|
348
405
|
x: the size parameter of the sphere
|
|
349
|
-
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.
|
|
350
409
|
n_pole: return n_pole term from series (default=0 means include all terms)
|
|
351
410
|
norm: (optional) string describing scattering function normalization
|
|
352
411
|
|
|
@@ -391,7 +450,9 @@ def i_per(m, x, mu, norm="albedo", n_pole=0):
|
|
|
391
450
|
Args:
|
|
392
451
|
m (complex): Complex index of refraction of the sphere.
|
|
393
452
|
x (float): Size parameter of the sphere.
|
|
394
|
-
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.
|
|
395
456
|
norm (str, optional): Normalization method for the scattered intensity. Default is 'albedo'.
|
|
396
457
|
n_pole (int, optional): If greater than zero, returns only the nth multipole term;
|
|
397
458
|
default is 0, which returns the sum of all terms.
|
|
@@ -420,7 +481,9 @@ def i_par(m, x, mu, norm="albedo", n_pole=0):
|
|
|
420
481
|
Args:
|
|
421
482
|
m (complex): Complex index of refraction of the sphere.
|
|
422
483
|
x (float): Size parameter of the sphere.
|
|
423
|
-
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.
|
|
424
487
|
norm (str, optional): Normalization method for the scattered intensity. Default is 'albedo'.
|
|
425
488
|
n_pole (int, optional): If greater than zero, returns only the nth multipole term;
|
|
426
489
|
default is 0, which returns the sum of all terms.
|
|
@@ -450,7 +513,8 @@ def i_unpolarized(m, x, mu, norm="albedo", n_pole=0):
|
|
|
450
513
|
Args:
|
|
451
514
|
m: the complex index of refraction of the sphere
|
|
452
515
|
x: the size parameter
|
|
453
|
-
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.
|
|
454
518
|
norm: (optional) string describing scattering function normalization
|
|
455
519
|
n_pole (int, optional): If greater than zero, returns only the nth multipole term;
|
|
456
520
|
default is 0, which returns the sum of all terms.
|