Completed
Push — develop ( 74ca54...4e7c62 )
by Angeline
14s queued 12s
created

test_helpers.TestHelpers.test_checklat_array()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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