pysat/pysatMissions

View on GitHub
pysatMissions/tests/test_instruments.py

Summary

Maintainability
A
1 hr
Test Coverage
# Full author list can be found in .zenodo.json file
# DOI:10.5281/zenodo.3475498
#
# DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
# unlimited.
# ----------------------------------------------------------------------------
"""Unit and Integration Tests for each instrument module.

Note
----
Imports test methods from pysat.tests.instrument_test_class

"""

import datetime as dt
import numpy as np
import warnings

import pytest

# Make sure to import your instrument package here:
import pysatMissions

# Import the test classes from pysat
from pysat.tests.classes import cls_instrument_library as clslib


# Developers for instrument libraries should update the following line to
# point to their own subpackage location
# e.g.,
# instruments = generate_instrument_list(inst_loc=mypackage.inst)
instruments = clslib.InstLibTests.initialize_test_package(
    clslib.InstLibTests, inst_loc=pysatMissions.instruments)

# Build a custom list of sgp4 instruments.  This can be reverted to the standard
# when the deprected instruments are removed.
instruments['sgp4'] = []
for inst in instruments['download']:
    if 'sgp4' in inst['inst_module'].name:
        instruments['sgp4'].append(inst)
    if 'skyfield' in inst['inst_module'].name:
        instruments['sgp4'].append(inst)


class TestInstruments(clslib.InstLibTests):
    """Main class for instrument tests.

    Note
    ----
    Uses class level setup and teardown so that all tests use the same
    temporary directory. We do not want to geneate a new tempdir for each test,
    as the load tests need to be the same as the download tests.

    """

    @pytest.mark.parametrize("inst_dict", [x for x in instruments['download']])
    @pytest.mark.parametrize("in_cad,out_cad,num_samples",
                             [(None, 1, 86400), ('10s', 10, 8640)])
    def test_inst_cadence(self, inst_dict, in_cad, out_cad, num_samples):
        """Test operation of cadence keyword, including default behavior.

        Parameters
        ----------
        inst_dict : dict
            Dictionary of instrument properties generated by pysat.
        in_cad : pds.freq or NoneType
            Input cadence as a frequency string
        out_cad : int
            Expected output cadence in seconds
        num_samples : int
            Expected output num_samples.  Only valid for sgp4 instrument.

        """

        if in_cad:
            inst_dict['kwargs'] = {'cadence': in_cad}

        self.test_inst, date = clslib.initialize_test_inst_and_date(inst_dict)
        self.test_inst.load(date=date)
        cadence = np.diff(self.test_inst.data.index.to_pydatetime())
        assert np.all(cadence == dt.timedelta(seconds=out_cad))

        if self.test_inst.name == 'sgp4':
            # Additional check for sgp4
            assert len(self.test_inst.data) == num_samples

        return

    @pytest.mark.parametrize("inst_dict", [x for x in instruments['sgp4']])
    @pytest.mark.parametrize("kwargs",
                             [{},
                              {'inclination': 20, 'alt_periapsis': 400},
                              {'inclination': 80, 'alt_periapsis': 500,
                               'alt_apoapsis': 600,
                               'epoch': dt.datetime(2019, 1, 1)}])
    def test_sgp4_data_continuity(self, inst_dict, kwargs):
        """Test that data is continuous for sequential days.

        Parameters
        ----------
        inst_dict : dict
            Dictionary of instrument properties generated by pysat.
        kwargs : dict
            Optional kwargs to pass through.  If empty, instrument will be
            default TLEs.

        """

        # Define sat with custom Keplerian inputs
        inst_dict['kwargs'] = kwargs
        self.test_inst, date = clslib.initialize_test_inst_and_date(inst_dict)

        # Get last 10 points of day 1
        self.test_inst.load(date=date)
        day1 = self.test_inst.data[-10:]

        # Get first 10 points of day 2
        self.test_inst.load(date=(date + dt.timedelta(days=1)))
        day2 = self.test_inst.data[:10]

        average_gradient = day1.diff().mean()
        std_gradient = day1.diff().std()
        gradient_between_days = day2.iloc[0] - day1.iloc[-1]

        # Check that the jump between days is within 3 sigma of average gradient
        del_g = np.abs(average_gradient - gradient_between_days)
        assert np.all(del_g < (3. * std_gradient)), \
            "Gap between days outside of 3 sigma"

        return

    @pytest.mark.parametrize("inst_dict", [x for x in instruments['sgp4']])
    @pytest.mark.parametrize(
        "kw_dict",
        [{'one_orbit': True},
         {'inclination': 13, 'alt_periapsis': 400, 'alt_apoapsis': 850,
          'bstar': 0, 'arg_periapsis': 0., 'raan': 0., 'mean_anomaly': 0.},
         {'tle1': '1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998',
          'tle2': '2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452'}
         ])
    def test_sgp4_options(self, inst_dict, kw_dict):
        """Test optional keywords for sgp4.

        Parameters
        ----------
        inst_dict : dict
            Dictionary of instrument properties generated by pysat.
        kw_dict : dict
            Dictionary of kwargs to pass through to the sgp4 instrument.

        """

        target = 'Fake Data to be cleared'

        inst_dict['kwargs'] = kw_dict
        self.test_inst, date = clslib.initialize_test_inst_and_date(inst_dict)

        self.test_inst.data = [target]
        self.test_inst.load(date=date)

        # If target is cleared, load has run successfully
        assert target not in self.test_inst.data
        return

    @pytest.mark.parametrize("inst_dict", [x for x in instruments['sgp4']])
    @pytest.mark.parametrize(
        "kw_dict",
        [{'inclination': 13, 'alt_apoapsis': 850},
         {'tle1': '1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998'}
         ])
    def test_sgp4_options_errors(self, inst_dict, kw_dict):
        """Test optional keyword combos for sgp4 that generate errors.

        Parameters
        ----------
        inst_dict : dict
            Dictionary of instrument properties generated by pysat.
        kw_dict : dict
            Dictionary of kwargs to pass through to the sgp4 instrument.

        """

        inst_dict['kwargs'] = kw_dict
        with pytest.raises(KeyError) as kerr:
            self.test_inst = clslib.initialize_test_inst_and_date(inst_dict)

        assert str(kerr).find('Insufficient kwargs') >= 0

        return

    @pytest.mark.parametrize("inst_dict", [x for x in instruments['sgp4']])
    @pytest.mark.parametrize(
        "kw_dict",
        [{'inclination': 13, 'alt_periapsis': 400, 'alt_apoapsis': 850,
          'bstar': 0, 'arg_periapsis': 0., 'raan': 0., 'mean_anomaly': 0.,
          'tle1': '1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998',
          'tle2': '2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452'}
         ])
    def test_sgp4_options_warnings(self, inst_dict, kw_dict):
        """Test optional keyword combos for sgp4 that generate warnings.

        Parameters
        ----------
        inst_dict : dict
            Dictionary of instrument properties generated by pysat.
        kw_dict : dict
            Dictionary of kwargs to pass through to the sgp4 instrument.

        """

        inst_dict['kwargs'] = kw_dict
        with warnings.catch_warnings(record=True) as war:
            self.test_inst = clslib.initialize_test_inst_and_date(inst_dict)

        assert len(war) >= 1
        categories = [war[j].category for j in range(0, len(war))]
        assert UserWarning in categories

        return