Passed
Pull Request — develop (#77)
by Angeline
01:21
created

test_py_aacgmv2.TestCoeffPath.setup_method()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import datetime as dt
2
from io import StringIO
3
import logging
4
import numpy as np
5
import os
6
import pytest
7
import warnings
8
9
import aacgmv2
10
11
12
class TestConvertArray(object):
13
    """Unit tests for array conversion."""
14
    def setup_method(self):
15
        """Create a clean test environment."""
16
        self.out = None
17
        self.ref = None
18
        self.rtol = 1.0e-4
19
20
    def teardown_method(self):
21
        """Clean up the test envrionment."""
22
        del self.out, self.ref, self.rtol
23
24
    def evaluate_output(self, ind=None):
25
        """Function used to evaluate convert_latlon_arr output.
26
27
        Parameters
28
        ----------
29
        ind : NoneType or int
30
           Index or NoneType if no index
31
32
        """
33
        if self.out is not None:
34
            if ind is not None:
35
                self.ref = [[rr[ind]] for rr in self.ref]
36
37
            np.testing.assert_equal(len(self.out), len(self.ref))
38
            for i, oo in enumerate(self.out):
39
                if not isinstance(oo, np.ndarray):
40
                    raise TypeError("output value is not a numpy array")
41
42
                np.testing.assert_equal(len(oo), len(self.ref[i]))
43
                np.testing.assert_allclose(oo, self.ref[i], rtol=self.rtol)
44
45
46
class TestConvertLatLon(object):
47
    """Unit tests for single value conversion."""
48
    def setup_method(self):
49
        """Create a clean test environment."""
50
        self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0)
51
        self.ddate = dt.date(2015, 1, 1)
52
        self.in_args = [60, 0]
53
        self.out = None
54
        self.rtol = 1.0e-4
55
56
    def teardown_method(self):
57
        """Clean up the test envrionment."""
58
        del self.out, self.in_args, self.rtol, self.dtime, self.ddate
59
60
    @pytest.mark.parametrize('alt,method_code,ref',
61
                             [(300, 'TRACE', [58.2268, 81.1613, 1.0457]),
62
                              (3000.0, "G2A|BADIDEA", [64.3578, 83.2895,
63
                                                       1.4694]),
64
                              (7000.0, "G2A|TRACE|BADIDEA",
65
                               [69.3187, 85.0845, 2.0973])])
66
    def test_convert_latlon(self, alt, method_code, ref):
67
        """Test single value latlon conversion.
68
69
        Parameters
70
        ----------
71
        alt : int or float
72
            Altitude in km
73
        method_code : str
74
            Tracing/calculation method code
75
        ref : list
76
            Expected output values
77
78
        """
79
        self.in_args.extend([alt, self.dtime, method_code])
80
        self.out = aacgmv2.convert_latlon(*self.in_args)
81
        np.testing.assert_allclose(self.out, ref, rtol=self.rtol)
82
83
    @pytest.mark.parametrize('lat,ref',
84
                             [(90.01, [83.927161, 170.1471396, 1.04481923]),
85
                              (-90.01, [-74.9814852, 17.990332, 1.044819236])])
86
    def test_convert_latlon_high_lat(self, lat, ref):
87
        """Test single latlon conversion with latitude just out of bounds.
88
89
        Parameters
90
        ----------
91
        lat : float
92
            Latitude in degrees N
93
        ref : list
94
            Expected output values
95
96
        """
97
        self.in_args[0] = lat
98
        self.in_args.extend([300, self.dtime, 'G2A'])
99
        self.out = aacgmv2.convert_latlon(*self.in_args)
100
        np.testing.assert_allclose(self.out, ref, rtol=self.rtol)
101
102
    def test_convert_latlon_datetime_date(self):
103
        """Test single latlon conversion with date and datetime input."""
104
        self.in_args.extend([300, self.ddate, 'TRACE'])
105
        self.out = aacgmv2.convert_latlon(*self.in_args)
106
        np.testing.assert_allclose(self.out, [58.2268, 81.1613, 1.0457],
107
                                   rtol=self.rtol)
108
109
    def test_convert_latlon_location_failure(self):
110
        """Test single value latlon conversion with a bad location."""
111
        self.out = aacgmv2.convert_latlon(0, 0, 0, self.dtime, self.in_args[-1])
112
        assert np.all(np.isnan(np.array(self.out)))
113
114
    def test_convert_latlon_maxalt_failure(self):
115
        """test convert_latlon failure for an altitude too high for coeffs."""
116
        self.in_args.extend([2001, self.dtime, ""])
117
        self.out = aacgmv2.convert_latlon(*self.in_args)
118
        assert np.all(np.isnan(np.array(self.out)))
119
120
    @pytest.mark.parametrize('in_rep,in_irep,msg',
121
                             [(None, 3, "must be a datetime object"),
122
                              (91, 0, "unrealistic latitude"),
123
                              (-91, 0, "unrealistic latitude"),
124
                              (None, 4, "unknown method code")])
125
    def test_convert_latlon_failure(self, in_rep, in_irep, msg):
126
        """Test ValueError raised with bad inputs.
127
128
        Parameters
129
        ----------
130
        in_ref : int or NoneType
131
            Input value
132
        in_irep : int
133
            Input index
134
        msg : str
135
            Expected error message
136
137
        """
138
        self.in_args.extend([300, self.dtime, "G2A"])
139
        self.in_args[in_irep] = in_rep
140
        with pytest.raises(ValueError, match=msg):
141
            aacgmv2.convert_latlon(*self.in_args)
142
143
144
class TestConvertLatLonArr(TestConvertArray):
145
    """Unit tests for Lat/Lon array conversion."""
146 View Code Duplication
    def setup_method(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
147
        """Create a clean test environment."""
148
        self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0)
149
        self.ddate = dt.date(2015, 1, 1)
150
        self.lat_in = [60.0, 61.0]
151
        self.lon_in = [0.0, 0.0]
152
        self.alt_in = [300.0, 300.0]
153
        self.method = 'TRACE'
154
        self.out = None
155
        self.ref = [[58.2268, 59.3184], [81.1613, 81.6080], [1.0457, 1.0456]]
156
        self.rtol = 1.0e-4
157
158
    def teardown_method(self):
159
        """Clean up the test envrionment."""
160
        del self.lat_in, self.lon_in, self.alt_in, self.dtime, self.ddate
161
        del self.method, self.out, self.ref, self.rtol
162
163
    def test_convert_latlon_arr_single_val(self):
164
        """Test array latlon conversion for a single value."""
165
        self.out = aacgmv2.convert_latlon_arr(self.lat_in[0], self.lon_in[0],
166
                                              self.alt_in[0], self.dtime,
167
                                              self.method)
168
        self.evaluate_output(ind=0)
169
170
    def test_convert_latlon_arr_arr_single(self):
171
        """Test array latlon conversion for array input of shape (1,)."""
172
        self.out = aacgmv2.convert_latlon_arr(np.array([self.lat_in[0]]),
173
                                              np.array([self.lon_in[0]]),
174
                                              np.array([self.alt_in[0]]),
175
                                              self.dtime, self.method)
176
        self.evaluate_output(ind=0)
177
178
    def test_convert_latlon_arr_list_single(self):
179
        """Test array latlon conversion for list input of single values."""
180
        self.out = aacgmv2.convert_latlon_arr([self.lat_in[0]],
181
                                              [self.lon_in[0]],
182
                                              [self.alt_in[0]], self.dtime,
183
                                              self.method)
184
        self.evaluate_output(ind=0)
185
186
    def test_convert_latlon_arr_list(self):
187
        """Test array latlon conversion for list input."""
188
        self.out = aacgmv2.convert_latlon_arr(self.lat_in, self.lon_in,
189
                                              self.alt_in, self.dtime,
190
                                              self.method)
191
        self.evaluate_output()
192
193
    def test_convert_latlon_arr_arr(self):
194
        """Test array latlon conversion for array input."""
195
        self.out = aacgmv2.convert_latlon_arr(np.array(self.lat_in),
196
                                              np.array(self.lon_in),
197
                                              np.array(self.alt_in),
198
                                              self.dtime, self.method)
199
        self.evaluate_output()
200
201
    def test_convert_latlon_arr_list_mix(self):
202
        """Test array latlon conversion for mixed types with list."""
203
        self.out = aacgmv2.convert_latlon_arr(self.lat_in, self.lon_in[0],
204
                                              self.alt_in[0], self.dtime,
205
                                              self.method)
206
        self.evaluate_output()
207
208
    def test_convert_latlon_arr_arr_mix(self):
209
        """Test array latlon conversion for mixed type with an array."""
210
        self.out = aacgmv2.convert_latlon_arr(np.array(self.lat_in),
211
                                              self.lon_in[0], self.alt_in[0],
212
                                              self.dtime, self.method)
213
        self.evaluate_output()
214
215
    def test_convert_latlon_arr_arr_mult_and_single_element(self):
216
        """Test latlon conversion for arrays with multiple and single vals."""
217
        self.out = aacgmv2.convert_latlon_arr(np.array(self.lat_in),
218
                                              np.array([self.lon_in[0]]),
219
                                              np.array(self.alt_in),
220
                                              self.dtime, self.method)
221
        self.evaluate_output()
222
223
    @pytest.mark.parametrize('method_code,alt,local_ref',
224
                             [("BADIDEA", 3000.0,
225
                               [[64.3580], [83.2895], [1.4694]]),
226
                              ("BADIDEA|TRACE", 7000.0,
227
                               [[69.3187], [85.0845], [2.0973]])])
228
    def test_convert_latlon_arr_badidea(self, method_code, alt, local_ref):
229
        """Test array latlon conversion for BADIDEA.
230
231
        Parameters
232
        ----------
233
        method_code : str
234
            Input method code
235
        alt : float
236
            Altitude in km
237
        local_ref : list
238
            Expected output values
239
240
        """
241
        self.out = aacgmv2.convert_latlon_arr(self.lat_in[0], self.lon_in[0],
242
                                              [alt], self.dtime, method_code)
243
        self.ref = local_ref
244
        self.evaluate_output()
245
246
    def test_convert_latlon_arr_location_failure(self):
247
        """Test array latlon conversion with a bad location."""
248
249
        with warnings.catch_warnings():
250
            # Causes all warnings to be surpressed
251
            warnings.simplefilter("ignore")
252
253
            # Trigger a warning
254
            self.out = aacgmv2.convert_latlon_arr([0], [0], [0], self.dtime, "")
255
256
            # Test the output
257
            np.testing.assert_equal(len(self.out), len(self.ref))
258
            assert np.any(~np.isfinite(np.array(self.out)))
259
260
    def test_convert_latlon_arr_datetime_date(self):
261
        """Test array latlon conversion with date and datetime input."""
262
        self.out = aacgmv2.convert_latlon_arr(self.lat_in, self.lon_in,
263
                                              self.alt_in, self.ddate,
264
                                              self.method)
265
        self.evaluate_output()
266
267
    def test_convert_latlon_arr_clip(self):
268
        """Test array latlon conversion with latitude clipping."""
269
        self.lat_in = [90.01, -90.01]
270
        self.ref = [[83.92352053, -74.98110552], [170.1381271, 17.98164313],
271
                    [1.04481924, 1.04481924]]
272
        self.out = aacgmv2.convert_latlon_arr(self.lat_in, self.lon_in,
273
                                              self.alt_in, self.ddate,
274
                                              self.method)
275
        self.evaluate_output()
276
277
    def test_convert_latlon_arr_maxalt_failure(self):
278
        """test convert_latlon_arr failure for altitudes too high for coeffs."""
279
        self.method = ""
280
        self.out = aacgmv2.convert_latlon_arr(self.lat_in[0], self.lon_in[0],
281
                                              [2001], self.dtime, self.method)
282
        assert np.all(np.isnan(np.array(self.out)))
283
284
    @pytest.mark.parametrize('in_rep,in_irep,msg',
285
                             [(None, 3, "must be a datetime object"),
286
                              ([np.full(shape=(3, 2), fill_value=50.0), 0],
287
                               [0, 1], "unable to process multi-dimensional"),
288
                              ([50, 60, 70], 0, "arrays are mismatched"),
289
                              ([[91, 60, -91], 0, 300], [0, 1, 2],
290
                               "unrealistic latitude"),
291
                              (None, 4, "unknown method code")])
292
    def test_convert_latlon_arr_failure(self, in_rep, in_irep, msg):
293
        in_args = np.array([self.lat_in, self.lon_in, self.alt_in, self.dtime,
294
                            "G2A"], dtype=object)
295
        in_args[in_irep] = in_rep
296
        with pytest.raises(ValueError, match=msg):
297
            aacgmv2.convert_latlon_arr(*in_args)
298
299
300
class TestGetAACGMCoord(object):
301
    """Unit tests for AACGM coordinate conversion."""
302
    def setup_method(self):
303
        """Create a clean test environment."""
304
        self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0)
305
        self.ddate = dt.date(2015, 1, 1)
306
        self.in_args = [60, 0]
307
        self.out = None
308
        self.rtol = 1.0e-4
309
310
    def teardown_method(self):
311
        """Clean up the test envrionment."""
312
        del self.out, self.in_args, self.rtol, self.dtime, self.ddate
313
314
    @pytest.mark.parametrize('alt,method_code,ref',
315
                             [(300, 'TRACE', [58.2268, 81.1613, 0.1888]),
316
                              (3000.0, "G2A|BADIDEA", [64.3578, 83.2895,
317
                                                       0.3307]),
318
                              (7000.0, "G2A|TRACE|BADIDEA",
319
                               [69.3187, 85.0845, 0.4503])])
320
    def test_get_aacgm_coord(self, alt, method_code, ref):
321
        """Test single value AACGMV2 calculation, defaults to TRACE.
322
323
        Parameters
324
        ----------
325
        alt : float
326
            Input alitude in km
327
        method_code : str
328
            Calc/tracing method code
329
        ref : list
330
            Expected output values
331
332
        """
333
        self.in_args.extend([alt, self.dtime, method_code])
334
        self.out = aacgmv2.get_aacgm_coord(*self.in_args)
335
        np.testing.assert_allclose(self.out, ref, rtol=self.rtol)
336
337
    def test_get_aacgm_coord_datetime_date(self):
338
        """Test single AACGMV2 calculation with date and datetime input."""
339
        self.in_args.extend([300.0, self.ddate, 'TRACE'])
340
        self.out = aacgmv2.get_aacgm_coord(*self.in_args)
341
        np.testing.assert_allclose(self.out, [58.2268, 81.1613, 0.1888],
342
                                   rtol=self.rtol)
343
344
    def test_get_aacgm_coord_location_failure(self):
345
        """Test single value AACGMV2 calculation with a bad location."""
346
        self.in_args.extend([0.0, self.dtime, 'TRACE'])
347
        self.in_args[0] = 0.0
348
349
        self.out = aacgmv2.get_aacgm_coord(*self.in_args)
350
        np.all(np.isnan(np.array(self.out)))
351
352
    def test_get_aacgm_coord_maxalt_failure(self):
353
        """test get_aacgm_coord failure for an altitude too high for coeffs."""
354
        self.in_args.extend([2001, self.dtime, ""])
355
        self.out = aacgmv2.get_aacgm_coord(*self.in_args)
356
        assert np.all(np.isnan(np.array(self.out)))
357
358
    @pytest.mark.parametrize('in_index,value',
359
                             [(3, None), (0, 91.0), (0, -91.0)])
360
    def test_get_aacgm_coord_raise_value_error(self, in_index, value):
361
        """Test different ways to raise a ValueError.
362
363
        Parameters
364
        ----------
365
        in_index : int
366
            Input index to replace
367
        value : NoneType or float
368
            Bad value to use as input
369
370
        """
371
        self.in_args.extend([300.0, self.dtime])
372
        self.in_args[in_index] = value
373
        with pytest.raises(ValueError):
374
            self.out = aacgmv2.get_aacgm_coord(*self.in_args)
375
376
377
class TestGetAACGMCoordArr(TestConvertArray):
378
    """Unit tests for AACGM coordinate array conversion."""
379 View Code Duplication
    def setup_method(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
380
        """Create a clean test environment."""
381
        self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0)
382
        self.ddate = dt.date(2015, 1, 1)
383
        self.lat_in = [60.0, 61.0]
384
        self.lon_in = [0.0, 0.0]
385
        self.alt_in = [300.0, 300.0]
386
        self.method = 'TRACE'
387
        self.out = None
388
        self.ref = [[58.22676, 59.31847], [81.16135, 81.60797],
389
                    [0.18880, 0.21857]]
390
        self.rtol = 1.0e-4
391
392
    def teardown_method(self):
393
        """Clean up the test envrionment."""
394
        del self.out, self.ref, self.lat_in, self.dtime, self.ddate
395
        del self.lon_in, self.alt_in, self.method, self.rtol
396
397
    def test_get_aacgm_coord_arr_single_val(self):
398
        """Test array AACGMV2 calculation for a single value."""
399
        self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in[0], self.lon_in[0],
400
                                               self.alt_in[0], self.dtime,
401
                                               self.method)
402
        self.evaluate_output(ind=0)
403
404
    def test_get_aacgm_coord_arr_list_single(self):
405
        """Test array AACGMV2 calculation for list input of single values."""
406
        self.out = aacgmv2.get_aacgm_coord_arr([self.lat_in[0]],
407
                                               [self.lon_in[0]],
408
                                               [self.alt_in[0]], self.dtime,
409
                                               self.method)
410
        self.evaluate_output(ind=0)
411
412
    def test_get_aacgm_coord_arr_arr_single(self):
413
        """Test array AACGMV2 calculation for array with a single value."""
414
        self.out = aacgmv2.get_aacgm_coord_arr(np.array([self.lat_in[0]]),
415
                                               np.array([self.lon_in[0]]),
416
                                               np.array([self.alt_in[0]]),
417
                                               self.dtime, self.method)
418
        self.evaluate_output(ind=0)
419
420
    def test_get_aacgm_coord_arr_list(self):
421
        """Test array AACGMV2 calculation for list input."""
422
        self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in,
423
                                               self.alt_in, self.dtime,
424
                                               self.method)
425
        self.evaluate_output()
426
427
    def test_get_aacgm_coord_arr_arr(self):
428
        """Test array AACGMV2 calculation for an array."""
429
        self.out = aacgmv2.get_aacgm_coord_arr(np.array(self.lat_in),
430
                                               np.array(self.lon_in),
431
                                               np.array(self.alt_in),
432
                                               self.dtime, self.method)
433
        self.evaluate_output()
434
435
    def test_get_aacgm_coord_arr_list_mix(self):
436
        """Test array AACGMV2 calculation for a list and floats."""
437
        self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in[0],
438
                                               self.alt_in[0], self.dtime,
439
                                               self.method)
440
        self.evaluate_output()
441
442
    def test_get_aacgm_coord_arr_arr_mix(self):
443
        """Test array AACGMV2 calculation for an array and floats."""
444
        self.out = aacgmv2.get_aacgm_coord_arr(np.array(self.lat_in),
445
                                               self.lon_in[0], self.alt_in[0],
446
                                               self.dtime, self.method)
447
        self.evaluate_output()
448
449
    def test_get_aacgm_coord_arr_badidea(self):
450
        """Test array AACGMV2 calculation for BADIDEA."""
451
        self.method = "|".join([self.method, "BADIDEA"])
452
        self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in[0], self.lon_in[0],
453
                                               [3000.0], self.dtime,
454
                                               self.method)
455
        self.ref = [[64.3481], [83.2885], [0.3306]]
456
        self.evaluate_output()
457
458
    def test_get_aacgm_coord_arr_location_failure(self):
459
        """Test array AACGMV2 calculation with a bad location."""
460
        self.out = aacgmv2.get_aacgm_coord_arr([0], [0], [0], self.dtime,
461
                                               self.method)
462
463
        np.testing.assert_equal(len(self.out), len(self.ref))
464
        assert [isinstance(oo, np.ndarray) and len(oo) == 1 for oo in self.out]
465
        assert np.any([np.isnan(oo) for oo in self.out])
466
467
    def test_get_aacgm_coord_arr_mult_failure(self):
468
        """Test aacgm_coord_arr failure with multi-dim array input."""
469
470
        with pytest.raises(ValueError):
471
            (self.mlat_out, self.mlon_out,
472
             self.mlt_out) = aacgmv2.get_aacgm_coord_arr(
473
                 np.array([[60, 61, 62], [63, 64, 65]]), 0, 300, self.dtime)
474
475
    def test_get_aacgm_coord_arr_time_failure(self):
476
        """Test array AACGMV2 calculation with a bad time."""
477
        with pytest.raises(ValueError):
478
            aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in, self.alt_in,
479
                                        None, self.method)
480
481
    def test_get_aacgm_coord_arr_mlat_failure(self):
482
        """Test error return for co-latitudes above 90 for an array."""
483
484
        self.lat_in = [91, 60, -91]
485
        with pytest.raises(ValueError):
486
            self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in[0],
487
                                                   self.alt_in[0], self.dtime,
488
                                                   self.method)
489
490
    def test_get_aacgm_coord_arr_datetime_date(self):
491
        """Test array AACGMV2 calculation with date and datetime input."""
492
        self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in,
493
                                               self.alt_in, self.ddate,
494
                                               self.method)
495
        self.ref = aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in,
496
                                               self.alt_in, self.dtime,
497
                                               self.method)
498
        self.evaluate_output()
499
500
    def test_get_aacgm_coord_arr_maxalt_failure(self):
501
        """test aacgm_coord_arr failure for an altitude too high for coeff."""
502
        self.method = ""
503
        self.alt_in = [2001 for ll in self.lat_in]
504
        self.out = aacgmv2.get_aacgm_coord_arr(self.lat_in, self.lon_in,
505
                                               self.alt_in, self.dtime,
506
                                               self.method)
507
508
        np.testing.assert_equal(len(self.out), len(self.ref))
509
        assert [isinstance(oo, np.ndarray) and len(oo) == len(self.lat_in)
510
                for oo in self.out]
511
        assert np.all(np.isnan(np.array(self.out)))
512
513
514
class TestConvertCode(object):
515
    """Unit tests for the conversion codes."""
516
    def setup_method(self):
517
        """Create a clean test environment."""
518
        self.c_method_code = None
519
        self.ref_code = None
520
        self.out = None
521
522
    def teardown_method(self):
523
        """Clean up the test envrionment."""
524
        del self.c_method_code, self.ref_code, self.out
525
526
    def set_c_code(self):
527
        """Utility test to get desired C method code."""
528
        if self.ref_code is not None:
529
            self.ref_code = self.ref_code.upper()
530
            self.c_method_code = getattr(aacgmv2._aacgmv2, self.ref_code)
531
532
    def set_bad_c_code(self):
533
        """Test failure to get bad code name."""
534
        self.ref_code = "not_a_valid_code"
535
        with pytest.raises(AttributeError):
536
            self.set_c_code()
537
538
    @pytest.mark.parametrize('method_code',
539
                             [('G2A'), ('A2G'), ('TRACE'), ('ALLOWTRACE'),
540
                              ('BADIDEA'), ('GEOCENTRIC'), ('g2a')])
541
    def test_standard_convert_str_to_bit(self, method_code):
542
        """Test conversion from string code to bit for standard cases.
543
544
        Parameters
545
        ----------
546
        method_code : str
547
            Calculcation and tracing method code
548
549
        """
550
        self.ref_code = method_code
551
        self.set_c_code()
552
        self.out = aacgmv2.convert_str_to_bit(method_code)
553
554
        np.testing.assert_equal(self.out, self.c_method_code)
555
556
    @pytest.mark.parametrize('str_code,bit_ref',
557
                             [("G2A | trace",
558
                               aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.TRACE),
559
                              ("ggoogg|", aacgmv2._aacgmv2.G2A)])
560
    def test_non_standard_convert_str_to_bit(self, str_code, bit_ref):
561
        """Test conversion from string code to bit for non-standard cases.
562
563
        Parameters
564
        ----------
565
        str_code : str
566
            String value of code
567
        bit_ref : int
568
            Integer value of code provided by attributes
569
570
        """
571
        self.out = aacgmv2.convert_str_to_bit(str_code)
572
        np.testing.assert_equal(self.out, bit_ref)
573
574
    @pytest.mark.parametrize('bool_dict,method_code',
575
                             [({}, 'G2A'), ({'a2g': True}, 'A2G'),
576
                              ({'trace': True}, 'TRACE'),
577
                              ({'allowtrace': True}, 'ALLOWTRACE'),
578
                              ({'badidea': True}, 'BADIDEA'),
579
                              ({'geocentric': True}, 'GEOCENTRIC')])
580
    def test_convert_bool_to_bit(self, bool_dict, method_code):
581
        """Test conversion from Boolean code to bit.
582
583
        Parameters
584
        ----------
585
        bool_dict : dict
586
            Dict with boolean flags
587
        method_code : str
588
            Expected output code
589
590
        """
591
        self.ref_code = method_code
592
        self.set_c_code()
593
        self.out = aacgmv2.convert_bool_to_bit(**bool_dict)
594
595
        np.testing.assert_equal(self.out, self.c_method_code)
596
597
598
class TestMLTConvert(object):
599
    """Unit tests for MLT conversion."""
600
    def setup_method(self):
601
        """Create a clean test environment."""
602
        self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0)
603
        self.dtime2 = dt.datetime(2015, 1, 1, 10, 0, 0)
604
        self.ddate = dt.date(2015, 1, 1)
605
        self.mlon_out = None
606
        self.mlt_out = None
607
        self.mlt_diff = None
608
        self.mlon_list = [270.0, 80.0, -95.0]
609
        self.mlt_list = [12.0, 25.0, -1.0]
610
        self.mlon_comp = [-101.670617955439, 93.329382044561, 63.329382044561]
611
        self.mlt_comp = [12.7780412, 0.11137453, 12.44470786]
612
        self.diff_comp = np.ones(shape=(3,)) * -10.52411552
613
614
    def teardown_method(self):
615
        """Clean up the test envrionment."""
616
        del self.mlon_out, self.mlt_out, self.mlt_list, self.mlon_list
617
        del self.mlon_comp, self.mlt_comp, self.mlt_diff, self.diff_comp
618
619
    def test_date_input(self):
620
        """Test to see that the date input works."""
621
        self.mlt_out = aacgmv2.convert_mlt(self.mlon_list, self.ddate,
622
                                           m2a=False)
623
        np.testing.assert_allclose(self.mlt_out, self.mlt_comp, rtol=1.0e-4)
624
625
    def test_datetime_exception(self):
626
        """Test to see that a value error is raised with bad time input."""
627
        with pytest.raises(ValueError):
628
            self.mlt_out = aacgmv2.wrapper.convert_mlt(self.mlon_list, 1997)
629
630
    def test_inv_convert_mlt_single(self):
631
        """Test MLT inversion for a single value."""
632
        for i, mlt in enumerate(self.mlt_list):
633
            self.mlon_out = aacgmv2.convert_mlt(mlt, self.dtime, m2a=True)
634
            np.testing.assert_almost_equal(self.mlon_out, self.mlon_comp[i],
635
                                           decimal=4)
636
637
    def test_inv_convert_mlt_list(self):
638
        """Test MLT inversion for a list."""
639
        self.mlon_out = aacgmv2.convert_mlt(self.mlt_list, self.dtime, m2a=True)
640
        np.testing.assert_allclose(self.mlon_out, self.mlon_comp, rtol=1.0e-4)
641
642
    def test_inv_convert_mlt_arr(self):
643
        """Test MLT inversion for an array."""
644
        self.mlon_out = aacgmv2.convert_mlt(np.array(self.mlt_list), self.dtime,
645
                                            m2a=True)
646
647
        np.testing.assert_allclose(self.mlon_out, self.mlon_comp, rtol=1.0e-4)
648
649
    def test_inv_convert_mlt_wrapping(self):
650
        """Test MLT wrapping."""
651
        self.mlon_out = aacgmv2.convert_mlt(np.array([1, 25, -1, 23]),
652
                                            self.dtime, m2a=True)
653
654
        np.testing.assert_almost_equal(self.mlon_out[0], self.mlon_out[1],
655
                                       decimal=6)
656
        np.testing.assert_almost_equal(self.mlon_out[2], self.mlon_out[3],
657
                                       decimal=6)
658
659
    def test_mlt_convert_mlon_wrapping(self):
660
        """Test mlon wrapping."""
661
        self.mlt_out = aacgmv2.convert_mlt(np.array([270, -90, 1, 361]),
662
                                           self.dtime, m2a=False)
663
664
        np.testing.assert_almost_equal(self.mlt_out[0], self.mlt_out[1],
665
                                       decimal=6)
666
        np.testing.assert_almost_equal(self.mlt_out[2], self.mlt_out[3],
667
                                       decimal=6)
668
669
    def test_mlt_convert_single(self):
670
        """Test MLT calculation for a single value."""
671
        for i, mlon in enumerate(self.mlon_list):
672
            self.mlt_out = aacgmv2.convert_mlt(mlon, self.dtime, m2a=False)
673
            np.testing.assert_almost_equal(self.mlt_out, self.mlt_comp[i],
674
                                           decimal=4)
675
676
    def test_mlt_convert_list(self):
677
        """Test MLT calculation for a list."""
678
        self.mlt_out = aacgmv2.convert_mlt(self.mlon_list, self.dtime,
679
                                           m2a=False)
680
        np.testing.assert_allclose(self.mlt_out, self.mlt_comp, rtol=1.0e-4)
681
682
    def test_mlt_convert_arr(self):
683
        """Test MLT calculation for an array."""
684
        self.mlt_out = aacgmv2.convert_mlt(np.array(self.mlon_list),
685
                                           self.dtime, m2a=False)
686
        np.testing.assert_allclose(self.mlt_out, self.mlt_comp, rtol=1.0e-4)
687
688
    def test_mlt_convert_list_w_times(self):
689
        """Test MLT calculation for data and time arrays."""
690
        self.dtime = [self.dtime for dd in self.mlon_list]
691
        self.mlt_out = aacgmv2.convert_mlt(self.mlon_list, self.dtime,
692
                                           m2a=False)
693
        np.testing.assert_allclose(self.mlt_out, self.mlt_comp, rtol=1.0e-4)
694
695
    def test_mlt_convert_change(self):
696
        """Test that MLT changes with UT."""
697
        self.mlt_out = aacgmv2.convert_mlt(self.mlon_list, self.dtime)
698
        self.mlt_diff = np.array(self.mlt_out) \
699
            - np.array(aacgmv2.convert_mlt(self.mlon_list, self.dtime2))
700
701
        np.testing.assert_allclose(self.mlt_diff, self.diff_comp, rtol=1.0e-4)
702
703
    def test_mlt_convert_multidim_failure(self):
704
        """Test MLT calculation failure for multi-dimensional arrays."""
705
        self.mlon_list = np.full(shape=(3, 2), fill_value=50.0)
706
        with pytest.raises(ValueError):
707
            aacgmv2.convert_mlt(self.mlon_list, self.dtime, m2a=False)
708
709
    def test_mlt_convert_mismatch_failure(self):
710
        """Test MLT calculation failure for mismatched array input."""
711
        with pytest.raises(ValueError):
712
            aacgmv2.convert_mlt(self.mlon_list, [self.dtime, self.dtime2],
713
                                m2a=False)
714
715
716
class TestCoeffPath(object):
717
    """Unit tests for the coefficient path."""
718
    def setup_method(self):
719
        """Create a clean test environment."""
720
        os.environ['IGRF_COEFFS'] = "default_igrf"
721
        os.environ['AACGM_v2_DAT_PREFIX'] = "default_coeff"
722
        self.ref = {"igrf_file": os.environ['IGRF_COEFFS'],
723
                    "coeff_prefix": os.environ['AACGM_v2_DAT_PREFIX']}
724
725
    def teardown_method(self):
726
        """Clean up the test envrionment."""
727
        del self.ref
728
729
    @pytest.mark.parametrize("in_coeff",
730
                             [({}),
731
                              ({"igrf_file": "hi", "coeff_prefix": "bye"}),
732
                              ({"igrf_file": True, "coeff_prefix": True}),
733
                              ({"coeff_prefix": "hi"}),
734
                              ({"igrf_file": "hi"}),
735
                              ({"igrf_file": None, "coeff_prefix": None})])
736
    def test_set_coeff_path(self, in_coeff):
737
        """Test the coefficient path setting using default values.
738
739
        Parameters
740
        ----------
741
        in_coeff : dict
742
           Input coefficient dictionary
743
744
        """
745
        # Update the reference key, if needed
746
        for ref_key in in_coeff.keys():
747
            if in_coeff[ref_key] is True or in_coeff[ref_key] is None:
748
                if ref_key == "igrf_file":
749
                    self.ref[ref_key] = aacgmv2.IGRF_COEFFS
750
                elif ref_key == "coeff_prefix":
751
                    self.ref[ref_key] = aacgmv2.AACGM_v2_DAT_PREFIX
752
            else:
753
                self.ref[ref_key] = in_coeff[ref_key]
754
755
        # Run the routine
756
        aacgmv2.wrapper.set_coeff_path(**in_coeff)
757
758
        # Ensure the environment variables were set correctly
759
        if os.environ['IGRF_COEFFS'] != self.ref['igrf_file']:
760
            raise AssertionError("{:} != {:}".format(os.environ['IGRF_COEFFS'],
761
                                                     self.ref['igrf_file']))
762
        if os.environ['AACGM_v2_DAT_PREFIX'] != self.ref['coeff_prefix']:
763
            raise AssertionError("{:} != {:}".format(
764
                os.environ['AACGM_v2_DAT_PREFIX'], self.ref['coeff_prefix']))
765
766
767
class TestHeightReturns(object):
768
    """Unit tests for heights."""
769
    def setup_method(self):
770
        """Create a clean test environment."""
771
        self.code = aacgmv2._aacgmv2.A2G
772
        self.bad_code = aacgmv2._aacgmv2.BADIDEA
773
        self.trace_code = aacgmv2._aacgmv2.TRACE
774
775
    def teardown_method(self):
776
        """Clean up the test envrionment."""
777
        del self.code, self.bad_code
778
779
    def test_low_height_good(self):
780
        """Test to see that a very low height is still accepted."""
781
782
        assert aacgmv2.wrapper.test_height(-1, self.code)
783
784
    def test_high_coeff_bad(self):
785
        """Test to see that a high altitude for coefficent use fails."""
786
787
        assert not aacgmv2.wrapper.test_height(aacgmv2.high_alt_coeff + 10.0,
788
                                               self.code)
789
790
    def test_high_coeff_good(self):
791
        """Test a high altitude for coefficent use with badidea."""
792
793
        assert aacgmv2.wrapper.test_height(aacgmv2.high_alt_coeff + 10.0,
794
                                           self.bad_code)
795
796
    def test_low_coeff_good(self):
797
        """Test that a normal height succeeds."""
798
        assert aacgmv2.wrapper.test_height(aacgmv2.high_alt_coeff * 0.5,
799
                                           self.code)
800
801
    def test_high_trace_bad(self):
802
        """Test that a high trace height fails."""
803
        assert not aacgmv2.wrapper.test_height(aacgmv2.high_alt_trace + 10.0,
804
                                               self.code)
805
806
    def test_low_trace_good(self):
807
        """Test that a high coefficient height succeeds with trace."""
808
        assert aacgmv2.wrapper.test_height(aacgmv2.high_alt_coeff + 10.0,
809
                                           self.trace_code)
810
811
    def test_high_trace_good(self):
812
        """Test that a high trace height succeeds with badidea."""
813
        assert aacgmv2.wrapper.test_height(aacgmv2.high_alt_trace + 10.0,
814
                                           self.bad_code)
815
816
817
class TestPyLogging(object):
818
    """Unit tests for logging output."""
819
    def setup_method(self):
820
        """Create a clean test environment."""
821
822
        self.lwarn = ""
823
        self.lout = ""
824
        self.log_capture = StringIO()
825
        aacgmv2.logger.addHandler(logging.StreamHandler(self.log_capture))
826
        aacgmv2.logger.setLevel(logging.INFO)
827
828
    def teardown_method(self):
829
        """Clean up the test envrionment."""
830
        self.log_capture.close()
831
        del self.lwarn, self.lout, self.log_capture
832
833
    def test_warning_below_ground(self):
834
        """Test that a warning is issued if height < 0 for height test."""
835
        self.lwarn = "conversion not intended for altitudes < 0 km"
836
837
        aacgmv2.wrapper.test_height(-1, 0)
838
        self.lout = self.log_capture.getvalue()
839
        if self.lout.find(self.lwarn) < 0:
840
            raise AssertionError()
841
842
    def test_warning_magnetosphere(self):
843
        """Test that a warning is issued if altitude is very high."""
844
        self.lwarn = "coordinates are not intended for the magnetosphere"
845
846
        aacgmv2.wrapper.test_height(70000, aacgmv2._aacgmv2.TRACE)
847
        self.lout = self.log_capture.getvalue()
848
        if self.lout.find(self.lwarn) < 0:
849
            raise AssertionError()
850
851
    def test_warning_high_coeff(self):
852
        """Test that a warning is issued if altitude is very high."""
853
        self.lwarn = "must either use field-line tracing (trace=True"
854
855
        aacgmv2.wrapper.test_height(3000, 0)
856
        self.lout = self.log_capture.getvalue()
857
        if self.lout.find(self.lwarn) < 0:
858
            raise AssertionError()
859
860
    def test_warning_single_loc_in_arr(self):
861
        """Test that user is warned they should be using simpler routine."""
862
        self.lwarn = "for a single location, consider using"
863
864
        aacgmv2.convert_latlon_arr(60, 0, 300, dt.datetime(2015, 1, 1, 0, 0, 0))
865
        self.lout = self.log_capture.getvalue()
866
        if self.lout.find(self.lwarn) < 0:
867
            raise AssertionError()
868
869
870
class TestTimeReturns(object):
871
    """Unit tests for time functions."""
872
    def setup_method(self):
873
        """Create a clean test environment."""
874
        self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0)
875
        self.dtime2 = dt.datetime(2015, 1, 1, 10, 10, 10)
876
        self.ddate = dt.date(2015, 1, 1)
877
878
    def teardown_method(self):
879
        """Clean up the test envrionment."""
880
        del self.dtime, self.ddate, self.dtime2
881
882
    def test_good_time(self):
883
        """Test to see that a good datetime is accepted."""
884
885
        assert self.dtime == aacgmv2.wrapper.test_time(self.dtime)
886
887
    def test_good_time_with_nonzero_time(self):
888
        """Test to see that a good datetime with h/m/s is accepted."""
889
890
        assert self.dtime2 == aacgmv2.wrapper.test_time(self.dtime2)
891
892
    def test_good_date(self):
893
        """Test to see that a good date has a good datetime output."""
894
895
        assert self.dtime == aacgmv2.wrapper.test_time(self.dtime)
896
897
    def test_bad_time(self):
898
        """Test to see that a warning is raised with a bad time input."""
899
        with pytest.raises(ValueError):
900
            aacgmv2.wrapper.test_time(2015)
901