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

TestApexMapMethods.test_map_to_height_array_location()   A

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 15
rs 9.95
c 0
b 0
f 0
cc 1
nop 2
1
# -*- coding: utf-8 -*-
2
"""Test the apexpy.Apex class
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 os
18
import pytest
19
import warnings
20
21
from apexpy import fortranapex as fa
22
from apexpy import Apex, ApexHeightError, helpers
23
24
25
@pytest.fixture()
26
def igrf_file():
27
    """A fixture for handling the coefficient file."""
28
    # Ensure the coefficient file exists
29
    original_file = os.path.join(os.path.dirname(helpers.__file__),
30
                                 'igrf13coeffs.txt')
31
    tmp_file = "temp_coeff.txt"
32
    assert os.path.isfile(original_file)
33
34
    # Move the coefficient file
35
    os.rename(original_file, tmp_file)
36
    yield original_file
37
38
    # Move the coefficient file back
39
    os.rename(tmp_file, original_file)
40
    return
41
42
43
def test_set_epoch_file_error(igrf_file):
44
    """Test raises OSError when IGRF coefficient file is missing."""
45
    # Test missing coefficient file failure
46
    with pytest.raises(OSError) as oerr:
47
        Apex()
48
    error_string = "File {:} does not exist".format(igrf_file)
49
    assert str(oerr.value).startswith(error_string)
50
    return
51
52
53
class TestApexInit():
54
    def setup(self):
55
        self.apex_out = None
56
        self.test_date = dt.datetime.utcnow()
57
        self.test_refh = 0
58
        self.bad_file = 'foo/path/to/datafile.blah'
59
60
    def teardown(self):
61
        del self.apex_out, self.test_date, self.test_refh, self.bad_file
62
63
    def eval_date(self):
64
        """Evaluate the times in self.test_date and self.apex_out."""
65
        if isinstance(self.test_date, dt.datetime) \
66
           or isinstance(self.test_date, dt.date):
67
            self.test_date = helpers.toYearFraction(self.test_date)
68
69
        # Assert the times are the same on the order of tens of seconds.
70
        # Necessary to evaluate the current UTC
71
        np.testing.assert_almost_equal(self.test_date, self.apex_out.year, 6)
72
        return
73
74
    def eval_refh(self):
75
        """Evaluate the reference height in self.refh and self.apex_out."""
76
        eval_str = "".join(["expected reference height [",
77
                            "{:}] not equal to Apex ".format(self.test_refh),
78
                            "reference height ",
79
                            "[{:}]".format(self.apex_out.refh)])
80
        assert self.test_refh == self.apex_out.refh, eval_str
81
        return
82
83
    def test_init_defaults(self):
84
        """Test Apex class default initialization."""
85
        self.apex_out = Apex()
86
        self.eval_date()
87
        self.eval_refh()
88
        return
89
90
    @pytest.mark.parametrize("in_date",
91
                             [2015, 2015.5, dt.date(2015, 1, 1),
92
                              dt.datetime(2015, 6, 1, 18, 23, 45)])
93
    def test_init_date(self, in_date):
94
        """Test Apex class with date initialization."""
95
        self.test_date = in_date
96
        self.apex_out = Apex(date=self.test_date)
97
        self.eval_date()
98
        self.eval_refh()
99
        return
100
101
    @pytest.mark.parametrize("new_date", [2015, 2015.5])
102
    def test_set_epoch(self, new_date):
103
        """Test successful setting of Apex epoch after initialization."""
104
        # Evaluate the default initialization
105
        self.apex_out = Apex()
106
        self.eval_date()
107
        self.eval_refh()
108
109
        # Update the epoch
110
        self.test_date = new_date
111
        self.apex_out.set_epoch(new_date)
112
        self.eval_date()
113
        self.eval_refh()
114
        return
115
116
    @pytest.mark.parametrize("in_refh", [0.0, 300.0, 30000.0, -1.0])
117
    def test_init_refh(self, in_refh):
118
        """Test Apex class with reference height initialization."""
119
        self.test_refh = in_refh
120
        self.apex_out = Apex(refh=self.test_refh)
121
        self.eval_date()
122
        self.eval_refh()
123
        return
124
125
    @pytest.mark.parametrize("new_refh", [0.0, 300.0, 30000.0, -1.0])
126
    def test_set_refh(self, new_refh):
127
        """Test the method used to set the reference height after the init."""
128
        # Verify the defaults are set
129
        self.apex_out = Apex(date=self.test_date)
130
        self.eval_date()
131
        self.eval_refh()
132
133
        # Update to a new reference height and test
134
        self.test_refh = new_refh
135
        self.apex_out.set_refh(new_refh)
136
        self.eval_refh()
137
        return
138
139
    def test_init_with_bad_datafile(self):
140
        """Test raises IOError with non-existent datafile input."""
141
        with pytest.raises(IOError) as oerr:
142
            Apex(datafile=self.bad_file)
143
        assert str(oerr.value).startswith('Data file does not exist')
144
        return
145
146
    def test_init_with_bad_fortranlib(self):
147
        """Test raises IOError with non-existent datafile input."""
148
        with pytest.raises(IOError) as oerr:
149
            Apex(fortranlib=self.bad_file)
150
        assert str(oerr.value).startswith('Fortran library does not exist')
151
        return
152
153
154
class TestApexMethod():
155
    """Test the Apex methods."""
156
    def setup(self):
157
        """Initialize all tests."""
158
        self.apex_out = Apex(date=2000, refh=300)
159
160
    def teardown(self):
161
        """Clean up after each test."""
162
        del self.apex_out
163
164
    def get_input_args(self, method_name, lat, lon, alt, precision=0.0):
165
        """Set the input arguments for the different Apex methods.
166
167
        Parameters
168
        ----------
169
        method_name : str
170
            Name of the Apex class method
171
        lat : float or array-like
172
            Value for the latitude
173
        lon : float or array-like
174
            Value for the longitude
175
        alt : float or array-like
176
            Value for the altitude
177
        precision : float
178
            Value for the precision (default=0.0)
179
180
        Returns
181
        -------
182
        in_args : list
183
            List of the appropriate input arguments
184
185
        """
186
        in_args = [lat, lon, alt]
187
188
        # Add precision, if needed
189
        if method_name in ["_qd2geo", "apxq2g", "apex2geo", "qd2geo",
190
                           "_apex2geo"]:
191
            in_args.append(precision)
192
193
        # Add a reference height, if needed
194
        if method_name in ["apxg2all"]:
195
            in_args.append(300)
196
197
        # Add a vector flag, if needed
198
        if method_name in ["apxg2all", "apxg2q"]:
199
            in_args.append(1)
200
201
        return in_args
202
203 View Code Duplication
    @pytest.mark.parametrize("apex_method,fortran_method,fslice",
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
204
                             [("_geo2qd", "apxg2q", slice(0, 2, 1)),
205
                              ("_geo2apex", "apxg2all", slice(2, 4, 1)),
206
                              ("_qd2geo", "apxq2g", slice(None)),
207
                              ("_basevec", "apxg2q", slice(2, 4, 1))])
208
    @pytest.mark.parametrize("lat", [(0), (30), (60), (89)])
209
    @pytest.mark.parametrize("lon", [(-179), (-90), (0), (90), (180)])
210
    def test_fortran_scalar_input(self, apex_method, fortran_method, fslice,
211
                                  lat, lon):
212
        """Tests Apex/fortran interface consistency for scalars."""
213
        # Get the Apex class method and the fortran function call
214
        apex_func = getattr(self.apex_out, apex_method)
215
        fortran_func = getattr(fa, fortran_method)
216
217
        # Get the appropriate input arguments
218
        apex_args = self.get_input_args(apex_method, lat, lon, 100)
219
        fortran_args = self.get_input_args(fortran_method, lat, lon, 100)
220
221
        # Evaluate the equivalent function calls
222
        np.testing.assert_allclose(apex_func(*apex_args),
223
                                   fortran_func(*fortran_args)[fslice])
224
        return
225
226 View Code Duplication
    @pytest.mark.parametrize("apex_method,fortran_method,fslice",
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
227
                             [("_geo2qd", "apxg2q", slice(0, 2, 1)),
228
                              ("_geo2apex", "apxg2all", slice(2, 4, 1)),
229
                              ("_qd2geo", "apxq2g", slice(None)),
230
                              ("_basevec", "apxg2q", slice(2, 4, 1))])
231
    @pytest.mark.parametrize("lat", [(0), (30), (60), (89)])
232
    @pytest.mark.parametrize("lon1,lon2", [(180, 180), (-180, -180),
233
                                           (180, -180), (-180, 180),
234
                                           (-345, 15), (375, 15)])
235
    def test_fortran_longitude_rollover(self, apex_method, fortran_method,
236
                                        fslice, lat, lon1, lon2):
237
        """Tests Apex/fortran interface consistency for longitude rollover."""
238
        # Get the Apex class method and the fortran function call
239
        apex_func = getattr(self.apex_out, apex_method)
240
        fortran_func = getattr(fa, fortran_method)
241
242
        # Get the appropriate input arguments
243
        apex_args = self.get_input_args(apex_method, lat, lon1, 100)
244
        fortran_args = self.get_input_args(fortran_method, lat, lon2, 100)
245
246
        # Evaluate the equivalent function calls
247
        np.testing.assert_allclose(apex_func(*apex_args),
248
                                   fortran_func(*fortran_args)[fslice])
249
        return
250
251
    @pytest.mark.parametrize("apex_method,fortran_method,fslice",
252
                             [("_geo2qd", "apxg2q", slice(0, 2, 1)),
253
                              ("_geo2apex", "apxg2all", slice(2, 4, 1)),
254
                              ("_qd2geo", "apxq2g", slice(None)),
255
                              ("_basevec", "apxg2q", slice(2, 4, 1))])
256
    def test_fortran_array_input(self, apex_method, fortran_method, fslice):
257
        """Tests Apex/fortran interface consistency for array input."""
258
        # Get the Apex class method and the fortran function call
259
        apex_func = getattr(self.apex_out, apex_method)
260
        fortran_func = getattr(fa, fortran_method)
261
262
        # Set up the input arrays
263
        in_lats = np.array([0, 30, 60, 90])
264
        in_lon = 15
265
        in_alts = np.array([100, 200, 300, 400])
266
        apex_args = self.get_input_args(apex_method, in_lats.reshape((2, 2)),
267
                                        in_lon, in_alts.reshape((2, 2)))
268
269
        # Get the Apex class results
270
        aret = apex_func(*apex_args)
271
272
        # Get the fortran function results
273
        flats = list()
274
        flons = list()
275
276
        for i, lat in enumerate(in_lats):
277
            fortran_args = self.get_input_args(fortran_method, lat, in_lon,
278
                                               in_alts[i])
279
            fret = fortran_func(*fortran_args)[fslice]
280
            flats.append(fret[0])
281
            flons.append(fret[1])
282
283
        flats = np.array(flats)
284
        flons = np.array(flons)
285
286
        # Evaluate results
287
        try:
288
            # This returned value is array of floats
289
            np.testing.assert_allclose(aret[0].astype(float),
290
                                       flats.reshape((2, 2)).astype(float))
291
            np.testing.assert_allclose(aret[1].astype(float),
292
                                       flons.reshape((2, 2)).astype(float))
293
        except ValueError:
294
            # This returned value is array of arrays
295
            alats = aret[0].reshape((4,))
296
            alons = aret[1].reshape((4,))
297
            for i, flat in enumerate(flats):
298
                np.testing.assert_array_almost_equal(alats[i], flat, 2)
299
                np.testing.assert_array_almost_equal(alons[i], flons[i], 2)
300
301
        return
302
303
    @pytest.mark.parametrize("lat", [(0), (30), (60), (89)])
304
    @pytest.mark.parametrize("lon", [(-179), (-90), (0), (90), (180)])
305
    def test_geo2apexall_scalar(self, lat, lon):
306
        """Test Apex/fortran geo2apexall interface consistency for scalars."""
307
        # Get the Apex and Fortran results
308
        aret = self.apex_out._geo2apexall(lat, lon, 100)
309
        fret = fa.apxg2all(lat, lon, 100, 300, 1)
310
311
        # Evaluate each element in the results
312
        for aval, fval in zip(aret, fret):
313
            np.testing.assert_allclose(aval, fval)
314
315
    def test_geo2apexall_array(self):
316
        """Test Apex/fortran geo2apexall interface consistency for arrays."""
317
        # Set the input
318
        in_lats = np.array([0, 30, 60, 90])
319
        in_lon = 15
320
        in_alts = np.array([100, 200, 300, 400])
321
322
        # Get the Apex class results
323
        aret = self.apex_out._geo2apexall(in_lats.reshape((2, 2)), in_lon,
324
                                          in_alts.reshape((2, 2)))
325
326
        # For each lat/alt pair, get the Fortran results
327
        fret = list()
328
        for i, lat in enumerate(in_lats):
329
            fret.append(fa.apxg2all(in_lats[i], in_lon, in_alts[i], 300, 1))
330
331
        # Cycle through all returned values
332
        for i, ret in enumerate(aret):
333
            try:
334
                # This returned value is array of floats
335
                np.testing.assert_allclose(ret.astype(float),
336
                                           np.array([[fret[0][i], fret[1][i]],
337
                                                     [fret[2][i], fret[3][i]]],
338
                                                    dtype=float))
339
            except ValueError:
340
                # This returned value is array of arrays
341
                ret = ret.reshape((4,))
342
                for j, single_fret in enumerate(fret):
343
                    np.testing.assert_allclose(ret[j], single_fret[i])
344
        return
345
346
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
347
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
348
    def test_convert_consistency(self, in_coord, out_coord):
349
        """Test the self-consistency of the Apex convert method."""
350
        if in_coord == out_coord:
351
            pytest.skip("Test not needed for same src and dest coordinates")
352
353
        # Define the method name
354
        method_name = "2".join([in_coord, out_coord])
355
356
        # Get the method and method inputs
357
        convert_kwargs = {'height': 100, 'precision': 0.0}
358
        apex_args = self.get_input_args(method_name, 60, 15, 100)
359
        apex_method = getattr(self.apex_out, method_name)
360
361
        # Define the slice needed to get equivalent output from the named method
362
        mslice = slice(0, -1, 1) if out_coord == "geo" else slice(None)
363
364
        # Get output using convert and named method
365
        convert_out = self.apex_out.convert(60, 15, in_coord, out_coord,
366
                                            **convert_kwargs)
367
        method_out = apex_method(*apex_args)[mslice]
368
369
        # Compare both outputs, should be identical
370
        np.testing.assert_allclose(convert_out, method_out)
371
        return
372
373
    @pytest.mark.parametrize("bound_lat", [(90), (-90)])
374
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
375
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
376
    def test_convert_at_lat_boundary(self, bound_lat, in_coord, out_coord):
377
        """Test the conversion at the latitude boundary, with allowed excess."""
378
        excess_lat = np.sign(bound_lat) * (abs(bound_lat) + 1.0e-5)
379
380
        # Get the two outputs, slight tolerance outside of boundary allowed
381
        bound_out = self.apex_out.convert(bound_lat, 0, in_coord, out_coord)
382
        excess_out = self.apex_out.convert(excess_lat, 0, in_coord, out_coord)
383
384
        # Test the outputs
385
        np.testing.assert_allclose(excess_out, bound_out, rtol=0, atol=1e-8)
386
        return
387
388
    def test_convert_qd2apex_at_equator(self):
389
        """Test the quasi-dipole to apex conversion at the magnetic equator."""
390
        eq_out = self.apex_out.convert(lat=0.0, lon=0, source='qd', dest='apex',
391
                                       height=320.0)
392
        close_out = self.apex_out.convert(lat=0.001, lon=0, source='qd',
393
                                          dest='apex', height=320.0)
394
        np.testing.assert_allclose(eq_out, close_out, atol=1e-4)
395
396
    @pytest.mark.parametrize("src", ["geo", "apex", "qd"])
397
    @pytest.mark.parametrize("dest", ["geo", "apex", "qd"])
398
    def test_convert_withnan(self, src, dest):
399
        """Test Apex.convert success with NaN input."""
400
        if src == dest:
401
            pytest.skip("Test not needed for same src and dest coordinates")
402
403
        num_nans = 5
404
        in_loc = np.arange(0, 10, dtype=float)
405
        in_loc[:num_nans] = np.nan
406
407
        out_loc = self.apex_out.convert(in_loc, in_loc, src, dest, height=320)
408
409
        for out in out_loc:
410
            assert np.all(np.isnan(out[:num_nans])), "NaN output expected"
411
            assert np.all(np.isfinite(out[num_nans:])), "Finite output expected"
412
413
        return
414
415
    @pytest.mark.parametrize("bad_lat", [(91), (-91)])
416
    def test_convert_invalid_lat(self, bad_lat):
417
        """Test convert raises ValueError for invalid latitudes."""
418
419
        with pytest.raises(ValueError):
420
            self.apex_out.convert(bad_lat, 0, 'geo', 'geo')
421
        return
422
423
    @pytest.mark.parametrize("coords", [("foobar", "geo"), ("geo", "foobar")])
424
    def test_convert_invalid_transformation(self, coords):
425
        """Test raises NotImplementedError for bad coordinates."""
426
        with pytest.raises(NotImplementedError):
427
            self.apex_out.convert(0, 0, *coords)
428
        return
429
430
    @pytest.mark.parametrize("method_name, out_comp",
431
                             [("geo2apex",
432
                               (55.94841766357422, 94.10684204101562)),
433
                              ("apex2geo",
434
                               (51.476322174072266, -66.22817993164062,
435
                                5.727287771151168e-06)),
436
                              ("geo2qd",
437
                               (56.531288146972656, 94.10684204101562)),
438
                              ("apex2qd", (60.498401178276744, 15.0)),
439
                              ("qd2apex", (59.49138097045895, 15.0))])
440
    def test_method_scalar_input(self, method_name, out_comp):
441
        """Test the user method against set values with scalars."""
442
        # Get the desired methods
443
        user_method = getattr(self.apex_out, method_name)
444
445
        # Get the user output
446
        user_out = user_method(60, 15, 100)
447
448
        # Evaluate the user output
449
        np.testing.assert_allclose(user_out, out_comp)
450
451
        for out_val in user_out:
452
            assert np.asarray(out_val).shape == (), "output is not a scalar"
453
        return
454
455
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
456
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
457
    @pytest.mark.parametrize("method_args, out_shape",
458
                             [([[60, 60], 15, 100], (2,)),
459
                              ([60, [15, 15], 100], (2,)),
460
                              ([60, 15, [100, 100]], (2,)),
461
                              ([[50, 60], [15, 16], [100, 200]], (2,))])
462
    def test_method_broadcast_input(self, in_coord, out_coord, method_args,
463
                                    out_shape):
464
        """Test the user method with inputs that require some broadcasting."""
465
        if in_coord == out_coord:
466
            pytest.skip("Test not needed for same src and dest coordinates")
467
468
        # Get the desired methods
469
        method_name = "2".join([in_coord, out_coord])
470
        user_method = getattr(self.apex_out, method_name)
471
472
        # Get the user output
473
        user_out = user_method(*method_args)
474
475
        # Evaluate the user output
476
        for out_val in user_out:
477
            assert hasattr(out_val, 'shape'), "output coordinate isn't np.array"
478
            assert out_val.shape == out_shape
479
        return
480
481
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
482
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
483
    @pytest.mark.parametrize("bad_lat", [(91), (-91)])
484
    def test_method_invalid_lat(self, in_coord, out_coord, bad_lat):
485
        """Test convert raises ValueError for invalid latitudes."""
486
        if in_coord == out_coord:
487
            pytest.skip("Test not needed for same src and dest coordinates")
488
489
        # Get the desired methods
490
        method_name = "2".join([in_coord, out_coord])
491
        user_method = getattr(self.apex_out, method_name)
492
493
        with pytest.raises(ValueError):
494
            user_method(bad_lat, 15, 100)
495
        return
496
497
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
498
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
499
    @pytest.mark.parametrize("bound_lat", [(90), (-90)])
500
    def test_method_at_lat_boundary(self, in_coord, out_coord, bound_lat):
501
        """Test user methods at the latitude boundary, with allowed excess."""
502
        if in_coord == out_coord:
503
            pytest.skip("Test not needed for same src and dest coordinates")
504
505
        # Get the desired methods
506
        method_name = "2".join([in_coord, out_coord])
507
        user_method = getattr(self.apex_out, method_name)
508
509
        # Get a latitude just beyond the limit
510
        excess_lat = np.sign(bound_lat) * (abs(bound_lat) + 1.0e-5)
511
512
        # Get the two outputs, slight tolerance outside of boundary allowed
513
        bound_out = user_method(bound_lat, 0, 100)
514
        excess_out = user_method(excess_lat, 0, 100)
515
516
        # Test the outputs
517
        np.testing.assert_allclose(excess_out, bound_out, rtol=0, atol=1e-8)
518
        return
519
520
    def test_geo2apex_undefined_warning(self):
521
        """Test geo2apex warning and fill values for an undefined location."""
522
523
        # Update the apex object
524
        self.apex_out = Apex(date=2000, refh=10000)
525
526
        # Get the output and the warnings
527
        with warnings.catch_warnings(record=True) as warn_rec:
528
            user_lat, user_lon = self.apex_out.geo2apex(0, 0, 0)
529
530
        assert np.isnan(user_lat)
531
        assert np.isfinite(user_lon)
532
        assert len(warn_rec) == 1
533
        assert issubclass(warn_rec[-1].category, UserWarning)
534
        assert 'latitude set to NaN where' in str(warn_rec[-1].message)
535
        return
536
537
    @pytest.mark.parametrize("method_name", ["apex2qd", "qd2apex"])
538
    @pytest.mark.parametrize("delta_h", [1.0e-6, -1.0e-6])
539
    def test_quasidipole_apexheight_close(self, method_name, delta_h):
540
        """Test quasi-dipole success with a height close to the reference."""
541
        qd_method = getattr(self.apex_out, method_name)
542
        in_args = [0, 15, self.apex_out.refh + delta_h]
543
        out_coords = qd_method(*in_args)
544
545
        for i, out_val in enumerate(out_coords):
546
            np.testing.assert_almost_equal(out_val, in_args[i], decimal=3)
547
        return
548
549
    @pytest.mark.parametrize("method_name, hinc, msg",
550
                             [("apex2qd", 1.0, "is > apex height"),
551
                              ("qd2apex", -1.0, "is < reference height")])
552
    def test_quasidipole_raises_apexheight(self, method_name, hinc, msg):
553
        """Quasi-dipole raises ApexHeightError when height above reference."""
554
        qd_method = getattr(self.apex_out, method_name)
555
556
        with pytest.raises(ApexHeightError) as aerr:
557
            qd_method(0, 15, self.apex_out.refh + hinc)
558
559
        assert str(aerr).find(msg) > 0
560
        return
561
562
563
class TestApexMLTMethods():
564
    """Test the Apex Magnetic Local Time (MLT) methods."""
565
    def setup(self):
566
        """Initialize all tests."""
567
        self.apex_out = Apex(date=2000, refh=300)
568
        self.in_time = dt.datetime(2000, 2, 3, 4, 5, 6)
569
570
    def teardown(self):
571
        """Clean up after each test."""
572
        del self.apex_out, self.in_time
573
574
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
575
    def test_convert_to_mlt(self, in_coord):
576
        """Test the conversions to MLT using Apex convert."""
577
578
        # Get the magnetic longitude from the appropriate method
579
        if in_coord == "geo":
580
            apex_method = getattr(self.apex_out, "{:s}2apex".format(in_coord))
581
            mlon = apex_method(60, 15, 100)[1]
582
        else:
583
            mlon = 15
584
585
        # Get the output MLT values
586
        convert_mlt = self.apex_out.convert(60, 15, in_coord, 'mlt',
587
                                            height=100, ssheight=2e5,
588
                                            datetime=self.in_time)[1]
589
        method_mlt = self.apex_out.mlon2mlt(mlon, self.in_time, ssheight=2e5)
590
591
        # Test the outputs
592
        np.testing.assert_allclose(convert_mlt, method_mlt)
593
        return
594
595
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
596
    def test_convert_mlt_to_lon(self, out_coord):
597
        """Test the conversions from MLT using Apex convert."""
598
        # Get the output longitudes
599
        convert_out = self.apex_out.convert(60, 15, 'mlt', out_coord,
600
                                            height=100, ssheight=2e5,
601
                                            datetime=self.in_time,
602
                                            precision=1e-2)
603
        mlon = self.apex_out.mlt2mlon(15, self.in_time, ssheight=2e5)
604
605
        if out_coord == "geo":
606
            method_out = self.apex_out.apex2geo(60, mlon, 100,
607
                                                precision=1e-2)[:-1]
608
        elif out_coord == "qd":
609
            method_out = self.apex_out.apex2qd(60, mlon, 100)
610
        else:
611
            method_out = (60, mlon)
612
613
        # Evaluate the outputs
614
        np.testing.assert_allclose(convert_out, method_out)
615
        return
616
617
    def test_convert_geo2mlt_nodate(self):
618
        """Test convert from geo to MLT raises ValueError with no datetime."""
619
        with pytest.raises(ValueError):
620
            self.apex_out.convert(60, 15, 'geo', 'mlt')
621
        return
622
623
    @pytest.mark.parametrize("mlon_kwargs,test_mlt",
624
                             [({}, 23.019629923502603),
625
                              ({"ssheight": 100000}, 23.026712036132814)])
626
    def test_mlon2mlt_scalar_inputs(self, mlon_kwargs, test_mlt):
627
        """Test mlon2mlt with scalar inputs."""
628
        mlt = self.apex_out.mlon2mlt(0, self.in_time, **mlon_kwargs)
629
630
        np.testing.assert_allclose(mlt, test_mlt)
631
        assert np.asarray(mlt).shape == ()
632
        return
633
634
    @pytest.mark.parametrize("mlt_kwargs,test_mlon",
635
                             [({}, 14.705535888671875),
636
                              ({"ssheight": 100000}, 14.599319458007812)])
637
    def test_mlt2mlon_scalar_inputs(self, mlt_kwargs, test_mlon):
638
        """Test mlt2mlon with scalar inputs."""
639
        mlon = self.apex_out.mlt2mlon(0, self.in_time, **mlt_kwargs)
640
641
        np.testing.assert_allclose(mlon, test_mlon)
642
        assert np.asarray(mlon).shape == ()
643
        return
644
645
    @pytest.mark.parametrize("mlon,test_mlt",
646
                             [([0, 180], [23.019261, 11.019261]),
647
                              (np.array([0, 180]), [23.019261, 11.019261]),
648
                              ([[0, 180], [0, 180]], [[23.019261, 11.019261],
649
                                                      [23.019261, 11.019261]]),
650
                              (range(0, 361, 30),
651
                               [23.01963, 1.01963, 3.01963, 5.01963, 7.01963,
652
                                9.01963, 11.01963, 13.01963, 15.01963, 17.01963,
653
                                19.01963, 21.01963, 23.01963])])
654
    def test_mlon2mlt_array(self, mlon, test_mlt):
655
        """Test mlon2mlt with array inputs."""
656
        mlt = self.apex_out.mlon2mlt(mlon, self.in_time)
657
658
        assert mlt.shape == np.asarray(test_mlt).shape
659
        np.testing.assert_allclose(mlt, test_mlt, rtol=1e-4)
660
        return
661
662
    @pytest.mark.parametrize("mlt,test_mlon",
663
                             [([0, 12], [14.705551, 194.705551]),
664
                              (np.array([0, 12]), [14.705551, 194.705551]),
665
                              ([[0, 12], [0, 12]], [[14.705551, 194.705551],
666
                                                    [14.705551, 194.705551]]),
667
                              (range(0, 25, 2),
668
                               [14.705551, 44.705551, 74.705551, 104.705551,
669
                                134.705551, 164.705551, 194.705551, 224.705551,
670
                                254.705551, 284.705551, 314.705551, 344.705551,
671
                                14.705551])])
672
    def test_mlt2mlon_array(self, mlt, test_mlon):
673
        """Test mlt2mlon with array inputs."""
674
        mlon = self.apex_out.mlt2mlon(mlt, self.in_time)
675
676
        assert mlon.shape == np.asarray(test_mlon).shape
677
        np.testing.assert_allclose(mlon, test_mlon, rtol=1e-4)
678
        return
679
680
    @pytest.mark.parametrize("method_name", ["mlon2mlt", "mlt2mlon"])
681
    def test_mlon2mlt_diffdates(self, method_name):
682
        """Test that MLT varies with universal time."""
683
        apex_method = getattr(self.apex_out, method_name)
684
        mlt1 = apex_method(0, self.in_time)
685
        mlt2 = apex_method(0, self.in_time + dt.timedelta(hours=1))
686
687
        assert mlt1 != mlt2
688
        return
689
690
    @pytest.mark.parametrize("mlt_offset", [1.0, 10.0])
691
    def test_mlon2mlt_offset(self, mlt_offset):
692
        """Test the time wrapping logic for the MLT."""
693
        mlt1 = self.apex_out.mlon2mlt(0.0, self.in_time)
694
        mlt2 = self.apex_out.mlon2mlt(-15.0 * mlt_offset,
695
                                      self.in_time) + mlt_offset
696
697
        np.testing.assert_allclose(mlt1, mlt2)
698
        return
699
700
    @pytest.mark.parametrize("mlon_offset", [15.0, 150.0])
701
    def test_mlt2mlon_offset(self, mlon_offset):
702
        """Test the time wrapping logic for the magnetic longitude."""
703
        mlon1 = self.apex_out.mlt2mlon(0, self.in_time)
704
        mlon2 = self.apex_out.mlt2mlon(mlon_offset / 15.0,
705
                                       self.in_time) - mlon_offset
706
707
        np.testing.assert_allclose(mlon1, mlon2)
708
        return
709
710
    @pytest.mark.parametrize("order", [["mlt", "mlon"], ["mlon", "mlt"]])
711
    @pytest.mark.parametrize("start_val", [0, 6, 12, 18, 22])
712
    def test_convert_and_return(self, order, start_val):
713
        """Test the conversion to magnetic longitude or MLT and back again."""
714
        first_method = getattr(self.apex_out, "2".join(order))
715
        second_method = getattr(self.apex_out, "2".join([order[1], order[0]]))
716
717
        middle_val = first_method(start_val, self.in_time)
718
        end_val = second_method(middle_val, self.in_time)
719
720
        np.testing.assert_allclose(start_val, end_val)
721
        return
722
723
724
class TestApexMapMethods():
725
    """Test the Apex height mapping methods."""
726
    def setup(self):
727
        """Initialize all tests."""
728
        self.apex_out = Apex(date=2000, refh=300)
729
730
    def teardown(self):
731
        """Clean up after each test."""
732
        del self.apex_out
733
734
    @pytest.mark.parametrize("in_args,test_mapped",
735
                             [([60, 15, 100, 10000],
736
                               [31.841466903686523, 17.916635513305664,
737
                                1.7075473124350538e-6]),
738
                              ([30, 170, 100, 500, False, 1e-2],
739
                               [25.727270126342773, 169.60546875,
740
                                0.00017573432705830783]),
741
                              ([60, 15, 100, 10000, True],
742
                               [-25.424888610839844, 27.310426712036133,
743
                                1.2074182222931995e-6]),
744
                              ([30, 170, 100, 500, True, 1e-2],
745
                               [-13.76642894744873, 164.24259948730469,
746
                                0.00056820799363777041])])
747
    def test_map_to_height(self, in_args, test_mapped):
748
        """Test the map_to_height function."""
749
        mapped = self.apex_out.map_to_height(*in_args)
750
        np.testing.assert_allclose(mapped, test_mapped, atol=1e-6)
751
        return
752
753
    def test_map_to_height_same_height(self):
754
        """Test the map_to_height function when mapping to same height."""
755
        mapped = self.apex_out.map_to_height(60, 15, 100, 100, conjugate=False,
756
                                             precision=1e-10)
757
        np.testing.assert_allclose(mapped, (60.0, 15.000003814697266, 0.0),
758
                                   rtol=1e-5)
759
        return
760
761
    @pytest.mark.parametrize('ivec', range(0, 4))
762
    def test_map_to_height_array_location(self, ivec):
763
        """Test map_to_height with array input."""
764
        # Set the base input and output values
765
        in_args = [60, 15, 100, 100]
766
        test_mapped = np.full(shape=(2, 3),
767
                              fill_value=[60, 15.00000381, 0.0]).transpose()
768
769
        # Update inputs for one vectorized value
770
        in_args[ivec] = [in_args[ivec], in_args[ivec]]
771
772
        # Calculate and test function
773
        mapped = self.apex_out.map_to_height(*in_args)
774
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
775
        return
776
777
    @pytest.mark.parametrize("method_name,in_args",
778
                             [("map_to_height", [0, 15, 100, 10000]),
779
                              ("map_E_to_height",
780
                               [0, 15, 100, 10000, [1, 2, 3]]),
781
                              ("map_V_to_height",
782
                               [0, 15, 100, 10000, [1, 2, 3]])])
783
    def test_mapping_height_raises_ApexHeightError(self, method_name, in_args):
784
        """Test map_to_height raises ApexHeightError."""
785
        apex_method = getattr(self.apex_out, method_name)
786
787
        with pytest.raises(ApexHeightError) as aerr:
788
            apex_method(*in_args)
789
790
        assert aerr.match("is > apex height")
791
        return
792
793
    @pytest.mark.parametrize("method_name",
794
                             ["map_E_to_height", "map_V_to_height"])
795
    @pytest.mark.parametrize("ev_input", [([1, 2, 3, 4, 5]),
796
                                          ([[1, 2], [3, 4], [5, 6], [7, 8]])])
797
    def test_mapping_EV_bad_shape(self, method_name, ev_input):
798
        """Test map_to_height raises ApexHeightError."""
799
        apex_method = getattr(self.apex_out, method_name)
800
        in_args = [60, 15, 100, 500, ev_input]
801
        with pytest.raises(ValueError) as verr:
802
            apex_method(*in_args)
803
804
        assert str(verr.value).find("must be (3, N) or (3,) ndarray") >= 0
805
        return
806
807
    @pytest.mark.parametrize("in_args,test_mapped",
808
                             [([60, 15, 100, 500, [1, 2, 3]],
809
                               [0.71152183, 2.35624876, 0.57260784]),
810
                              ([60, 15, 100, 500, [2, 3, 4]],
811
                               [1.56028502, 3.43916636, 0.78235384]),
812
                              ([60, 15, 100, 1000, [1, 2, 3]],
813
                               [0.67796492, 2.08982134, 0.55860785]),
814
                              ([60, 15, 200, 500, [1, 2, 3]],
815
                               [0.72377397, 2.42737471, 0.59083726]),
816
                              ([60, 30, 100, 500, [1, 2, 3]],
817
                               [0.68626344, 2.37530133, 0.60060124]),
818
                              ([70, 15, 100, 500, [1, 2, 3]],
819
                               [0.72760378, 2.18082305, 0.29141979])])
820
    def test_map_E_to_height_scalar_location(self, in_args, test_mapped):
821
        """Test mapping of E-field to a specified height."""
822
        mapped = self.apex_out.map_E_to_height(*in_args)
823
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
824
        return
825
826 View Code Duplication
    @pytest.mark.parametrize('ivec', range(0, 5))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
827
    def test_map_E_to_height_array_location(self, ivec):
828
        """Test mapping of E-field to a specified height with array input."""
829
        # Set the base input and output values
830
        efield = np.array([[1, 2, 3]] * 2).transpose()
831
        in_args = [60, 15, 100, 500, efield]
832
        test_mapped = np.full(shape=(2, 3),
833
                              fill_value=[0.71152183, 2.35624876,
834
                                          0.57260784]).transpose()
835
836
        # Update inputs for one vectorized value if this is a location input
837
        if ivec < 4:
838
            in_args[ivec] = [in_args[ivec], in_args[ivec]]
839
840
        # Get the mapped output and test the results
841
        mapped = self.apex_out.map_E_to_height(*in_args)
842
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
843
        return
844
845
    @pytest.mark.parametrize("in_args,test_mapped",
846
                             [([60, 15, 100, 500, [1, 2, 3]],
847
                               [0.81971957, 2.84512495, 0.69545001]),
848
                              ([60, 15, 100, 500, [2, 3, 4]],
849
                               [1.83027746, 4.14346436, 0.94764179]),
850
                              ([60, 15, 100, 1000, [1, 2, 3]],
851
                               [0.92457698, 3.14997661, 0.85135187]),
852
                              ([60, 15, 200, 500, [1, 2, 3]],
853
                               [0.80388262, 2.79321504, 0.68285158]),
854
                              ([60, 30, 100, 500, [1, 2, 3]],
855
                               [0.76141245, 2.87884673, 0.73655941]),
856
                              ([70, 15, 100, 500, [1, 2, 3]],
857
                               [0.84681866, 2.5925821,  0.34792655])])
858
    def test_map_V_to_height_scalar_location(self, in_args, test_mapped):
859
        """Test mapping of velocity to a specified height."""
860
        mapped = self.apex_out.map_V_to_height(*in_args)
861
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
862
        return
863
864 View Code Duplication
    @pytest.mark.parametrize('ivec', range(0, 5))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
865
    def test_map_V_to_height_array_location(self, ivec):
866
        """Test mapping of velocity to a specified height with array input."""
867
        # Set the base input and output values
868
        evel = np.array([[1, 2, 3]] * 2).transpose()
869
        in_args = [60, 15, 100, 500, evel]
870
        test_mapped = np.full(shape=(2, 3),
871
                              fill_value=[0.81971957, 2.84512495,
872
                                          0.69545001]).transpose()
873
874
        # Update inputs for one vectorized value if this is a location input
875
        if ivec < 4:
876
            in_args[ivec] = [in_args[ivec], in_args[ivec]]
877
878
        # Get the mapped output and test the results
879
        mapped = self.apex_out.map_V_to_height(*in_args)
880
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
881
        return
882
883
884
class TestApexBasevectorMethods():
885
    """Test the Apex height base vector methods."""
886
    def setup(self):
887
        """Initialize all tests."""
888
        self.apex_out = Apex(date=2000, refh=300)
889
        self.lat = 60
890
        self.lon = 15
891
        self.height = 100
892
        self.test_basevec = None
893
894
    def teardown(self):
895
        """Clean up after each test."""
896
        del self.apex_out, self.test_basevec, self.lat, self.lon, self.height
897
898
    def get_comparison_results(self, bv_coord, coords, precision):
899
        """Get the base vector results using the hidden function for comparison.
900
901
        Parameters
902
        ----------
903
        bv_coord : str
904
            Basevector coordinate scheme, expects on of 'apex', 'qd',
905
            or 'bvectors_apex'
906
        coords : str
907
            Expects one of 'geo', 'apex', or 'qd'
908
        precision : float
909
            Float specifiying precision
910
911
        """
912
        if coords == "geo":
913
            glat = self.lat
914
            glon = self.lon
915
        else:
916
            apex_method = getattr(self.apex_out, "{:s}2geo".format(coords))
917
            glat, glon, _ = apex_method(self.lat, self.lon, self.height,
918
                                        precision=precision)
919
920
        if bv_coord == 'qd':
921
            self.test_basevec = self.apex_out._basevec(glat, glon, self.height)
922
        elif bv_coord == 'apex':
923
            (_, _, _, _, f1, f2, _, d1, d2, d3, _, e1, e2,
924
             e3) = self.apex_out._geo2apexall(glat, glon, 100)
925
            self.test_basevec = (f1, f2, d1, d2, d3, e1, e2, e3)
926
        else:
927
            # These are set results that need to be updated with IGRF
928
            if coords == "geo":
929
                self.test_basevec = (
930
                    np.array([4.42368795e-05, 4.42368795e-05]),
931
                    np.array([[0.01047826, 0.01047826],
932
                              [0.33089194, 0.33089194],
933
                              [-1.04941, -1.04941]]),
934
                    np.array([5.3564698e-05, 5.3564698e-05]),
935
                    np.array([[0.00865356, 0.00865356],
936
                              [0.27327004, 0.27327004],
937
                              [-0.8666646, -0.8666646]]))
938
            elif coords == "apex":
939
                self.test_basevec = (
940
                    np.array([4.48672735e-05, 4.48672735e-05]),
941
                    np.array([[-0.12510721, -0.12510721],
942
                              [0.28945938, 0.28945938],
943
                              [-1.1505738, -1.1505738]]),
944
                    np.array([6.38577444e-05, 6.38577444e-05]),
945
                    np.array([[-0.08790194, -0.08790194],
946
                              [0.2033779, 0.2033779],
947
                              [-0.808408, -0.808408]]))
948
            else:
949
                self.test_basevec = (
950
                    np.array([4.46348578e-05, 4.46348578e-05]),
951
                    np.array([[-0.12642345, -0.12642345],
952
                              [0.29695055, 0.29695055],
953
                              [-1.1517885, -1.1517885]]),
954
                    np.array([6.38626285e-05, 6.38626285e-05]),
955
                    np.array([[-0.08835986, -0.08835986],
956
                              [0.20754464, 0.20754464],
957
                              [-0.8050078, -0.8050078]]))
958
959
        return
960
961
    @pytest.mark.parametrize("bv_coord", ["qd", "apex"])
962
    @pytest.mark.parametrize("coords,precision",
963
                             [("geo", 1e-10), ("apex", 1.0e-2), ("qd", 1.0e-2)])
964
    def test_basevectors_scalar(self, bv_coord, coords, precision):
965
        """Test the base vector calculations with scalars."""
966
        # Get the base vectors
967
        base_method = getattr(self.apex_out,
968
                              "basevectors_{:s}".format(bv_coord))
969
        basevec = base_method(self.lat, self.lon, self.height, coords=coords,
970
                              precision=precision)
971
        self.get_comparison_results(bv_coord, coords, precision)
972
        if bv_coord == "apex":
973
            basevec = list(basevec)
974
            for i in range(4):
975
                # Not able to compare indices 2, 3, 4, and 5
976
                basevec.pop(2)
977
978
        # Test the results
979
        for i, vec in enumerate(basevec):
980
            np.testing.assert_allclose(vec, self.test_basevec[i])
981
        return
982
983
    @pytest.mark.parametrize("bv_coord", ["qd", "apex"])
984
    def test_basevectors_scalar_shape(self, bv_coord):
985
        """Test the shape of the scalar output."""
986
        base_method = getattr(self.apex_out,
987
                              "basevectors_{:s}".format(bv_coord))
988
        basevec = base_method(self.lat, self.lon, self.height)
989
990
        for i, vec in enumerate(basevec):
991
            if i < 2:
992
                assert vec.shape == (2,)
993
            else:
994
                assert vec.shape == (3,)
995
        return
996
997
    @pytest.mark.parametrize("bv_coord", ["qd", "apex"])
998
    @pytest.mark.parametrize("ivec", range(3))
999
    def test_basevectors_array(self, bv_coord, ivec):
1000
        """Test the output shape for array inputs."""
1001
        # Define the input arguments
1002
        in_args = [self.lat, self.lon, self.height]
1003
        in_args[ivec] = [in_args[ivec] for i in range(4)]
1004
1005
        # Get the basevectors
1006
        base_method = getattr(self.apex_out,
1007
                              "basevectors_{:s}".format(bv_coord))
1008
        basevec = base_method(*in_args, coords='geo', precision=1e-10)
1009
        self.get_comparison_results(bv_coord, "geo", 1e-10)
1010
        if bv_coord == "apex":
1011
            basevec = list(basevec)
1012
            for i in range(4):
1013
                # Not able to compare indices 2, 3, 4, and 5
1014
                basevec.pop(2)
1015
1016
        # Evaluate the shape and the values
1017
        for i, vec in enumerate(basevec):
1018
            idim = 2 if i < 2 else 3
1019
            assert vec.shape == (idim, 4)
1020
            assert np.all(self.test_basevec[i][0] == vec[0])
1021
            assert np.all(self.test_basevec[i][1] == vec[1])
1022
        return
1023
1024
    @pytest.mark.parametrize("coords", ["geo", "apex", "qd"])
1025
    def test_bvectors_apex(self, coords):
1026
        """Test the bvectors_apex method."""
1027
        in_args = [[self.lat, self.lat], [self.lon, self.lon],
1028
                   [self.height, self.height]]
1029
        self.get_comparison_results("bvectors_apex", coords, 1e-10)
1030
1031
        basevec = self.apex_out.bvectors_apex(*in_args, coords=coords,
1032
                                              precision=1e-10)
1033
        for i, vec in enumerate(basevec):
1034
            np.testing.assert_array_almost_equal(vec, self.test_basevec[i],
1035
                                                 decimal=5)
1036
        return
1037
1038
    def test_basevectors_apex_extra_values(self):
1039
        """Test specific values in the apex base vector output."""
1040
        # Set the testing arrays
1041
        self.test_basevec = [np.array([0.092637, -0.245951, 0.938848]),
1042
                             np.array([0.939012, 0.073416, -0.07342]),
1043
                             np.array([0.055389, 1.004155, 0.257594]),
1044
                             np.array([0, 0, 1.065135])]
1045
1046
        # Get the desired output
1047
        basevec = self.apex_out.basevectors_apex(0, 15, 100, coords='geo')
1048
1049
        # Test the values not covered by `test_basevectors_scalar`
1050
        for itest, ibase in enumerate(np.arange(2, 6, 1)):
1051
            np.testing.assert_allclose(basevec[ibase],
1052
                                       self.test_basevec[itest], rtol=1e-4)
1053
        return
1054
1055
    @pytest.mark.parametrize("lat", range(0, 90, 10))
1056
    @pytest.mark.parametrize("lon", range(0, 360, 15))
1057
    def test_basevectors_apex_delta(self, lat, lon):
1058
        """Test that vectors are calculated correctly."""
1059
        # Get the apex base vectors and sort them for easy testing
1060
        (f1, f2, f3, g1, g2, g3, d1, d2, d3, e1, e2,
1061
         e3) = self.apex_out.basevectors_apex(lat, lon, 500)
1062
        fvec = [np.append(f1, 0), np.append(f2, 0), f3]
1063
        gvec = [g1, g2, g3]
1064
        dvec = [d1, d2, d3]
1065
        evec = [e1, e2, e3]
1066
1067
        for idelta, jdelta in [(i, j) for i in range(3) for j in range(3)]:
1068
            delta = 1 if idelta == jdelta else 0
1069
            np.testing.assert_allclose(np.sum(fvec[idelta] * gvec[jdelta]),
1070
                                       delta, rtol=0, atol=1e-5)
1071
            np.testing.assert_allclose(np.sum(dvec[idelta] * evec[jdelta]),
1072
                                       delta, rtol=0, atol=1e-5)
1073
        return
1074
1075
    def test_basevectors_apex_invalid_scalar(self):
1076
        """Test warning and fill values for base vectors with bad inputs."""
1077
        self.apex_out = Apex(date=2000, refh=10000)
1078
        invalid = np.full(shape=(3,), fill_value=np.nan)
1079
1080
        # Get the output and the warnings
1081
        with warnings.catch_warnings(record=True) as warn_rec:
1082
            basevec = self.apex_out.basevectors_apex(0, 0, 0)
1083
1084
        for i, bvec in enumerate(basevec):
1085
            if i < 2:
1086
                assert not np.allclose(bvec, invalid[:2])
1087
            else:
1088
                np.testing.assert_allclose(bvec, invalid)
1089
1090
        assert issubclass(warn_rec[-1].category, UserWarning)
1091
        assert 'set to NaN where' in str(warn_rec[-1].message)
1092
        return
1093
1094
1095
class TestApexGetMethods():
1096
    """Test the Apex `get` methods."""
1097
    def setup(self):
1098
        """Initialize all tests."""
1099
        self.apex_out = Apex(date=2000, refh=300)
1100
1101
    def teardown(self):
1102
        """Clean up after each test."""
1103
        del self.apex_out
1104
1105
    @pytest.mark.parametrize("alat, aheight", [(10, 507.409702543805),
1106
                                               (60, 20313.026999999987)])
1107
    def test_get_apex(self, alat, aheight):
1108
        """Test the apex height retrieval results."""
1109
        alt = self.apex_out.get_apex(alat)
1110
        np.testing.assert_allclose(alt, aheight)
1111
        return
1112
1113
    @pytest.mark.parametrize("glat,glon,height,test_bmag",
1114
                             [([80], [100], [300], 5.100682377815247e-05),
1115
                              (range(50, 90, 8), range(0, 360, 80), [300] * 5,
1116
                               np.array([4.18657154e-05, 5.11118114e-05,
1117
                                         4.91969854e-05, 5.10519207e-05,
1118
                                         4.90054816e-05])),
1119
                              (90.0, 0, 1000, 3.7834718823432923e-05)])
1120
    def test_get_babs(self, glat, glon, height, test_bmag):
1121
        """Test the method to get the magnitude of the magnetic field."""
1122
        bmag = self.apex_out.get_babs(glat, glon, height)
1123
        np.testing.assert_allclose(bmag, test_bmag, rtol=0, atol=1e-5)
1124
        return
1125
1126
    @pytest.mark.parametrize("bad_lat", [(91), (-91)])
1127
    def test_get_with_invalid_lat(self, bad_lat):
1128
        """Test get methods raise ValueError for invalid latitudes."""
1129
1130
        with pytest.raises(ValueError):
1131
            self.apex_out.get_apex(bad_lat)
1132
        return
1133
1134
    @pytest.mark.parametrize("bound_lat", [(90), (-90)])
1135
    def test_get_at_lat_boundary(self, bound_lat):
1136
        """Test get methods at the latitude boundary, with allowed excess."""
1137
        # Get a latitude just beyond the limit
1138
        excess_lat = np.sign(bound_lat) * (abs(bound_lat) + 1.0e-5)
1139
1140
        # Get the two outputs, slight tolerance outside of boundary allowed
1141
        bound_out = self.apex_out.get_apex(bound_lat)
1142
        excess_out = self.apex_out.get_apex(excess_lat)
1143
1144
        # Test the outputs
1145
        np.testing.assert_allclose(excess_out, bound_out, rtol=0, atol=1e-8)
1146
        return
1147