Repository: PlasmaPy/PlasmaPy
Pull request #492 (2018-06-10 07:31:38)

Numpy array tests
Working on issue #469 .  This adds tests to the functions in collisions.py and parameters.py to verify that they can work with nparray Quantities.

The main thing with this one is the addition of a helper function in the utils/pytest_helpers module, assert_can_handle_nparray, which I then used all over test_collision and test_parameters. This new test does test with multi-dimensional nparray Quantities. Comments and improvements welcome!

I was hoping this was going to close the issue, but under the cold light of all the new testing, I think there are a handful more issues, although they are mostly in non-default-argument nooks and crannies. For the time being I just added todo comments. I can address them as part of this pull request if you guys would like, or do a new one.


Patch:
diff --git a/plasmapy/physics/parameters.py b/plasmapy/physics/parameters.py
index 24ff421f..f4b66aeb 100644
--- a/plasmapy/physics/parameters.py
+++ b/plasmapy/physics/parameters.py
@@ -29,7 +29,6 @@
 
 import numpy as np
 # import warnings
-import inspect
 from plasmapy.constants import (m_p, m_e, c, mu0, k_B, e, eps0, pi)
 from plasmapy import atomic, utils
 
diff --git a/plasmapy/physics/transport/collisions.py b/plasmapy/physics/transport/collisions.py
index 577c15b9..4ee7d07c 100644
--- a/plasmapy/physics/transport/collisions.py
+++ b/plasmapy/physics/transport/collisions.py
@@ -322,7 +322,7 @@ def _replaceNanVwithThermalV(V, T, m):
     Handles vector checks for V, you must already know that T and m are okay.
     """
     if np.any(V == 0):
-        raise utils.exceptions.PhysicsError("You cannot have a collision for zero velocity!")
+        raise utils.PhysicsError("You cannot have a collision for zero velocity!")
     # getting thermal velocity of system if no velocity is given
     if V is None:
         V = parameters.thermal_speed(T, mass=m)
@@ -757,6 +757,7 @@ def collision_frequency(T,
         bPerp = impact_parameter_perp(T=T,
                                       particles=particles,
                                       V=V_reduced)
+        print(T, n, particles, z_mean, method)
         # Coulomb logarithm
         cou_log = Coulomb_logarithm(T,
                                     n,
@@ -961,9 +962,8 @@ def fundamental_electron_collision_freq(T_e,
                              )
     coeff = 4 / np.sqrt(np.pi) / 3
 
-
     # accounting for when a Coulomb logarithm value is passed
-    if coulomb_log:
+    if np.any(coulomb_log):
         cLog = Coulomb_logarithm(T_e,
                                  n_e,
                                  particles,
@@ -977,6 +977,7 @@ def fundamental_electron_collision_freq(T_e,
         nu_e = coeff * nu_mod
     else:
         nu_e = coeff * nu
+
     return nu_e.to(1 / u.s)
 
 
@@ -1101,7 +1102,7 @@ def fundamental_ion_collision_freq(T_i,
     coeff = np.sqrt(8 / np.pi) / 3 / 4
 
     # accounting for when a Coulomb logarithm value is passed
-    if coulomb_log:
+    if np.any(coulomb_log):
         cLog = Coulomb_logarithm(T_i,
                                  n_i,
                                  particles,
@@ -1786,7 +1787,7 @@ def coupling_parameter(T,
         fermiIntegral = Fermi_integral(chemicalPotential.si.value, 1.5)
         denom = (n_e * lambda_deBroglie ** 3) * fermiIntegral
         kineticEnergy = 2 * k_B * T / denom
-        if np.imag(kineticEnergy) == 0:
+        if np.all(np.imag(kineticEnergy) == 0):
             kineticEnergy = np.real(kineticEnergy)
         else:
             raise ValueError("Kinetic energy should not be imaginary."
diff --git a/plasmapy/utils/__init__.py b/plasmapy/utils/__init__.py
index 1120bfee..0974f11a 100644
--- a/plasmapy/utils/__init__.py
+++ b/plasmapy/utils/__init__.py
@@ -31,4 +31,5 @@
     IncorrectResultError,
     MissingExceptionError,
     MissingWarningError,
+    assert_can_handle_nparray,
 )


Test patch:
diff --git a/plasmapy/physics/tests/test_parameters.py b/plasmapy/physics/tests/test_parameters.py
index b9974119..65452c56 100644
--- a/plasmapy/physics/tests/test_parameters.py
+++ b/plasmapy/physics/tests/test_parameters.py
@@ -8,6 +8,7 @@
 
 from plasmapy.utils.exceptions import RelativityWarning, RelativityError
 from plasmapy.utils.exceptions import PhysicsError, InvalidParticleError
+from plasmapy.utils.pytest_helpers import assert_can_handle_nparray
 from astropy.constants import c, m_p, m_e, e, mu0
 
 from plasmapy.physics.parameters import (mass_density,
@@ -60,13 +61,20 @@
 
 
 class Test_mass_density:
+    r"""Test the mass_density function in parameters.py."""
+
     def test_particleless(self):
         with pytest.raises(ValueError):
             mass_density(1 * u.m ** -3)
+
     def test_wrong_units(self):
         with pytest.raises(ValueError):
             mass_density(1 * u.J)
 
+    def test_handle_nparrays(self):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(mass_density)
+
 
 # Assertions below that are in CGS units with 2-3 significant digits
 # are generally from the NRL Plasma Formulary.
@@ -174,6 +182,8 @@ def test_Alfven_speed():
                       atol=0.0,
                       rtol=1e-15), errStr
 
+    assert_can_handle_nparray(Alfven_speed)
+
 
 def test_ion_sound_speed():
     r"""Test the ion_sound_speed function in parameters.py."""
@@ -262,9 +272,7 @@ def test_ion_sound_speed():
                       atol=0.0,
                       rtol=1e-15), errStr
 
-
-def test_thermal_pressure():
-    assert thermal_pressure(T_e, n_i).unit.is_equivalent(u.Pa)
+    assert_can_handle_nparray(ion_sound_speed)
 
 
 def test_thermal_speed():
@@ -333,6 +341,15 @@ def test_thermal_speed():
     with pytest.raises(ValueError):
         thermal_speed(T_i, method="sadks")
 
+    assert_can_handle_nparray(thermal_speed)
+
+
+def test_thermal_pressure():
+    assert thermal_pressure(T_e, n_i).unit.is_equivalent(u.Pa)
+
+    # TODO: may be array issues with arg "mass"
+    assert_can_handle_nparray(thermal_pressure)
+
 
 # test class for kappa_thermal_speed() function:
 
@@ -420,6 +437,10 @@ def test_mean1(self):
                           atol=0.0), errStr
         return
 
+    def test_handle_nparrays(self, kwargs={"kappa": 2}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(kappa_thermal_speed, kwargs=kwargs)
+
 
 def test_gyrofrequency():
     r"""Test the gyrofrequency function in parameters.py."""
@@ -497,6 +518,10 @@ def test_gyrofrequency():
                       atol=0.0,
                       rtol=1e-15), errStr
 
+    assert_can_handle_nparray(gyrofrequency, kwargs={"signed": True})
+
+    assert_can_handle_nparray(gyrofrequency, kwargs={"signed": False})
+
 
 def test_gyroradius():
     r"""Test the gyroradius function in parameters.py."""
@@ -598,6 +623,7 @@ def test_gyroradius():
 
 class Test_gyroradius:
 
+    # some custom numpy array tests here, because of the T_i / Vperp situation
     def test_handle_numpy_array(self):
         # Tests to verify that can handle Quantities with numpy array as the value:
         assert gyroradius(B_arr, Vperp=V_arr)[0] == gyroradius(B_arr[0], Vperp=V_arr[0])
@@ -635,9 +661,10 @@ def test_keeps_arguments_unchanged(self):
         Vperp2 = u.Quantity([np.nan, 1], unit=u.m/u.s)  # an exact copy
         T_i = u.Quantity([1, np.nan], unit=u.K)
 
-        gyroradius(B_arr, Vperp=Vperp1, T_i = T_i)
+        gyroradius(B_arr, Vperp=Vperp1, T_i=T_i)
         assert_quantity_allclose(Vperp1, Vperp2)
 
+
 def test_plasma_frequency():
     r"""Test the plasma_frequency function in parameters.py."""
 
@@ -684,6 +711,8 @@ def test_plasma_frequency():
                       atol=0.0,
                       rtol=1e-15), errStr
 
+    assert_can_handle_nparray(plasma_frequency)
+
 
 def test_Debye_length():
     r"""Test the Debye_length function in parameters.py."""
@@ -716,6 +745,8 @@ def test_Debye_length():
     with pytest.warns(u.UnitsWarning):
         assert Debye_length(2.0 * u.K, 2.0) == Debye_length(2.0, 2.0 * u.m ** -3)
 
+    assert_can_handle_nparray(Debye_length)
+
 
 def test_Debye_number():
     r"""Test the Debye_number function in parameters.py."""
@@ -754,6 +785,8 @@ def test_Debye_number():
     with pytest.warns(u.UnitsWarning):
         assert Debye_number(1.1 * u.K, 1.1) == Debye_number(1.1, 1.1 * u.m ** -3)
 
+    assert_can_handle_nparray(Debye_number)
+
 
 def test_inertial_length():
     r"""Test the inertial_length function in parameters.py."""
@@ -800,6 +833,8 @@ def test_inertial_length():
     with pytest.warns(u.UnitsWarning):
         assert inertial_length(1e19) == inertial_length(1e19 * u.m ** -3)
 
+    assert_can_handle_nparray(inertial_length)
+
 
 def test_magnetic_pressure():
     r"""Test the magnetic_pressure function in parameters.py."""
@@ -834,6 +869,8 @@ def test_magnetic_pressure():
     with pytest.warns(u.UnitsWarning):
         assert magnetic_pressure(22.2) == magnetic_pressure(22.2 * u.T)
 
+    assert_can_handle_nparray(magnetic_pressure)
+
 
 def test_magnetic_energy_density():
     r"""Test the magnetic_energy_density function in parameters.py."""
@@ -850,8 +887,6 @@ def test_magnetic_energy_density():
 
     assert_quantity_allclose(magnetic_energy_density(B), magnetic_energy_density(B.to(u.G)))
 
-    # TODO Add an array test!
-
     assert magnetic_energy_density(B_arr)
 
     with pytest.warns(u.UnitsWarning):
@@ -872,6 +907,8 @@ def test_magnetic_energy_density():
     with pytest.warns(u.UnitsWarning):
         assert magnetic_energy_density(22.2) == magnetic_energy_density(22.2 * u.T)
 
+    assert_can_handle_nparray(magnetic_energy_density)
+
 
 def test_upper_hybrid_frequency():
     r"""Test the upper_hybrid_frequency function in parameters.py."""
@@ -897,6 +934,8 @@ def test_upper_hybrid_frequency():
         assert upper_hybrid_frequency(1.4 * u.T, 1.3) == upper_hybrid_frequency(1.4,
                                                                                 1.3 * u.m ** -3)
 
+    assert_can_handle_nparray(upper_hybrid_frequency)
+
 
 def test_lower_hybrid_frequency():
     r"""Test the lower_hybrid_frequency function in parameters.py."""
@@ -927,3 +966,4 @@ def test_lower_hybrid_frequency():
     with pytest.warns(u.UnitsWarning):
         assert lower_hybrid_frequency(1.3, 1e19) == lower_hybrid_frequency(1.3 * u.T,
                                                                            1e19 * u.m ** -3)
+    assert_can_handle_nparray(lower_hybrid_frequency)
diff --git a/plasmapy/physics/transport/tests/test_collisions.py b/plasmapy/physics/transport/tests/test_collisions.py
index eb48ef4b..feb1c115 100644
--- a/plasmapy/physics/transport/tests/test_collisions.py
+++ b/plasmapy/physics/transport/tests/test_collisions.py
@@ -15,6 +15,7 @@
                                                    fundamental_electron_collision_freq,
                                                    fundamental_ion_collision_freq)
 from plasmapy.utils import exceptions
+from plasmapy.utils.pytest_helpers import assert_can_handle_nparray
 from plasmapy.constants import m_p, m_e, c
 
 
@@ -43,6 +44,19 @@ def setup_class(self):
         self.gms6 = 3.635342040477818
         self.gms6_negative = 0.030720859361047514
 
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    @pytest.mark.parametrize("kwargs", [{"method": "classical"},
+                                        {"method": "GMS-1"},
+                                        {"method": "GMS-2", "z_mean": 1.0},
+                                        {"method": "GMS-3"},
+                                        {"method": "GMS-4"},
+                                        {"method": "GMS-5", "z_mean": 1.0},
+                                        {"method": "GMS-6", "z_mean": 1.0}, ])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(Coulomb_logarithm, insert_some_nans, insert_all_nans, kwargs)
+
     def test_unknown_method(self):
         """Test that function will raise ValueError on non-existent method"""
         with pytest.raises(ValueError):
@@ -79,24 +93,6 @@ def test_handle_zero_V(self):
                               z_mean=1 * u.dimensionless_unscaled,
                               V=0 * u.m / u.s)
 
-    @pytest.mark.parametrize("method", ["classical", "GMS-1", "GMS-2", "GMS-3", "GMS-4", "GMS-5",
-                                        "GMS-6"])
-    def test_handle_numpy_array(self, method):
-        """Tests to verify that can handle Quantities with numpy array as the value"""
-        methodVal = Coulomb_logarithm(self.T_arr,
-                                      self.n_arr,
-                                      self.particles,
-                                      z_mean=1 * u.dimensionless_unscaled,
-                                      V=np.nan * u.m / u.s,
-                                      method=method)
-        methodVal_0 = Coulomb_logarithm(self.T_arr[0],
-                                        self.n_arr[0],
-                                        self.particles,
-                                        z_mean=1 * u.dimensionless_unscaled,
-                                        V=np.nan * u.m / u.s,
-                                        method=method)
-        assert_quantity_allclose(methodVal[0], methodVal_0)
-
     def test_handle_V_arraysizes(self):
         """Test that different sized V input array gets handled by _boilerplate"""
         methodVal_0 = Coulomb_logarithm(self.T_arr[0],
@@ -590,6 +586,14 @@ def test_fail1(self):
                   f"should not be equal to {fail1}.")
         assert testTrue, errStr
 
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans,
+                             insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(impact_parameter_perp, insert_some_nans,
+                                  insert_all_nans, kwargs)
+
     assert np.isclose(Coulomb_logarithm(1 * u.eV, 5 * u.m ** -3, ('e', 'e')),
                       Coulomb_logarithm(11604.5220 * u.K,
                                         5 * u.m ** -3,
@@ -666,23 +670,18 @@ def test_bad_method(self):
                              V=np.nan * u.m / u.s,
                              method="meow")
 
-    @pytest.mark.parametrize("method", ["classical", "GMS-1", "GMS-2", "GMS-3", "GMS-4", "GMS-5",
-                                        "GMS-6"])
-    def test_handle_numpy_array(self, method):
-        """Tests to verify that can handle Quantities with numpy array as the value"""
-        methodVal = impact_parameter(self.T_arr,
-                                     self.n_e_arr,
-                                     self.particles,
-                                     z_mean=1 * u.dimensionless_unscaled,
-                                     V=np.nan * u.m / u.s,
-                                     method=method)
-        methodVal_0 = impact_parameter(self.T_arr[0],
-                                       self.n_e_arr[0],
-                                       self.particles,
-                                       z_mean=1 * u.dimensionless_unscaled,
-                                       V=np.nan * u.m / u.s,
-                                       method=method)
-        assert_quantity_allclose((methodVal[0][0], methodVal[1][0]), methodVal_0)
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    @pytest.mark.parametrize("kwargs", [{"method": "classical"},
+                                        {"method": "GMS-1"},
+                                        {"method": "GMS-2", "z_mean": 1.0},
+                                        {"method": "GMS-3"},
+                                        {"method": "GMS-4"},
+                                        {"method": "GMS-5", "z_mean": 1.0},
+                                        {"method": "GMS-6", "z_mean": 1.0}, ])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(impact_parameter, insert_some_nans, insert_all_nans, kwargs)
 
     def test_extend_scalar_bmin(self):
         """
@@ -756,6 +755,15 @@ def test_fail1(self):
                   f"should not be equal to {fail1}.")
         assert testTrue, errStr
 
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    @pytest.mark.parametrize("kwargs", [{"particles": ("e", "e")},
+                                        {"particles": ("e", "p")},
+                                        {"particles": ("p", "p")}, ])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(collision_frequency, insert_some_nans, insert_all_nans, kwargs)
+
     def test_electrons(self):
         """
         Testing collision frequency between electrons.
@@ -823,17 +831,13 @@ def setup_class(self):
         self.ion_particle = 'p'
         self.coulomb_log = 10
 
-    def test_handle_numpy_array(self):
-        """Tests to verify that can handle Quantities with numpy array as the value"""
-        methodVal = fundamental_electron_collision_freq(self.T_arr,
-                                                        self.n_arr,
-                                                        self.ion_particle,
-                                                        coulomb_log=self.coulomb_log)
-        methodVal_0 = fundamental_electron_collision_freq(self.T_arr[0],
-                                                          self.n_arr[0],
-                                                          self.ion_particle,
-                                                          coulomb_log=self.coulomb_log)
-        assert_quantity_allclose(methodVal[0], methodVal_0)
+    # TODO: array coulomb log
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(fundamental_electron_collision_freq, insert_some_nans,
+                                  insert_all_nans, kwargs)
 
 
 class Test_fundamental_ion_collision_freq():
@@ -845,17 +849,13 @@ def setup_class(self):
         self.ion_particle = 'p'
         self.coulomb_log = 10
 
-    def test_handle_numpy_array(self):
-        """Tests to verify that can handle Quantities with numpy array as the value"""
-        methodVal = fundamental_ion_collision_freq(self.T_arr,
-                                                   self.n_arr,
-                                                   self.ion_particle,
-                                                   coulomb_log=self.coulomb_log)
-        methodVal_0 = fundamental_ion_collision_freq(self.T_arr[0],
-                                                     self.n_arr[0],
-                                                     self.ion_particle,
-                                                     coulomb_log=self.coulomb_log)
-        assert_quantity_allclose(methodVal[0], methodVal_0)
+    # TODO: array coulomb log
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(fundamental_ion_collision_freq, insert_some_nans,
+                                  insert_all_nans, kwargs)
 
 
 class Test_mean_free_path:
@@ -914,6 +914,12 @@ def test_fail1(self):
                   f"should not be equal to {fail1}.")
         assert testTrue, errStr
 
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(mean_free_path, insert_some_nans, insert_all_nans, kwargs)
+
 
 class Test_Spitzer_resistivity:
     @classmethod
@@ -986,6 +992,13 @@ def test_zmean(self):
                   f"not {methodVal}.")
         assert testTrue, errStr
 
+    # TODO vector z_mean
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(Spitzer_resistivity, insert_some_nans, insert_all_nans, kwargs)
+
 
 class Test_mobility:
     @classmethod
@@ -1061,6 +1074,13 @@ def test_zmean(self):
                   f"not {methodVal}.")
         assert testTrue, errStr
 
+    # TODO vector z_mean
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(mobility, insert_some_nans, insert_all_nans, kwargs)
+
 
 class Test_Knudsen_number:
     @classmethod
@@ -1121,6 +1141,12 @@ def test_fail1(self):
                   f"should not be equal to {fail1}.")
         assert testTrue, errStr
 
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    def test_handle_nparrays(self, insert_some_nans, insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(Knudsen_number, insert_some_nans, insert_all_nans, kwargs)
+
 
 class Test_coupling_parameter:
     @classmethod
@@ -1196,6 +1222,17 @@ def test_zmean(self):
                   f"not {methodVal}.")
         assert testTrue, errStr
 
+    # TODO vector z_mean
+    @pytest.mark.parametrize("insert_some_nans", [[], ["V"]])
+    @pytest.mark.parametrize("insert_all_nans", [[], ["V"]])
+    # @pytest.mark.parametrize("kwargs", [{"method": "classical"},
+    #                                     {"method": "quantum"},])   # TODO quantum issues
+    def test_handle_nparrays(self, insert_some_nans,
+                             insert_all_nans, kwargs={}):
+        """Test for ability to handle numpy array quantities"""
+        assert_can_handle_nparray(coupling_parameter, insert_some_nans,
+                                  insert_all_nans, kwargs)
+
     def test_quantum(self):
         """
         Testing quantum method for coupling parameter.
diff --git a/plasmapy/utils/pytest_helpers.py b/plasmapy/utils/pytest_helpers.py
index 0e67877c..0f0e2dd0 100644
--- a/plasmapy/utils/pytest_helpers.py
+++ b/plasmapy/utils/pytest_helpers.py
@@ -8,6 +8,7 @@
 import astropy.units as u
 import astropy.constants as const
 import colorama
+import astropy.tests.helper as astrohelper
 
 # These colors/styles are used to highlight certain parts of the error
 # messages in consistent ways.
@@ -719,3 +720,157 @@ def run_test_equivalent_calls(*test_inputs, require_same_type: bool = True):
                 f"of type {test_case['type']}")
 
         raise UnexpectedResultError(errmsg)
+
+
+def assert_can_handle_nparray(function_to_test, insert_some_nans=[], insert_all_nans=[],
+                              kwargs={}):
+    """
+    Test for ability to handle numpy array quantities.
+
+    Parameters
+    ----------
+    function_to_test
+        The function to be tested for ability to handle numpy array quantities.
+        Arguments are automatically given a vector input based on their
+        variable name. Current args that are interpreted as vectors are:
+        `["T", "T_i", "T_e", "temperature"]`
+        `["n", "n_i", "n_e", "density"]`
+        `["B"]`
+        `["V", "Vperp"]`
+        `["coulomb_log"]`
+        `["characteristic_length"]`
+
+    insert_some_nans: list
+        List of argument names in which to insert some np.nan values.
+        These must be arguments that will be tested as vectors as listed
+        above.
+
+    insert_all_nans: list
+        List of argument names to fill entirely with np.nan values.
+
+    kwargs: dict
+        Arguments to pass directly to the function in under test, in the
+        normal kwargs python dictionary format.
+
+    Raises
+    ------
+    ~ValueError
+        If this function cannot interpret a parameter of function_to_test,
+
+    Examples
+    --------
+    >>> from plasmapy.physics.parameters import Alfven_speed, gyrofrequency
+    >>> assert_can_handle_nparray(Alfven_speed)
+    >>> assert_can_handle_nparray(gyrofrequency, kwargs={"signed": True})
+    >>> assert_can_handle_nparray(gyrofrequency, kwargs={"signed": False})
+    """
+    def _prepare_input(param_name, param_default, insert_some_nans, insert_all_nans, kwargs):
+        """
+        Parse parameter names and set up values to input for 0d, 1d, and 2d array tests.
+        """
+        # first things first: let any passed in kwarg right through (VIP access)
+        if param_name in kwargs.keys():
+            return (kwargs[param_name], ) * 4
+
+        # else, if it's a recognized variable name, give it a reasonable unit and magnitude
+        elif param_name in ["particle", "ion_particle", "ion"]:
+            if not (param_default is inspect._empty or param_default is None):
+                return (param_default, ) * 4
+            else:
+                return ("p", ) * 4
+        elif param_name == "particles":
+            if not (param_default is inspect._empty):
+                return (param_default, ) * 4
+            else:
+                return (("e", "p"), ) * 4
+        elif param_name in ["T", "T_i", "T_e", "temperature"]:
+            unit = u.eV
+            magnitude = 1.0
+        elif param_name in ["n", "n_i", "n_e", "density"]:
+            unit = u.m ** -3
+            magnitude = 1e20
+        elif param_name == "B":
+            unit = u.G
+            magnitude = 1e3
+        elif param_name in ["V", "Vperp"]:
+            unit = u.m / u.s
+            magnitude = 1e5
+        elif param_name == "coulomb_log":
+            unit = 1.0
+            magnitude = 1e1
+        elif param_name == "characteristic_length":
+            unit = u.m
+            magnitude = 1.0
+
+        # else, last resort, if it has a default argument, go with that:
+        elif not (param_default is inspect._empty):
+            return (param_default, ) * 4
+
+        else:
+            raise ValueError("Unrecognized function input")
+
+        # now knowing unit and magnitude, set up the 0d, 1d, 2d, and 3d arrays:
+        input_data_3d = np.reshape(np.arange(1.0, 9.0, 1.0), (2, 2, 2))
+        input_data_2d = np.reshape(np.arange(1.0, 5.0, 1.0), (2, 2))
+        input_data_1d = np.arange(1.0, 5.0, 1.0)
+        if param_name in insert_some_nans:
+            input_data_3d[0, 0, 1] = np.nan
+            input_data_3d[0, 1, 0] = np.nan
+            input_data_2d[0, 1] = np.nan
+            input_data_2d[1, 0] = np.nan
+            input_data_1d[1] = np.nan
+        elif param_name in insert_all_nans:
+            input_data_3d = np.ones((2, 2, 2)) * np.nan
+            input_data_2d = np.ones((2, 2)) * np.nan
+            input_data_1d = np.ones(4) * np.nan
+        input_data_3d *= magnitude
+        input_data_3d *= unit
+        input_data_2d *= magnitude
+        input_data_2d *= unit
+        input_data_1d *= magnitude
+        input_data_1d *= unit
+        input_data_0d = input_data_1d[3]
+        return input_data_0d, input_data_1d, input_data_2d, input_data_3d
+
+    #
+    # *** body of assert_can_handle_nparray function ***
+    #
+
+    # call _prepare_input to prepare 0d, 1d, and 2d sets of arguments for the function:
+    function_sig = inspect.signature(function_to_test)
+    function_params = function_sig.parameters
+    args_0d = dict()
+    args_1d = dict()
+    args_2d = dict()
+    args_3d = dict()
+    param_names = [elm for elm in function_params.keys()]
+    for idx, key in enumerate(function_params):
+        args_0d[key], args_1d[key], args_2d[key], args_3d[key] = _prepare_input(
+            param_names[idx],
+            function_params[key].default,
+            insert_some_nans,
+            insert_all_nans,
+            kwargs,
+            )
+
+    # call the function with the prepared argument sets:
+    result_0d = function_to_test(**args_0d)
+    result_1d = function_to_test(**args_1d)
+    result_2d = function_to_test(**args_2d)
+    result_3d = function_to_test(**args_3d)
+
+    # assert that the 1d, 2d, 3d versions get the same result (elementwise) as the 0d version:
+    # (if the function returns multiple values, loop through and test each)
+    try:
+        scalar_testable = result_0d.value
+    except AttributeError:
+        scalar_testable = result_0d
+    if np.isscalar(scalar_testable):
+        astrohelper.assert_quantity_allclose(result_0d, result_1d[3])
+        astrohelper.assert_quantity_allclose(result_0d, result_2d[1, 1])
+        astrohelper.assert_quantity_allclose(result_0d, result_3d[0, 1, 1])
+    else:
+        for idx, res_0d in enumerate(result_0d):
+            astrohelper.assert_quantity_allclose(res_0d, result_1d[idx][3])
+            astrohelper.assert_quantity_allclose(res_0d, result_2d[idx][1, 1])
+            astrohelper.assert_quantity_allclose(res_0d, result_3d[idx][0, 1, 1])