From 728530f786bb5bc121194f8119edbd911710211f Mon Sep 17 00:00:00 2001 From: Russell Manser Date: Wed, 3 Jun 2020 14:07:05 -0500 Subject: [PATCH] Update tests for wind_components in calc module This commit is a draft of implementing unit tests for data types in each calc function and parameterizing tests for edge cases. - Add fixtures in conftest.py containing test data for each supported data type. - Add/update tests for individual data types using fixtures. - Group tests into a class. - Add fixture for truth values specific to the test class. - Parameterize the edge case. Makes progress toward closing #1214 --- conftest.py | 39 +++++++++++ tests/calc/test_basic.py | 143 +++++++++++++++++++++++++++++---------- 2 files changed, 148 insertions(+), 34 deletions(-) diff --git a/conftest.py b/conftest.py index 48645841be6..6d069037667 100644 --- a/conftest.py +++ b/conftest.py @@ -14,6 +14,7 @@ import xarray import metpy.calc +from metpy.units import units # Need to disable fallback before importing pint os.environ['PINT_ARRAY_PROTOCOL_FALLBACK'] = '0' @@ -35,3 +36,41 @@ def doctest_available_modules(doctest_namespace): doctest_namespace['metpy'] = metpy doctest_namespace['metpy.calc'] = metpy.calc doctest_namespace['plt'] = matplotlib.pyplot + + +@pytest.fixture(scope="session") +def scalars(): + return { + 'speed': 4 * units.mph, + 'dirs': 45 * units.deg, + } + + +@pytest.fixture(scope="session") +def arrays(): + return { + 'speed': numpy.array([4, 4, 4, 4, 25, 25, 25, 25, 10.]) * units.mph, + 'dirs': numpy.array([0, 45, 90, 135, 180, 225, 270, 315, 360]) * units.deg, + } + + +@pytest.fixture(scope="session") +def masked(): + mask = numpy.array([True, True, False, True, True, False, True, True, False]) + return { + 'mask': mask, + 'speed': units.Quantity( + numpy.ma.array([4, 4, 4, 4, 25, 25, 25, 25, 10.], mask=mask), units.mph + ), + 'dirs': units.Quantity( + numpy.ma.array([0, 45, 90, 135, 180, 225, 270, 315, 360], mask=mask), units.deg + ), + } + + +@pytest.fixture(scope="session") +def nans(): + return { + 'speed': numpy.array([numpy.nan, 4, numpy.nan, 4, numpy.nan, 25, numpy.nan, 25, numpy.nan]) * units.mph, + 'dirs': numpy.array([numpy.nan, 45, numpy.nan, 135, numpy.nan, 225, numpy.nan, 315, numpy.nan]) * units.deg, + } diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index 4d30c7f8305..a9ac64597fc 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -19,40 +19,115 @@ from metpy.units import units -def test_wind_comps_basic(): - """Test the basic wind component calculation.""" - speed = np.array([4, 4, 4, 4, 25, 25, 25, 25, 10.]) * units.mph - dirs = np.array([0, 45, 90, 135, 180, 225, 270, 315, 360]) * units.deg - s2 = np.sqrt(2.) - - u, v = wind_components(speed, dirs) - - true_u = np.array([0, -4 / s2, -4, -4 / s2, 0, 25 / s2, 25, 25 / s2, 0]) * units.mph - true_v = np.array([-4, -4 / s2, 0, 4 / s2, 25, 25 / s2, 0, -25 / s2, -10]) * units.mph - - assert_array_almost_equal(true_u, u, 4) - assert_array_almost_equal(true_v, v, 4) - - -def test_wind_comps_with_north_and_calm(): - """Test that the wind component calculation handles northerly and calm winds.""" - speed = np.array([0, 5, 5]) * units.mph - dirs = np.array([0, 360, 0]) * units.deg - - u, v = wind_components(speed, dirs) - - true_u = np.array([0, 0, 0]) * units.mph - true_v = np.array([0, -5, -5]) * units.mph - - assert_array_almost_equal(true_u, u, 4) - assert_array_almost_equal(true_v, v, 4) - - -def test_wind_comps_scalar(): - """Test wind components calculation with scalars.""" - u, v = wind_components(8 * units('m/s'), 150 * units.deg) - assert_almost_equal(u, -4 * units('m/s'), 3) - assert_almost_equal(v, 6.9282 * units('m/s'), 3) +class TestWindComponents: + """Test the wind components function""" + + @pytest.fixture(scope="session") + def truth(self): + s2 = np.sqrt(2.) + return { + 'u_scalar': -4 / s2 * units.mph, + 'v_scalar': -4 / s2 * units.mph, + 'u_array': np.array([0, -4 / s2, -4, -4 / s2, 0, 25 / s2, 25, 25 / s2, 0]) * units.mph, + 'v_array': np.array([-4, -4 / s2, 0, 4 / s2, 25, 25 / s2, 0, -25 / s2, -10]) * units.mph, + } + + def test_scalars(self, truth, scalars): + """Test with scalars.""" + u, v = wind_components(scalars['speed'], scalars['dirs']) + + assert_almost_equal(truth['u_scalar'], u, 4) + assert_almost_equal(truth['v_scalar'], v, 4) + + + def test_arrays(self, truth, arrays): + """Test with arrays.""" + u, v = wind_components(arrays['speed'], arrays['dirs']) + + assert_array_almost_equal(truth['u_array'], u, 4) + assert_array_almost_equal(truth['v_array'], v, 4) + + + def test_masked(self, truth, masked): + """Test with masked arrays.""" + u, v = wind_components(masked['speed'], masked['dirs']) + + true_u = units.Quantity( + np.ma.array(truth['u_array'], mask=masked['mask']), units.mph) + true_v = units.Quantity( + np.ma.array(truth['v_array'], mask=masked['mask']), units.mph) + + assert_array_almost_equal(true_u, u, 4) + assert_array_almost_equal(true_v, v, 4) + + + def test_nans(self, truth, nans): + """Test with nans.""" + u, v = wind_components(nans['speed'], nans['dirs']) + + true_u = truth['u_array'] + true_u[::2] = np.nan + true_v = truth['v_array'] + true_v[::2] = np.nan + + assert_array_almost_equal(true_u, u, 4) + assert_array_almost_equal(true_v, v, 4) + + + @pytest.mark.parametrize( + "speed, dirs, true_u, true_v", + [ + # Test northerly and calm winds + pytest.param( + np.array([0, 5, 5]) * units.mph, + np.array([0, 360, 0]) * units.deg, + np.array([0, 0, 0]) * units.mph, + np.array([0, -5, -5]) * units.mph + ) + ] + ) + def test_edge_cases(self, speed, dirs, true_u, true_v): + """Test that the wind component calculation handles northerly and calm winds.""" + u, v = wind_components(speed, dirs) + + assert_array_almost_equal(true_u, u, 4) + assert_array_almost_equal(true_v, v, 4) + + +# def test_wind_comps_basic(): +# """Test the basic wind component calculation.""" +# speed = np.array([4, 4, 4, 4, 25, 25, 25, 25, 10.]) * units.mph +# dirs = np.array([0, 45, 90, 135, 180, 225, 270, 315, 360]) * units.deg +# s2 = np.sqrt(2.) +# +# u, v = wind_components(speed, dirs) +# +# true_u = np.array([0, -4 / s2, -4, -4 / s2, 0, 25 / s2, 25, 25 / s2, 0]) * units.mph +# true_v = np.array([-4, -4 / s2, 0, 4 / s2, 25, 25 / s2, 0, -25 / s2, -10]) * units.mph +# +# assert_array_almost_equal(true_u, u, 4) +# assert_array_almost_equal(true_v, v, 4) +# +# +# def test_wind_comps_with_north_and_calm(): +# """Test that the wind component calculation handles northerly and calm winds.""" +# speed = np.array([0, 5, 5]) * units.mph +# dirs = np.array([0, 360, 0]) * units.deg +# +# u, v = wind_components(speed, dirs) +# +# true_u = np.array([0, 0, 0]) * units.mph +# true_v = np.array([0, -5, -5]) * units.mph +# +# assert_array_almost_equal(true_u, u, 4) +# assert_array_almost_equal(true_v, v, 4) +# +# +# def test_wind_comps_scalar(): +# """Test wind components calculation with scalars.""" +# u, v = wind_components(8 * units('m/s'), 150 * units.deg) +# assert_almost_equal(u, -4 * units('m/s'), 3) +# assert_almost_equal(v, 6.9282 * units('m/s'), 3) def test_speed():