Repository: PlasmaPy/PlasmaPy

Issue:
Apparent bug in ion contribution to Thomson scattering spectrum
We received a bug report over email.  

> I believe there may be a bug in the computation of the ion contribution for the Thomson scattering in diagnostics/thomson.py.  Specifically, the contribution from the species' charge ([line 247](https://github.com/PlasmaPy/PlasmaPy/blob/main/src/plasmapy/diagnostics/thomson.py#L247)) appears to be slightly incorrect. Rather than `ion_z​` it should be `ion_z**2 / zbar​` according to [Sheffield](https://www.sciencedirect.com/book/9780123748775/plasma-scattering-of-electromagnetic-radiation) (Section 5.1, Eq. 5.1.2 and following on page 104 - see screenshot below)
![unnamed](https://github.com/PlasmaPy/PlasmaPy/assets/8931994/a8ac4e2b-ef4f-4439-b07c-8192553f002c)



@pheuer — could you look into this?



Discussion:

Resolving patch:
diff --git a/src/plasmapy/diagnostics/thomson.py b/src/plasmapy/diagnostics/thomson.py
--- a/src/plasmapy/diagnostics/thomson.py
+++ b/src/plasmapy/diagnostics/thomson.py
@@ -244,7 +244,8 @@ def spectral_density_lite(
         icontr[m, :] = ifract[m] * (
             2
             * np.sqrt(np.pi)
-            * ion_z[m]
+            * ion_z[m] ** 2
+            / zbar
             / k
             / vT_i[m]
             * np.power(np.abs(np.sum(chiE, axis=0) / epsilon), 2)
@@ -403,14 +404,14 @@ def spectral_density(  # noqa: C901, PLR0912, PLR0915
         S(k,ω) = \sum_e \frac{2π}{k}
         \bigg |1 - \frac{χ_e}{ε} \bigg |^2
         f_{e0,e} \bigg (\frac{ω}{k} \bigg ) +
-        \sum_i \frac{2π Z_i}{k}
+        \sum_i \frac{2π}{k} \frac{Z_i^2}{\bar Z}
         \bigg |\frac{χ_e}{ε} \bigg |^2 f_{i0,i}
         \bigg ( \frac{ω}{k} \bigg )
 
     where :math:`χ_e` is the electron population susceptibility of the
     plasma and :math:`ε = 1 + ∑_e χ_e + ∑_i χ_i` is the total plasma
     dielectric function (with :math:`χ_i` being the ion population of
-    the susceptibility), :math:`Z_i` is the charge of each ion,
+    the susceptibility),
     :math:`k` is the scattering wavenumber, :math:`ω` is the scattering
     frequency, and :math:`f_{e0,e}` and :math:`f_{i0,i}` are the
     electron and ion velocity distribution functions, respectively. In
@@ -434,6 +435,11 @@ def spectral_density(  # noqa: C901, PLR0912, PLR0915
     .. math::
         \sum_e F_e = 1
 
+    and
+
+    .. math::
+        \bar Z = \sum_k F_k Z_k
+
     The plasma is assumed to be quasineutral, and therefore the number
     density of the i\ :sup:`th` ion population is
 


Test patch:
diff --git a/tests/diagnostics/test_thomson.py b/tests/diagnostics/test_thomson.py
--- a/tests/diagnostics/test_thomson.py
+++ b/tests/diagnostics/test_thomson.py
@@ -364,14 +364,14 @@ def multiple_species_collective_args():
         "n": 5e17 * u.cm**-3,
         "T_e": 10 * u.eV,
     }
-    kwargs["T_i"] = np.array([5, 5]) * u.eV
+    kwargs["T_i"] = np.array([25, 25]) * u.eV
     kwargs["ions"] = [Particle("p+"), Particle("C-12 5+")]
     kwargs["probe_vec"] = np.array([1, 0, 0])
     kwargs["scatter_vec"] = np.array([0, 1, 0])
     kwargs["efract"] = np.array([1.0])
     kwargs["ifract"] = np.array([0.7, 0.3])
-    kwargs["electron_vel"] = np.array([[300, 0, 0]]) * u.km / u.s
-    kwargs["ion_vel"] = np.array([[-500, 0, 0], [0, 500, 0]]) * u.km / u.s
+    kwargs["electron_vel"] = np.array([[0, 0, 0]]) * u.km / u.s
+    kwargs["ion_vel"] = np.array([[-100, 0, 0], [0, 100, 0]]) * u.km / u.s
 
     return kwargs
 
@@ -418,26 +418,27 @@ def test_multiple_species_collective_spectrum(
 
     # Compute the width and max of the spectrum, and the wavelength
     # of the max (sensitive to ion vel)
-    width = width_at_value(wavelength.value, Skw.value, 0.2e-11)
-    max_skw = np.max(Skw.value)
+    max_skw = np.nanmax(Skw.value)
+    width = width_at_value(wavelength.value, Skw.value, 2e-12)
+
     max_wavelength = wavelength.value[np.argmax(Skw.value)]
 
     # Check width
-    assert np.isclose(width, 0.14, 1e-2), (
+    assert np.isclose(width, 0.17, 1e-2), (
         f"Multiple ion species case spectrum width is {width} instead of "
-        "expected 0.14"
+        "expected 0.17"
     )
 
     # Check max value
-    assert np.isclose(max_skw, 2.4e-11, 1e-11), (
+    assert np.isclose(max_skw, 6e-12, 1e-11), (
         f"Multiple ion species case spectrum max is {max_skw} instead of "
-        "expected 2.4e-11"
+        "expected 6e-12"
     )
 
     # Check max peak location
-    assert np.isclose(max_wavelength, 530.799, 1e-2), (
+    assert np.isclose(max_wavelength, 532, 1e-2), (
         "Multiple ion species case spectrum peak wavelength is "
-        f"{max_wavelength} instead of expected 530.79"
+        f"{max_wavelength} instead of expected 532"
     )