Repository: PlasmaPy/PlasmaPy
Pull request #165 (2017-11-03 16:10:15)

Work on Langmuir diagnostic submodule
First progress towards https://github.com/PlasmaPy/PlasmaPy/issues/158. Creation of the diagnostics submodule and `langmuir.py` file.

I've created a function for obtaining the plasma parameters such as energy density and temperature from a given Langmuir V-I profile, as well as for obtaining the plasma EEDF and EEPF.

pep8speaks: Hello @jasperbeckers! Thanks for submitting the PR.

 - In the file [`plasmapy/diagnostics/langmuir.py`](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py), following are the PEP8 issues :

> [Line 11:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L11): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 25:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L25): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 32:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L32): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 38:15](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L38): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 41:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L41): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 44:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L44): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 47:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L47): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 70:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L70): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 73:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L73): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 77:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L77): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 80:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L80): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 82:61](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L82): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 83:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L83): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 87:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L87): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 93:15](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L93): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 96:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L96): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 99:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L99): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 102:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L102): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 111:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L111): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 117:15](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L117): [W291](https://duckduckgo.com/?q=pep8%20W291) trailing whitespace
> [Line 120:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L120): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 123:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L123): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace
> [Line 126:1](https://github.com/PlasmaPy/PlasmaPy/blob/4508c96d64e78ae4c639ebeb6b56bf3237b995f6/plasmapy/diagnostics/langmuir.py#L126): [W293](https://duckduckgo.com/?q=pep8%20W293) blank line contains whitespace


jasperbeckers: @lemmatum Ideally the analysis function would iteratively converge on an optimal estimate starting from a rough guess.
jasperbeckers: @lemmatum I pushed all changes you requested. What are your thoughts on my last comment?

> Regarding the former, I think we should provide different functions for each method / section of the analysis, so that they can be called independently. The `swept_probe_analysis` function would then call upon these functions to provide a standard "off the shelf" analysis. That way a user can either use our quick and easy analysis function, or incorporate the subfunctions in a custom analysis.
lemmatum: @jasperbeckers Looks good. Have you changed the "best estimate" terminology to "initial guess" or have you decided to keep it as "best estimate"?

I think your idea for having a separate function for each section/type of analysis is sensible.

Can you write a simple `test_langmuir.py` file to make coveralls happy? You can use my `test_distribution.py` as a template for how to do tests in a classy way 😉 
StanczakDominik: Hey, @jasperbeckers, how's those tests coming along? If you're too busy, can I contribute those? :)
jasperbeckers: @StanczakDominik Yes, you can go ahead! I've been traveling so I've only been passively following the project.
StanczakDominik: Oh right, I think we'd need to change __init__.py in the main code directory to add a line importing langmuir so that it's visible. We may also need an init py in the langmuir directory.

If need be, I'll fix this and push to this branch by Saturday.
jasperbeckers: @StanczakDominik Please, go ahead. I'd like to get this PR out of the way so I can start writing some actual code :)

Patch:
diff --git a/docs/diagnostics/index.rst b/docs/diagnostics/index.rst
new file mode 100644
index 00000000..42d88f51
--- /dev/null
+++ b/docs/diagnostics/index.rst
@@ -0,0 +1,5 @@
+.. py:module:: diagnostics
+
+.. _plasmapy-diagnostics:
+
+.. automodapi:: plasmapy.diagnostics
diff --git a/docs/index.rst b/docs/index.rst
index 7aee910f..e4667826 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -38,6 +38,7 @@ Modules
     :maxdepth: 1
 
     atomic/index
+    diagnostics/index
     mathematics/index
     physics/index
 
diff --git a/plasmapy/__init__.py b/plasmapy/__init__.py
index da070773..4495b309 100644
--- a/plasmapy/__init__.py
+++ b/plasmapy/__init__.py
@@ -52,6 +52,7 @@ def _split_version(version):
     from . import atomic
     from . import mathematics
     from . import physics
+    from . import diagnostics
 except ImportError:
     raise ImportError("Unable to load PlasmaPy subpackages.")
 
diff --git a/plasmapy/diagnostics/__init__.py b/plasmapy/diagnostics/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/plasmapy/diagnostics/langmuir.py b/plasmapy/diagnostics/langmuir.py
new file mode 100644
index 00000000..dcfcf30e
--- /dev/null
+++ b/plasmapy/diagnostics/langmuir.py
@@ -0,0 +1,142 @@
+"""
+Defines the Langmuir analysis module as part of the diagnostics package.
+"""
+
+import numpy as np
+import astropy.units as u
+
+
+class Probe():
+    r"""Class containing various Langmuir probe properties. For the moment
+    cylindrical probes are assumed as spherical ones are generally impractical
+    to make.
+
+    Attributes
+    ----------
+    area : Quantity or array
+        Area or array of areas of the probe(s) in units convertible to m^2.
+
+    config : string, optional
+        Electrode configuration. Possible options are 'single' (default),
+        'double', 'double-asym', 'triple', 'tetra', 'penta'. The electrode
+        configuration must correspond to the number of areas provided.
+
+    """
+    def __init__(self, area=u.m**2, config='single'):
+        # Initialize probe properties
+        self.area = area
+        self.config = config
+
+
+def swept_probe_analysis(potential_sweep, 
+                         measured_current, 
+                         probe, 
+                         ion='H',
+                         method='maxwellian', 
+                         smoothing=None, 
+                         polyorder=3):
+    r"""Performs a Langmuir analysis of a given probe V-I profile in order to
+    obtain various plasma parameters.
+
+    Parameters
+    ----------
+    potential_sweep : Quantity
+        The swept electric potential applied to the probe in units convertible to V.
+
+    measured_current : Quantity
+        The corresponding measured current in units convertible to A.
+
+    probe : Probe
+        An instance of the Probe class containing probe properties.
+
+    ion : string, optional
+        Representation of the ion species. Defaults to Hydrogen.
+
+    method : string, optional
+        The preferred analysis method. Options are 'maxwellian' (default) and
+        'OML', which applies Orbital Motion Limit theory.
+
+    smoothing : string, optional
+        The smoothing method applied to the measured current in order to
+        obtain clean derivatives. Options are None (default) and 'savgol',
+        which uses scipy.signal.savgol_filter. When `smoothing` is 'savgol', 
+        the polynomial order is given by polyorder.
+
+    polyorder : int
+        The order of the polynomial used to smooth the measured current and
+        derivatives if `smoothing` is 'savgol'. Default is 3.
+
+    Returns
+    -------
+    T_e : Quantity
+        Best estimate of the electron temperature in units of eV.
+
+    n_e : Quantity
+        Best estimate of the electron density in units of m^-3.
+
+    n_i : Quantity
+        Best estimate of the ion density in units of m^-3. Equal to n_e
+        unless the OML method is used.
+
+    V_F : Quantity
+        Best estimate of the floating potential in units of V.
+
+    V_P : Quantity
+        Best estimate of the plasma potential in units of V.
+
+    Notes
+    -----
+    Due to the use of different methods to obtain n_e and n_i, the ratio
+    between these does not necessarily give an accurate average ionization.
+    """
+    return
+
+
+def obtain_EEDF(potential_sweep, measured_current, probe):
+    r"""Obtains the Electron Energy Distribution Function of the plasma
+    from the second derivative of the measured current response using
+    Druyvestein's equations.
+
+    Parameters
+    ----------
+    potential_sweep : Quantity
+        The swept electric potential applied to the probe in units convertible to V.
+
+    measured_current : Quantity
+        The corresponding measured current in units convertible to A.
+
+    probe : Probe
+        An instance of the Probe class containing probe properties.
+
+    Returns
+    -------
+    EEDF : ndarray
+        Array containing electron energies in units of eV and their
+        corresponding probabilities in units of eV^-1 m^-3.
+    """
+    return
+
+
+def obtain_EEPF(potential_sweep, measured_current, probe):
+    r"""Obtains the Electron Energy Probability Function of the plasma
+    from the second derivative of the measured current response using
+    Druyvestein's equations.
+
+    Parameters
+    ----------
+    potential_sweep : Quantity
+        The swept electric potential applied to the probe in units convertible to V.
+
+    measured_current : Quantity
+        The corresponding measured current in units convertible to A.
+
+    probe : Probe
+        An instance of the Probe class containing probe properties.
+
+    Returns
+    -------
+    EEPF : ndarray
+        Array of shape (2, N) containing electron energies in units of eV and
+        their corresponding probabilities in units of eV^-3/2 m^-3.
+    """
+    return


Test patch:
diff --git a/plasmapy/diagnostics/tests/test_langmuir.py b/plasmapy/diagnostics/tests/test_langmuir.py
new file mode 100644
index 00000000..0ff8881e
--- /dev/null
+++ b/plasmapy/diagnostics/tests/test_langmuir.py
@@ -0,0 +1,37 @@
+"""Tests for functions that use Langmuir analysis functions."""
+
+import numpy as np
+
+from plasmapy.diagnostics.langmuir import (swept_probe_analysis,
+                               obtain_EEDF,
+                               obtain_EEPF)
+
+class Test_swept_probe_analysis():
+    def test_no_signal(self):
+        """
+        Checks whether an error is raised when zero signal is passed to the
+        function.
+        """
+        errStr = ("Performing a swept probe analysis should return an error "
+                  "when zero signal is passed, but doesn't.")
+        assert True, errStr
+
+class Test_obtain_EEDF():
+    def test_no_signal(self):
+        """
+        Checks whether an error is raised when zero signal is passed to the
+        function.
+        """
+        errStr = ("Attempting to obtain the EEDF should return an error when "
+                  "zero signal is passed, but doesn't.")
+        assert True, errStr
+
+class Test_obtain_EEPF():
+    def test_no_signal(self):
+        """
+        Checks whether an error is raised when zero signal is passed to the
+        function.
+        """
+        errStr = ("Attempting to obtain the EEPF should return an error when "
+                  "zero signal is passed, but doesn't.")
+        assert True, errStr