Completed
Pull Request — develop (#73)
by Angeline
03:18 queued 02:12
created

test_Apex.TestApexMethod.test_geo2apexall_array()   B

Complexity

Conditions 5

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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