Passed
Pull Request — main (#137)
by
unknown
04:35
created

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