Passed
Pull Request — develop (#90)
by Angeline
01:27
created

TestHelpers.test_toYearFraction()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 10
rs 9.95
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
                              ([[-90, 0], [0, 90]], [[-90, 0], [0, 90]]),
108
                              ([[-90], [0], [90]], [[-90], [0], [90]])])
109
    def test_checklat_array(self, lat, test_lat):
110
        """Test good latitude with finite values."""
111
        self.calc_val = helpers.checklat(lat)
112
        self.in_shape = np.asarray(lat).shape
113
        self.test_val = test_lat
114
        self.eval_output(atol=1e-8)
115
        return
116
117
    @pytest.mark.parametrize('lat,test_sin', [
118
        (60, 0.96076892283052284), (10, 0.33257924500670238),
119
        ([60, 10], [0.96076892283052284, 0.33257924500670238]),
120
        ([[60, 10], [60, 10]], [[0.96076892283052284, 0.33257924500670238],
121
                                [0.96076892283052284, 0.33257924500670238]])])
122
    def test_getsinIm(self, lat, test_sin):
123
        """Test sin(Im) calculation for scalar and array inputs."""
124
        self.calc_val = helpers.getsinIm(lat)
125
        self.in_shape = np.asarray(lat).shape
126
        self.test_val = test_sin
127
        self.eval_output()
128
        return
129
130
    @pytest.mark.parametrize('lat,test_cos', [
131
        (60, 0.27735009811261463), (10, 0.94307531289434765),
132
        ([60, 10], [0.27735009811261463, 0.94307531289434765]),
133
        ([[60, 10], [60, 10]], [[0.27735009811261463, 0.94307531289434765],
134
                                [0.27735009811261463, 0.94307531289434765]])])
135
    def test_getcosIm(self, lat, test_cos):
136
        """Test cos(Im) calculation for scalar and array inputs."""
137
        self.calc_val = helpers.getcosIm(lat)
138
        self.in_shape = np.asarray(lat).shape
139
        self.test_val = test_cos
140
        self.eval_output()
141
        return
142
143
    @pytest.mark.parametrize('in_time,year', [
144
        (dt.datetime(2001, 1, 1), 2001), (dt.date(2001, 1, 1), 2001),
145
        (dt.datetime(2002, 1, 1), 2002),
146
        (dt.datetime(2005, 2, 3, 4, 5, 6), 2005.090877283105),
147
        (dt.datetime(2005, 12, 11, 10, 9, 8), 2005.943624682902)])
148
    def test_toYearFraction(self, in_time, year):
149
        """Test the datetime to fractional year calculation."""
150
        self.calc_val = helpers.toYearFraction(in_time)
151
        np.testing.assert_allclose(self.calc_val, year)
152
        return
153
154
    @pytest.mark.parametrize('gc_lat,gd_lat', [
155
        (0, 0), (90, 90), (30, 30.166923849507356), (60, 60.166364190170931),
156
        ([0, 90, 30], [0, 90, 30.166923849507356]),
157
        ([[0, 30], [90, 60]], [[0, 30.16692384950735],
158
                               [90, 60.166364190170931]])])
159
    def test_gc2gdlat(self, gc_lat, gd_lat):
160
        """Test geocentric to geodetic calculation."""
161
        self.calc_val = helpers.gc2gdlat(gc_lat)
162
        self.in_shape = np.asarray(gc_lat).shape
163
        self.test_val = gd_lat
164
        self.eval_output()
165
        return
166
167
    @pytest.mark.parametrize('in_time,test_loc', [
168
        (dt.datetime(2005, 2, 3, 4, 5, 6), (-16.505391672592904,
169
                                            122.17768157084515)),
170
        (dt.datetime(2010, 12, 11, 10, 9, 8), (-23.001554595838947,
171
                                               26.008999999955023)),
172
        (dt.datetime(1601, 1, 1, 0, 0, 0), (-23.06239721771427,
173
                                            -178.90131731228584)),
174
        (dt.datetime(2100, 12, 31, 23, 59, 59), (-23.021061422069053,
175
                                                 -179.23129780639425))])
176
    def test_subsol(self, in_time, test_loc):
177
        """Test the subsolar location calculation."""
178
        self.calc_val = helpers.subsol(in_time)
179
        np.testing.assert_allclose(self.calc_val, test_loc)
180
        return
181
182
    @pytest.mark.parametrize('in_time', [dt.datetime(1600, 12, 31, 23, 59, 59),
183
                                         dt.datetime(2101, 1, 1, 0, 0, 0)])
184
    def test_bad_subsol_date(self, in_time):
185
        """Test raises ValueError for bad time in subsolar calculation."""
186
        with pytest.raises(ValueError) as verr:
187
            helpers.subsol(in_time)
188
189
        assert str(verr.value).startswith('Year must be in')
190
        return
191
192
    @pytest.mark.parametrize('in_time', [None, 2015.0])
193
    def test_bad_subsol_input(self, in_time):
194
        """Test raises ValueError for bad input type in subsolar calculation."""
195
        with pytest.raises(ValueError) as verr:
196
            helpers.subsol(in_time)
197
198
        assert str(verr.value).startswith('input must be datetime')
199
        return
200
201
    @pytest.mark.parametrize('in_dates', [
202
        np.arange(np.datetime64("2000"), np.datetime64("2001"),
203
                  np.timedelta64(1, 'M')).astype('datetime64[s]').reshape((3,
204
                                                                           4)),
205
        np.arange(np.datetime64("1601"), np.datetime64("2100"),
206
                  np.timedelta64(1, 'Y')).astype('datetime64[s]')])
207
    def test_subsol_datetime64_array(self, in_dates):
208
        """Verify subsolar point calculation using an array of np.datetime64.
209
210
        Notes
211
        -----
212
        Tested by ensuring the array of np.datetime64 is equivalent to
213
        converting using single dt.datetime values
214
215
        """
216
        # Get the datetime64 output
217
        ss_out = helpers.subsol(in_dates)
218
219
        # Get the datetime scalar output for comparison
220
        self.in_shape = in_dates.shape
221
        true_out = [list(), list()]
222
        for in_date in in_dates.flatten():
223
            dtime = datetime64_to_datetime(in_date)
224
            out = helpers.subsol(dtime)
225
            true_out[0].append(out[0])
226
            true_out[1].append(out[1])
227
228
        # Evaluate the two outputs
229
        for i, self.calc_val in enumerate(ss_out):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable self does not seem to be defined.
Loading history...
230
            self.test_val = np.array(true_out[i]).reshape(self.in_shape)
231
            self.eval_output()
232
233
        return
234