Passed
Pull Request — develop (#123)
by
unknown
01:24
created

test_Apex.TestApexInit.test_default_datafile()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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 shutil
20
import warnings
21
try:
22
    from importlib.resources import files
23
except ModuleNotFoundError:
24
    from importlib_resources import files
25
26
import apexpy
27
28
29
#def igrf_file(max_attempts=100):
30
#    """A fixture for handling the coefficient file.
31
#
32
#    Parameters
33
#    ----------
34
#    max_attempts : int
35
#        Maximum rename attemps, needed for Windows (default=100)
36
#
37
#    """
38
#    # Ensure the coefficient file exists
39
#    original_file = os.path.join(os.path.dirname(apexpy.helpers.__file__),
40
#                                 'igrf13coeffs.txt')
41
#    tmp_file = "temp_coeff.txt"
42
#    assert os.path.isfile(original_file)
43
#
44
#    # Move the coefficient file
45
#    for _ in range(max_attempts):
46
#        try:
47
#            shutil.move(original_file, tmp_file)
48
#            break
49
#        except Exception:
50
#            pass
51
#    yield original_file
52
#
53
#    # Move the coefficient file back
54
#    for _ in range(max_attempts):
55
#        try:
56
#            shutil.move(tmp_file, original_file)
57
#            break
58
#        except Exception:
59
#            pass
60
#    return
61
62
63
#def test_set_epoch_file_error(igrf_file):
64
#    """Test raises OSError when IGRF coefficient file is missing."""
65
#    # Test missing coefficient file failure
66
#    with pytest.raises(OSError) as oerr:
67
#        apexpy.Apex()
68
#    error_string = "File {:} does not exist".format(igrf_file)
69
#    assert str(oerr.value).startswith(error_string)
70
#    return
71
72
73
class TestApexInit(object):
74
    """Test class for the Apex class object."""
75
76
    def setup_method(self):
77
        """Initialize all tests."""
78
        self.apex_out = None
79
        self.test_date = dt.datetime.utcnow()
80
        self.test_refh = 0
81
        self.bad_file = 'foo/path/to/datafile.blah'
82
83
    def teardown_method(self):
84
        """Clean up after each test."""
85
        del self.apex_out, self.test_date, self.test_refh, self.bad_file
86
87
    def eval_date(self):
88
        """Evaluate the times in self.test_date and self.apex_out."""
89
        if isinstance(self.test_date, dt.datetime) \
90
           or isinstance(self.test_date, dt.date):
91
            self.test_date = apexpy.helpers.toYearFraction(self.test_date)
92
93
        # Assert the times are the same on the order of tens of seconds.
94
        # Necessary to evaluate the current UTC
95
        np.testing.assert_almost_equal(self.test_date, self.apex_out.year, 6)
96
        return
97
98
    def eval_refh(self):
99
        """Evaluate the reference height in self.refh and self.apex_out."""
100
        eval_str = "".join(["expected reference height [",
101
                            "{:}] not equal to Apex ".format(self.test_refh),
102
                            "reference height ",
103
                            "[{:}]".format(self.apex_out.refh)])
104
        assert self.test_refh == self.apex_out.refh, eval_str
105
        return
106
107
    def test_init_defaults(self):
108
        """Test Apex class default initialization."""
109
        self.apex_out = apexpy.Apex()
110
        self.eval_date()
111
        self.eval_refh()
112
        return
113
114
    def test_init_today(self):
115
        """Test Apex class initialization with today's date."""
116
        self.apex_out = apexpy.Apex(date=self.test_date)
117
        self.eval_date()
118
        self.eval_refh()
119
        return
120
121
    @pytest.mark.parametrize("in_date",
122
                             [2015, 2015.5, dt.date(2015, 1, 1),
123
                              dt.datetime(2015, 6, 1, 18, 23, 45)])
124
    def test_init_date(self, in_date):
125
        """Test Apex class with date initialization.
126
127
        Parameters
128
        ----------
129
        in_date : int, float, dt.date, or dt.datetime
130
            Input date in a variety of formats
131
132
        """
133
        self.test_date = in_date
134
        self.apex_out = apexpy.Apex(date=self.test_date)
135
        self.eval_date()
136
        self.eval_refh()
137
        return
138
139
    @pytest.mark.parametrize("new_date", [2015, 2015.5])
140
    def test_set_epoch(self, new_date):
141
        """Test successful setting of Apex epoch after initialization.
142
143
        Parameters
144
        ----------
145
        new_date : int or float
146
            New date for the Apex class
147
148
        """
149
        # Evaluate the default initialization
150
        self.apex_out = apexpy.Apex()
151
        self.eval_date()
152
        self.eval_refh()
153
154
        # Update the epoch
155
        ref_apex = eval(self.apex_out.__repr__())
156
        self.apex_out.set_epoch(new_date)
157
        assert ref_apex != self.apex_out
158
        self.test_date = new_date
159
        self.eval_date()
160
        return
161
162
    @pytest.mark.parametrize("in_refh", [0.0, 300.0, 30000.0, -1.0])
163
    def test_init_refh(self, in_refh):
164
        """Test Apex class with reference height initialization.
165
166
        Parameters
167
        ----------
168
        in_refh : float
169
            Input reference height in km
170
171
        """
172
        self.test_refh = in_refh
173
        self.apex_out = apexpy.Apex(refh=self.test_refh)
174
        self.eval_date()
175
        self.eval_refh()
176
        return
177
178
    @pytest.mark.parametrize("new_refh", [0.0, 300.0, 30000.0, -1.0])
179
    def test_set_refh(self, new_refh):
180
        """Test the method used to set the reference height after the init.
181
182
        Parameters
183
        ----------
184
        new_refh : float
185
            Reference height in km
186
187
        """
188
        # Verify the defaults are set
189
        self.apex_out = apexpy.Apex(date=self.test_date)
190
        self.eval_date()
191
        self.eval_refh()
192
193
        # Update to a new reference height and test
194
        ref_apex = eval(self.apex_out.__repr__())
195
        self.apex_out.set_refh(new_refh)
196
197
        if self.test_refh == new_refh:
198
            assert ref_apex == self.apex_out
199
        else:
200
            assert ref_apex != self.apex_out
201
            self.test_refh = new_refh
202
        self.eval_refh()
203
        return
204
205
    def copy_file(self, original, max_attempts=100):
206
        
207
        _, ext = os.path.splitext(original)
208
        temp_file = 'temp'+ext
209
        
210
        for _ in range(max_attempts):
211
            try:
212
                shutil.copy(original, temp_file)
213
                break
214
            except Exception:
215
                pass
216
        
217
        return temp_file
218
219
    def test_default_datafile(self):
220
        """Test that the class initializes with the default datafile."""
221
        apex_out = apexpy.Apex()
222
        assert os.path.isfile(apex_out.datafile)
223
        return
224
225
    def test_custom_datafile(self):
226
        """Test that the class initializes with a good datafile input."""
227
228
        # Get the original datafile name
229
        apex_out_orig = apexpy.Apex()
230
        original_file = apex_out_orig.datafile
231
        del apex_out_orig
232
233
        # Create copy of datafile
234
        custom_file = self.copy_file(original_file)
235
236
        apex_out = apexpy.Apex(datafile=custom_file)
237
        assert apex_out.datafile == custom_file
238
239
        os.remove(custom_file)
240
        return
241
242
    def test_init_with_bad_datafile(self):
243
        """Test raises IOError with non-existent datafile input."""
244
        with pytest.raises(IOError) as oerr:
245
            apexpy.Apex(datafile=self.bad_file)
246
        assert str(oerr.value).startswith('Data file does not exist')
247
        return
248
249
    def test_default_fortranlib(self):
250
        """Test that the class initializes with the default fortranlib."""
251
        apex_out = apexpy.Apex()
252
        assert os.path.isfile(apex_out.fortranlib)
253
        return
254
255
    def test_custom_fortranlib(self):
256
        """Test that the class initializes with a good fortranlib input."""
257
 
258
        # Get the original fortranlib name
259
        apex_out_orig = apexpy.Apex()
260
        original_lib = apex_out_orig.fortranlib
261
        del apex_out_orig
262
263
        # Create copy of datafile
264
        custom_lib = self.copy_file(original_lib)
265
266
        apex_out = apexpy.Apex(fortranlib=custom_lib)
267
        assert apex_out.fortranlib == custom_lib
268
269
        os.remove(custom_lib)
270
        return
271
272
    def test_init_with_bad_fortranlib(self):
273
        """Test raises IOError with non-existent fortranlib input."""
274
        with pytest.raises(IOError) as oerr:
275
            apexpy.Apex(fortranlib=self.bad_file)
276
        assert str(oerr.value).startswith('Fortran library does not exist')
277
        return
278
279
    def test_igrf_fn(self):
280
        """Test the default igrf_fn."""
281
        apex_out = apexpy.Apex()
282
        assert os.path.isfile(apex_out.igrf_fn)
283
        return
284
    
285
    def test_repr_eval(self):
286
        """Test the Apex.__repr__ results."""
287
        # Initialize the apex object
288
        self.apex_out = apexpy.Apex()
289
        self.eval_date()
290
        self.eval_refh()
291
292
        # Get and test the repr string
293
        out_str = self.apex_out.__repr__()
294
        assert out_str.find("apexpy.Apex(") == 0
295
296
        # Test the ability to re-create the apex object from the repr string
297
        new_apex = eval(out_str)
298
        assert new_apex == self.apex_out
299
        return
300
301
    def test_ne_other_class(self):
302
        """Test Apex class inequality to a different class."""
303
        self.apex_out = apexpy.Apex()
304
        self.eval_date()
305
        self.eval_refh()
306
307
        assert self.apex_out != self.test_date
308
        return
309
310
    def test_ne_missing_attr(self):
311
        """Test Apex class inequality when attributes are missing from one."""
312
        self.apex_out = apexpy.Apex()
313
        self.eval_date()
314
        self.eval_refh()
315
        ref_apex = eval(self.apex_out.__repr__())
316
        del ref_apex.RE
317
318
        assert ref_apex != self.apex_out
319
        assert self.apex_out != ref_apex
320
        return
321
322
    def test_eq_missing_attr(self):
323
        """Test Apex class equality when attributes are missing from both."""
324
        self.apex_out = apexpy.Apex()
325
        self.eval_date()
326
        self.eval_refh()
327
        ref_apex = eval(self.apex_out.__repr__())
328
        del ref_apex.RE, self.apex_out.RE
329
330
        assert ref_apex == self.apex_out
331
        return
332
333
    def test_str_eval(self):
334
        """Test the Apex.__str__ results."""
335
        # Initialize the apex object
336
        self.apex_out = apexpy.Apex()
337
        self.eval_date()
338
        self.eval_refh()
339
340
        # Get and test the printed string
341
        out_str = self.apex_out.__str__()
342
        assert out_str.find("Decimal year") > 0
343
        return
344
345
346
class TestApexMethod(object):
347
    """Test the Apex methods."""
348
    def setup_method(self):
349
        """Initialize all tests."""
350
        self.apex_out = apexpy.Apex(date=2000, refh=300)
351
        self.in_lat = 60
352
        self.in_lon = 15
353
        self.in_alt = 100
354
355
    def teardown_method(self):
356
        """Clean up after each test."""
357
        del self.apex_out, self.in_lat, self.in_lon, self.in_alt
358
359
    def get_input_args(self, method_name, precision=0.0):
360
        """Set the input arguments for the different Apex methods.
361
362
        Parameters
363
        ----------
364
        method_name : str
365
            Name of the Apex class method
366
        precision : float
367
            Value for the precision (default=0.0)
368
369
        Returns
370
        -------
371
        in_args : list
372
            List of the appropriate input arguments
373
374
        """
375
        in_args = [self.in_lat, self.in_lon, self.in_alt]
376
377
        # Add precision, if needed
378
        if method_name in ["_qd2geo", "apxq2g", "apex2geo", "qd2geo",
379
                           "_apex2geo"]:
380
            in_args.append(precision)
381
382
        # Add a reference height, if needed
383
        if method_name in ["apxg2all"]:
384
            in_args.append(300)
385
386
        # Add a vector flag, if needed
387
        if method_name in ["apxg2all", "apxg2q"]:
388
            in_args.append(1)
389
390
        return in_args
391
392
    def test_apex_conversion_today(self):
393
        """Test Apex class conversion with today's date."""
394
        self.apex_out = apexpy.Apex(date=dt.datetime.utcnow(), refh=300)
395
        assert not np.isnan(self.apex_out.geo2apex(self.in_lat, self.in_lon,
396
                                                   self.in_alt)).any()
397
        return
398
399
    @pytest.mark.parametrize("apex_method,fortran_method,fslice",
400
                             [("_geo2qd", "apxg2q", slice(0, 2, 1)),
401
                              ("_geo2apex", "apxg2all", slice(2, 4, 1)),
402
                              ("_qd2geo", "apxq2g", slice(None)),
403
                              ("_basevec", "apxg2q", slice(2, 4, 1))])
404
    @pytest.mark.parametrize("lat", [0, 30, 60, 89])
405
    @pytest.mark.parametrize("lon", [-179, -90, 0, 90, 180])
406
    def test_fortran_scalar_input(self, apex_method, fortran_method, fslice,
407
                                  lat, lon):
408
        """Tests Apex/fortran interface consistency for scalars.
409
410
        Parameters
411
        ----------
412
        apex_method : str
413
            Name of the Apex class method to test
414
        fortran_method : str
415
            Name of the Fortran function to test
416
        fslice : slice
417
            Slice used select the appropriate Fortran outputs
418
        lat : int or float
419
            Latitude in degrees N
420
        lon : int or float
421
            Longitude in degrees E
422
423
        """
424
        # Set the input coordinates
425
        self.in_lat = lat
426
        self.in_lon = lon
427
428
        # Get the Apex class method and the fortran function call
429
        apex_func = getattr(self.apex_out, apex_method)
430
        fortran_func = getattr(apexpy.fortranapex, fortran_method)
431
432
        # Get the appropriate input arguments
433
        apex_args = self.get_input_args(apex_method)
434
        fortran_args = self.get_input_args(fortran_method)
435
436
        # Evaluate the equivalent function calls
437
        np.testing.assert_allclose(apex_func(*apex_args),
438
                                   fortran_func(*fortran_args)[fslice])
439
        return
440
441
    @pytest.mark.parametrize("apex_method,fortran_method,fslice",
442
                             [("_geo2qd", "apxg2q", slice(0, 2, 1)),
443
                              ("_geo2apex", "apxg2all", slice(2, 4, 1)),
444
                              ("_qd2geo", "apxq2g", slice(None)),
445
                              ("_basevec", "apxg2q", slice(2, 4, 1))])
446
    @pytest.mark.parametrize("lat", [0, 30, 60, 89])
447
    @pytest.mark.parametrize("lon1,lon2", [(180, 180), (-180, -180),
448
                                           (180, -180), (-180, 180),
449
                                           (-345, 15), (375, 15)])
450
    def test_fortran_longitude_rollover(self, apex_method, fortran_method,
451
                                        fslice, lat, lon1, lon2):
452
        """Tests Apex/fortran interface consistency for longitude rollover.
453
454
        Parameters
455
        ----------
456
        apex_method : str
457
            Name of the Apex class method to test
458
        fortran_method : str
459
            Name of the Fortran function to test
460
        fslice : slice
461
            Slice used select the appropriate Fortran outputs
462
        lat : int or float
463
            Latitude in degrees N
464
        lon1 : int or float
465
            Longitude in degrees E
466
        lon2 : int or float
467
            Equivalent longitude in degrees E
468
469
        """
470
        # Set the fixed input coordinate
471
        self.in_lat = lat
472
473
        # Get the Apex class method and the fortran function call
474
        apex_func = getattr(self.apex_out, apex_method)
475
        fortran_func = getattr(apexpy.fortranapex, fortran_method)
476
477
        # Get the appropriate input arguments
478
        self.in_lon = lon1
479
        apex_args = self.get_input_args(apex_method)
480
481
        self.in_lon = lon2
482
        fortran_args = self.get_input_args(fortran_method)
483
484
        # Evaluate the equivalent function calls
485
        np.testing.assert_allclose(apex_func(*apex_args),
486
                                   fortran_func(*fortran_args)[fslice])
487
        return
488
489
    @pytest.mark.parametrize("arr_shape", [(2, 2), (4,), (1, 4)])
490
    @pytest.mark.parametrize("apex_method,fortran_method,fslice",
491
                             [("_geo2qd", "apxg2q", slice(0, 2, 1)),
492
                              ("_geo2apex", "apxg2all", slice(2, 4, 1)),
493
                              ("_qd2geo", "apxq2g", slice(None)),
494
                              ("_basevec", "apxg2q", slice(2, 4, 1))])
495
    def test_fortran_array_input(self, arr_shape, apex_method, fortran_method,
496
                                 fslice):
497
        """Tests Apex/fortran interface consistency for array input.
498
499
        Parameters
500
        ----------
501
        arr_shape : tuple
502
            Expected output shape
503
        apex_method : str
504
            Name of the Apex class method to test
505
        fortran_method : str
506
            Name of the Fortran function to test
507
        fslice : slice
508
            Slice used select the appropriate Fortran outputs
509
510
        """
511
        # Get the Apex class method and the fortran function call
512
        apex_func = getattr(self.apex_out, apex_method)
513
        fortran_func = getattr(apexpy.fortranapex, fortran_method)
514
515
        # Set up the input arrays
516
        ref_lat = np.array([0, 30, 60, 90])
517
        ref_alt = np.array([100, 200, 300, 400])
518
        self.in_lat = ref_lat.reshape(arr_shape)
519
        self.in_alt = ref_alt.reshape(arr_shape)
520
        apex_args = self.get_input_args(apex_method)
521
522
        # Get the Apex class results
523
        aret = apex_func(*apex_args)
524
525
        # Get the fortran function results
526
        flats = list()
527
        flons = list()
528
529
        for i, lat in enumerate(ref_lat):
530
            self.in_lat = lat
531
            self.in_alt = ref_alt[i]
532
            fortran_args = self.get_input_args(fortran_method)
533
            fret = fortran_func(*fortran_args)[fslice]
534
            flats.append(fret[0])
535
            flons.append(fret[1])
536
537
        flats = np.array(flats)
538
        flons = np.array(flons)
539
540
        # Evaluate results
541
        try:
542
            # This returned value is array of floats
543
            np.testing.assert_allclose(aret[0].astype(float),
544
                                       flats.reshape(arr_shape).astype(float))
545
            np.testing.assert_allclose(aret[1].astype(float),
546
                                       flons.reshape(arr_shape).astype(float))
547
        except ValueError:
548
            # This returned value is array of arrays
549
            alats = aret[0].reshape((4,))
550
            alons = aret[1].reshape((4,))
551
            for i, flat in enumerate(flats):
552
                np.testing.assert_array_almost_equal(alats[i], flat, 2)
553
                np.testing.assert_array_almost_equal(alons[i], flons[i], 2)
554
555
        return
556
557
    @pytest.mark.parametrize("lat", [0, 30, 60, 89])
558
    @pytest.mark.parametrize("lon", [-179, -90, 0, 90, 180])
559
    def test_geo2apexall_scalar(self, lat, lon):
560
        """Test Apex/fortran geo2apexall interface consistency for scalars.
561
562
        Parameters
563
        ----------
564
        lat : int or float
565
            Latitude in degrees N
566
        long : int or float
567
            Longitude in degrees E
568
569
        """
570
        # Get the Apex and Fortran results
571
        aret = self.apex_out._geo2apexall(lat, lon, self.in_alt)
572
        fret = apexpy.fortranapex.apxg2all(lat, lon, self.in_alt, 300, 1)
573
574
        # Evaluate each element in the results
575
        for aval, fval in zip(aret, fret):
576
            np.testing.assert_allclose(aval, fval)
577
578
    @pytest.mark.parametrize("arr_shape", [(2, 2), (4,), (1, 4)])
579
    def test_geo2apexall_array(self, arr_shape):
580
        """Test Apex/fortran geo2apexall interface consistency for arrays.
581
582
        Parameters
583
        ----------
584
        arr_shape : tuple
585
            Expected output shape
586
587
        """
588
        # Set the input
589
        self.in_lat = np.array([0, 30, 60, 90])
590
        self.in_alt = np.array([100, 200, 300, 400])
591
592
        # Get the Apex class results
593
        aret = self.apex_out._geo2apexall(self.in_lat.reshape(arr_shape),
594
                                          self.in_lon,
595
                                          self.in_alt.reshape(arr_shape))
596
597
        # For each lat/alt pair, get the Fortran results
598
        fret = list()
599
        for i, lat in enumerate(self.in_lat):
600
            fret.append(apexpy.fortranapex.apxg2all(lat, self.in_lon,
601
                                                    self.in_alt[i], 300, 1))
602
603
        # Cycle through all returned values
604
        for i, ret in enumerate(aret):
605
            try:
606
                # This returned value is array of floats
607
                fret_test = np.array([fret[0][i], fret[1][i], fret[2][i],
608
                                      fret[3][i]]).reshape(arr_shape)
609
                np.testing.assert_allclose(ret.astype(float),
610
                                           fret_test.astype(float))
611
            except ValueError:
612
                # This returned value is array of arrays
613
                ret = ret.reshape((4,))
614
                for j, single_fret in enumerate(fret):
615
                    np.testing.assert_allclose(ret[j], single_fret[i])
616
        return
617
618
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
619
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
620
    def test_convert_consistency(self, in_coord, out_coord):
621
        """Test the self-consistency of the Apex convert method.
622
623
        Parameters
624
        ----------
625
        in_coord : str
626
            Input coordinate system
627
        out_coord : str
628
            Output coordinate system
629
630
        """
631
        if in_coord == out_coord:
632
            pytest.skip("Test not needed for same src and dest coordinates")
633
634
        # Define the method name
635
        method_name = "2".join([in_coord, out_coord])
636
637
        # Get the method and method inputs
638
        convert_kwargs = {'height': self.in_alt, 'precision': 0.0}
639
        apex_args = self.get_input_args(method_name)
640
        apex_method = getattr(self.apex_out, method_name)
641
642
        # Define the slice needed to get equivalent output from the named method
643
        mslice = slice(0, -1, 1) if out_coord == "geo" else slice(None)
644
645
        # Get output using convert and named method
646
        convert_out = self.apex_out.convert(self.in_lat, self.in_lon, in_coord,
647
                                            out_coord, **convert_kwargs)
648
        method_out = apex_method(*apex_args)[mslice]
649
650
        # Compare both outputs, should be identical
651
        np.testing.assert_allclose(convert_out, method_out)
652
        return
653
654
    @pytest.mark.parametrize("bound_lat", [90, -90])
655
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
656
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
657
    def test_convert_at_lat_boundary(self, bound_lat, in_coord, out_coord):
658
        """Test the conversion at the latitude boundary, with allowed excess.
659
660
        Parameters
661
        ----------
662
        bound_lat : int or float
663
            Boundary latitude in degrees N
664
        in_coord : str
665
            Input coordinate system
666
        out_coord : str
667
            Output coordinate system
668
669
        """
670
        excess_lat = np.sign(bound_lat) * (abs(bound_lat) + 1.0e-5)
671
672
        # Get the two outputs, slight tolerance outside of boundary allowed
673
        bound_out = self.apex_out.convert(bound_lat, 0, in_coord, out_coord)
674
        excess_out = self.apex_out.convert(excess_lat, 0, in_coord, out_coord)
675
676
        # Test the outputs
677
        np.testing.assert_allclose(excess_out, bound_out, rtol=0, atol=1e-8)
678
        return
679
680
    def test_convert_qd2apex_at_equator(self):
681
        """Test the quasi-dipole to apex conversion at the magnetic equator."""
682
        eq_out = self.apex_out.convert(lat=0.0, lon=0, source='qd', dest='apex',
683
                                       height=320.0)
684
        close_out = self.apex_out.convert(lat=0.001, lon=0, source='qd',
685
                                          dest='apex', height=320.0)
686
        np.testing.assert_allclose(eq_out, close_out, atol=1e-4)
687
        return
688
689
    @pytest.mark.parametrize("src", ["geo", "apex", "qd"])
690
    @pytest.mark.parametrize("dest", ["geo", "apex", "qd"])
691
    def test_convert_withnan(self, src, dest):
692
        """Test Apex.convert success with NaN input.
693
694
        Parameters
695
        ----------
696
        src : str
697
            Input coordinate system
698
        dest : str
699
            Output coordinate system
700
701
        """
702
        if src == dest:
703
            pytest.skip("Test not needed for same src and dest coordinates")
704
705
        num_nans = 5
706
        in_loc = np.arange(0, 10, dtype=float)
707
        in_loc[:num_nans] = np.nan
708
709
        out_loc = self.apex_out.convert(in_loc, in_loc, src, dest, height=320)
710
711
        for out in out_loc:
712
            assert np.all(np.isnan(out[:num_nans])), "NaN output expected"
713
            assert np.all(np.isfinite(out[num_nans:])), "Finite output expected"
714
715
        return
716
717
    @pytest.mark.parametrize("bad_lat", [91, -91])
718
    def test_convert_invalid_lat(self, bad_lat):
719
        """Test convert raises ValueError for invalid latitudes.
720
721
        Parameters
722
        ----------
723
        bad_lat : int or float
724
            Latitude ouside the supported range in degrees N
725
726
        """
727
728
        with pytest.raises(ValueError) as verr:
729
            self.apex_out.convert(bad_lat, 0, 'geo', 'geo')
730
731
        assert str(verr.value).find("must be in [-90, 90]") > 0
732
        return
733
734
    @pytest.mark.parametrize("coords", [("foobar", "geo"), ("geo", "foobar"),
735
                                        ("geo", "mlt")])
736
    def test_convert_invalid_transformation(self, coords):
737
        """Test raises NotImplementedError for bad coordinates.
738
739
        Parameters
740
        ----------
741
        coords : tuple
742
            Tuple specifying the input and output coordinate systems
743
744
        """
745
        if "mlt" in coords:
746
            estr = "datetime must be given for MLT calculations"
747
        else:
748
            estr = "Unknown coordinate transformation"
749
750
        with pytest.raises(ValueError) as verr:
751
            self.apex_out.convert(0, 0, *coords)
752
753
        assert str(verr).find(estr) >= 0
754
        return
755
756 View Code Duplication
    @pytest.mark.parametrize("method_name, out_comp",
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
757
                             [("geo2apex",
758
                               (55.94841766357422, 94.10684204101562)),
759
                              ("apex2geo",
760
                               (51.476322174072266, -66.22817993164062,
761
                                5.727287771151168e-06)),
762
                              ("geo2qd",
763
                               (56.531288146972656, 94.10684204101562)),
764
                              ("apex2qd", (60.498401178276744, 15.0)),
765
                              ("qd2apex", (59.49138097045895, 15.0))])
766
    def test_method_scalar_input(self, method_name, out_comp):
767
        """Test the user method against set values with scalars.
768
769
        Parameters
770
        ----------
771
        method_name : str
772
            Apex class method to be tested
773
        out_comp : tuple of floats
774
            Expected output values
775
776
        """
777
        # Get the desired methods
778
        user_method = getattr(self.apex_out, method_name)
779
780
        # Get the user output
781
        user_out = user_method(self.in_lat, self.in_lon, self.in_alt)
782
783
        # Evaluate the user output
784
        np.testing.assert_allclose(user_out, out_comp, rtol=1e-5, atol=1e-5)
785
786
        for out_val in user_out:
787
            assert np.asarray(out_val).shape == (), "output is not a scalar"
788
        return
789
790
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
791
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
792
    @pytest.mark.parametrize("method_args, out_shape",
793
                             [([[60, 60], 15, 100], (2,)),
794
                              ([60, [15, 15], 100], (2,)),
795
                              ([60, 15, [100, 100]], (2,)),
796
                              ([[50, 60], [15, 16], [100, 200]], (2,))])
797
    def test_method_broadcast_input(self, in_coord, out_coord, method_args,
798
                                    out_shape):
799
        """Test the user method with inputs that require some broadcasting.
800
801
        Parameters
802
        ----------
803
        in_coord : str
804
            Input coordiante system
805
        out_coord : str
806
            Output coordiante system
807
        method_args : list
808
            List of input arguments
809
        out_shape : tuple
810
            Expected shape of output values
811
812
        """
813
        if in_coord == out_coord:
814
            pytest.skip("Test not needed for same src and dest coordinates")
815
816
        # Get the desired methods
817
        method_name = "2".join([in_coord, out_coord])
818
        user_method = getattr(self.apex_out, method_name)
819
820
        # Get the user output
821
        user_out = user_method(*method_args)
822
823
        # Evaluate the user output
824
        for out_val in user_out:
825
            assert hasattr(out_val, 'shape'), "output coordinate isn't np.array"
826
            assert out_val.shape == out_shape
827
        return
828
829
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
830
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
831
    @pytest.mark.parametrize("bad_lat", [91, -91])
832
    def test_method_invalid_lat(self, in_coord, out_coord, bad_lat):
833
        """Test convert raises ValueError for invalid latitudes.
834
835
        Parameters
836
        ----------
837
        in_coord : str
838
            Input coordiante system
839
        out_coord : str
840
            Output coordiante system
841
        bad_lat : int
842
            Latitude in degrees N that is out of bounds
843
844
        """
845
        if in_coord == out_coord:
846
            pytest.skip("Test not needed for same src and dest coordinates")
847
848
        # Get the desired methods
849
        method_name = "2".join([in_coord, out_coord])
850
        user_method = getattr(self.apex_out, method_name)
851
852
        with pytest.raises(ValueError) as verr:
853
            user_method(bad_lat, 15, 100)
854
855
        assert str(verr.value).find("must be in [-90, 90]") > 0
856
        return
857
858
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
859
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
860
    @pytest.mark.parametrize("bound_lat", [90, -90])
861
    def test_method_at_lat_boundary(self, in_coord, out_coord, bound_lat):
862
        """Test user methods at the latitude boundary, with allowed excess.
863
864
        Parameters
865
        ----------
866
        in_coord : str
867
            Input coordiante system
868
        out_coord : str
869
            Output coordiante system
870
        bad_lat : int
871
            Latitude in degrees N that is at the limits of the boundary
872
873
        """
874
        if in_coord == out_coord:
875
            pytest.skip("Test not needed for same src and dest coordinates")
876
877
        # Get the desired methods
878
        method_name = "2".join([in_coord, out_coord])
879
        user_method = getattr(self.apex_out, method_name)
880
881
        # Get a latitude just beyond the limit
882
        excess_lat = np.sign(bound_lat) * (abs(bound_lat) + 1.0e-5)
883
884
        # Get the two outputs, slight tolerance outside of boundary allowed
885
        bound_out = user_method(bound_lat, 0, 100)
886
        excess_out = user_method(excess_lat, 0, 100)
887
888
        # Test the outputs
889
        np.testing.assert_allclose(excess_out, bound_out, rtol=0, atol=1e-8)
890
        return
891
892
    def test_geo2apex_undefined_warning(self):
893
        """Test geo2apex warning and fill values for an undefined location."""
894
895
        # Update the apex object
896
        self.apex_out = apexpy.Apex(date=2000, refh=10000)
897
898
        # Get the output and the warnings
899
        with warnings.catch_warnings(record=True) as warn_rec:
900
            user_lat, user_lon = self.apex_out.geo2apex(0, 0, 0)
901
902
        assert np.isnan(user_lat)
903
        assert np.isfinite(user_lon)
904
        assert len(warn_rec) == 1
905
        assert issubclass(warn_rec[-1].category, UserWarning)
906
        assert 'latitude set to NaN where' in str(warn_rec[-1].message)
907
        return
908
909
    @pytest.mark.parametrize("method_name", ["apex2qd", "qd2apex"])
910
    @pytest.mark.parametrize("delta_h", [1.0e-6, -1.0e-6])
911
    def test_quasidipole_apexheight_close(self, method_name, delta_h):
912
        """Test quasi-dipole success with a height close to the reference.
913
914
        Parameters
915
        ----------
916
        method_name : str
917
            Apex class method name to be tested
918
        delta_h : float
919
            tolerance for height in km
920
921
        """
922
        qd_method = getattr(self.apex_out, method_name)
923
        in_args = [0, 15, self.apex_out.refh + delta_h]
924
        out_coords = qd_method(*in_args)
925
926
        for i, out_val in enumerate(out_coords):
927
            np.testing.assert_almost_equal(out_val, in_args[i], decimal=3)
928
        return
929
930
    @pytest.mark.parametrize("method_name, hinc, msg",
931
                             [("apex2qd", 1.0, "is > apex height"),
932
                              ("qd2apex", -1.0, "is < reference height")])
933
    def test_quasidipole_raises_apexheight(self, method_name, hinc, msg):
934
        """Quasi-dipole raises ApexHeightError when height above reference.
935
936
        Parameters
937
        ----------
938
        method_name : str
939
            Apex class method name to be tested
940
        hinc : float
941
            Height increment in km
942
        msg : str
943
            Expected output message
944
945
        """
946
        qd_method = getattr(self.apex_out, method_name)
947
948
        with pytest.raises(apexpy.ApexHeightError) as aerr:
949
            qd_method(0, 15, self.apex_out.refh + hinc)
950
951
        assert str(aerr).find(msg) > 0
952
        return
953
954
955
class TestApexMLTMethods(object):
956
    """Test the Apex Magnetic Local Time (MLT) methods."""
957
    def setup_method(self):
958
        """Initialize all tests."""
959
        self.apex_out = apexpy.Apex(date=2000, refh=300)
960
        self.in_time = dt.datetime(2000, 2, 3, 4, 5, 6)
961
962
    def teardown_method(self):
963
        """Clean up after each test."""
964
        del self.apex_out, self.in_time
965
966
    @pytest.mark.parametrize("in_coord", ["geo", "apex", "qd"])
967
    def test_convert_to_mlt(self, in_coord):
968
        """Test the conversions to MLT using Apex convert.
969
970
        Parameters
971
        ----------
972
        in_coord : str
973
            Input coordinate system
974
975
        """
976
977
        # Get the magnetic longitude from the appropriate method
978
        if in_coord == "geo":
979
            apex_method = getattr(self.apex_out, "{:s}2apex".format(in_coord))
980
            mlon = apex_method(60, 15, 100)[1]
981
        else:
982
            mlon = 15
983
984
        # Get the output MLT values
985
        convert_mlt = self.apex_out.convert(60, 15, in_coord, 'mlt',
986
                                            height=100, ssheight=2e5,
987
                                            datetime=self.in_time)[1]
988
        method_mlt = self.apex_out.mlon2mlt(mlon, self.in_time, ssheight=2e5)
989
990
        # Test the outputs
991
        np.testing.assert_allclose(convert_mlt, method_mlt)
992
        return
993
994
    @pytest.mark.parametrize("out_coord", ["geo", "apex", "qd"])
995
    def test_convert_mlt_to_lon(self, out_coord):
996
        """Test the conversions from MLT using Apex convert.
997
998
        Parameters
999
        ----------
1000
        out_coord : str
1001
            Output coordinate system
1002
1003
        """
1004
        # Get the output longitudes
1005
        convert_out = self.apex_out.convert(60, 15, 'mlt', out_coord,
1006
                                            height=100, ssheight=2e5,
1007
                                            datetime=self.in_time,
1008
                                            precision=1e-2)
1009
        mlon = self.apex_out.mlt2mlon(15, self.in_time, ssheight=2e5)
1010
1011
        if out_coord == "geo":
1012
            method_out = self.apex_out.apex2geo(60, mlon, 100,
1013
                                                precision=1e-2)[:-1]
1014
        elif out_coord == "qd":
1015
            method_out = self.apex_out.apex2qd(60, mlon, 100)
1016
        else:
1017
            method_out = (60, mlon)
1018
1019
        # Evaluate the outputs
1020
        np.testing.assert_allclose(convert_out, method_out)
1021
        return
1022
1023
    def test_convert_geo2mlt_nodate(self):
1024
        """Test convert from geo to MLT raises ValueError with no datetime."""
1025
        with pytest.raises(ValueError):
1026
            self.apex_out.convert(60, 15, 'geo', 'mlt')
1027
        return
1028
1029
    @pytest.mark.parametrize("mlon_kwargs,test_mlt",
1030
                             [({}, 23.019629923502603),
1031
                              ({"ssheight": 100000}, 23.026712036132814)])
1032
    def test_mlon2mlt_scalar_inputs(self, mlon_kwargs, test_mlt):
1033
        """Test mlon2mlt with scalar inputs.
1034
1035
        Parameters
1036
        ----------
1037
        mlon_kwargs : dict
1038
            Input kwargs
1039
        test_mlt : float
1040
            Output MLT in hours
1041
1042
        """
1043
        mlt = self.apex_out.mlon2mlt(0, self.in_time, **mlon_kwargs)
1044
1045
        np.testing.assert_allclose(mlt, test_mlt)
1046
        assert np.asarray(mlt).shape == ()
1047
        return
1048
1049
    @pytest.mark.parametrize("mlt_kwargs,test_mlon",
1050
                             [({}, 14.705535888671875),
1051
                              ({"ssheight": 100000}, 14.599319458007812)])
1052
    def test_mlt2mlon_scalar_inputs(self, mlt_kwargs, test_mlon):
1053
        """Test mlt2mlon with scalar inputs.
1054
1055
        Parameters
1056
        ----------
1057
        mlt_kwargs : dict
1058
            Input kwargs
1059
        test_mlon : float
1060
            Output longitude in degrees E
1061
1062
        """
1063
        mlon = self.apex_out.mlt2mlon(0, self.in_time, **mlt_kwargs)
1064
1065
        np.testing.assert_allclose(mlon, test_mlon)
1066
        assert np.asarray(mlon).shape == ()
1067
        return
1068
1069
    @pytest.mark.parametrize("mlon,test_mlt",
1070
                             [([0, 180], [23.019261, 11.019261]),
1071
                              (np.array([0, 180]), [23.019261, 11.019261]),
1072
                              (np.array([[0], [180]]),
1073
                               np.array([[23.019261], [11.019261]])),
1074
                              ([[0, 180], [0, 180]], [[23.019261, 11.019261],
1075
                                                      [23.019261, 11.019261]]),
1076
                              (range(0, 361, 30),
1077
                               [23.01963, 1.01963, 3.01963, 5.01963, 7.01963,
1078
                                9.01963, 11.01963, 13.01963, 15.01963, 17.01963,
1079
                                19.01963, 21.01963, 23.01963])])
1080
    def test_mlon2mlt_array(self, mlon, test_mlt):
1081
        """Test mlon2mlt with array inputs.
1082
1083
        Parameters
1084
        ----------
1085
        mlon : array-like
1086
            Input longitudes in degrees E
1087
        test_mlt : float
1088
            Output MLT in hours
1089
1090
        """
1091
        mlt = self.apex_out.mlon2mlt(mlon, self.in_time)
1092
1093
        assert mlt.shape == np.asarray(test_mlt).shape
1094
        np.testing.assert_allclose(mlt, test_mlt, rtol=1e-4)
1095
        return
1096
1097
    @pytest.mark.parametrize("mlt,test_mlon",
1098
                             [([0, 12], [14.705551, 194.705551]),
1099
                              (np.array([0, 12]), [14.705551, 194.705551]),
1100
                              (np.array([[0], [12]]),
1101
                               np.array([[14.705551], [194.705551]])),
1102
                              ([[0, 12], [0, 12]], [[14.705551, 194.705551],
1103
                                                    [14.705551, 194.705551]]),
1104
                              (range(0, 25, 2),
1105
                               [14.705551, 44.705551, 74.705551, 104.705551,
1106
                                134.705551, 164.705551, 194.705551, 224.705551,
1107
                                254.705551, 284.705551, 314.705551, 344.705551,
1108
                                14.705551])])
1109
    def test_mlt2mlon_array(self, mlt, test_mlon):
1110
        """Test mlt2mlon with array inputs.
1111
1112
        Parameters
1113
        ----------
1114
        mlt : array-like
1115
            Input MLT in hours
1116
        test_mlon : float
1117
            Output longitude in degrees E
1118
1119
        """
1120
        mlon = self.apex_out.mlt2mlon(mlt, self.in_time)
1121
1122
        assert mlon.shape == np.asarray(test_mlon).shape
1123
        np.testing.assert_allclose(mlon, test_mlon, rtol=1e-4)
1124
        return
1125
1126
    @pytest.mark.parametrize("method_name", ["mlon2mlt", "mlt2mlon"])
1127
    def test_mlon2mlt_diffdates(self, method_name):
1128
        """Test that MLT varies with universal time.
1129
1130
        Parameters
1131
        ----------
1132
        method_name : str
1133
            Name of Apex class method to be tested
1134
1135
        """
1136
        apex_method = getattr(self.apex_out, method_name)
1137
        mlt1 = apex_method(0, self.in_time)
1138
        mlt2 = apex_method(0, self.in_time + dt.timedelta(hours=1))
1139
1140
        assert mlt1 != mlt2
1141
        return
1142
1143
    @pytest.mark.parametrize("mlt_offset", [1.0, 10.0])
1144
    def test_mlon2mlt_offset(self, mlt_offset):
1145
        """Test the time wrapping logic for the MLT.
1146
1147
        Parameters
1148
        ----------
1149
        mlt_offset : float
1150
            MLT offset in hours
1151
1152
        """
1153
        mlt1 = self.apex_out.mlon2mlt(0.0, self.in_time)
1154
        mlt2 = self.apex_out.mlon2mlt(-15.0 * mlt_offset,
1155
                                      self.in_time) + mlt_offset
1156
1157
        np.testing.assert_allclose(mlt1, mlt2)
1158
        return
1159
1160
    @pytest.mark.parametrize("mlon_offset", [15.0, 150.0])
1161
    def test_mlt2mlon_offset(self, mlon_offset):
1162
        """Test the time wrapping logic for the magnetic longitude.
1163
1164
        Parameters
1165
        ----------
1166
        mlt_offset : float
1167
            MLT offset in hours
1168
1169
        """
1170
        mlon1 = self.apex_out.mlt2mlon(0, self.in_time)
1171
        mlon2 = self.apex_out.mlt2mlon(mlon_offset / 15.0,
1172
                                       self.in_time) - mlon_offset
1173
1174
        np.testing.assert_allclose(mlon1, mlon2)
1175
        return
1176
1177
    @pytest.mark.parametrize("order", [["mlt", "mlon"], ["mlon", "mlt"]])
1178
    @pytest.mark.parametrize("start_val", [0, 6, 12, 18, 22])
1179
    def test_convert_and_return(self, order, start_val):
1180
        """Test the conversion to magnetic longitude or MLT and back again.
1181
1182
        Parameters
1183
        ----------
1184
        order : list
1185
            List of strings specifying the order to run functions
1186
        start_val : int or float
1187
            Input value
1188
1189
        """
1190
        first_method = getattr(self.apex_out, "2".join(order))
1191
        second_method = getattr(self.apex_out, "2".join([order[1], order[0]]))
1192
1193
        middle_val = first_method(start_val, self.in_time)
1194
        end_val = second_method(middle_val, self.in_time)
1195
1196
        np.testing.assert_allclose(start_val, end_val)
1197
        return
1198
1199
1200
class TestApexMapMethods(object):
1201
    """Test the Apex height mapping methods."""
1202
    def setup_method(self):
1203
        """Initialize all tests."""
1204
        self.apex_out = apexpy.Apex(date=2000, refh=300)
1205
1206
    def teardown_method(self):
1207
        """Clean up after each test."""
1208
        del self.apex_out
1209
1210
    @pytest.mark.parametrize("in_args,test_mapped",
1211
                             [([60, 15, 100, 10000],
1212
                               [31.841466903686523, 17.916635513305664,
1213
                                1.7075473124350538e-6]),
1214
                              ([30, 170, 100, 500, False, 1e-2],
1215
                               [25.727270126342773, 169.60546875,
1216
                                0.00017573432705830783]),
1217
                              ([60, 15, 100, 10000, True],
1218
                               [-25.424888610839844, 27.310426712036133,
1219
                                1.2074182222931995e-6]),
1220
                              ([30, 170, 100, 500, True, 1e-2],
1221
                               [-13.76642894744873, 164.24259948730469,
1222
                                0.00056820799363777041])])
1223
    def test_map_to_height(self, in_args, test_mapped):
1224
        """Test the map_to_height function.
1225
1226
        Parameters
1227
        ----------
1228
        in_args : list
1229
            List of input arguments
1230
        test_mapped : list
1231
            List of expected outputs
1232
1233
        """
1234
        mapped = self.apex_out.map_to_height(*in_args)
1235
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5, atol=1e-5)
1236
        return
1237
1238
    def test_map_to_height_same_height(self):
1239
        """Test the map_to_height function when mapping to same height."""
1240
        mapped = self.apex_out.map_to_height(60, 15, 100, 100, conjugate=False,
1241
                                             precision=1e-10)
1242
        np.testing.assert_allclose(mapped, (60.0, 15.000003814697266, 0.0),
1243
                                   rtol=1e-5, atol=1e-5)
1244
        return
1245
1246
    @pytest.mark.parametrize('arr_shape', [(2,), (2, 2), (1, 4)])
1247
    @pytest.mark.parametrize('ivec', range(0, 4))
1248
    def test_map_to_height_array_location(self, arr_shape, ivec):
1249
        """Test map_to_height with array input.
1250
1251
        Parameters
1252
        ----------
1253
        arr_shape : tuple
1254
            Expected array shape
1255
        ivec : int
1256
            Input argument index for vectorized input
1257
1258
        """
1259
        # Set the base input and output values
1260
        in_args = [60, 15, 100, 100]
1261
        test_mapped = [60, 15.00000381, 0.0]
1262
1263
        # Update inputs for one vectorized value
1264
        in_args[ivec] = np.full(shape=arr_shape, fill_value=in_args[ivec])
1265
1266
        # Calculate and test function
1267
        mapped = self.apex_out.map_to_height(*in_args)
1268
        for i, test_val in enumerate(test_mapped):
1269
            assert mapped[i].shape == arr_shape
1270
            np.testing.assert_allclose(mapped[i], test_val, rtol=1e-5,
1271
                                       atol=1e-5)
1272
        return
1273
1274
    @pytest.mark.parametrize("method_name,in_args",
1275
                             [("map_to_height", [0, 15, 100, 10000]),
1276
                              ("map_E_to_height",
1277
                               [0, 15, 100, 10000, [1, 2, 3]]),
1278
                              ("map_V_to_height",
1279
                               [0, 15, 100, 10000, [1, 2, 3]])])
1280
    def test_mapping_height_raises_ApexHeightError(self, method_name, in_args):
1281
        """Test map_to_height raises ApexHeightError.
1282
1283
        Parameters
1284
        ----------
1285
        method_name : str
1286
            Name of the Apex class method to test
1287
        in_args : list
1288
            List of input arguments
1289
1290
        """
1291
        apex_method = getattr(self.apex_out, method_name)
1292
1293
        with pytest.raises(apexpy.ApexHeightError) as aerr:
1294
            apex_method(*in_args)
1295
1296
        assert aerr.match("is > apex height")
1297
        return
1298
1299
    @pytest.mark.parametrize("method_name",
1300
                             ["map_E_to_height", "map_V_to_height"])
1301
    @pytest.mark.parametrize("ev_input", [([1, 2, 3, 4, 5]),
1302
                                          ([[1, 2], [3, 4], [5, 6], [7, 8]])])
1303
    def test_mapping_EV_bad_shape(self, method_name, ev_input):
1304
        """Test height mapping of E/V with baddly shaped input raises Error.
1305
1306
        Parameters
1307
        ----------
1308
        method_name : str
1309
            Name of the Apex class method to test
1310
        ev_input : list
1311
            E/V input arguments
1312
1313
        """
1314
        apex_method = getattr(self.apex_out, method_name)
1315
        in_args = [60, 15, 100, 500, ev_input]
1316
        with pytest.raises(ValueError) as verr:
1317
            apex_method(*in_args)
1318
1319
        assert str(verr.value).find("must be (3, N) or (3,) ndarray") >= 0
1320
        return
1321
1322
    def test_mapping_EV_bad_flag(self):
1323
        """Test _map_EV_to_height raises error for bad data type flag."""
1324
        with pytest.raises(ValueError) as verr:
1325
            self.apex_out._map_EV_to_height(60, 15, 100, 500, [1, 2, 3], "P")
1326
1327
        assert str(verr.value).find("unknown electric field/drift flag") >= 0
1328
        return
1329
1330
    @pytest.mark.parametrize("in_args,test_mapped",
1331
                             [([60, 15, 100, 500, [1, 2, 3]],
1332
                               [0.71152183, 2.35624876, 0.57260784]),
1333
                              ([60, 15, 100, 500, [2, 3, 4]],
1334
                               [1.56028502, 3.43916636, 0.78235384]),
1335
                              ([60, 15, 100, 1000, [1, 2, 3]],
1336
                               [0.67796492, 2.08982134, 0.55860785]),
1337
                              ([60, 15, 200, 500, [1, 2, 3]],
1338
                               [0.72377397, 2.42737471, 0.59083726]),
1339
                              ([60, 30, 100, 500, [1, 2, 3]],
1340
                               [0.68626344, 2.37530133, 0.60060124]),
1341
                              ([70, 15, 100, 500, [1, 2, 3]],
1342
                               [0.72760378, 2.18082305, 0.29141979])])
1343
    def test_map_E_to_height_scalar_location(self, in_args, test_mapped):
1344
        """Test mapping of E-field to a specified height.
1345
1346
        Parameters
1347
        ----------
1348
        in_args : list
1349
            List of input arguments
1350
        test_mapped : list
1351
            List of expected outputs
1352
1353
        """
1354
        mapped = self.apex_out.map_E_to_height(*in_args)
1355
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
1356
        return
1357
1358
    @pytest.mark.parametrize('ev_flag, test_mapped',
1359
                             [('E', [0.71152183, 2.35624876, 0.57260784]),
1360
                              ('V', [0.81971957, 2.84512495, 0.69545001])])
1361
    @pytest.mark.parametrize('arr_shape', [(2,), (5,)])
1362
    @pytest.mark.parametrize('ivec', range(0, 5))
1363
    def test_map_EV_to_height_array_location(self, ev_flag, test_mapped,
1364
                                             arr_shape, ivec):
1365
        """Test mapping of E-field/drift to a specified height with arrays.
1366
1367
        Parameters
1368
        ----------
1369
        ev_flag : str
1370
            Character flag specifying whether to run 'E' or 'V' methods
1371
        test_mapped : list
1372
            List of expected outputs
1373
        arr_shape : tuple
1374
            Shape of the expected output
1375
        ivec : int
1376
            Index of the expected output
1377
1378
        """
1379
        # Set the base input and output values
1380
        eshape = list(arr_shape)
1381
        eshape.insert(0, 3)
1382
        edata = np.array([[1, 2, 3]] * np.product(arr_shape)).transpose()
1383
        in_args = [60, 15, 100, 500, edata.reshape(tuple(eshape))]
1384
1385
        # Update inputs for one vectorized value if this is a location input
1386
        if ivec < 4:
1387
            in_args[ivec] = np.full(shape=arr_shape, fill_value=in_args[ivec])
1388
1389
        # Get the mapped output
1390
        apex_method = getattr(self.apex_out,
1391
                              "map_{:s}_to_height".format(ev_flag))
1392
        mapped = apex_method(*in_args)
1393
1394
        # Test the results
1395
        for i, test_val in enumerate(test_mapped):
1396
            assert mapped[i].shape == arr_shape
1397
            np.testing.assert_allclose(mapped[i], test_val, rtol=1e-5)
1398
        return
1399
1400
    @pytest.mark.parametrize("in_args,test_mapped",
1401
                             [([60, 15, 100, 500, [1, 2, 3]],
1402
                               [0.81971957, 2.84512495, 0.69545001]),
1403
                              ([60, 15, 100, 500, [2, 3, 4]],
1404
                               [1.83027746, 4.14346436, 0.94764179]),
1405
                              ([60, 15, 100, 1000, [1, 2, 3]],
1406
                               [0.92457698, 3.14997661, 0.85135187]),
1407
                              ([60, 15, 200, 500, [1, 2, 3]],
1408
                               [0.80388262, 2.79321504, 0.68285158]),
1409
                              ([60, 30, 100, 500, [1, 2, 3]],
1410
                               [0.76141245, 2.87884673, 0.73655941]),
1411
                              ([70, 15, 100, 500, [1, 2, 3]],
1412
                               [0.84681866, 2.5925821, 0.34792655])])
1413
    def test_map_V_to_height_scalar_location(self, in_args, test_mapped):
1414
        """Test mapping of velocity to a specified height.
1415
1416
        Parameters
1417
        ----------
1418
        in_args : list
1419
            List of input arguments
1420
        test_mapped : list
1421
            List of expected outputs
1422
1423
        """
1424
        mapped = self.apex_out.map_V_to_height(*in_args)
1425
        np.testing.assert_allclose(mapped, test_mapped, rtol=1e-5)
1426
        return
1427
1428
1429
class TestApexBasevectorMethods(object):
1430
    """Test the Apex height base vector methods."""
1431
    def setup_method(self):
1432
        """Initialize all tests."""
1433
        self.apex_out = apexpy.Apex(date=2000, refh=300)
1434
        self.lat = 60
1435
        self.lon = 15
1436
        self.height = 100
1437
        self.test_basevec = None
1438
1439
    def teardown_method(self):
1440
        """Clean up after each test."""
1441
        del self.apex_out, self.test_basevec, self.lat, self.lon, self.height
1442
1443
    def get_comparison_results(self, bv_coord, coords, precision):
1444
        """Get the base vector results using the hidden function for comparison.
1445
1446
        Parameters
1447
        ----------
1448
        bv_coord : str
1449
            Basevector coordinate scheme, expects on of 'apex', 'qd',
1450
            or 'bvectors_apex'
1451
        coords : str
1452
            Expects one of 'geo', 'apex', or 'qd'
1453
        precision : float
1454
            Float specifiying precision
1455
1456
        """
1457
        if coords == "geo":
1458
            glat = self.lat
1459
            glon = self.lon
1460
        else:
1461
            apex_method = getattr(self.apex_out, "{:s}2geo".format(coords))
1462
            glat, glon, _ = apex_method(self.lat, self.lon, self.height,
1463
                                        precision=precision)
1464
1465
        if bv_coord == 'qd':
1466
            self.test_basevec = self.apex_out._basevec(glat, glon, self.height)
1467
        elif bv_coord == 'apex':
1468
            (_, _, _, _, f1, f2, _, d1, d2, d3, _, e1, e2,
1469
             e3) = self.apex_out._geo2apexall(glat, glon, 100)
1470
            self.test_basevec = (f1, f2, d1, d2, d3, e1, e2, e3)
1471
        else:
1472
            # These are set results that need to be updated with IGRF
1473
            if coords == "geo":
1474
                self.test_basevec = (
1475
                    np.array([4.42368795e-05, 4.42368795e-05]),
1476
                    np.array([[0.01047826, 0.01047826],
1477
                              [0.33089194, 0.33089194],
1478
                              [-1.04941, -1.04941]]),
1479
                    np.array([5.3564698e-05, 5.3564698e-05]),
1480
                    np.array([[0.00865356, 0.00865356],
1481
                              [0.27327004, 0.27327004],
1482
                              [-0.8666646, -0.8666646]]))
1483
            elif coords == "apex":
1484
                self.test_basevec = (
1485
                    np.array([4.48672735e-05, 4.48672735e-05]),
1486
                    np.array([[-0.12510721, -0.12510721],
1487
                              [0.28945938, 0.28945938],
1488
                              [-1.1505738, -1.1505738]]),
1489
                    np.array([6.38577444e-05, 6.38577444e-05]),
1490
                    np.array([[-0.08790194, -0.08790194],
1491
                              [0.2033779, 0.2033779],
1492
                              [-0.808408, -0.808408]]))
1493
            else:
1494
                self.test_basevec = (
1495
                    np.array([4.46348578e-05, 4.46348578e-05]),
1496
                    np.array([[-0.12642345, -0.12642345],
1497
                              [0.29695055, 0.29695055],
1498
                              [-1.1517885, -1.1517885]]),
1499
                    np.array([6.38626285e-05, 6.38626285e-05]),
1500
                    np.array([[-0.08835986, -0.08835986],
1501
                              [0.20754464, 0.20754464],
1502
                              [-0.8050078, -0.8050078]]))
1503
1504
        return
1505
1506
    @pytest.mark.parametrize("bv_coord", ["qd", "apex"])
1507
    @pytest.mark.parametrize("coords,precision",
1508
                             [("geo", 1e-10), ("apex", 1.0e-2), ("qd", 1.0e-2)])
1509
    def test_basevectors_scalar(self, bv_coord, coords, precision):
1510
        """Test the base vector calculations with scalars.
1511
1512
        Parameters
1513
        ----------
1514
        bv_coord : str
1515
            Name of the input coordinate system
1516
        coords : str
1517
            Name of the output coordinate system
1518
        precision : float
1519
            Level of run precision requested
1520
1521
        """
1522
        # Get the base vectors
1523
        base_method = getattr(self.apex_out,
1524
                              "basevectors_{:s}".format(bv_coord))
1525
        basevec = base_method(self.lat, self.lon, self.height, coords=coords,
1526
                              precision=precision)
1527
        self.get_comparison_results(bv_coord, coords, precision)
1528
        if bv_coord == "apex":
1529
            basevec = list(basevec)
1530
            for i in range(4):
1531
                # Not able to compare indices 2, 3, 4, and 5
1532
                basevec.pop(2)
1533
1534
        # Test the results
1535
        for i, vec in enumerate(basevec):
1536
            np.testing.assert_allclose(vec, self.test_basevec[i])
1537
        return
1538
1539
    @pytest.mark.parametrize("bv_coord", ["qd", "apex"])
1540
    def test_basevectors_scalar_shape(self, bv_coord):
1541
        """Test the shape of the scalar output.
1542
1543
        Parameters
1544
        ----------
1545
        bv_coord : str
1546
            Name of the input coordinate system
1547
1548
        """
1549
        base_method = getattr(self.apex_out,
1550
                              "basevectors_{:s}".format(bv_coord))
1551
        basevec = base_method(self.lat, self.lon, self.height)
1552
1553
        for i, vec in enumerate(basevec):
1554
            if i < 2:
1555
                assert vec.shape == (2,)
1556
            else:
1557
                assert vec.shape == (3,)
1558
        return
1559
1560
    @pytest.mark.parametrize('arr_shape', [(2,), (5,)])
1561
    @pytest.mark.parametrize("bv_coord", ["qd", "apex"])
1562
    @pytest.mark.parametrize("ivec", range(3))
1563
    def test_basevectors_array(self, arr_shape, bv_coord, ivec):
1564
        """Test the output shape for array inputs.
1565
1566
        Parameters
1567
        ----------
1568
        arr_shape : tuple
1569
            Expected output shape
1570
        bv_coord : str
1571
            Name of the input coordinate system
1572
        ivec : int
1573
            Index of the evaluated output value
1574
1575
        """
1576
        # Define the input arguments
1577
        in_args = [self.lat, self.lon, self.height]
1578
        in_args[ivec] = np.full(shape=arr_shape, fill_value=in_args[ivec])
1579
1580
        # Get the basevectors
1581
        base_method = getattr(self.apex_out,
1582
                              "basevectors_{:s}".format(bv_coord))
1583
        basevec = base_method(*in_args, coords='geo', precision=1e-10)
1584
        self.get_comparison_results(bv_coord, "geo", 1e-10)
1585
        if bv_coord == "apex":
1586
            basevec = list(basevec)
1587
            for i in range(4):
1588
                # Not able to compare indices 2, 3, 4, and 5
1589
                basevec.pop(2)
1590
1591
        # Evaluate the shape and the values
1592
        for i, vec in enumerate(basevec):
1593
            test_shape = list(arr_shape)
1594
            test_shape.insert(0, 2 if i < 2 else 3)
1595
            assert vec.shape == tuple(test_shape)
1596
            assert np.all(self.test_basevec[i][0] == vec[0])
1597
            assert np.all(self.test_basevec[i][1] == vec[1])
1598
        return
1599
1600
    @pytest.mark.parametrize("coords", ["geo", "apex", "qd"])
1601
    def test_bvectors_apex(self, coords):
1602
        """Test the bvectors_apex method.
1603
1604
        Parameters
1605
        ----------
1606
        coords : str
1607
            Name of the coordiante system
1608
1609
        """
1610
        in_args = [[self.lat, self.lat], [self.lon, self.lon],
1611
                   [self.height, self.height]]
1612
        self.get_comparison_results("bvectors_apex", coords, 1e-10)
1613
1614
        basevec = self.apex_out.bvectors_apex(*in_args, coords=coords,
1615
                                              precision=1e-10)
1616
        for i, vec in enumerate(basevec):
1617
            np.testing.assert_array_almost_equal(vec, self.test_basevec[i],
1618
                                                 decimal=5)
1619
        return
1620
1621
    def test_basevectors_apex_extra_values(self):
1622
        """Test specific values in the apex base vector output."""
1623
        # Set the testing arrays
1624
        self.test_basevec = [np.array([0.092637, -0.245951, 0.938848]),
1625
                             np.array([0.939012, 0.073416, -0.07342]),
1626
                             np.array([0.055389, 1.004155, 0.257594]),
1627
                             np.array([0, 0, 1.065135])]
1628
1629
        # Get the desired output
1630
        basevec = self.apex_out.basevectors_apex(0, 15, 100, coords='geo')
1631
1632
        # Test the values not covered by `test_basevectors_scalar`
1633
        for itest, ibase in enumerate(np.arange(2, 6, 1)):
1634
            np.testing.assert_allclose(basevec[ibase],
1635
                                       self.test_basevec[itest], rtol=1e-4)
1636
        return
1637
1638
    @pytest.mark.parametrize("lat", range(0, 90, 10))
1639
    @pytest.mark.parametrize("lon", range(0, 360, 15))
1640
    def test_basevectors_apex_delta(self, lat, lon):
1641
        """Test that vectors are calculated correctly.
1642
1643
        Parameters
1644
        ----------
1645
        lat : int or float
1646
            Latitude in degrees N
1647
        lon : int or float
1648
            Longitude in degrees E
1649
1650
        """
1651
        # Get the apex base vectors and sort them for easy testing
1652
        (f1, f2, f3, g1, g2, g3, d1, d2, d3, e1, e2,
1653
         e3) = self.apex_out.basevectors_apex(lat, lon, 500)
1654
        fvec = [np.append(f1, 0), np.append(f2, 0), f3]
1655
        gvec = [g1, g2, g3]
1656
        dvec = [d1, d2, d3]
1657
        evec = [e1, e2, e3]
1658
1659
        for idelta, jdelta in [(i, j) for i in range(3) for j in range(3)]:
1660
            delta = 1 if idelta == jdelta else 0
1661
            np.testing.assert_allclose(np.sum(fvec[idelta] * gvec[jdelta]),
1662
                                       delta, rtol=0, atol=1e-5)
1663
            np.testing.assert_allclose(np.sum(dvec[idelta] * evec[jdelta]),
1664
                                       delta, rtol=0, atol=1e-5)
1665
        return
1666
1667
    def test_basevectors_apex_invalid_scalar(self):
1668
        """Test warning and fill values for base vectors with bad inputs."""
1669
        self.apex_out = apexpy.Apex(date=2000, refh=10000)
1670
        invalid = np.full(shape=(3,), fill_value=np.nan)
1671
1672
        # Get the output and the warnings
1673
        with warnings.catch_warnings(record=True) as warn_rec:
1674
            basevec = self.apex_out.basevectors_apex(0, 0, 0)
1675
1676
        for i, bvec in enumerate(basevec):
1677
            if i < 2:
1678
                assert not np.allclose(bvec, invalid[:2])
1679
            else:
1680
                np.testing.assert_allclose(bvec, invalid)
1681
1682
        assert issubclass(warn_rec[-1].category, UserWarning)
1683
        assert 'set to NaN where' in str(warn_rec[-1].message)
1684
        return
1685
1686
1687
class TestApexGetMethods(object):
1688
    """Test the Apex `get` methods."""
1689
    def setup_method(self):
1690
        """Initialize all tests."""
1691
        self.apex_out = apexpy.Apex(date=2000, refh=300)
1692
1693
    def teardown_method(self):
1694
        """Clean up after each test."""
1695
        del self.apex_out
1696
1697
    @pytest.mark.parametrize("alat, aheight",
1698
                             [(10, 507.409702543805),
1699
                              (60, 20313.026999999987),
1700
                              ([10, 60],
1701
                               [507.409702543805, 20313.026999999987]),
1702
                              ([[10], [60]],
1703
                               [[507.409702543805], [20313.026999999987]])])
1704
    def test_get_apex(self, alat, aheight):
1705
        """Test the apex height retrieval results.
1706
1707
        Parameters
1708
        ----------
1709
        alat : int or float
1710
            Apex latitude in degrees N
1711
        aheight : int or float
1712
            Apex height in km
1713
1714
        """
1715
        alt = self.apex_out.get_apex(alat)
1716
        np.testing.assert_allclose(alt, aheight)
1717
        return
1718
1719
    @pytest.mark.parametrize("glat,glon,height,test_bmag",
1720
                             [([80], [100], [300], 5.100682377815247e-05),
1721
                              ([80, 80], [100], [300],
1722
                               [5.100682377815247e-05, 5.100682377815247e-05]),
1723
                              ([[80], [80]], [100], [300],
1724
                               [[5.100682377815247e-05],
1725
                                [5.100682377815247e-05]]),
1726
                              (range(50, 90, 8), range(0, 360, 80), [300] * 5,
1727
                               np.array([4.18657154e-05, 5.11118114e-05,
1728
                                         4.91969854e-05, 5.10519207e-05,
1729
                                         4.90054816e-05])),
1730
                              (90.0, 0, 1000, 3.7834718823432923e-05)])
1731
    def test_get_babs(self, glat, glon, height, test_bmag):
1732
        """Test the method to get the magnitude of the magnetic field.
1733
1734
        Parameters
1735
        ----------
1736
        glat : list
1737
            List of latitudes in degrees N
1738
        glon : list
1739
            List of longitudes in degrees E
1740
        height : list
1741
            List of heights in km
1742
        test_bmag : float
1743
            Expected B field magnitude
1744
1745
        """
1746
        bmag = self.apex_out.get_babs(glat, glon, height)
1747
        np.testing.assert_allclose(bmag, test_bmag, rtol=0, atol=1e-5)
1748
        return
1749
1750
    @pytest.mark.parametrize("bad_lat", [(91), (-91)])
1751
    def test_get_apex_with_invalid_lat(self, bad_lat):
1752
        """Test get methods raise ValueError for invalid latitudes.
1753
1754
        Parameters
1755
        ----------
1756
        bad_lat : int or float
1757
            Bad input latitude in degrees N
1758
1759
        """
1760
1761
        with pytest.raises(ValueError) as verr:
1762
            self.apex_out.get_apex(bad_lat)
1763
1764
        assert str(verr.value).find("must be in [-90, 90]") > 0
1765
        return
1766
1767
    @pytest.mark.parametrize("bad_lat", [(91), (-91)])
1768
    def test_get_babs_with_invalid_lat(self, bad_lat):
1769
        """Test get methods raise ValueError for invalid latitudes.
1770
1771
        Parameters
1772
        ----------
1773
        bad_lat : int or float
1774
            Bad input latitude in degrees N
1775
1776
        """
1777
1778
        with pytest.raises(ValueError) as verr:
1779
            self.apex_out.get_babs(bad_lat, 15, 100)
1780
1781
        assert str(verr.value).find("must be in [-90, 90]") > 0
1782
        return
1783
1784
    @pytest.mark.parametrize("bound_lat", [(90), (-90)])
1785
    def test_get_at_lat_boundary(self, bound_lat):
1786
        """Test get methods at the latitude boundary, with allowed excess.
1787
1788
        Parameters
1789
        ----------
1790
        bound_lat : int or float
1791
            Boundary input latitude in degrees N
1792
1793
        """
1794
        # Get a latitude just beyond the limit
1795
        excess_lat = np.sign(bound_lat) * (abs(bound_lat) + 1.0e-5)
1796
1797
        # Get the two outputs, slight tolerance outside of boundary allowed
1798
        bound_out = self.apex_out.get_apex(bound_lat)
1799
        excess_out = self.apex_out.get_apex(excess_lat)
1800
1801
        # Test the outputs
1802
        np.testing.assert_allclose(excess_out, bound_out, rtol=0, atol=1e-8)
1803
        return
1804
1805
    @pytest.mark.parametrize("apex_height", [-100, 0, 300, 10000])
1806
    def test_get_height_at_equator(self, apex_height):
1807
        """Test that `get_height` returns apex height at equator.
1808
1809
        Parameters
1810
        ----------
1811
        apex_height : float
1812
            Apex height
1813
1814
        """
1815
1816
        assert apex_height == self.apex_out.get_height(0.0, apex_height)
1817
        return
1818
1819
    @pytest.mark.parametrize("lat, height", [
1820
        (-90, -6371.009), (-80, -6088.438503309167), (-70, -5274.8091854339655),
1821
        (-60, -4028.256749999999), (-50, -2499.1338178752017),
1822
        (-40, -871.8751821247979), (-30, 657.2477500000014),
1823
        (-20, 1903.8001854339655), (-10, 2717.4295033091657), (0, 3000.0),
1824
        (10, 2717.4295033091657), (20, 1903.8001854339655),
1825
        (30, 657.2477500000014), (40, -871.8751821247979),
1826
        (50, -2499.1338178752017), (60, -4028.256749999999),
1827
        (70, -5274.8091854339655), (80, -6088.438503309167)])
1828
    def test_get_height_along_fieldline(self, lat, height):
1829
        """Test that `get_height` returns expected height of field line.
1830
1831
        Parameters
1832
        ----------
1833
        lat : float
1834
            Input latitude
1835
        height : float
1836
            Output field-line height for line with apex of 3000 km
1837
1838
        """
1839
1840
        fheight = self.apex_out.get_height(lat, 3000.0)
1841
        assert abs(height - fheight) < 1.0e-7, \
1842
            "bad height calculation: {:.7f} != {:.7f}".format(height, fheight)
1843
        return
1844
1845
1846
class TestApexMethodExtrapolateIGRF(object):
1847
    """Test the Apex methods on a year when IGRF must be extrapolated.
1848
1849
    Notes
1850
    -----
1851
    Extrapolation should be done using a year within 5 years of the latest IGRF
1852
    model epoch.
1853
1854
    """
1855
1856
    def setup_method(self):
1857
        """Initialize all tests."""
1858
        self.apex_out = apexpy.Apex(date=2025, refh=300)
1859
        self.in_lat = 60
1860
        self.in_lon = 15
1861
        self.in_alt = 100
1862
        self.in_time = dt.datetime(2024, 2, 3, 4, 5, 6)
1863
        return
1864
1865
    def teardown_method(self):
1866
        """Clean up after each test."""
1867
        del self.apex_out, self.in_lat, self.in_lon, self.in_alt
1868
        return
1869
1870 View Code Duplication
    @pytest.mark.parametrize("method_name, out_comp",
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1871
                             [("geo2apex",
1872
                               (56.25343704223633, 92.04932403564453)),
1873
                              ("apex2geo",
1874
                               (53.84184265136719, -66.93045806884766,
1875
                                3.6222547805664362e-06)),
1876
                              ("geo2qd",
1877
                               (56.82968521118164, 92.04932403564453)),
1878
                              ("apex2qd", (60.498401178276744, 15.0)),
1879
                              ("qd2apex", (59.49138097045895, 15.0))])
1880
    def test_method_scalar_input(self, method_name, out_comp):
1881
        """Test the user method against set values with scalars.
1882
1883
        Parameters
1884
        ----------
1885
        method_name : str
1886
            Apex class method to be tested
1887
        out_comp : tuple of floats
1888
            Expected output values
1889
1890
        """
1891
        # Get the desired methods
1892
        user_method = getattr(self.apex_out, method_name)
1893
1894
        # Get the user output
1895
        user_out = user_method(self.in_lat, self.in_lon, self.in_alt)
1896
1897
        # Evaluate the user output
1898
        np.testing.assert_allclose(user_out, out_comp, rtol=1e-5, atol=1e-5)
1899
1900
        for out_val in user_out:
1901
            assert np.asarray(out_val).shape == (), "output is not a scalar"
1902
        return
1903
1904
    def test_convert_to_mlt(self):
1905
        """Test conversion from mlon to mlt with scalars."""
1906
1907
        # Get user output
1908
        user_out = self.apex_out.mlon2mlt(self.in_lon, self.in_time)
1909
1910
        # Set comparison values
1911
        out_comp = 23.955474853515625
1912
1913
        # Evaluate user output
1914
        np.testing.assert_allclose(user_out, out_comp, rtol=1e-5, atol=1e-5)
1915
        return
1916