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

test_py_aacgmv2.TestGetAACGMCoordArr.setup()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 11

Duplication

Lines 12
Ratio 100 %

Importance

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