miepython 3.2.0__tar.gz → 3.3.1__tar.gz

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.
Files changed (61) hide show
  1. miepython-3.3.1/CHANGELOG.rst +706 -0
  2. {miepython-3.2.0 → miepython-3.3.1}/CITATION.cff +3 -3
  3. miepython-3.3.1/MANIFEST.in +12 -0
  4. {miepython-3.2.0 → miepython-3.3.1}/PKG-INFO +8 -8
  5. {miepython-3.2.0 → miepython-3.3.1}/README.rst +4 -4
  6. {miepython-3.2.0 → miepython-3.3.1}/miepython/__init__.py +33 -2
  7. {miepython-3.2.0 → miepython-3.3.1}/miepython/core.py +172 -40
  8. {miepython-3.2.0 → miepython-3.3.1}/miepython/field.py +239 -293
  9. {miepython-3.2.0 → miepython-3.3.1}/miepython/mie_jit.py +202 -97
  10. {miepython-3.2.0 → miepython-3.3.1}/miepython/mie_nojit.py +166 -86
  11. miepython-3.3.1/miepython/monte_carlo.py +215 -0
  12. miepython-3.3.1/miepython/rayleigh.py +430 -0
  13. {miepython-3.2.0 → miepython-3.3.1}/miepython/util.py +25 -5
  14. {miepython-3.2.0 → miepython-3.3.1}/miepython/vsh.py +16 -7
  15. {miepython-3.2.0 → miepython-3.3.1}/miepython.egg-info/PKG-INFO +8 -8
  16. {miepython-3.2.0 → miepython-3.3.1}/miepython.egg-info/SOURCES.txt +16 -9
  17. {miepython-3.2.0 → miepython-3.3.1}/miepython.egg-info/requires.txt +1 -1
  18. {miepython-3.2.0 → miepython-3.3.1}/pyproject.toml +26 -5
  19. miepython-3.3.1/tests/benchmark_efficiencies.py +126 -0
  20. miepython-3.3.1/tests/conftest.py +93 -0
  21. miepython-3.3.1/tests/reference.py +239 -0
  22. miepython-3.2.0/tests/test_jit_D.py → miepython-3.3.1/tests/test_D.py +25 -24
  23. miepython-3.2.0/tests/test_jit_abcd.py → miepython-3.3.1/tests/test_abcd.py +11 -415
  24. miepython-3.3.1/tests/test_all_examples.py +23 -0
  25. {miepython-3.2.0 → miepython-3.3.1}/tests/test_all_notebooks.py +5 -4
  26. miepython-3.3.1/tests/test_backend_parity.py +214 -0
  27. miepython-3.3.1/tests/test_docstrings.py +76 -0
  28. {miepython-3.2.0 → miepython-3.3.1}/tests/test_field.py +244 -0
  29. miepython-3.3.1/tests/test_file_encodings.py +123 -0
  30. miepython-3.3.1/tests/test_kernels.py +469 -0
  31. miepython-3.3.1/tests/test_makefile.py +109 -0
  32. miepython-3.2.0/tests/test_jit.py → miepython-3.3.1/tests/test_mie.py +441 -7
  33. miepython-3.3.1/tests/test_monte_carlo.py +301 -0
  34. miepython-3.3.1/tests/test_optional_scipy.py +155 -0
  35. miepython-3.3.1/tests/test_package_surface.py +161 -0
  36. miepython-3.3.1/tests/test_rayleigh.py +392 -0
  37. miepython-3.3.1/tests/test_scattnlay_reference.py +169 -0
  38. miepython-3.3.1/tests/test_util.py +197 -0
  39. {miepython-3.2.0 → miepython-3.3.1}/tests/test_vsh.py +114 -4
  40. miepython-3.2.0/CHANGELOG.rst +0 -296
  41. miepython-3.2.0/MANIFEST.in +0 -8
  42. miepython-3.2.0/miepython/monte_carlo.py +0 -124
  43. miepython-3.2.0/miepython/rayleigh.py +0 -292
  44. miepython-3.2.0/tests/test_all_examples.py +0 -19
  45. miepython-3.2.0/tests/test_jit_speed.py +0 -45
  46. miepython-3.2.0/tests/test_mie_backend_speed.py +0 -86
  47. miepython-3.2.0/tests/test_nojit.py +0 -602
  48. miepython-3.2.0/tests/test_nojit_D.py +0 -247
  49. miepython-3.2.0/tests/test_nojit_abcd.py +0 -898
  50. miepython-3.2.0/tests/test_nojit_speed.py +0 -45
  51. miepython-3.2.0/tests/test_rayleigh.py +0 -83
  52. {miepython-3.2.0 → miepython-3.3.1}/LICENSE.txt +0 -0
  53. {miepython-3.2.0 → miepython-3.3.1}/miepython/_backend.py +0 -0
  54. {miepython-3.2.0 → miepython-3.3.1}/miepython/bessel.py +0 -0
  55. {miepython-3.2.0 → miepython-3.3.1}/miepython/data/Johnson.txt +0 -0
  56. {miepython-3.2.0 → miepython-3.3.1}/miepython/data/segelstein81_index.txt +0 -0
  57. {miepython-3.2.0 → miepython-3.3.1}/miepython.egg-info/dependency_links.txt +0 -0
  58. {miepython-3.2.0 → miepython-3.3.1}/miepython.egg-info/top_level.txt +0 -0
  59. {miepython-3.2.0 → miepython-3.3.1}/setup.cfg +0 -0
  60. {miepython-3.2.0 → miepython-3.3.1}/tests/test_bessel.py +0 -0
  61. {miepython-3.2.0 → miepython-3.3.1}/tests/test_unpolarized_intensity.py +0 -0
@@ -0,0 +1,706 @@
1
+ Changelog
2
+ =========
3
+
4
+ 3.3.1 (10/28/2026)
5
+ -------------------
6
+ * fix ``rayleigh.efficiencies_mx`` subtracting scattering from extinction. With
7
+ the m = n - ik convention the absorption term is negative, so
8
+ ``abs(absorption + qsca)`` gave absorption minus scattering, and qext for
9
+ m = 1.5 - 1e-6j at x = 0.01 came out 21% low (thanks @antonsoo). The x**4 term
10
+ now follows Bohren & Huffman's ``Re(ratio**2)`` rather than ``|ratio|**2``,
11
+ which agrees with Mie to 2e-5 instead of 6e-4 at m = 1.5 - 1j, x = 0.1. ``m``
12
+ is conjugated first, as ``core.py`` does. The Rayleigh ``qext`` also sets the
13
+ ``albedo`` and ``qext`` normalizations, so ``rayleigh.S1_S2`` and the
14
+ intensities change too
15
+ * handle perfectly conducting spheres in ``rayleigh``. As elsewhere in the
16
+ package, m.real == 0 now means a perfect conductor, which radiates a magnetic
17
+ dipole b1 = -a1/2 alongside its electric one: qext = qsca = 10/3 x**4,
18
+ qback = 9 x**4 and g = -2/5. The dielectric formulas were used before, so
19
+ m = -1j gave a qext about three times the Mie value; it now agrees to 2e-5 at
20
+ x = 0.01
21
+ * validate input to the ``rayleigh`` functions as ``core.py`` now does. A NaN or
22
+ infinite m or x, a negative x, or a negative real part of m raises
23
+ ``ValueError``; before, ``rayleigh.efficiencies_mx(nan, 0.05)`` returned NaN and
24
+ ``m = -1.5 - 0.1j`` or ``x = -0.05`` returned normal-looking efficiencies
25
+ * say why ``S1_S2`` and the functions built on it reject an array of spheres, in
26
+ both ``core`` and ``rayleigh``. They vectorize over mu and describe one sphere,
27
+ but an array m or x failed with numpy's "truth value of an array is ambiguous"
28
+ * return ``g`` from ``rayleigh.efficiencies_mx`` with the same shape and type as
29
+ the other efficiencies. It was the int 0 even for an array of spheres
30
+ * document the ``rayleigh`` module: what the approximation is, which expressions
31
+ go beyond the leading Rayleigh term, its accuracy against Mie, and tested
32
+ examples
33
+ * support Python 3.11 through 3.15. ``requires-python`` is now ``>=3.11``, 3.10 is
34
+ dropped from the classifiers, the black target versions and the CI matrix, and
35
+ 3.15 is added to all three
36
+ * raise ``ValueError`` for a refractive index or size parameter the kernels would
37
+ misread. The kernels treat any index that fails ``m.real > 0`` as a perfect
38
+ conductor, so ``m = nan`` or ``m = -1.5`` silently returned the answer for a metal
39
+ sphere, and an infinite m returned NaN. A NaN x raised "cannot convert float
40
+ NaN to integer" on the pure-Python backend while numba returned NaN, an infinite
41
+ x raised a different unhelpful error on each backend, and a negative x returned
42
+ zeros. ``coefficients``, ``efficiencies_mx``, ``S1_S2`` and the near fields, and
43
+ everything built on them, now reject a NaN or infinite m or x, a negative x, and
44
+ a negative real part of m, checking every entry of an array. ``m.real == 0``
45
+ still means a perfect conductor and x = 0 is still allowed
46
+ * fix ``monte_carlo.mu_with_uniform_cdf`` returning wrong angles for fine tables.
47
+ ``cdf`` began at I(-1)/num rather than zero, so a target below that value left
48
+ the inversion loop at index zero and interpolated against the last entry.
49
+ ``cdf`` now integrates the phase function with the trapezoidal rule, starting at
50
+ exactly 0 and ending at exactly 1 (it used to end slightly above 1), so tables
51
+ differ slightly from 3.3.0. ``cdf`` and ``mu_with_uniform_cdf`` also raise
52
+ ``ValueError`` when num is less than 2 (thanks @antonsoo)
53
+ * replace the inversion loop in ``monte_carlo.mu_with_uniform_cdf`` with
54
+ ``np.interp``
55
+ * warn when ``monte_carlo.cdf`` uses too few angles to resolve the phase function.
56
+ The raw integral should be one, so a ``RuntimeWarning`` is issued when it is off
57
+ by more than 1%; normalizing used to hide the error silently. A large sphere
58
+ needs roughly x**2 angles, because its forward peak is only about 8/x**2 wide in
59
+ cos(theta)
60
+ * size the table inside ``monte_carlo.mu_with_uniform_cdf`` from x, as
61
+ max(2000, 5*x**2) angles instead of a fixed 2000. At x = 100 the sampled mean
62
+ cosine was 0.930 against g = 0.868; it is now 0.869. Large spheres are slower
63
+ to tabulate: seconds at x = 100, about a minute at x = 300
64
+ * raise ``ValueError`` from ``monte_carlo.cdf`` and ``mu_with_uniform_cdf`` when the
65
+ phase function is not finite
66
+ * rewrite the ``monte_carlo`` docstrings, with tested examples of drawing random
67
+ scattering angles
68
+ * name the encoding on every text read and write. ``open(path)`` and
69
+ ``Path.read_text()`` decode with the locale's preferred encoding, which is UTF-8
70
+ on Linux and macOS but cp1252 on Windows, so ``tests/test_makefile.py`` passed
71
+ everywhere except the four Windows jobs, where it died on the emoji in the
72
+ Makefile's echo lines. Two more latent cases are fixed alongside it: the
73
+ scattnlay metadata read in ``tests/test_scattnlay_reference.py``, which survived
74
+ only because that JSON happens to be pure ASCII, and the ``write_text`` in
75
+ ``docs/data/generate_scattnlay_reference_fields.py``
76
+ * check for locale-dependent text I/O in the test suite, by walking the syntax trees
77
+ of every Python file in the repository and requiring an explicit ``encoding=``.
78
+ The failure is invisible on the platforms this package is developed on, so a
79
+ Windows-only break was waiting for whoever next added a non-ASCII character to a
80
+ file something reads
81
+
82
+ 3.3.0 (07/28/2026)
83
+ -------------------
84
+ * fix the docstring examples in ``core.py``, which had rotted unnoticed. The
85
+ ``coefficients`` examples called ``mie_coefficients``, a name that never
86
+ existed, showed the imaginary parts with the wrong sign, and still included the
87
+ padding element ``an_bn`` no longer returns; the ``efficiencies_mx`` examples
88
+ used a different function's signature and printed an invented repr. The
89
+ Returns section also described a tuple where an ndarray comes back
90
+ * run the docstring examples as part of the test suite, so they cannot rot again.
91
+ They are written with explicit formatting rather than bare array reprs, which
92
+ keeps them independent of NumPy's print precision
93
+ * keep one copy of the scattering-function normalization rules. ``core`` and
94
+ ``rayleigh`` held the same thirty lines differing only in which efficiencies
95
+ they consulted, so ``core.normalization_factor`` now takes an
96
+ ``efficiency_source`` and ``rayleigh`` passes its own
97
+ * accept an ``rng`` argument in ``generate_mie_costheta``. It drew from the
98
+ process-wide ``numpy.random``, which a caller could only control by reseeding
99
+ the whole program; passing ``numpy.random.default_rng(seed)`` now gives a
100
+ reproducible stream and leaves the global one alone. Omitting it behaves as
101
+ before
102
+ * document the angular-argument convention: a scalar ``mu`` counts as one angle
103
+ and returns a length-one array, except in ``phase_matrix``, which squeezes it
104
+ away. ``docs/01_basics.ipynb`` demonstrates the first half deliberately, so
105
+ both are now stated in the docstrings and pinned by tests
106
+ * fix ``make readme_images``, which had never worked: ``.PHONY`` declared that name
107
+ but the rule below it was written ``readme:``, so the advertised command failed
108
+ with "No rule to make target" while an undocumented ``make readme`` did the job
109
+ * drop ``sync`` from ``make help``. No such target exists; the one that syncs the
110
+ environment is ``venv``, and it was not listed at all. Also corrected
111
+ ``pylint-check``, described as "Same as lint above" when it runs pylint alone, and
112
+ moved the individual check targets out of the packaging section into the lint one
113
+ where they belong
114
+ * check the Makefile's help text against the rules it defines, so a target cannot be
115
+ advertised, or declared phony, without existing. These four bugs were all of the
116
+ same kind -- the help block is a pile of ``@echo`` lines with nothing tying it to
117
+ the rules underneath -- and nothing could have caught them
118
+ * generate the README figures reproducibly. matplotlib stamps each SVG with the
119
+ current time and names clip paths and glyphs from a random salt, so re-running
120
+ ``make readme_images`` produced files differing on a hundred lines even when the
121
+ figures were pixel-for-pixel identical. There was no way to tell "a plot changed"
122
+ from "matplotlib rolled different ids". Fixing ``svg.hashsalt`` and passing
123
+ ``metadata={"Date": None}`` makes repeated runs byte-identical, and the committed
124
+ images are regenerated once against that
125
+ * point the README license badge at ``blob/main`` rather than ``blob/master``. There
126
+ is no master branch; the link worked only because GitHub silently redirects
127
+ * evaluate the near fields for every point at once instead of one point at a time.
128
+ ``field.py`` walked the grid with ``np.ndindex`` and called into
129
+ ``scipy.special`` separately for each point, so a 41x41 slice spent 0.4 s almost
130
+ entirely in scipy's per-call overhead rather than on Bessel functions. The points
131
+ are now split once into those inside the sphere and those outside -- the two sides
132
+ need different radial functions and different media, but within a side every point
133
+ does the same arithmetic -- and each group is evaluated as a
134
+ ``(n_points, n_terms)`` batch. Roughly 18x faster: that slice takes 0.022 s, and
135
+ a 101x101 slice drops from 2.4 s to 0.13 s. Every returned value is bitwise
136
+ identical to before, which is checked point by point against evaluating the same
137
+ coordinates individually
138
+ * evaluate the spherical Hankel functions over orders ``0..n+1`` in one call rather
139
+ than making four separate calls that recompute shared orders. ``xi'_n`` needs
140
+ ``h1`` at ``n-1``, ``n`` and ``n+1``, and the order-``n`` value was being computed
141
+ twice; slicing one array instead cuts that part of the work to a quarter
142
+ * drop the three near-field per-point evaluators and the two loop drivers in favour
143
+ of a single batched routine, 63 fewer statements for the same physics.
144
+ ``_vsh_components_base`` now takes an explicit ``inside`` flag and arrays of
145
+ coordinates, so it no longer re-derives from ``d_sphere`` which side it is on
146
+ * make the submodules reachable, each in the way that suits it.
147
+ ``miepython.rayleigh`` needs only NumPy and is now imported with the package, so
148
+ ``import miepython`` is enough to reach it; ``miepython.vsh`` requires the
149
+ optional ``scipy`` and is imported inside the same guard as ``field``, staying
150
+ absent when scipy is not installed; ``miepython.monte_carlo`` imports
151
+ ``miepython`` itself, so importing it from the package would be circular and it
152
+ remains a separate ``import miepython.monte_carlo``. All three are now described
153
+ in the package docstring, and ``monte_carlo``'s own docstring shows the full
154
+ workflow and says why it is not imported for you
155
+ * have ``vsh`` reach ``D_calc`` through ``._backend`` instead of importing
156
+ ``miepython`` for it. The old import was a cycle that worked only because the
157
+ call is deferred to run time, and importing ``vsh`` from ``__init__`` would have
158
+ depended on that
159
+ * fix the call signatures listed in ``vsh``'s module docstring. All four vector
160
+ spherical harmonics were advertised as ``(n, k, d_sphere, r, theta, phi)``; they
161
+ actually take a wavelength rather than a wavenumber and a refractive index
162
+ besides, ``(n, lambda0, d_sphere, m_index, r, theta, phi)``, so anyone following
163
+ the docstring got a ``TypeError``. The four ``*_array`` variants went unmentioned
164
+ altogether. Signatures written out in prose are now checked against the code
165
+ * fix two stale references in the notebooks: ``mie.mie_S1_S2`` in
166
+ ``docs/03a_normalization.ipynb``, together with the comparison values it quotes,
167
+ and a commented-out ``mie._D_calc`` in ``docs/10_basic_tests.ipynb``
168
+ * fix off-by-one in ``n_pole`` for ``S1_S2`` and everything built on it
169
+ (``i_par``, ``i_per``, ``i_unpolarized``, ``phase_matrix``, ``intensities``).
170
+ ``n_pole=1`` returned the quadrupole instead of the dipole; it now matches
171
+ the convention already used by ``efficiencies_mx``
172
+ * raise ``ValueError`` for an ``n_pole`` beyond the truncated series instead of
173
+ returning zeros or an ``IndexError``
174
+ * add multipole regression tests (series sum, closed form, per-multipole
175
+ optical theorem) and re-execute ``docs/12_multipoles.ipynb``
176
+ * implement the ``e_field`` argument of ``efficiencies_mx``, which was accepted
177
+ and documented but silently ignored. With ``n_pole > 0`` it now selects the
178
+ electric multipole a_n (``e_field=True``, the default) or the magnetic
179
+ multipole b_n (``e_field=False``). **Behavior change:** ``n_pole > 0``
180
+ previously returned a_n and b_n combined; that total is now
181
+ ``e_field=True`` plus ``e_field=False``
182
+ * make the two backends agree on ``g`` when ``n_pole > 0``. The no-JIT backend
183
+ returned ``None`` (which became ``nan`` for array input) while the JIT
184
+ backend returned ``0``. Both now return ``0.0``, which is the exact value:
185
+ an isolated multipole of one parity scatters symmetrically about 90 degrees
186
+ * fix an operator-precedence bug in the perfectly-conducting guard of ``cn_dn``.
187
+ ``a and b or c`` made the test true for every finite index, so ``cn_dn(0j, x)``
188
+ raised ``ZeroDivisionError`` and an infinite index produced ``nan``. The
189
+ internal coefficients are now zero for a perfect conductor, as intended
190
+ * drop ``fastmath`` from the numba ``_cn_dn_nb`` kernel. It implies LLVM's
191
+ ``ninf``, which folded the ``np.isinf`` guard to ``False`` and defeated the
192
+ fix above in the JIT backend. This also improves JIT/no-JIT agreement
193
+ * make the internal-field coefficients ``c_n`` and ``d_n`` numerically stable.
194
+ ``psi_n`` came from the three-term upwards recurrence, which is contaminated
195
+ by the growing ``chi_n`` solution once n exceeds ``|mx|``. That is the normal
196
+ case for a relative index below one, where the coefficients were wrong by up
197
+ to 140%. ``psi_n`` now uses Miller's downwards recurrence seeded above ``|z|``
198
+ and normalised against whichever of ``psi_0`` or ``psi_1`` is larger, and
199
+ the logarithmic derivatives take the always-stable downwards route.
200
+ Worst error against a direct SciPy evaluation over 7320 cases drops from
201
+ 90 to 3e-9, and the JIT and no-JIT backends now agree to 5e-15 where they
202
+ previously differed by up to a factor of 39
203
+ * always use the downwards recurrence for the logarithmic derivative ``D_n``.
204
+ Wiscombe's criterion chose between the two recurrences from the refractive
205
+ index alone, never from the number of terms, so it picked the upwards
206
+ recurrence for small spheres where it is unstable. For a lossless sphere
207
+ ``Re(a_n)`` must equal ``|a_n|**2``; at m=1.05, x=0.1 the real parts of the
208
+ quadrupole and higher coefficients came out with the wrong sign and up to 12
209
+ orders of magnitude too large, which made ``efficiencies_mx(n_pole=3)``
210
+ report a negative extinction efficiency. That identity now holds to 4e-24
211
+ over all lossless cases tested, and ``qext`` and ``qsca`` agree to 5e-16
212
+ where they used to differ by 2e-7. The pure-python backend is about 20%
213
+ slower for a large sweep of size parameters; the numba backend is unchanged
214
+ * remove ``fastmath`` from every numba kernel. Besides defeating the
215
+ perfectly-conducting guard above, its reassociation degraded the exact
216
+ lossless identity by roughly six times relative to the pure-python backend.
217
+ Costs about 10% on a large sweep of size parameters
218
+ * ``an_bn`` no longer pads a zero onto the end of ``a`` and ``b``, and ``cn_dn``
219
+ no longer computes one order more than ``an_bn``. Both now return exactly
220
+ Wiscombe's number of terms with every entry a real coefficient, so the
221
+ internal and external series share one truncation. The retained ``a_n`` and
222
+ ``b_n`` are bit-for-bit unchanged, and the arrays are one element shorter
223
+ * ``an_bn(m, x)`` works on both backends. ``_an_bn_py`` defaulted ``n_pole`` to
224
+ zero but the numba kernel's eager signature could not, so the two-argument
225
+ call raised ``TypeError`` with the JIT on. ``_an_bn_nb`` is now compiled
226
+ lazily, which allows the default
227
+ * group the arithmetic in the numba ``_an_bn_nb`` and ``_cn_dn_nb`` kernels the
228
+ way the pure-python ones do. Multiplying by a precomputed ``1/x`` in one and
229
+ dividing by ``x`` in the other differed by one ulp, and the psi recurrence
230
+ amplified that to 2.6e-7 between the backends on significant coefficients.
231
+ They now agree to 1e-13
232
+ * ``an_bn`` builds ``psi_n`` with Miller's downwards recurrence, the same
233
+ treatment ``cn_dn`` already had, and builds ``xi_n`` as ``psi_n + i*chi_n``
234
+ from an upwards ``chi_n`` recurrence. The upwards ``psi_n`` recurrence was
235
+ accurate enough inside Wiscombe's truncation but lost all relative accuracy
236
+ beyond it -- about 1e-6 at the last kept order, and 1e10 or worse a few orders
237
+ later -- so asking for extra terms returned noise. Requesting more orders now
238
+ converges instead: the near-field boundary mismatch at W+8 terms improves from
239
+ 4.7e-2 to 3.1e-6, and it keeps improving to W+16 rather than diverging.
240
+ Building ``xi_n`` from the same ``psi_n`` preserves the lossless identity
241
+ ``Re(a_n) == |a_n|**2`` at 5.6e-16. Within the standard truncation nothing
242
+ moves by more than 1e-11, so this is headroom rather than a correction. The
243
+ pure-python backend costs about 7% more for a large sweep; numba is unchanged.
244
+ ``xi_n`` itself was already fine: it is dominated by the growing ``chi_n``
245
+ solution, for which the upwards recurrence is the stable direction, and it
246
+ matches SciPy to 1e-15 even twenty orders past the truncation
247
+ * assert the scattnlay comparison instead of only plotting it. The reference
248
+ arrays under ``docs/data`` were used solely by ``docs/15_2D_fields.ipynb``,
249
+ which printed relative errors without checking them, so a near-field
250
+ regression was invisible; the median error improved 19x during this release
251
+ and nothing would have noticed either that or its reversal.
252
+ ``tests/test_scattnlay_reference.py`` now checks the scalar efficiencies,
253
+ which are inlined like the existing MIEV0 values and need no data files, and
254
+ compares both 121x121 field grids point by point. Grid points that land
255
+ exactly on the sphere surface are excluded, since the radial E component is
256
+ discontinuous there and the two codes may put them on opposite sides
257
+ * ``docs/15_2D_fields.ipynb`` reads the reference arrays from ``docs/data``
258
+ rather than downloading them from the published branch, so ``make note-test``
259
+ works offline and checks the working tree instead of the last release. Its
260
+ stored error figures were also stale and have been refreshed
261
+ * collapse the per-backend test files. ``test_jit.py``/``test_nojit.py`` and
262
+ ``test_jit_abcd.py``/``test_nojit_abcd.py`` became ``test_mie.py`` and
263
+ ``test_abcd.py`` for the high-level API, which needs one copy because
264
+ ``core.py`` has no backend-specific code, plus ``test_kernels.py`` for the
265
+ kernel-level tests, which the ``kernels`` fixture runs against both backends
266
+ in one process. All 74 distinct test names are preserved, and the merge picks
267
+ up what had drifted between the two halves: an extra MIEV0 conducting case,
268
+ four extra ``qext`` assertions, and both spellings of a perfectly conducting
269
+ index. Shared reference implementations moved to ``tests/reference.py``
270
+ * drop the ``MIEPYTHON_RUN_MIE_SPEED`` switch and ``test_mie_backend_speed.py``.
271
+ Nothing ever set that variable, so the three real assertions in that file --
272
+ a backend agreement check over 4000 random particles and 361 angles -- had
273
+ never run, while the four assertions it also gated were tautologies
274
+ (``elapsed > 0``). The agreement check moved to ``test_backend_parity.py``
275
+ where it always runs and costs about a tenth of a second, and the speedup
276
+ report it printed became ``benchmark_efficiencies.py --compare``, which
277
+ ``make speed`` now calls
278
+ * the benchmark scripts ``test_jit_speed.py`` and ``test_nojit_speed.py`` became
279
+ one ``tests/benchmark_efficiencies.py``. They timed at import rather than in a
280
+ test function, so pytest spent about seven seconds running benchmarks during
281
+ collection while collecting nothing; a full test pass is now much quicker.
282
+ They also both timed whichever backend happened to be bound, so the JIT-named
283
+ one could report pure-python speed. ``make speed`` runs the single script once
284
+ per backend
285
+ * add ``tests/test_backend_parity.py``, which compares the two kernel sets
286
+ directly -- signatures and values -- in a single process, and merge the
287
+ ``test_*_D.py`` pair into one ``test_D.py`` driven by a ``kernels`` fixture
288
+ that parametrizes over both backends. Tests written this way need only one
289
+ copy instead of a JIT/no-JIT pair
290
+ * test ``monte_carlo.py``, which had no tests at all, and drop the unreachable
291
+ index clamp in ``generate_mie_costheta``: ``numpy.random.random`` draws from
292
+ [0, 1), so the index can never run off the table. A test forces the largest
293
+ float below one to show that. The sampler is checked against physics rather
294
+ than against itself: the mean of the drawn cosines reproduces the asymmetry
295
+ parameter g, computed by a completely separate path, and the empirical
296
+ distribution follows the inverse-transform table. With this the whole package
297
+ is covered, so ``fail_under`` is now 100
298
+ * record that ``cdf`` overshoots one. It sums ``i_unpolarized / num`` instead of
299
+ multiplying by the true spacing 2/(num-1), so the last value is about
300
+ 1 + 1.3/num: at the num=20 of ``docs/06_random_deviates.ipynb`` the cumulative
301
+ distribution reaches 1.067 even though the docstring promises a maximum of 1.
302
+ A test pins the 1/num convergence so the quadrature cannot quietly change
303
+ * test the kernel paths an ordinary Mie call never reaches, taking both
304
+ ``mie_jit.py`` and ``mie_nojit.py`` to 100%. ``_D_upwards`` is kept for
305
+ comparison but ``D_calc`` no longer selects it, so it is now checked against
306
+ SciPy where it is valid and against ``_D_downwards``. Also covered: the
307
+ overflow rescale inside ``_psi_downwards``, which a small argument triggers and
308
+ which the normalisation afterwards has to undo exactly; ``cn_dn`` for a sphere
309
+ of no size; and the ``m=0`` shorthand for a perfect conductor, now shown to be
310
+ identical to passing ``1-10000j``. Every module except ``monte_carlo.py`` is
311
+ fully covered, with no partial branches left anywhere
312
+ * test the scattered-only fields and the explicit term count in ``field.py``,
313
+ taking it to 100%. Nothing had ever called a near-field routine with
314
+ ``include_incident=False`` for H, or passed ``n_pole`` at all. Outside the
315
+ sphere the total field minus the scattered field is now checked against an
316
+ incident plane wave written out independently of miepython, and inside the
317
+ sphere ``include_incident`` is confirmed to make no difference
318
+ * test the array dispatch and error paths in ``core.py``, taking it to 100%.
319
+ The ``RuntimeError`` for mismatched ``m`` and ``x`` lengths, the one for array
320
+ input without ``n_pole``, the ``internal`` variants of ``coefficients`` and the
321
+ mixed scalar-and-array calls were all untested, as was the rejection of an
322
+ unknown normalization. Every array result is now checked against the
323
+ equivalent scalar calls
324
+ * fix ``S1_S2`` rejecting a list or an integer array of angles under numba. The
325
+ kernel is declared for ``float64[:]``, so ``S1_S2(m, x, [0, 1])`` worked with the
326
+ JIT off and raised ``TypeError`` with it on; ``core.py`` now coerces the angles
327
+ once, at the boundary, so both backends take the same arguments
328
+ * test the missing-SciPy path in ``__init__.py`` and the remaining parts of
329
+ ``rayleigh.py``, taking them from 52% and 72% to 100%; the total reaches 95%.
330
+ The SciPy fallback is what keeps the package usable in JupyterLite and had never
331
+ been exercised: the tests re-import the package behind an import hook, check the
332
+ placeholders name themselves and chain the original error, and confirm that a
333
+ failure other than SciPy still propagates. ``rayleigh``'s physical-units
334
+ wrappers, all thirteen normalization spellings and its error paths were also
335
+ untested
336
+ * record that ``rayleigh``'s normalization closes only to O(x**2): ``qsca`` in
337
+ ``efficiencies_mx`` stops at x**4 while the ``a1`` used by ``S1_S2`` carries an
338
+ x**5 term, so the 'one' normalization integrates to 1 + 0.07 x**2 rather than 1.
339
+ A test pins that order so extending one expansion without the other shows up
340
+ * test ``vsh.py`` and ``util.py``, which the new coverage run showed at 60% and
341
+ 55%. Both are now at 100% branch coverage, and the total rises from 86% to
342
+ 91%. The four ``M_*_array``/``N_*_array`` helpers, the ``deg=True`` angle
343
+ paths and the small-argument series in ``N_base`` had no tests at all. One of
344
+ the new tests checks ``vsh.py`` against ``field.py``, which holds a second copy
345
+ of the same vector spherical harmonics, so the two can no longer drift apart
346
+ * measure coverage. ``make coverage`` runs the suite once per backend and
347
+ combines the results, and a CI job publishes the HTML report as an artifact.
348
+ Naive coverage is misleading here: coverage.py cannot see inside numba's
349
+ compiled functions, so ``mie_jit.py`` reports 11% no matter how well it is
350
+ tested. The second pass therefore sets ``NUMBA_DISABLE_JIT=1``, which runs the
351
+ njit bodies as plain Python and lifts that file to 96%. The combined figure is
352
+ 86% with branch coverage, and ``fail_under`` is set to 84 as a floor to ratchet
353
+ upward. Two tests that deliberately assert numba is compiled were rewritten to
354
+ hold in either mode rather than being skipped, so all three modes stay green
355
+ * widen the CI matrix. It covered ubuntu on Python 3.10 and 3.14 only, and
356
+ because it also ran just two test files, most of the suite had never executed
357
+ on the oldest supported Python. Linux now runs every version the classifiers
358
+ promise, 3.10 through 3.14, and macOS and Windows run both ends of that range,
359
+ since numba holds the platform-specific half of the package. numba 0.66 ships
360
+ wheels for all of those combinations
361
+ * discover example scripts and notebooks relative to the test file rather than
362
+ the working directory. ``test_all_examples.py`` globbed a relative path, so a
363
+ run started anywhere but the repository root collected nothing and reported
364
+ success; it now asserts that it found something
365
+ * CI now lints. It only ran pytest, so a pull request with formatting or lint
366
+ errors passed clean and the checks in ``make rcheck`` were enforced on nobody.
367
+ A new job runs ``make lint``, the same target a release check uses, through uv,
368
+ so the list of checks lives in the Makefile alone and cannot drift from CI
369
+ * add a ``make lint`` target covering ruff, black, pylint, rstcheck, yamllint,
370
+ check-manifest and pyroma, and have ``make rcheck`` delegate to it. That also
371
+ closes a gap: ``yaml-check`` existed but was never part of ``rcheck``, so the
372
+ workflow files were only ever checked by hand
373
+ * the ``test_jit*`` files now really exercise the numba backend. ``_backend.py``
374
+ binds its kernels the first time ``miepython`` is imported, so setting
375
+ ``MIEPYTHON_USE_JIT`` inside a test module only worked when that module
376
+ happened to import the package first. In a full ``make test`` run something
377
+ always imported it earlier with the JIT off, so those files silently retested
378
+ the pure-python kernels and passed. ``tests/conftest.py`` now reads the
379
+ backend that actually got bound and skips the files belonging to the other
380
+ one, and ``make test`` runs the suite once per backend. The backend-agnostic
381
+ tests run under both, which is new coverage for them. New ``make test-jit``
382
+ and ``make test-nojit`` targets run a single pass, and CI now runs the whole
383
+ suite per backend rather than one file each
384
+ * raise a clear error instead of returning ``inf`` or ``nan`` when a scattering
385
+ function is asked to normalise against a sphere that does not scatter. A
386
+ sphere whose index matches its surroundings gave ``i_unpolarized`` values of
387
+ ``inf`` for the 'albedo', 'one' and '4pi' normalizations and a bare
388
+ ``ZeroDivisionError`` for 'qext'. The 'wiscombe', 'bohren' and 'qsca'
389
+ choices do not divide by an efficiency and still work
390
+ * ``efficiencies_mx`` returns zero rather than ``nan`` for ``qback`` when the
391
+ size parameter is zero. The small-sphere form of ``qback`` is 0/0 there,
392
+ although its limit is zero, and a single zero in an array of size parameters
393
+ used to leave a ``nan`` behind
394
+ * fix ``util.cartesian_to_spherical``, which raised on array input because of a
395
+ scalar ``if r != 0`` test. It now broadcasts its three arguments, reports
396
+ theta as zero at the origin, and clips ``z/r`` so rounding cannot push the
397
+ arccos argument out of range. ``field.py`` dropped its private duplicate of
398
+ this routine
399
+ * fix ``util`` exporting ``_all_`` instead of ``__all__``, and list
400
+ ``phasor_str_scalar`` alongside the rest
401
+ * the near-field routines keep two more terms than Wiscombe's criterion. That
402
+ criterion truncates the scattered series, which converges faster than the
403
+ field evaluated right at the sphere surface: the tangential E and H mismatch
404
+ across the boundary drops from 1.5e-5 to 3.2e-6, and the median disagreement
405
+ with scattnlay over a 121x121 slice falls from 1.1e-11 to 3.3e-14. Further
406
+ orders gain almost nothing once ``psi_n`` is computed stably, so two is where
407
+ it stops. The scattering quantities are untouched, and ``e_far`` still uses
408
+ the criterion unchanged. ``miepython.core.wiscombe_terms`` names the count
409
+ * ``pi_tau`` now fills ``tau`` for the highest order. It had always left the
410
+ last entry zero, which was hidden by the ``an_bn`` padding above and would
411
+ have silently dropped a term once the padding went away. ``n_pole`` may now
412
+ address the highest order, which the old bounds check rejected
413
+
414
+ 3.2.0 (03/06/2026)
415
+ -------------------
416
+ * fix error in E & H calculations in the near field when y≠0 (thanks @dorianherle)
417
+ * add regression test
418
+
419
+ 3.1.0 (02/07/2026)
420
+ -------------------
421
+ * add near-field E and H field APIs, fix boundary continuity/medium handling, and expand validation tests
422
+ * add field calculation utilities, field module cleanup/help text improvements, and precomputed E/H reference data (via scattnlay)
423
+ * improve performance: ~20% speedups in Mie backends and faster near-field calculations; add speed benchmarks
424
+ * documentation updates: new/updated notebooks (boundary conditions, 2D fields, performance), clarified conventions
425
+ * refresh docs/README visuals and assets; add custom CSS for docs images
426
+ * jupyterlite/RTD updates: config moves, build modernizations, and avoid numba install in JupyterLite
427
+ * packaging/CI: pyproject and requirements cleanup, improved PyPI workflow, updated citation automation/config
428
+ * misc cleanups: remove unused files/images, minor typos, Makefile and docs config tidy-ups, CITATION.cff refresh
429
+
430
+ 3.0.5 (1/2/2026)
431
+ -------------------
432
+ * fix versioning
433
+
434
+ 3.0.4 (1/2/2026)
435
+ -------------------
436
+ * Host jupyterlite instance of github
437
+ * improve citation guidelines
438
+ * add test for unpolarized intensity methods
439
+ * begin work on local electrical and magnetic fields
440
+ * using black now with longer lines
441
+ * improved packaging
442
+ * improve readme
443
+
444
+ 3.0.3
445
+ -------------------
446
+ * skipped
447
+
448
+ 3.0.2 (5/25/2025)
449
+ -------------------
450
+ * fix version number
451
+
452
+ 3.0.1 (5/25/2025)
453
+ -------------------
454
+ * fix JIT regression (thanks @avgeiss)
455
+ * clarify polarization in docstrings
456
+ * improve README.rst
457
+ * fix git branches
458
+ * rename mie.mie_scalar to mie.single_sphere
459
+ * rename small_mie_sphere to small_sphere
460
+ * rename small_conducting_mie to small_conducting_sphere
461
+ * rationalize importing of jit and non-jit code
462
+ * add test_jit_speed.py and test_nojit_speed.py
463
+
464
+ 3.0.0 (3/16/2025)
465
+ -------------------
466
+ * breaking api changes
467
+ * api is more sane: mie.efficiencies() instead of miepython.ez_mie()
468
+ * use core.py to cleanly separate jit and non-jit code
469
+ * new function to calculate mie coefficients inside sphere
470
+ * new function to calculate E-fields near and far from sphere (only works in far-field)
471
+ * new rayleigh.py
472
+ * new vsh.py to calculate vector spherical harmonics
473
+ * new util.py for printing complex numbers
474
+ * new bessel.py for complete spherical bessel function support
475
+ * put Monte Carlo routines into their own file
476
+ * use black for python formatting
477
+ * update all notebooks to use new api
478
+ * add more tests
479
+
480
+
481
+ 2.5.5 (12/01/2025)
482
+ -------------------
483
+ * add support for specific spherical modes
484
+ * only branch is now 'main'
485
+
486
+ 2.5.4 (05/07/2024)
487
+ -------------------
488
+ * add auto-dating in CITATION
489
+ * add python 3.12
490
+ * add ruff, pylint config
491
+ * simplify mie_cdf and fix notebook
492
+ * set 3.7 as earliest python version
493
+
494
+ v2.5.3 (8/5/2023)
495
+ -------------------
496
+ * conda-forge fails because test files are not included
497
+
498
+ v2.5.1 (8/5/2023)
499
+ -------------------
500
+ * change tests to accommodate conda-forge
501
+ * require python>=3.9 to accommodate latest numba
502
+ * get rid of tox
503
+
504
+ v2.5.0 (8/4/2023)
505
+ -------------------
506
+ * fix scattering function for very small spheres
507
+
508
+ v2.4.0 (6/10/2023)
509
+ -------------------
510
+ * add mie_phase_matrix() to calculate scattering (Mueller) matrix
511
+
512
+ v2.3.2
513
+ -------------------
514
+ * fix typo in README.rst that prevented pypi upload
515
+ * add CITATION.cff to base level of miepython repository
516
+
517
+ v2.3.1
518
+ -------------------
519
+ * add DOI for citation purposes
520
+
521
+ v2.3.0
522
+ -------------------
523
+ * add optional argument to change scattering function normalization
524
+ * document normalization in new notebook
525
+ * store data in correct place
526
+ * store version __init__.py so scripts can query it
527
+ * fix typo in header of gold sphere example script
528
+ * remove workaround for older Sphinx version
529
+
530
+ v2.2.3 (1/26/2022)
531
+ ------------------
532
+ * update _mie_An_Bn in miepython_nojit
533
+ * store data in module so github testing passes
534
+ * fix build of API documentation on miepython.readthedocs.io
535
+ * only test back to python 3.9 because importlib.resources
536
+ * 3.9 is only needed for a few of the jupyter notebooks
537
+
538
+ v2.2.2 (1/25/2022)
539
+ ------------------
540
+ * modify _mie_An_Bn to allocate and return An and Bn
541
+ * fix minor packaging issue
542
+ * explicitly define encoding when opening files
543
+ * explicitly use .readthedocs.yaml to build docs
544
+ * use rtd theme for docs
545
+ * add docs/requirements
546
+ * restrict Jinja2 to 2.11.3 in docs/requirements
547
+
548
+ v2.2.1 (9/5/2021)
549
+ -----------------
550
+ * create pure python packages
551
+ * include wheel file
552
+ * package as python3 only
553
+
554
+ 2.1.0 (05/22/21)
555
+ ----------------
556
+ * fix case when scalar angle used with mie_S1_S2()
557
+ * add pypi badge
558
+ * fix notebook testing
559
+ * thanks to @zmoon for the following changes:
560
+ * add requirements-dev.txt
561
+ * add example script testing
562
+ * add workflow testing
563
+ * fix Au/Ag error
564
+ * fix examples that use refractiveindex.info
565
+ * add testing badge
566
+
567
+ 2.0.1 (04/25/21)
568
+ ----------------
569
+ * fix packaging mistake
570
+
571
+ 2.0.0 (04/25/21)
572
+ ----------------
573
+ * use numba for 10-700X speed improvement
574
+ * thanks to @jbecca and @pscicluna for their help
575
+ * add performance notebook
576
+ * add automated notebook checking
577
+ * test more code
578
+
579
+ 1.3.3 (03/21/21)
580
+ ----------------
581
+ * colab badge and link
582
+ * change theme for sphinx documentation
583
+ * add requirements.txt to avoid installing sphinx
584
+ * fix restructured text errors
585
+ * advise everywhere to `pip install --user miepython` to avoid permission problems
586
+
587
+ 1.3.2 (01/13/21)
588
+ ----------------
589
+ * add ez_mie(m, d, lambda0)
590
+ * add ez_intensities(m, d, lambda0, mu)
591
+ * fix formatting
592
+ * fix api autodoc
593
+ * specify newer pythons
594
+ * better install instructions
595
+
596
+ 1.3.1 (03/30/20)
597
+ ----------------
598
+ * improve docstrings
599
+ * use Sphinx documentation
600
+ * host docs on readthedocs.io
601
+ * use tox
602
+
603
+ 1.3.0 (02/19/19)
604
+ ----------------
605
+ * fix calculations for small spheres (x<0.05)
606
+ * added notebook doc/09_backscattering.ipynb
607
+ * general tweaks to documentation throughout
608
+ * improved README.md
609
+
610
+ 1.2.0. (02/08/19)
611
+ -----------------
612
+ * fix bug so that large sphere calculations work correctly
613
+ * add tests for large spheres
614
+ * add tests for backscattering efficiency
615
+ * add documentation notebook for large spheres
616
+ * add direct links to documentation
617
+ * finish fixing fractions in notebooks
618
+ * improve README.md
619
+
620
+ 1.1.1. (06/25/18)
621
+ ------------------
622
+ * fix github rendering of fractions in equations
623
+ * add developer instructions
624
+ * fix fractions for github
625
+ * add missing doc files found my check-manifest
626
+ * setup.py fixes suggested by pyroma
627
+ * pep8 compliance and delinting using pylint
628
+ * add missing doc files found my check-manifest
629
+ * setup.py fixes suggested by pyroma
630
+ * pep8 compliance and delinting using pylint
631
+ * update version
632
+ * add notebook doc/08_large_spheres.ipynb
633
+
634
+ 1.1.0 (03/02/2018)
635
+ ------------------
636
+ * update version
637
+ * initial commit of 04_rayleigh.ipynb
638
+ * renamed doc files
639
+ * use new functions from miepython
640
+ * omit low level tests
641
+ * add __author__ and __version__
642
+ * add i_par, i_per, i_unpolarized, and hide private functions
643
+ * rename doc files
644
+ * add quantitative comparisons of angular scattering
645
+ * tweak verbiage
646
+ * ignore more
647
+ * initial commit
648
+ * more cleanup
649
+ * ignore dist files
650
+ * minor reorg of contents
651
+ * fix typos, add more refraction stuff
652
+ * Changes to match PEP8 style
653
+ * add minor comments, fix typos
654
+
655
+ 1.0.0 (08/27/2017)
656
+ ------------------
657
+ * Added docs in form of Jupyter notebooks
658
+
659
+ 0.4.2 (08/26/2017)
660
+ ------------------
661
+ * messed up github release 0.4.1
662
+
663
+ 0.4.1 (08/26/2017)
664
+ ------------------
665
+ * fix typo
666
+
667
+ 0.4.0 (08/26/2017)
668
+ ------------------
669
+ * update README to include basic testing
670
+ * mie(m,x) work automatically with arrays
671
+ * adding MANIFEST.in so examples get included
672
+
673
+ 0.3.2 (07/07/2017)
674
+ ------------------
675
+ * update README, bump to 0.3.2
676
+ * Fix examples so they work.
677
+
678
+ 0.3.1 (07/07/2017)
679
+ ------------------
680
+ * Bump version.
681
+ * Add functions to __init__.py.
682
+
683
+ 0.3.0 (07/07/2017)
684
+ ------------------
685
+ * Update README again.
686
+ * Update README.
687
+ * More packaging issues.
688
+ * Only include normalized scattering functions.
689
+ * Tweak setup.py and add .gitignore.
690
+ * Rename README.
691
+ * Add small sphere calc for S1 and S2.
692
+ * Label tests with MIEV0 cases.
693
+ * Rename example.
694
+ * Add gold sphere example.
695
+ * Add a few example programs.
696
+ * Remove unused tests.
697
+ * Remove extraneous ; simplify test.py, add tests.
698
+ * Simplify test suite management.
699
+ * Rename awkward test_miepython to just test.
700
+ * Reorganize tests, add S1 & S2 test.
701
+ * Added capabilities. Barely working test suite.
702
+ * Add more tests that fail.
703
+ * Move files around.
704
+ * Add boilerplate files and start adding unit tests.
705
+ * Rename to miepython.
706
+ * Initial check in.