aacgmv2.wrapper   F
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 673
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 69
eloc 226
dl 0
loc 673
rs 2.88
c 0
b 0
f 0

10 Functions

Rating   Name   Duplication   Size   Complexity  
B convert_bool_to_bit() 0 37 6
C set_coeff_path() 0 41 9
B test_height() 0 57 6
A test_time() 0 29 4
A get_aacgm_coord() 0 46 2
B convert_latlon() 0 100 8
A convert_str_to_bit() 0 46 1
A get_aacgm_coord_arr() 0 49 2
F convert_latlon_arr() 0 153 22
C convert_mlt() 0 79 9

How to fix   Complexity   

Complexity

Complex classes like aacgmv2.wrapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Copyright (C) 2019 NRL
2
# Author: Angeline Burrell
3
# Disclaimer: This code is under the MIT license, whose details can be found at
4
# the root in the LICENSE file
5
#
6
# -*- coding: utf-8 -*-
7
"""Pythonic wrappers for AACGM-V2 C functions."""
8
9
import datetime as dt
10
import numpy as np
11
import os
12
13
import aacgmv2
14
import aacgmv2._aacgmv2 as c_aacgmv2
15
from aacgmv2._aacgmv2 import TRACE, ALLOWTRACE, BADIDEA
16
17
18
def test_time(dtime):
19
    """Test the time input and ensure it is a dt.datetime object.
20
21
    Parameters
22
    ----------
23
    dtime : any
24
        Time input in an untested format
25
26
    Returns
27
    -------
28
    dtime : dt.datetime
29
        Time as a datetime object
30
31
    Raises
32
    ------
33
    ValueError
34
        If time is not a dt.date or dt.datetime object
35
36
    """
37
    if isinstance(dtime, dt.date):
38
        # Because datetime objects identify as both dt.date and dt.datetime,
39
        # you need an extra test here to ensure you don't lose the time
40
        # attributes
41
        if not isinstance(dtime, dt.datetime):
42
            dtime = dt.datetime.combine(dtime, dt.time(0))
43
    elif not isinstance(dtime, dt.datetime):
44
        raise ValueError('time variable (dtime) must be a datetime object')
45
46
    return dtime
47
48
49
def test_height(height, bit_code):
50
    """Test the input height and ensure it is appropriate for the method.
51
52
    Parameters
53
    ----------
54
    height : float
55
        Height to test in km
56
    bit_code : int
57
        Code string denoting method to use
58
59
    Returns
60
    -------
61
    good_height : bool
62
        True if height and method are appropriate, False if not
63
64
    Notes
65
    -----
66
    Appropriate altitude ranges for the different methods are explored in
67
    Shepherd (2014).  Summarized, they are:
68
    1. Coefficients: 0-2000 km
69
    2. Tracing: 0-1 Earth Radius
70
71
    Altitudes below zero will work, but will not provide a good representation
72
    of the magnetic field because it goes beyond the intended scope of these
73
    coordiantes.
74
75
    If you use the 'BADIDEA' code, you can bypass all constraints, but it
76
    is a Bad Idea!  If you include a high enough altiutde, the code may hang.
77
78
    """
79
    # Test for heights that are allowed but not within the intended scope
80
    # of the coordinate system.  The routine will work, but the user should
81
    # be aware that the results are not as reliable
82
    if height < 0:
83
        aacgmv2.logger.warning('conversion not intended for altitudes < 0 km')
84
85
    # Test the conditions for using the coefficient method
86
    trace_opt = (TRACE | ALLOWTRACE | BADIDEA)
87
    if height > aacgmv2.high_alt_coeff and not (bit_code & trace_opt):
88
        estr = ''.join(['coefficients are not valid for altitudes above ',
89
                        '{:.0f} km. You '.format(aacgmv2.high_alt_coeff),
90
                        'must either use field-line tracing (trace=True or',
91
                        ' allowtrace=True) or indicate you know this is a',
92
                        ' bad idea'])
93
        aacgmv2.logger.error(estr)
94
        return False
95
96
    # Test the conditions for using the tracing method
97
    if height > aacgmv2.high_alt_trace and not (bit_code & BADIDEA):
98
        estr = ''.join(['these coordinates are not intended for the ',
99
                        'magnetosphere! You must indicate that you know ',
100
                        'this is a bad idea.  If you continue, it is ',
101
                        'possible that the code will hang.'])
102
        aacgmv2.logger.error(estr)
103
        return False
104
105
    return True
106
107
108
def set_coeff_path(igrf_file=False, coeff_prefix=False):
109
    """Set the IGRF_COEFF and AACGMV_V2_DAT_PREFIX environment variables.
110
111
    Parameters
112
    ----------
113
    igrf_file : str or bool
114
        Full filename of IGRF coefficient file, True to use
115
        aacgmv2.IGRF_COEFFS, or False to leave as is. (default=False)
116
    coeff_prefix : str or bool
117
        Location and file prefix for aacgm coefficient files, True to use
118
        aacgmv2.AACGM_V2_DAT_PREFIX, or False to leave as is. (default=False)
119
120
    """
121
122
    # Define coefficient file prefix if requested
123
    if coeff_prefix is not False:
124
        # Use the default value, if one was not supplied (allow None to
125
        # comply with depricated behaviour)
126
        if coeff_prefix is True or coeff_prefix is None:
127
            coeff_prefix = aacgmv2.AACGM_v2_DAT_PREFIX
128
129
        if hasattr(os, "unsetenv"):
130
            os.unsetenv('AACGM_v2_DAT_PREFIX')
131
        else:
132
            del os.environ['AACGM_v2_DAT_PREFIX']
133
        os.environ['AACGM_v2_DAT_PREFIX'] = coeff_prefix
134
135
    # Define IGRF file if requested
136
    if igrf_file is not False:
137
        # Use the default value, if one was not supplied (allow None to
138
        # comply with depricated behaviour)
139
        if igrf_file is True or igrf_file is None:
140
            igrf_file = aacgmv2.IGRF_COEFFS
141
142
        if hasattr(os, "unsetenv"):
143
            os.unsetenv('IGRF_COEFFS')
144
        else:
145
            del os.environ['IGRF_COEFFS']
146
        os.environ['IGRF_COEFFS'] = igrf_file
147
148
    return
149
150
151
def convert_latlon(in_lat, in_lon, height, dtime, method_code="G2A"):
152
    """Convert between geomagnetic coordinates and AACGM coordinates.
153
154
    Parameters
155
    ----------
156
    in_lat : float
157
        Input latitude in degrees N (code specifies type of latitude)
158
    in_lon : float
159
        Input longitude in degrees E (code specifies type of longitude)
160
    height : float
161
        Altitude above the surface of the earth in km
162
    dtime : dt.datetime
163
        Datetime for magnetic field
164
    method_code : str or int
165
        Bit code or string denoting which type(s) of conversion to perform
166
        (default="G2A")
167
168
        G2A
169
            Geographic (geodetic) to AACGM-v2
170
        A2G
171
            AACGM-v2 to geographic (geodetic)
172
        TRACE
173
            Use field-line tracing, not coefficients
174
        ALLOWTRACE
175
            Use trace only above 2000 km
176
        BADIDEA
177
            Use coefficients above 2000 km
178
        GEOCENTRIC
179
            Assume inputs are geocentric w/ RE=6371.2
180
181
    Returns
182
    -------
183
    out_lat : float
184
        Output latitude in degrees N
185
    out_lon : float
186
        Output longitude in degrees E
187
    out_r : float
188
        Geocentric radial distance (R_Earth) or altitude above the surface of
189
        the Earth (km)
190
191
    Raises
192
    ------
193
    ValueError
194
        If input is incorrect.
195
    RuntimeError
196
        If unable to set AACGMV2 datetime.
197
198
    """
199
    # Test time
200
    dtime = test_time(dtime)
201
202
    # Initialise output
203
    lat_out = np.nan
204
    lon_out = np.nan
205
    r_out = np.nan
206
207
    # Set the coordinate coversion method code in bits
208
    try:
209
        bit_code = convert_str_to_bit(method_code.upper())
210
    except AttributeError:
211
        bit_code = method_code
212
213
    if not isinstance(bit_code, int):
214
        raise ValueError("unknown method code {:}".format(method_code))
215
216
    # Test height that may or may not cause failure
217
    if not test_height(height, bit_code):
218
        return lat_out, lon_out, r_out
219
220
    # Test latitude range
221
    if abs(in_lat) > 90.0:
222
        # Allow latitudes with a small deviation from the maximum
223
        # (+/- 90 degrees) to be set to 90
224
        if abs(in_lat) > 90.1:
225
            raise ValueError('unrealistic latitude')
226
        in_lat = np.sign(in_lat) * 90.0
227
228
    # Constrain longitudes between -180 and 180
229
    in_lon = ((in_lon + 180.0) % 360.0) - 180.0
230
231
    # Set current date and time
232
    try:
233
        c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour,
234
                               dtime.minute, dtime.second)
235
    except (TypeError, RuntimeError) as err:
236
        raise RuntimeError("cannot set time for {:}: {:}".format(dtime, err))
237
238
    # convert location
239
    try:
240
        lat_out, lon_out, r_out = c_aacgmv2.convert(in_lat, in_lon, height,
241
                                                    bit_code)
242
    except Exception as err:
243
        estr = "".join(["unable to perform conversion at {:.1f}".format(in_lat),
244
                        ", {:.1f} {:.1f} km, {:}".format(in_lon, height, dtime),
245
                        " using method {:} <{:}>. Recall".format(bit_code, err),
246
                        " that AACGMV2 is undefined near the equator."])
247
        aacgmv2.logger.warning(estr)
248
        pass
249
250
    return lat_out, lon_out, r_out
251
252
253
def convert_latlon_arr(in_lat, in_lon, height, dtime, method_code="G2A"):
254
    """Convert between geomagnetic coordinates and AACGM coordinates.
255
256
    Parameters
257
    ----------
258
    in_lat : np.ndarray, list, or float
259
        Input latitude in degrees N (method_code specifies type of latitude)
260
    in_lon : np.ndarray or list or float
261
        Input longitude in degrees E (method_code specifies type of longitude)
262
    height : np.ndarray or list or float
263
        Altitude above the surface of the earth in km
264
    dtime : dt.datetime
265
        Single datetime object for magnetic field
266
    method_code : int or str
267
        Bit code or string denoting which type(s) of conversion to perform
268
        (default="G2A")
269
270
        G2A
271
            Geographic (geodetic) to AACGM-v2
272
        A2G
273
            AACGM-v2 to geographic (geodetic)
274
        TRACE
275
            Use field-line tracing, not coefficients
276
        ALLOWTRACE
277
            Use trace only above 2000 km
278
        BADIDEA
279
            Use coefficients above 2000 km
280
        GEOCENTRIC
281
            Assume inputs are geocentric w/ RE=6371.2
282
283
    Returns
284
    -------
285
    out_lat : np.ndarray
286
        Output latitudes in degrees N
287
    out_lon : np.ndarray
288
        Output longitudes in degrees E
289
    out_r : np.ndarray
290
        Geocentric radial distance (R_Earth) or altitude above the surface of
291
        the Earth (km)
292
293
    Raises
294
    ------
295
    ValueError
296
        If input is incorrect.
297
    RuntimeError
298
        If unable to set AACGMV2 datetime.
299
300
    Notes
301
    -----
302
    At least one of in_lat, in_lon, and height must be a list or array.
303
304
    If errors are encountered, NaN or Inf will be included in the input so
305
    that all successful calculations are returned.  To select only good values
306
    use a function like `np.isfinite`.
307
308
    Multi-dimensional arrays are not allowed.
309
310
    """
311
    # Recast the data as numpy arrays
312
    in_lat = np.array(in_lat)
313
    in_lon = np.array(in_lon)
314
    height = np.array(height)
315
316
    # If one or two of these elements is a float, int, or single element array,
317
    # create an array equal to the length of the longest input
318
    test_array = np.array([len(in_lat.shape), len(in_lon.shape),
319
                           len(height.shape)])
320
321
    if test_array.max() > 1:
322
        raise ValueError("unable to process multi-dimensional arrays")
323
    else:
324
        if test_array.max() == 0:
325
            aacgmv2.logger.info("".join(["for a single location, consider ",
326
                                         "using convert_latlon or ",
327
                                         "get_aacgm_coord"]))
328
            in_lat = np.array([in_lat])
329
            in_lon = np.array([in_lon])
330
            height = np.array([height])
331
        else:
332
            max_len = max([len(arr) for i, arr in enumerate([in_lat, in_lon,
333
                                                             height])
334
                           if test_array[i] > 0])
335
336
            if not test_array[0] or (len(in_lat) == 1 and max_len > 1):
337
                in_lat = np.full(shape=(max_len,), fill_value=in_lat)
338
            if not test_array[1] or (len(in_lon) == 1 and max_len > 1):
339
                in_lon = np.full(shape=(max_len,), fill_value=in_lon)
340
            if not test_array[2] or (len(height) == 1 and max_len > 1):
341
                height = np.full(shape=(max_len,), fill_value=height)
342
343
    # Ensure that lat, lon, and height are the same length or if the lengths
344
    # differ that the different ones contain only a single value
345
    if not (in_lat.shape == in_lon.shape and in_lat.shape == height.shape):
346
        raise ValueError('lat, lon, and height arrays are mismatched')
347
348
    # Test time
349
    dtime = test_time(dtime)
350
351
    # Initialise output
352
    lat_out = np.full(shape=in_lat.shape, fill_value=np.nan)
353
    lon_out = np.full(shape=in_lon.shape, fill_value=np.nan)
354
    r_out = np.full(shape=height.shape, fill_value=np.nan)
355
356
    # Test and set the conversion method code
357
    try:
358
        bit_code = convert_str_to_bit(method_code.upper())
359
    except AttributeError:
360
        bit_code = method_code
361
362
    if not isinstance(bit_code, int):
363
        raise ValueError("unknown method code {:}".format(method_code))
364
365
    # Test height
366
    if not test_height(np.nanmax(height), bit_code):
367
        return lat_out, lon_out, r_out
368
369
    # Test latitude range
370
    if np.abs(in_lat).max() > 90.0:
371
        if np.abs(in_lat).max() > 90.1:
372
            raise ValueError('unrealistic latitude')
373
        in_lat = np.clip(in_lat, -90.0, 90.0)
374
375
    # Constrain longitudes between -180 and 180
376
    in_lon = ((in_lon + 180.0) % 360.0) - 180.0
377
378
    # Set current date and time
379
    try:
380
        c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour,
381
                               dtime.minute, dtime.second)
382
    except (TypeError, RuntimeError) as err:
383
        raise RuntimeError("cannot set time for {:}: {:}".format(dtime, err))
384
385
    try:
386
        lat_out, lon_out, r_out, bad_ind = c_aacgmv2.convert_arr(list(in_lat),
387
                                                                 list(in_lon),
388
                                                                 list(height),
389
                                                                 bit_code)
390
391
        # Cast the output as numpy arrays or masks
392
        lat_out = np.array(lat_out)
393
        lon_out = np.array(lon_out)
394
        r_out = np.array(r_out)
395
        bad_ind = np.array(bad_ind) >= 0
396
397
        # Replace any bad indices with NaN, casting output as numpy arrays
398
        if np.any(bad_ind):
399
            lat_out[bad_ind] = np.nan
400
            lon_out[bad_ind] = np.nan
401
            r_out[bad_ind] = np.nan
402
    except SystemError as serr:
403
        aacgmv2.logger.warning('C Error encountered: {:}'.format(serr))
404
405
    return lat_out, lon_out, r_out
406
407
408
def get_aacgm_coord(glat, glon, height, dtime, method="ALLOWTRACE"):
409
    """Get AACGM latitude, longitude, and magnetic local time.
410
411
    Parameters
412
    ----------
413
    glat : float
414
        Geodetic latitude in degrees N
415
    glon : float
416
        Geodetic longitude in degrees E
417
    height : float
418
        Altitude above the surface of the earth in km
419
    dtime : dt.datetime
420
        Date and time to calculate magnetic location
421
    method : str
422
        The type(s) of conversion to perform (default="ALLOWTRACE")
423
424
        TRACE
425
            Use field-line tracing, not coefficients
426
        ALLOWTRACE
427
            Use trace only above 2000 km
428
        BADIDEA
429
            Use coefficients above 2000 km
430
        GEOCENTRIC
431
            Assume inputs are geocentric w/ RE=6371.2
432
433
    Returns
434
    -------
435
    mlat : float
436
        Magnetic latitude in degrees N
437
    mlon : float
438
        Magnetic longitude in degrees E
439
    mlt : float
440
        Magnetic local time in hours
441
442
    """
443
    # Initialize method code
444
    method_code = "G2A|{:s}".format(method)
445
446
    # Get magnetic lat and lon.
447
    mlat, mlon, _ = convert_latlon(glat, glon, height, dtime,
448
                                   method_code=method_code)
449
450
    # Get magnetic local time (output is always an array, so extract value)
451
    mlt = np.nan if np.isnan(mlon) else convert_mlt(mlon, dtime, m2a=False)[0]
452
453
    return mlat, mlon, mlt
454
455
456
def get_aacgm_coord_arr(glat, glon, height, dtime, method="ALLOWTRACE"):
457
    """Get AACGM latitude, longitude, and magnetic local time.
458
459
    Parameters
460
    ----------
461
    glat : np.array or list
462
        Geodetic latitude in degrees N
463
    glon : np.array or list
464
        Geodetic longitude in degrees E
465
    height : np.array or list
466
        Altitude above the surface of the earth in km
467
    dtime : dt.datetime
468
        Date and time to calculate magnetic location
469
    method : str
470
        The type(s) of conversion to perform (default="ALLOWTRACE")
471
472
        TRACE
473
            Use field-line tracing, not coefficients
474
        ALLOWTRACE
475
            Use trace only above 2000 km
476
        BADIDEA
477
            Use coefficients above 2000 km
478
        GEOCENTRIC
479
            Assume inputs are geocentric w/ RE=6371.2
480
481
    Returns
482
    -------
483
    mlat : float
484
        Magnetic latitude in degrees N
485
    mlon : float
486
        Magnetic longitude in degrees E
487
    mlt : float
488
        Magnetic local time in hours
489
490
    """
491
    # Initialize method code
492
    method_code = "G2A|{:s}".format(method)
493
494
    # Get magnetic lat and lon.
495
    mlat, mlon, _ = convert_latlon_arr(glat, glon, height, dtime,
496
                                       method_code=method_code)
497
498
    if np.any(np.isfinite(mlon)):
499
        # Get magnetic local time
500
        mlt = convert_mlt(mlon, dtime, m2a=False)
501
    else:
502
        mlt = np.full(shape=len(mlat), fill_value=np.nan)
503
504
    return mlat, mlon, mlt
505
506
507
def convert_str_to_bit(method_code):
508
    """Convert string code specification to bit code specification.
509
510
    Parameters
511
    ----------
512
    method_code : str
513
        Bitwise code for passing options into converter:
514
515
        G2A
516
            Geographic (geodetic) to AACGM-v2
517
        A2G
518
            AACGM-v2 to geographic (geodetic)
519
        TRACE
520
            Use field-line tracing, not coefficients
521
        ALLOWTRACE
522
            Use trace only above 2000 km
523
        BADIDEA
524
            Use coefficients above 2000 km
525
        GEOCENTRIC
526
            Assume inputs are geocentric w/ RE=6371.2
527
528
    Returns
529
    -------
530
    bit_code : int
531
        Method code specification in bits
532
533
    Notes
534
    -----
535
    Multiple codes should be seperated by pipes `|`.  Invalid parts of the code
536
    are ignored and no code defaults to 'G2A'.
537
538
    """
539
540
    convert_code = {"G2A": c_aacgmv2.G2A, "A2G": c_aacgmv2.A2G,
541
                    "TRACE": c_aacgmv2.TRACE, "BADIDEA": c_aacgmv2.BADIDEA,
542
                    "GEOCENTRIC": c_aacgmv2.GEOCENTRIC,
543
                    "ALLOWTRACE": c_aacgmv2.ALLOWTRACE}
544
545
    # Force upper case, remove any spaces, and split along pipes
546
    method_codes = method_code.upper().replace(" ", "").split("|")
547
548
    # Add the valid parts of the code, invalid elements are ignored
549
    bit_code = sum([convert_code[k] for k in method_codes
550
                    if k in convert_code.keys()])
551
552
    return bit_code
553
554
555
def convert_bool_to_bit(a2g=False, trace=False, allowtrace=False,
556
                        badidea=False, geocentric=False):
557
    """Convert boolian flags to bit code specification.
558
559
    Parameters
560
    ----------
561
    a2g : bool
562
        True for AACGM-v2 to geographic (geodetic), False otherwise
563
        (default=False)
564
    trace : bool
565
        If True, use field-line tracing, not coefficients (default=False)
566
    allowtrace : bool
567
        If True, use trace only above 2000 km (default=False)
568
    badidea : bool
569
        If True, use coefficients above 2000 km (default=False)
570
    geocentric : bool
571
        True for geodetic, False for geocentric w/RE=6371.2 (default=False)
572
573
    Returns
574
    -------
575
    bit_code : int
576
        code specification in bits
577
578
    """
579
580
    bit_code = c_aacgmv2.A2G if a2g else c_aacgmv2.G2A
581
582
    if trace:
583
        bit_code += c_aacgmv2.TRACE
584
    if allowtrace:
585
        bit_code += c_aacgmv2.ALLOWTRACE
586
    if badidea:
587
        bit_code += c_aacgmv2.BADIDEA
588
    if geocentric:
589
        bit_code += c_aacgmv2.GEOCENTRIC
590
591
    return bit_code
592
593
594
def convert_mlt(arr, dtime, m2a=False):
595
    """Converts between magnetic local time (MLT) and AACGM-v2 longitude.
596
597
    Parameters
598
    ----------
599
    arr : array-like or float
600
        Magnetic longitudes (degrees E) or MLTs (hours) to convert
601
    dtime : array-like or dt.datetime
602
        Date and time for MLT conversion in Universal Time (UT).
603
    m2a : bool
604
        Convert MLT to AACGM-v2 longitude (True) or magnetic longitude to MLT
605
        (False).  (default=False)
606
607
    Returns
608
    -------
609
    out : np.ndarray
610
        Converted coordinates/MLT in degrees E or hours (as appropriate)
611
612
    Notes
613
    -----
614
    This routine previously based on Laundal et al. 2016, but now uses the
615
    improved calculation available in AACGM-V2.4.
616
617
    """
618
619
    arr = np.asarray(arr)
620
    if arr.shape == ():
621
        arr = np.array([arr])
622
623
    if len(arr.shape) > 1:
624
        raise ValueError("unable to process multi-dimensional arrays")
625
626
    # Test time
627
    try:
628
        dtime = test_time(dtime)
629
        years = [dtime.year for dd in arr]
630
        months = [dtime.month for dd in arr]
631
        days = [dtime.day for dd in arr]
632
        hours = [dtime.hour for dd in arr]
633
        minutes = [dtime.minute for dd in arr]
634
        seconds = [dtime.second for dd in arr]
635
    except ValueError as verr:
636
        dtime = np.asarray(dtime)
637
        if dtime.shape == ():
638
            raise ValueError(verr)
639
        elif dtime.shape != arr.shape:
640
            raise ValueError("array input for datetime and MLon/MLT must match")
641
642
        years = [dd.year for dd in dtime]
643
        months = [dd.month for dd in dtime]
644
        days = [dd.day for dd in dtime]
645
        hours = [dd.hour for dd in dtime]
646
        minutes = [dd.minute for dd in dtime]
647
        seconds = [dd.second for dd in dtime]
648
649
    arr = list(arr)
650
651
    # Calculate desired location, C routines set date and time
652
    if m2a:
653
        # Get the magnetic longitude
654
        if len(arr) == 1:
655
            out = c_aacgmv2.inv_mlt_convert(years[0], months[0], days[0],
656
                                            hours[0], minutes[0], seconds[0],
657
                                            arr[0])
658
            out = np.array([out])
659
        else:
660
            out = c_aacgmv2.inv_mlt_convert_arr(years, months, days, hours,
661
                                                minutes, seconds, arr)
662
    else:
663
        # Get magnetic local time
664
        if len(arr) == 1:
665
            out = c_aacgmv2.mlt_convert(years[0], months[0], days[0], hours[0],
666
                                        minutes[0], seconds[0], arr[0])
667
            out = np.array([out])
668
        else:
669
            out = np.array(c_aacgmv2.mlt_convert_arr(years, months, days, hours,
670
                                                     minutes, seconds, arr))
671
672
    return out
673