Completed
Pull Request — develop (#73)
by Angeline
11:09
created

test_helpers.TestHelpers.test_gc2gdlat()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 12
rs 9.85
c 0
b 0
f 0
cc 1
nop 3
1
# -*- coding: utf-8 -*-
2
"""Test the apexpy.helper submodule
3
4
Notes
5
-----
6
Whenever function outputs are tested against hard-coded numbers, the test
7
results (numbers) were obtained by running the code that is tested.  Therefore,
8
these tests below only check that nothing changes when refactoring, etc., and
9
not if the results are actually correct.
10
11
These results are expected to change when IGRF is updated.
12
13
"""
14
15
import datetime as dt
16
import numpy as np
17
import pytest
18
19
from apexpy import helpers
20
21
22
class TestHelpers():
23
    def setup(self):
24
        self.in_shape = None
25
        self.calc_val = None
26
        self.test_val = None
27
28
    def teardown(self):
29
        del self.in_shape, self.calc_val, self.test_val
30
31
    def eval_output(self, rtol=1e-7, atol=0.0):
32
        """Evaluate the values and shape of the calculated and expected output.
33
        """
34
        np.testing.assert_allclose(self.calc_val, self.test_val, rtol=rtol,
35
                                   atol=atol)
36
        assert np.asarray(self.calc_val).shape == self.in_shape
37
        return
38
39
    def datetime64_to_datetime(self, dt64):
40
        """Convert numpy datetime64 object to a datetime datetime object.
41
42
        Parameters
43
        ----------
44
        dt64 : np.datetime64
45
            Numpy datetime64 object
46
47
        Returns
48
        -------
49
        dt.datetime
50
            Equivalent datetime object with a resolution of days
51
52
        Notes
53
        -----
54
        Works outside 32 bit int second range of 1970
55
56
        """
57
        year_floor = dt64.astype('datetime64[Y]')
58
        month_floor = dt64.astype('datetime64[M]')
59
        day_floor = dt64.astype('datetime64[D]')
60
        year = year_floor.astype(int) + 1970
61
        month = (month_floor
62
                 - year_floor).astype('timedelta64[M]').astype(int) + 1
63
        day = (day_floor - month_floor).astype('timedelta64[D]').astype(int) + 1
64
        return dt.datetime(year, month, day)
65
66
    @pytest.mark.parametrize('lat', [90, 0, -90, np.nan])
67
    def test_checklat_scalar(self, lat):
68
        """Test good latitude check with scalars."""
69
        if np.isnan(lat):
70
            assert np.isnan(helpers.checklat(lat))
71
        else:
72
            assert helpers.checklat(lat) == lat
73
        return
74
75
    @pytest.mark.parametrize('lat', [(90 + 1e-5), (-90 - 1e-5)])
76
    def test_checklat_scalar_clip(self, lat):
77
        """Test good latitude check with scalars just beyond the lat limits."""
78
        assert helpers.checklat(lat) == np.sign(lat) * np.floor(abs(lat))
79
        return
80
81
    @pytest.mark.parametrize('in_args,msg',
82
                             [([90 + 1e-4], "lat must be in"),
83
                              ([-90 - 1e-4, 'glat'], "glat must be in"),
84
                              ([[-90 - 1e-5, -90, 0, 90, 90 + 1e-4], 'glat'],
85
                               "glat must be in"),
86
                              ([[-90 - 1e-4, -90, np.nan, np.nan, 90 + 1e-5]],
87
                               'lat must be in')])
88
    def test_checklat_error(self, in_args, msg):
89
        """Test bad latitude raises ValueError with appropriate message."""
90
        with pytest.raises(ValueError) as verr:
91
            helpers.checklat(*in_args)
92
93
        assert str(verr.value).startswith(msg)
94
        return
95
96
    @pytest.mark.parametrize('lat,test_lat',
97
                             [(np.linspace(-90 - 1e-5, 90 + 1e-5, 3),
98
                               [-90, 0, 90]),
99
                              (np.linspace(-90, 90, 3), [-90, 0, 90]),
100
                              ([-90 - 1e-5, 0, 90, np.nan],
101
                               [-90, 0, 90, np.nan])])
102
    def test_checklat_array(self, lat, test_lat):
103
        """Test good latitude with finite values."""
104
        self.calc_val = helpers.checklat(lat)
105
        self.in_shape = np.asarray(lat).shape
106
        self.test_val = test_lat
107
        self.eval_output(atol=1e-8)
108
        return
109
110
    @pytest.mark.parametrize('lat,test_sin', [
111
        (60, 0.96076892283052284), (10, 0.33257924500670238),
112
        ([60, 10], [0.96076892283052284, 0.33257924500670238]),
113
        ([[60, 10], [60, 10]], [[0.96076892283052284, 0.33257924500670238],
114
                                [0.96076892283052284, 0.33257924500670238]])])
115
    def test_getsinIm(self, lat, test_sin):
116
        """Test sin(Im) calculation for scalar and array inputs."""
117
        self.calc_val = helpers.getsinIm(lat)
118
        self.in_shape = np.asarray(lat).shape
119
        self.test_val = test_sin
120
        self.eval_output()
121
        return
122
123
    @pytest.mark.parametrize('lat,test_cos', [
124
        (60, 0.27735009811261463), (10, 0.94307531289434765),
125
        ([60, 10], [0.27735009811261463, 0.94307531289434765]),
126
        ([[60, 10], [60, 10]], [[0.27735009811261463, 0.94307531289434765],
127
                                [0.27735009811261463, 0.94307531289434765]])])
128
    def test_getcosIm(self, lat, test_cos):
129
        """Test cos(Im) calculation for scalar and array inputs."""
130
        self.calc_val = helpers.getcosIm(lat)
131
        self.in_shape = np.asarray(lat).shape
132
        self.test_val = test_cos
133
        self.eval_output()
134
        return
135
136
    @pytest.mark.parametrize('in_time,year', [
137
        (dt.datetime(2001, 1, 1), 2001), (dt.date(2001, 1, 1), 2001),
138
        (dt.datetime(2002, 1, 1), 2002),
139
        (dt.datetime(2005, 2, 3, 4, 5, 6), 2005.090877283105),
140
        (dt.datetime(2005, 12, 11, 10, 9, 8), 2005.943624682902)])
141
    def test_toYearFraction(self, in_time, year):
142
        """Test the datetime to fractional year calculation."""
143
        self.calc_val = helpers.toYearFraction(in_time)
144
        np.testing.assert_allclose(self.calc_val, year)
145
        return
146
147
    @pytest.mark.parametrize('gc_lat,gd_lat', [
148
        (0, 0), (90, 90), (30, 30.166923849507356), (60, 60.166364190170931),
149
        ([0, 90, 30], [0, 90, 30.166923849507356]),
150
        ([[0, 30], [90, 60]], [[0, 30.16692384950735],
151
                               [90, 60.166364190170931]])])
152
    def test_gc2gdlat(self, gc_lat, gd_lat):
153
        """Test geocentric to geodetic calculation."""
154
        self.calc_val = helpers.gc2gdlat(gc_lat)
155
        self.in_shape = np.asarray(gc_lat).shape
156
        self.test_val = gd_lat
157
        self.eval_output()
158
        return
159
160
    @pytest.mark.parametrize('in_time,test_loc', [
161
        (dt.datetime(2005, 2, 3, 4, 5, 6), (-16.505391672592904,
162
                                            122.17768157084515)),
163
        (dt.datetime(2010, 12, 11, 10, 9, 8), (-23.001554595838947,
164
                                               26.008999999955023)),
165
        (dt.datetime(1601, 1, 1, 0, 0, 0), (-23.06239721771427,
166
                                            -178.90131731228584)),
167
        (dt.datetime(2100, 12, 31, 23, 59, 59), (-23.021061422069053,
168
                                                 -179.23129780639425))])
169
    def test_subsol(self, in_time, test_loc):
170
        """Test the subsolar location calculation."""
171
        self.calc_val = helpers.subsol(in_time)
172
        np.testing.assert_allclose(self.calc_val, test_loc)
173
        return
174
175
    @pytest.mark.parametrize('in_time', [dt.datetime(1600, 12, 31, 23, 59, 59),
176
                                         dt.datetime(2101, 1, 1, 0, 0, 0)])
177
    def test_bad_subsol_date(self, in_time):
178
        """Test raises ValueError for bad time in subsolar calculation."""
179
        with pytest.raises(ValueError) as verr:
180
            helpers.subsol(in_time)
181
182
        assert str(verr.value).startswith('Year must be in')
183
        return
184
185
    @pytest.mark.parametrize('in_time', [None, 2015.0])
186
    def test_bad_subsol_input(self, in_time):
187
        """Test raises ValueError for bad input type in subsolar calculation."""
188
        with pytest.raises(ValueError) as verr:
189
            helpers.subsol(in_time)
190
191
        assert str(verr.value).startswith('input must be datetime')
192
        return
193
194
    def test_subsol_array(self):
195
        """Verify subsolar point calculation using an array of np.datetime64.
196
197
        Notes
198
        -----
199
        Tested by ensuring the array of np.datetime64 is equivalent to
200
        converting using single dt.datetime values
201
202
        """
203
        in_dates = np.arange(np.datetime64("1601"), np.datetime64("2100"),
204
                             np.timedelta64(100, 'D')).astype('datetime64[s]')
205
        sslat, sslon = helpers.subsol(in_dates)
206
207
        # Test the shape of the output
208
        assert sslat.shape == in_dates.shape
209
        assert sslon.shape == in_dates.shape
210
211
        # Test the values
212
        for i, in_date in enumerate(in_dates):
213
            dtime = self.datetime64_to_datetime(in_date)
214
            true_sslat, true_sslon = helpers.subsol(dtime)
215
            assert sslat[i] == true_sslat
216
            assert sslon[i] == true_sslon
217
        return
218