Repository: PlasmaPy/PlasmaPy

Issue:
Create module of dielectric functions
We should make a module of dielectric (susceptibility) functions. These are super useful for things like simulating Thomson scattering spectra. 

I already have some code for this, so I could get it started. Are there any specific dielectric functions we should include?


Discussion:
[Here](https://arachnoid.com/latex/?equ=%5Cepsilon(%5Comega%2C%5Cvec%7Bk%7D)%20%3D%20v_%7Bphase%7D%5E2%20%5Cint_%7B-%5Cinfty%7D%5E%7B%2B%5Cinfty%7D%20%5Cfrac%7B%5Cvec%7Bk%7D%20%5Ccdot%20%5Cfrac%7B%5Cpartial%20f(%5Cvec%7Bv%7D)%7D%7B%5Cpartial%20%5Cvec%7Bv%7D%7D%7D%20%7B%20(%5Comega%20-%20%5Cvec%7Bk%7D%20%5Ccdot%20%5Cvec%7Bv%7D%20-%20i%20%5Cgamma)%7D%20d%20%5Cvec%7Bv%7D) is one form dielectric function I would like to add. I think it is from Landau's Physical Kinetics, but can be found in a number of plasma physics texts.
Is the module have been already created ? If not, where should it be created ? I've got few function for cold plasma dielectric tensor function (Stix parameters for instance)
I don't believe we have it yet. If you'd like to contribute those functions, `plasmapy/physics/dielectric.py` would probably be a reasonable place. We'd of course need to update the __init__ file in that directory to add that file.
I've not gotten around to creating it yet. You are welcome to start it!
I agree `plasmapy/physics/dielectric.py` is a good name/place.
We've made a good amount of progress on this in `dielectric.py`.  What else do we need to do before we can close this issue?
I'll add a classical plasma function based on Faddeeva along with an example plot of the dispersion curves.

Resolving patch:
diff --git a/plasmapy/examples/plot_dispersion_function.py b/plasmapy/examples/plot_dispersion_function.py
--- a/plasmapy/examples/plot_dispersion_function.py
+++ b/plasmapy/examples/plot_dispersion_function.py
@@ -75,3 +75,26 @@ def plot_complex(X, Y, Z, N=50):
 
 F = plasmapy.mathematics.plasma_dispersion_func(Z)
 plot_complex(X, Y, F, 100)
+
+#######################################################################
+# Now we examine the derivative of the dispersion function as a function
+# of the phase velocity of an electromagnetic wave propagating through
+# the plasma. This is recreating figure 5.1 in:
+# J. Sheffield, D. Froula, S. H. Glenzer, and N. C. Luhmann Jr,
+# Plasma scattering of electromagnetic radiation: theory and measurement
+# techniques. Chapter 5 Pg 106 (Academic press, 2010).
+
+xs = np.linspace(0, 4, 100)
+ws = (-1 / 2) * plasmapy.mathematics.plasma_dispersion_func_deriv(xs)
+wRe = np.real(ws)
+wIm = np.imag(ws)
+
+plt.plot(xs, wRe, label="Re")
+plt.plot(xs, wIm, label="Im")
+plt.axis([0, 4, -0.3, 1])
+plt.legend(loc='upper right',
+           frameon=False,
+           labelspacing=0.001,
+           fontsize=14,
+           borderaxespad=0.1)
+plt.show()
\ No newline at end of file
diff --git a/plasmapy/physics/__init__.py b/plasmapy/physics/__init__.py
--- a/plasmapy/physics/__init__.py
+++ b/plasmapy/physics/__init__.py
@@ -14,7 +14,8 @@
                          lower_hybrid_frequency)
 
 from .dielectric import (cold_plasma_permittivity_LRP,
-                         cold_plasma_permittivity_SDP)
+                         cold_plasma_permittivity_SDP,
+                         permittivity_1D_Maxwellian)
 
 from .distribution import (Maxwellian_1D,
                            Maxwellian_velocity_2D,
diff --git a/plasmapy/physics/dielectric.py b/plasmapy/physics/dielectric.py
--- a/plasmapy/physics/dielectric.py
+++ b/plasmapy/physics/dielectric.py
@@ -1,21 +1,26 @@
 """Functions to calculate plasma dielectric parameters"""
 
+import numpy as np
 from astropy import units as u
 from plasmapy import utils
 from plasmapy.physics import parameters
+from plasmapy.constants import (pi, m_e, c, mu0, e, eps0)
+import scipy.special as spec
+from plasmapy.mathematics import plasma_dispersion_func_deriv
 
 r"""
 Values should be returned as a `~astropy.units.Quantity` in SI units.
 """
 
 __all__ = ['cold_plasma_permittivity_SDP',
-           'cold_plasma_permittivity_LRP']
+           'cold_plasma_permittivity_LRP',
+           'permittivity_1D_Maxwellian']
 
 
 @utils.check_quantity(
     B={'units': u.T, 'can_be_negative': False},
     omega={'units': u.rad / u.s, 'can_be_negative': False},
-    )
+)
 def cold_plasma_permittivity_SDP(B, species, n, omega):
     r"""
     Magnetized Cold Plasma Dielectric Permittivity Tensor Elements.
@@ -112,7 +117,7 @@ def cold_plasma_permittivity_SDP(B, species, n, omega):
 @utils.check_quantity(
     B={'units': u.T, 'can_be_negative': False},
     omega={'units': u.rad / u.s, 'can_be_negative': False},
-    )
+)
 def cold_plasma_permittivity_LRP(B: u.T, species, n, omega: u.rad / u.s):
     r"""
     Magnetized Cold Plasma Dielectric Permittivity Tensor Elements.
@@ -200,3 +205,111 @@ def cold_plasma_permittivity_LRP(B: u.T, species, n, omega: u.rad / u.s):
         R += - omega_p ** 2 / (omega * (omega + omega_c))
         P += - omega_p ** 2 / omega ** 2
     return L, R, P
+
+
+@u.quantity_input(omega=u.rad / u.s,
+                  kWave=u.rad / u.m,
+                  T=u.K,
+                  n=u.m**-3,
+                  z_mean=u.dimensionless_unscaled)
+def permittivity_1D_Maxwellian(omega,
+                               kWave,
+                               T,
+                               n,
+                               particle,
+                               z_mean=None):
+    r"""
+    The classical dielectric permittivity for a 1D Maxwellian plasma. This 
+    function can calculate both the ion and electron permittivities. No
+    additional effects are considered (e.g. magnetic fields, relativistic
+    effects, strongly coupled regime, etc.)
+
+    Parameters
+    ----------
+    omega : ~astropy.units.Quantity
+        The frequency in rad/s of the electromagnetic wave propagating
+        through the plasma.
+
+    kWave : ~astropy.units.Quantity
+        The corresponding wavenumber, in rad/m, of the electromagnetic wave
+        propagating through the plasma. This is often modulated by the
+        dispersion of the plasma or by relativistic effects. See em_wave.py
+        for ways to calculate this.
+
+    T : ~astropy.units.Quantity
+        The plasma temperature - this can be either the electron or the ion
+        temperature, but should be consistent with density and particle.
+
+    n : ~astropy.units.Quantity
+        The plasma density - this can be either the electron or the ion
+        density, but should be consistent with temperature and particle.
+
+    particle : str
+        The plasma particle species.
+
+    z_mean : str
+        The average ionization of the plasma. This is only required for
+        calculating the ion permittivity.
+
+    Returns
+    -------
+    chi : ~astropy.units.Quantity
+        The ion or the electron dielectric permittivity of the plasma.
+        This is a dimensionless quantity.
+
+    Notes
+    -----
+    The dielectric permittivities for a Maxwellian plasma are described
+    by the following equations [1]_
+    
+    .. math::
+        \chi_e(k, \omega) = - \frac{\alpha_e^2}{2} Z'(x_e)
+        
+        \chi_i(k, \omega) = - \frac{\alpha_i^2}{2}\frac{Z}{} Z'(x_i)
+        
+        \alpha = \frac{\omega_p}{k v_{Th}}
+        
+        x = \frac{\omega}{k v_{Th}}
+
+    :math:`chi_e` and :math:`chi_i` are the electron and ion permittivities
+    respectively. :math:`Z'` is the derivative of the plasma dispersion
+    function. :math:`\alpha` is the scattering parameter which delineates
+    the difference between the collective and non-collective Thomson
+    scattering regimes. :math:`x` is the dimensionless phase velocity
+    of the EM wave propagating through the plasma.
+
+    References
+    ----------
+    .. [1] J. Sheffield, D. Froula, S. H. Glenzer, and N. C. Luhmann Jr,
+       Plasma scattering of electromagnetic radiation: theory and measurement
+       techniques. Chapter 5 Pg 106 (Academic press, 2010).
+
+    Example
+    -------
+    >>> from astropy import units as u
+    >>> from plasmapy.constants import pi, c
+    >>> T = 30 * 11600 * u.K
+    >>> n = 1e18 * u.cm**-3
+    >>> particle = 'Ne'
+    >>> z_mean = 8 * u.dimensionless_unscaled
+    >>> vTh = parameters.thermal_speed(T, particle, method="most_probable")
+    >>> omega = 5.635e14 * 2 * pi * u.rad / u.s
+    >>> kWave = omega / vTh
+    >>> permittivity_1D_Maxwellian(omega, kWave, T, n, particle, z_mean)
+    <Quantity -6.72809257e-08+5.76037956e-07j>
+    """
+    # thermal velocity
+    vTh = parameters.thermal_speed(T=T,
+                                   particle=particle,
+                                   method="most_probable")
+    # plasma frequency
+    wp = parameters.plasma_frequency(n=n,
+                                     particle=particle,
+                                     z_mean=z_mean)
+    # scattering parameter alpha.
+    # explicitly removing factor of sqrt(2) to be consistent with Froula
+    alpha = np.sqrt(2) * (wp / (kWave * vTh)).to(u.dimensionless_unscaled)
+    # The dimensionless phase velocity of the propagating EM wave.
+    zeta = (omega / (kWave * vTh)).to(u.dimensionless_unscaled)
+    chi = alpha ** 2 * (-1 / 2) * plasma_dispersion_func_deriv(zeta.value)
+    return chi.to(u.dimensionless_unscaled)


Test patch:
diff --git a/plasmapy/physics/tests/test_dielectric.py b/plasmapy/physics/tests/test_dielectric.py
--- a/plasmapy/physics/tests/test_dielectric.py
+++ b/plasmapy/physics/tests/test_dielectric.py
@@ -5,9 +5,12 @@
 from astropy import units as u
 
 from ..dielectric import (cold_plasma_permittivity_LRP,
-                          cold_plasma_permittivity_SDP)
+                          cold_plasma_permittivity_SDP,
+                          permittivity_1D_Maxwellian)
 
-from ..parameters import (plasma_frequency, gyrofrequency)
+from ..parameters import (plasma_frequency,
+                          gyrofrequency,
+                          thermal_speed)
 
 B = 1.0 * u.T
 n = [1e18 / u.m ** 3]
@@ -75,3 +78,58 @@ def test_SD_to_LR_relationships(self):
         assert np.isclose(L, S - D)
         assert np.isclose(S, (R + L) / 2)
         assert np.isclose(D, (R - L) / 2)
+
+
+class Test_permittivity_1D_Maxwellian:
+    @classmethod
+    def setup_class(self):
+        """initializing parameters for tests """
+        self.T = 30 * 11600 * u.K
+        self.n = 1e18 * u.cm**-3
+        self.particle = 'Ne'
+        self.z_mean = 8 * u.dimensionless_unscaled
+        self.vTh = thermal_speed(self.T,
+                                 self.particle,
+                                 method="most_probable")
+        self.omega = 5.635e14 * 2 * np.pi * u.rad / u.s
+        self.kWave = self.omega / self.vTh
+        self.True1 = (-6.728092569241431e-08 +
+                      5.760379561405176e-07j) * u.dimensionless_unscaled
+
+    def test_known1(self):
+        """
+        Tests Fermi_integral for expected value.
+        """
+        methodVal = permittivity_1D_Maxwellian(self.omega,
+                                               self.kWave,
+                                               self.T,
+                                               self.n,
+                                               self.particle,
+                                               self.z_mean)
+        testTrue = np.isclose(methodVal,
+                              self.True1,
+                              rtol=1e-16,
+                              atol=0.0)
+        errStr = (f"Permittivity value should be {self.True1} and not "
+                  f"{methodVal}.")
+        assert testTrue, errStr
+
+    def test_fail1(self):
+        """
+        Tests if test_known1() would fail if we slightly adjusted the
+        value comparison by some quantity close to numerical error.
+        """
+        fail1 = self.True1 + 1e-15
+        methodVal = permittivity_1D_Maxwellian(self.omega,
+                                               self.kWave,
+                                               self.T,
+                                               self.n,
+                                               self.particle,
+                                               self.z_mean)
+        testTrue = not np.isclose(methodVal,
+                                  fail1,
+                                  rtol=1e-16,
+                                  atol=0.0)
+        errStr = (f"Permittivity value test gives {methodVal} "
+                  f"and should not be equal to {fail1}.")
+        assert testTrue, errStr