• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

victoriyaforsythe / PyIRTAM / 19685025084

25 Nov 2025 09:32PM UTC coverage: 50.647%. First build
19685025084

push

github

web-flow
Merge pull request #38 from victoriyaforsythe/develop

New Release

1 of 2 new or added lines in 1 file covered. (50.0%)

235 of 464 relevant lines covered (50.65%)

3.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.55
/PyIRTAM/lib.py
1
#!/usr/bin/env python
2
# --------------------------------------------------------
3
# Distribution statement A. Approved for public release.
4
# Distribution is unlimited.
5
# This work was supported by the Office of Naval Research.
6
# --------------------------------------------------------
7
"""This library contains components for PyIRTAM software.
8

9
References
10
----------
11
Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
12
International Reference Ionosphere Modeling Implemented in Python,
13
Space Weather, ESS Open Archive, September 28, 2023.
14

15
Bilitza et al. (2022), The International Reference Ionosphere
16
model: A review and description of an ionospheric benchmark, Reviews
17
of Geophysics, 60.
18

19
"""
20

21
import datetime as dt
7✔
22
import numpy as np
7✔
23
import os
7✔
24
import warnings
7✔
25

26
import PyIRI
7✔
27
import PyIRI.edp_update as ml_up
7✔
28
import PyIRI.main_library as ml
7✔
29
import PyIRI.sh_library as sh
7✔
30

31
from PyIRTAM import coeff
7✔
32

33

34
def IRTAM_density(dtime, alon, alat, modip, TOV, irtam_dir='',
7✔
35
                  use_subdirs=True):
36
    """Output ionospheric parameters from daily set of IRTAM coefficients.
37

38
    Parameters
39
    ----------
40
    dtime : object
41
        Datetime Python object.
42
    alon : array-like
43
        Flattened array of geographic longitudes in degrees.
44
        Must be Numpy array
45
        of any size [N_G].
46
    alat : array-like
47
        Flattened array of geographic latitudes in degrees.
48
        Must be Numpy array
49
        of any size [N_G].
50
    modip : array-like
51
        Modified dip angle in degrees.
52
    TOV : float
53
        Time of Validity for IRtAM coefficients (use 24 if unknown).
54
    irtam_dir : str
55
        Directory for IRTAM coefficients, or '' to use package directory.
56
        (default='')
57
    use_subdirs : bool
58
        If True, adds YYYY/MMDD subdirectories to the filename path, if False
59
        assumes that the entire path to the coefficient directory is provided
60
        by `irtam_dir` (default=True)
61

62
    Returns
63
    -------
64
    F2 : dict
65
        'fo' is critical frequency of F2 region in MHz.
66
        'hm' is height of the F2 peak in km.
67
        'B0' is bottom thickness parameter of the F2 region in km.
68
        'B1' is bottom thickness parameter of the F2 region in km.
69
        Shape [N_T, N_G].
70

71
    Notes
72
    -----
73
    This function returns ionospheric parameters and 3-D electron density
74
    for a given time using IRTAM coefficients for that time frame.
75

76
    References
77
    ----------
78
    Galkin et al. (2015), GAMBIT Database and Explorer for Real-Time
79
    IRI Maps of F2 Layer Peak Height and Density, IES.
80

81
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
82
    International Reference Ionosphere Modeling Implemented in Python,
83
    Space Weather.
84

85
    """
86
    # ------------------------------------------------------------------------
87
    # Calculate geographic Jones and Gallet (JG) functions F_G for the given
88
    # grid and corresponding map of modip angles
89
    G_IRTAM = IRTAM_set_gl_G(alon, alat, modip)
×
90

91
    # ------------------------------------------------------------------------
92
    # Calculate diurnal Fourier functions F_D for the given time array aUT
93
    # create time array that matches IRTAM files (15 min)
94

95
    # Diurnal function for a single time frame (nat all, like in PyIRI)
96
    aUT = np.array([dtime.hour + dtime.minute / 60.])
×
97
    D_IRTAM = IRTAM_diurnal_functions(aUT, TOV)
×
98

99
    # ------------------------------------------------------------------------
100
    # Read IRTAM coefficients and form matrix U
101
    F_B0, F_B1, F_f0F2, F_hmF2 = IRTAM_read_coeff(dtime, irtam_dir, use_subdirs)
×
102

103
    # ------------------------------------------------------------------------
104
    # Multiply matrices (F_D U)F_G
105
    B0, B1, foF2, hmF2 = IRTAM_gamma(G_IRTAM, D_IRTAM,
×
106
                                     F_B0, F_B1, F_f0F2, F_hmF2)
107

108
    # Limit B1 values (same as in IRI) to prevent ugly thin profiles:
109
    B1 = np.clip(B1, 1.0, 6.0)
×
110
    B0 = np.clip(B0, 1.0, 350.0)
×
111
    # ------------------------------------------------------------------------
112
    # Add all parameters to dictionaries:
113
    F2 = {'B0': B0,
×
114
          'B1': B1,
115
          'fo': foF2,
116
          'hm': hmF2}
117

118
    return F2
×
119

120

121
def IRTAM_set_gl_G(alon, alat, modip):
7✔
122
    """Calculate global functions.
123

124
    Parameters
125
    ----------
126
    alon : array-like
127
        Flattened array of geographic longitudes in degrees.
128
    alat : array-like
129
        Flattened array of geographic latitudes in degrees.
130
    modip : array-like
131
        Modified dip angle in degrees.
132

133
    Returns
134
    -------
135
    G : array-like
136
        Global functions for IRTAM (same as for CCIR foF2).
137

138
    Notes
139
    -----
140
    This function sets Geographic Coordinate Functions G_k(position) page
141
    # 18 of Jones & Graham 1965 for F0F2, M3000, and Es coefficients
142

143
    References
144
    ----------
145
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
146
    International Reference Ionosphere Modeling Implemented in Python,
147
    Space Weather.
148

149
    Galkin et al. (2015), GAMBIT Database and Explorer for Real-Time
150
    IRI Maps of F2 Layer Peak Height and Density, IES.
151

152
    Jones, W. B., & Gallet, R. M. (1965). Representation of diurnal
153
    and geographic variations of ionospheric data by numerical methods,
154
    control of instability, ITU Telecommunication Journal , 32 (1), 18–28.
155

156
    """
157
    coef = ml.highest_power_of_extension()
×
158

159
    G = ml.set_global_functions(coef['QM']['F0F2'],
×
160
                                coef['nk']['F0F2'],
161
                                alon, alat, modip)
162

163
    return G
×
164

165

166
def IRTAM_diurnal_functions(time_array, TOV):
7✔
167
    """Set diurnal functions for F2, M3000, and Es.
168

169
    Parameters
170
    ----------
171
    time_array : array-like
172
        Array of UTs in hours.
173

174
    Returns
175
    -------
176
    D : array-like
177
        Diurnal functions for IRTAM.
178

179
    Notes
180
    -----
181
    This function calculates diurnal functions for IRTAM
182
    coefficients
183

184
    References
185
    ----------
186
    Galkin et al. (2015), GAMBIT Database and Explorer for Real-Time
187
    IRI Maps of F2 Layer Peak Height and Density, IES.
188

189
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
190
    International Reference Ionosphere Modeling Implemented in Python,
191
    Space Weather.
192

193
    Jones, W. B., Graham, R. P., & Leftin, M. (1966). Advances
194
    in ionospheric mapping by numerical methods.
195

196
    """
197
    # nj is the highest order of the expansion
198
    coef = IRTAM_highest_power_of_extension()
×
199
    time_array = time_array
×
200
    # First find CCIR-like diurnal functions
201
    D_CCIR = ml.set_diurnal_functions(coef['nj']['F0F2'], time_array)
×
202

203
    # Then we need to reorder them to insert additional term for IRTAM
204
    D = np.zeros((coef['nj']['IRTAM'], time_array.size))
×
205
    D[0, :] = D_CCIR[0, :]
×
206
    D[1, :] = (time_array - TOV) * 60. + 720.
×
207
    D[2:, :] = D_CCIR[1:, :]
×
208

209
    # Transpose array so that multiplication can be done later
210
    D = np.transpose(D)
×
211

212
    return D
×
213

214

215
def IRTAM_read_coeff(dtime, coeff_dir='', use_subdirs=True):
7✔
216
    """Read coefficients from IRTAM.
217

218
    Parameters
219
    ----------
220
    dtime : int
221
        Month.
222
    coeff_dir : str
223
        Directory for IRTAM coefficients, or '' to use package directory.
224
        (default='')
225
    use_subdirs : bool
226
        If True, adds YYYY/MMDD subdirectories to the filename path, if False
227
        assumes that the entire path to the coefficient directory is provided
228
        by `irtam_dir` (default=True)
229

230
    Returns
231
    -------
232
    b0_irtam : array-like
233
        IRTAM coefficients for B0 thickness.
234
    b1_irtam : array-like
235
        IRTAM coefficients for B1 thickness.
236
    fof2_irtam : array-like
237
        IRTAM coefficients for F2 frequency.
238
    hmf2_irtam : array-like
239
        IRTAM coefficients for F2 peak height.
240

241
    Notes
242
    -----
243
    This function reads U_jk coefficients (from IRTAM and Es maps).
244
    Acknowledgement for Es coefficients:
245
    Mrs. Estelle D. Powell and Mrs. Gladys I. Waggoner in supervising the
246
    collection, keypunching and processing of the foEs data.
247
    This work was sponsored by U.S. Navy as part of the SS-267 program.
248
    The final development work and production of the foEs maps was supported
249
    by the U.S Information Agency.
250
    Acknowledgments to Doug Drob (NRL) for giving me these coefficients.
251

252
    References
253
    ----------
254
    Galkin et al. (2015), GAMBIT Database and Explorer for Real-Time
255
    IRI Maps of F2 Layer Peak Height and Density, IES.
256

257
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
258
    International Reference Ionosphere Modeling Implemented in Python,
259
    Space Weather.
260

261
    Jones, W. B., Graham, R. P., & Leftin, M. (1966). Advances
262
    in ionospheric mapping 476 by numerical methods.
263

264
    """
265
    # IRTAM coefficient files have the first 988 numbers in same format as CCIR
266
    # followed by 76 coefficients for the additional diurnal term. Load the
267
    # parameter data into arrays from the various files.
268
    #
269
    # B0
270
    file_b0 = coeff.get_irtam_param_filename(dtime, 'B0', irtam_dir=coeff_dir,
7✔
271
                                             use_subdirs=use_subdirs)
272
    b0_irtam = IRTAM_read_files(file_b0)
7✔
273

274
    # B1
275
    file_b1 = coeff.get_irtam_param_filename(dtime, 'B1', irtam_dir=coeff_dir,
×
276
                                             use_subdirs=use_subdirs)
277
    b1_irtam = IRTAM_read_files(file_b1)
×
278

279
    # f0F2
280
    file_f0f2 = coeff.get_irtam_param_filename(dtime, 'foF2',
×
281
                                               irtam_dir=coeff_dir,
282
                                               use_subdirs=use_subdirs)
283
    f0f2_irtam = IRTAM_read_files(file_f0f2)
×
284

285
    # hmF2
286
    file_hmf2 = coeff.get_irtam_param_filename(dtime, 'hmF2',
×
287
                                               irtam_dir=coeff_dir,
288
                                               use_subdirs=use_subdirs)
289
    hmf2_irtam = IRTAM_read_files(file_hmf2)
×
290

291
    return b0_irtam, b1_irtam, f0f2_irtam, hmf2_irtam
×
292

293

294
def IRTAM_read_files(filename):
7✔
295
    """Read IRTAM file.
296

297
    Parameters
298
    ----------
299
    filename : str
300
        Path to the IRTAM folder file.
301

302
    Returns
303
    -------
304
    F_IRTAM : array-like
305
        Array of IRTAM coefficients.
306

307
    Raises
308
    ------
309
    IOError
310
        If filename is unknown
311

312
    Notes
313
    -----
314
    This function reads IRTAM file and outputs [14, 96] array.
315

316
    """
317
    # Test that the desired file exists
318
    if not os.path.isfile(filename):
7✔
319
        raise IOError('unknown IRTAM coefficient file: {:}'.format(filename))
7✔
320

321
    # Read in the file
322
    full_array = []
×
323
    with open(filename, mode='r') as file_f:
×
324
        for line in file_f:
×
325
            if line[0] != '#':
×
326
                line_vals = np.fromstring(line, dtype=float, sep=' ')
×
327
                full_array = np.concatenate((full_array, line_vals), axis=None)
×
328

329
    # Assign the file input into separate arrays
330
    array_main = full_array[0:988]
×
331
    array_add = full_array[988:]
×
332

333
    # Pull the predefined sizes of the function extensions
334
    coef = IRTAM_highest_power_of_extension()
×
335

336
    # For IRTAM: reshape array to [nj, nk] shape
337
    F_CCIR = np.zeros((coef['nj']['F0F2'], coef['nk']['F0F2']))
×
338
    F_CCIR_like = np.reshape(array_main, F_CCIR.shape, order='F')
×
339

340
    # Insert column between 0 and 1, for additional b0 coefficients
341
    F_IRTAM = np.zeros((coef['nj']['IRTAM'], coef['nk']['IRTAM']))
×
342
    F_IRTAM[0, :] = F_CCIR_like[0, :]
×
343
    F_IRTAM[1, :] = array_add
×
344
    F_IRTAM[2:, :] = F_CCIR_like[1:, :]
×
345

346
    return F_IRTAM
×
347

348

349
def IRTAM_gamma(G, D, F_B0, F_B1, F_foF2, F_hmF2):
7✔
350
    """Calculate foF2, M3000 propagation parameter, and foEs.
351

352
    Parameters
353
    ----------
354
    G : array-like
355
        Global functions for F2 region.
356
    D : array-like
357
        Diurnal functions for F2 region.
358
    F_B0 : array-like
359
        IRTAM coefficients for B0.
360
    F_B1 : array-like
361
        IRTAM coefficients for B1.
362
    F_foF2 : array-like
363
        IRTAM coefficients for foF2.
364
    F_hmF2 : array-like
365
        IRTAM coefficients for hmF2.
366

367
    Returns
368
    -------
369
    gamma_B0 : array-like
370
        Thickness B0 in km.
371
    gamma_B1 : array-like
372
        Thickness B1 in km.
373
    gamma_foF2 : array-like
374
        Critical frequency of F2 layer in MHz.
375
    gamma_hmF2 : array-like
376
        Height of F2 layer.
377

378
    Notes
379
    -----
380
    This function calculates numerical maps for B0, B1, foF2, and hmF2
381
    using matrix multiplication
382

383
    References
384
    ----------
385
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
386
    International Reference Ionosphere Modeling Implemented in Python,
387
    Space Weather.
388

389
    """
390

391
    # Find numerical map
392
    gamma_B0 = np.matmul(np.matmul(D, F_B0), G)
×
393
    gamma_B1 = np.matmul(np.matmul(D, F_B1), G)
×
394
    gamma_foF2 = np.matmul(np.matmul(D, F_foF2), G)
×
395
    gamma_hmF2 = np.matmul(np.matmul(D, F_hmF2), G)
×
396

397
    return gamma_B0, gamma_B1, gamma_foF2, gamma_hmF2
×
398

399

400
def IRTAM_highest_power_of_extension():
7✔
401
    """Provide the highest power of extension.
402

403
    Returns
404
    -------
405
    const : dict
406
        Dictionary that has QM, nk, and nj parameters.
407

408
    Notes
409
    -----
410
    This function sets a common set of constants that define the power of
411
    expansions.
412
    QM = array of highest power of sin(x).
413
    nk = highest order of geographic extension.
414
    e.g. there are 76 functions in Table 3 on page 18 in Jones & Graham 1965.
415
    nj = highest order in diurnal variation.
416

417
    References
418
    ----------
419
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
420
    International Reference Ionosphere Modeling Implemented in Python,
421
    Space Weather.
422

423
    Jones, W. B., & Gallet, R. M. (1965). Representation of diurnal
424
    and geographic variations of ionospheric data by numerical methods,
425
    control of instability, ITU Telecommunication Journal , 32 (1), 18–28.
426

427
    """
428
    # Degree of extension
429
    QM_F0F2 = [12, 12, 9, 5, 2, 1, 1, 1, 1]
×
430
    QM_M3000 = [7, 8, 6, 3, 2, 1, 1]
×
431
    QM_Es_upper = [11, 12, 6, 3, 1]
×
432
    QM_Es_median = [11, 13, 7, 3, 1, 1]
×
433
    QM_Es_lower = [11, 13, 7, 1, 1]
×
434
    QM = {'F0F2': QM_F0F2, 'M3000': QM_M3000,
×
435
          'Es_upper': QM_Es_upper, 'Es_median': QM_Es_median,
436
          'Es_lower': QM_Es_lower}
437

438
    # Geographic
439
    nk_F0F2 = 76
×
440
    nk_IRTAM = 76
×
441
    nk_M3000 = 49
×
442
    nk_Es_upper = 55
×
443
    nk_Es_median = 61
×
444
    nk_Es_lower = 55
×
445
    nk = {'F0F2': nk_F0F2, 'M3000': nk_M3000,
×
446
          'Es_upper': nk_Es_upper, 'Es_median': nk_Es_median,
447
          'Es_lower': nk_Es_lower, 'IRTAM': nk_IRTAM}
448

449
    # Diurnal
450
    nj_F0F2 = 13
×
451
    nj_IRTAM = 14
×
452
    nj_M3000 = 9
×
453
    nj_Es_upper = 5
×
454
    nj_Es_median = 7
×
455
    nj_Es_lower = 5
×
456

457
    nj = {'F0F2': nj_F0F2, 'M3000': nj_M3000,
×
458
          'Es_upper': nj_Es_upper, 'Es_median': nj_Es_median,
459
          'Es_lower': nj_Es_lower, 'IRTAM': nj_IRTAM}
460

461
    const = {'QM': QM, 'nk': nk, 'nj': nj}
×
462

463
    return const
×
464

465

466
def IRTAM_find_hmF1(B0, B1, NmF2, hmF2, NmF1):
7✔
467
    """Return hmF1 for the given parameters.
468

469
    Parameters
470
    ----------
471
    B0 : array-like
472
        Thickness parameter in km.
473
    B1 : array-like
474
        Thickness parameter in km.
475
    NmF2 : array-like
476
        Peak of F2 layer in m-3.
477
    hmF2 : int
478
        Height of F2 layer in km.
479
    NmF1 : array-like
480
        Peak of F1 layer in m-3.
481

482
    Returns
483
    -------
484
    hmF1 : array-like
485
        Height of F1 layer in km.
486

487
    Notes
488
    -----
489
    This function returns height of F1 layer if the bottom of F2 is
490
    constructed using Ramakrishnan & Rawer equation.
491

492
    References
493
    ----------
494
    Bilitza et al. (2022), The International Reference Ionosphere
495
    model: A review and description of an ionospheric benchmark, Reviews
496
    of Geophysics, 60.
497

498
    """
499

500
    B0 = np.transpose(B0)
×
501
    B1 = np.transpose(B1)
×
502
    NmF2 = np.transpose(NmF2)
×
503
    hmF2 = np.transpose(hmF2)
×
504
    NmF1 = np.transpose(NmF1)
×
505

506
    # Create a random array of heights with high resolution
507
    h = np.arange(0, 700, 1)
×
508

509
    hmF1 = np.zeros((1, B0.size)) + np.nan
×
510

511
    a = np.where(np.isfinite(NmF1))[0]
×
512

513
    # Ramakrishnan_and_Rawer:
514
    for i in range(0, a.size):
×
515
        den = Ramakrishnan_Rawer_function(NmF2[a[i]], hmF2[a[i]],
×
516
                                          B0[a[i]], B1[a[i]], h)
517
        hmF1[0, a[i]] = np.interp(NmF1[a[i]], den, h)
×
518

519
    return hmF1
×
520

521

522
def IRTAM_freq_to_Nm(f):
7✔
523
    """Convert critical frequency to plasma density.
524

525
    Parameters
526
    ----------
527
    f : array-like
528
        Critical frequency in MHz.
529

530
    Returns
531
    -------
532
    Nm : array-like
533
       Peak density in m-3.
534

535
    Notes
536
    -----
537
    This function returns maximum density for the given critical frequency and
538
    limits it to 1 if it is below zero.
539

540
    """
541

542
    Nm = 0.124 * f**2 * 1e11
×
543

544
    # Exclude negative values, in case there are any
545
    Nm[np.where(Nm <= 0)] = 1.
×
546

547
    return Nm
×
548

549

550
def IRTAM_F2_top_thickness(foF2, hmF2, B_0, F107):
7✔
551
    """Return thicknesses of ionospheric layers.
552

553
    Parameters
554
    ----------
555
    foF2 : array-like
556
        Critical frequency of F2 region in MHz.
557
    hmF2 : array-like
558
        Height of the F2 layer.
559
    hmE : array-like
560
        Height of the E layer.
561
    mth : int
562
        Month of the year.
563
    F107 : array-like
564
        Solar flux index in SFU.
565

566
    Returns
567
    -------
568
    B_F2_top : array-like
569
        Thickness of F2 top in km.
570
    B_E_bot : array-like
571
        Thickness of E bottom in km.
572
    B_E_top : array-like
573
        Thickness of E top in km.
574

575
    Notes
576
    -----
577
    This function returns thicknesses of ionospheric layers.
578
    We assume that B0 from IRTAM is equivalent to the
579
    thickness of the bottom side formulated in PyIRI.
580
    In reality, it could be different, since PyIRI uses
581
    Epstein function for the bottom side and IRTAM uses
582
    Ramakrishnan & Rawer function.
583

584
    References
585
    ----------
586
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
587
    International Reference Ionosphere Modeling Implemented in Python,
588
    Space Weather.
589

590
    """
591

592
    # B_F2_top................................................................
593
    # Shape parameter depends on solar activity:
594
    # Effective sunspot number
595
    R12 = ml.F107_2_R12(F107)
×
596
    k = (3.22 - 0.0538 * foF2 - 0.00664 * hmF2 + (0.113 * hmF2 / B_0)
×
597
         + 0.00257 * R12)
598

599
    # Auxiliary parameters x and v:
600
    x = (k * B_0 - 150.) / 100.
×
601
    # Thickness
602
    B_F2_top = (100. * x + 150.) / (0.041163 * x**2 - 0.183981 * x + 1.424472)
×
603

604
    return B_F2_top
×
605

606

607
def IRTAM_EDP_builder(x, aalt):
7✔
608
    """Construct vertical EDP. This is an old function and will be depreciated.
609

610
    Parameters
611
    ----------
612
    x : array-like
613
        Array where 1st dimension indicates the parameter (total 11
614
        parameters), second dimension is time, and third is horizontal grid
615
        [11, N_T, N_G].
616
    aalt : array-like
617
        1-D array of altitudes [N_V] in km.
618

619
    Returns
620
    -------
621
    density : array-like
622
        3-D electron density [N_T, N_V, N_G] in m-3.
623

624
    Notes
625
    -----
626
    This function builds the EDP from the provided parameters for all time
627
    frames, all vertical and all horizontal points.
628

629
    References
630
    ----------
631
    Forsythe et al. (2023), PyIRI: Whole-Globe Approach to the
632
    International Reference Ionosphere Modeling Implemented in Python,
633
    Space Weather.
634

635
    """
636
    message1 = "This function is replaced by IRTAM_EDP_builder_continuous"
×
637
    message2 = " and will be removed in version 0.0.7+"
×
638
    warnings.warn(message1 + message2,
×
639
                  DeprecationWarning, stacklevel=2)
640

641
    # Number of elements in horizontal dimension of grid
642
    ngrid = x.shape[1]
×
643

644
    # vertical dimention
645
    nalt = aalt.size
×
646

647
    # Empty arrays
648
    density_F2 = np.zeros((nalt, ngrid))
×
649
    density_F1 = np.zeros((nalt, ngrid))
×
650
    density_E = np.zeros((nalt, ngrid))
×
651
    drop_1 = np.zeros((nalt, ngrid))
×
652
    drop_2 = np.zeros((nalt, ngrid))
×
653

654
    # Shapes:
655
    # for filling with altitudes because the last dimensions should match
656
    # the source
657
    shape1 = (ngrid, nalt)
×
658

659
    # For filling with horizontal maps because the last dimentsions should
660
    # match the source
661
    shape2 = (nalt, ngrid)
×
662

663
    order = 'F'
×
664
    NmF2 = np.reshape(x[0, :], ngrid, order=order)
×
665
    NmF1 = np.reshape(x[1, :], ngrid, order=order)
×
666
    NmE = np.reshape(x[2, :], ngrid, order=order)
×
667
    hmF2 = np.reshape(x[3, :], ngrid, order=order)
×
668
    hmF1 = np.reshape(x[4, :], ngrid, order=order)
×
669
    hmE = np.reshape(x[5, :], ngrid, order=order)
×
670
    B0 = np.reshape(x[6, :], ngrid, order=order)
×
671
    B1 = np.reshape(x[7, :], ngrid, order=order)
×
672
    B_F2_top = np.reshape(x[8, :], ngrid, order=order)
×
673
    B_F1_bot = np.reshape(x[9, :], ngrid, order=order)
×
674
    B_E_bot = np.reshape(x[10, :], ngrid, order=order)
×
675
    B_E_top = np.reshape(x[11, :], ngrid, order=order)
×
676

677
    # Set to some parameters if zero or lower:
678
    B_F1_bot[np.where(B_F1_bot <= 0)] = 10
×
679
    B_F2_top[np.where(B_F2_top <= 0)] = 30
×
680

681
    # Array of hmFs with same dimensions as result, to later search
682
    # using argwhere
683
    a_alt = np.full(shape1, aalt, order='F')
×
684
    a_alt = np.swapaxes(a_alt, 0, 1)
×
685

686
    # Fill arrays with parameters to add height dimension and populate it
687
    # with same values, this is important to keep all operations in matrix
688
    # form
689
    a_NmF2 = np.full(shape2, NmF2)
×
690
    a_NmF1 = np.full(shape2, NmF1)
×
691
    a_NmE = np.full(shape2, NmE)
×
692
    a_hmF2 = np.full(shape2, hmF2)
×
693
    a_hmF1 = np.full(shape2, hmF1)
×
694
    a_hmE = np.full(shape2, hmE)
×
695
    a_B_F2_top = np.full(shape2, B_F2_top)
×
696
    a_B0 = np.full(shape2, B0)
×
697
    a_B1 = np.full(shape2, B1)
×
698
    a_B_F1_bot = np.full(shape2, B_F1_bot)
×
699
    a_B_E_top = np.full(shape2, B_E_top)
×
700
    a_B_E_bot = np.full(shape2, B_E_bot)
×
701

702
    # Amplitude for Epstein functions
703
    a_A1 = 4. * a_NmF2
×
704
    a_A2 = 4. * a_NmF1
×
705
    a_A3 = 4. * a_NmE
×
706

707
    # !!! do not use a[0], because all 3 dimensions are needed. this is
708
    # the same as density[a]= density[a[0], a[1], a[2]]
709

710
    # F2 top (same for yes F1 and no F1)
711
    a = np.where(a_alt >= a_hmF2)
×
712
    density_F2[a] = ml.epstein_function_top_array(a_A1[a], a_hmF2[a],
×
713
                                                  a_B_F2_top[a], a_alt[a])
714

715
    # E bottom (same for yes F1 and no F1)
716
    a = np.where(a_alt <= a_hmE)
×
717
    density_E[a] = ml.epstein_function_array(a_A3[a], a_hmE[a], a_B_E_bot[a],
×
718
                                             a_alt[a])
719

720
    # when F1 is present-----------------------------------------
721
    # F2 bottom down to F1
722
    a = np.where((np.isfinite(a_NmF1)) & (a_alt < a_hmF2) & (a_alt >= a_hmF1))
×
723
    density_F2[a] = Ramakrishnan_Rawer_function(a_NmF2[a], a_hmF2[a],
×
724
                                                a_B0[a], a_B1[a], a_alt[a])
725

726
    # E top plus F1 bottom (hard boundaries)
727
    a = np.where((a_alt > a_hmE) & (a_alt < a_hmF1))
×
728
    drop_1[a] = 1. - ((a_alt[a] - a_hmE[a]) / (a_hmF1[a] - a_hmE[a]))**4.
×
729
    drop_2[a] = 1. - ((a_hmF1[a] - a_alt[a]) / (a_hmF1[a] - a_hmE[a]))**4.
×
730

731
    density_E[a] = ml.epstein_function_array(a_A3[a],
×
732
                                             a_hmE[a],
733
                                             a_B_E_top[a],
734
                                             a_alt[a]) * drop_1[a]
735
    density_F1[a] = ml.epstein_function_array(a_A2[a],
×
736
                                              a_hmF1[a],
737
                                              a_B_F1_bot[a],
738
                                              a_alt[a]) * drop_2[a]
739

740
    # When F1 is not present(hard boundaries)--------------------
741
    a = np.where((np.isnan(a_NmF1)) & (a_alt < a_hmF2) & (a_alt > a_hmE))
×
742
    drop_1[a] = 1.0 - ((a_alt[a] - a_hmE[a]) / (a_hmF2[a] - a_hmE[a]))**4.0
×
743
    drop_2[a] = 1.0 - ((a_hmF2[a] - a_alt[a]) / (a_hmF2[a] - a_hmE[a]))**4.0
×
744
    density_E[a] = ml.epstein_function_array(a_A3[a], a_hmE[a], a_B_E_top[a],
×
745
                                             a_alt[a]) * drop_1[a]
746

747
    density_F2[a] = Ramakrishnan_Rawer_function(a_NmF2[a], a_hmF2[a], a_B0[a],
×
748
                                                a_B1[a], a_alt[a]) * drop_2[a]
749
    density = density_F2 + density_F1 + density_E
×
750

751
    # Make 1 everything that is <= 0
752
    density[np.where(density <= 1.0)] = 1.0
×
753

754
    return density
×
755

756

757
def Ramakrishnan_Rawer_function(NmF2, hmF2, B0, B1, h):
7✔
758
    """Construct density Ramakrishnan & Rawer F2 bottomside.
759

760
    Parameters
761
    ----------
762
    NmF2 : array-like
763
        F2 region peak in m-3.
764
    hmF2 : array-like
765
        Height of F2 layer in km.
766
    B0 : array-like
767
        Thickness parameter for F2 region in km.
768
    B1 : array-like
769
        Thickness parameter for F2 region in km.
770
    h : array-like
771
        Altitude in km.
772

773
    Returns
774
    -------
775
    den : array-like
776
        Constructed density in m-3.
777

778
    Notes
779
    -----
780
    This function constructs bottomside of F2 layer using
781
    Ramakrishnan & Rawer equation (as in IRI). All inputs are supposed
782
    to have same size.
783

784
    References
785
    ----------
786
    Bilitza et al. (2022), The International Reference Ionosphere
787
    model: A review and description of an ionospheric benchmark, Reviews
788
    of Geophysics, 60.
789

790
    """
791
    x = (hmF2 - h) / B0
×
792
    den = NmF2 * ml.fexp(-(np.sign(x) * (np.abs(x)**B1))) / np.cosh(x)
×
793

794
    return den
×
795

796

797
def call_IRTAM_PyIRI(aUT, dtime, alon, alat, aalt, f2, f1, e_peak, es_peak,
7✔
798
                     modip, TOV, irtam_dir='', use_subdirs=True):
799
    """Update parameters and build EDP for IRTAM for one time frame.
800

801
    Parameters
802
    ----------
803
    aUT : array-like
804
        Time array that was used for PyIRI in hours.
805
    dtime : dtime object
806
        F2 region peak in m-3.
807
    alon : array-like
808
        Longitudes 1-D array in degrees.
809
    alat : array-like
810
        Latitudes 1-D array in degrees.
811
    aalt : array-like
812
        Altitude 1-D array in km for EDP construction.
813
    f2 : dict
814
        'Nm' is peak density of F2 region in m-3.
815
        'fo' is critical frequency of F2 region in MHz.
816
        'M3000' is the obliquity factor for a distance of 3,000 km.
817
        Defined as refracted in the ionosphere, can be received at a
818
        distance of 3,000 km, unitless.
819
        'hm' is height of the F2 peak in km.
820
        'B_topi is top thickness of the F2 region in km.
821
        'B_bot' is bottom thickness of the F2 region in km.
822
        Shape [N_T, N_G].
823
    f1 : dict
824
        'Nm' is peak density of F1 region in m-3.
825
        'fo' is critical frequency of F1 region in MHz.
826
        'P' is the probability occurrence of F1 region, unitless.
827
        'hm' is height of the F1 peak in km.
828
        'B_bot' is bottom thickness of the F1 region in km.
829
        Shape [N_T, N_G].
830
    e_peak : dict
831
        'Nm' is peak density of E region in m-3.
832
        'fo' is critical frequency of E region in MHz.
833
        'hm' is height of the E peak in km.
834
        'B_top' is bottom thickness of the E region in km.
835
        'B_bot' is bottom thickness of the E region in km.
836
        Shape [N_T, N_G].
837
    es_peak : dict
838
        'Nm' is peak density of Es region in m-3.
839
        'fo' is critical frequency of Es region in MHz.
840
        'hm' is height of the Es peak in km.
841
        'B_top' is bottom thickness of the Es region in km.
842
        'B_bot' is bottom thickness of the Es region in km.
843
        Shape [N_T, N_G].
844
    modip : array-like
845
        Modified dip angle in degrees.
846
    TOV : float
847
        Time of Validity in decimal hours. Use 24 if not known.
848
    irtam_dir : str
849
        Directory for IRTAM coefficients, or '' to use package directory.
850
        (default='')
851
    use_subdirs : bool
852
        If True, adds YYYY/MMDD subdirectories to the filename path, if False
853
        assumes that the entire path to the coefficient directory is provided
854
        by `irtam_dir` (default=True)
855

856
    Returns
857
    -------
858
    f2 : dict
859
        'Nm' is peak density of F2 region in m-3.
860
        'hm' is height of the F2 peak in km.
861
        'B_top is top thickness of the F2 region in km.
862
        'B0' is bottom thickness parameter of the F2 region in km.
863
        'B1' is bottom thickness parameter of the F2 region in km.
864
        Shape [N_T, N_G].
865
    f1 : dict
866
        'Nm' is peak density of F1 region in m-3.
867
        'hm' is height of the F1 peak in km.
868
        'B_bot' is bottom thickness of the F1 region in km.
869
        Shape [N_T, N_G].
870
    e_peak : dict
871
        'Nm' is peak density of E region in m-3.
872
        'hm' is height of the E peak in km.
873
        'B_top' is bottom thickness of the E region in km.
874
        'B_bot' is bottom thickness of the E region in km.
875
        Shape [N_T, N_G].
876
    es_peak : dict
877
        'Nm' is peak density of Es region in m-3.
878
        'hm' is height of the Es peak in km.
879
        'B_top' is bottom thickness of the Es region in km.
880
        'B_bot' is bottom thickness of the Es region in km.
881
        Shape [N_T, N_G].
882
    EDP_result : array-like
883
        Electron density profile in m-3. Shape [N_T, N_V, N_G].
884

885
    Notes
886
    -----
887
    This function uses IRTAM coefficients to construct NmF2, hmF2, B0, B1
888
    maps, collects other parameters from PyIRI, updates hmF1 and B_F1_bot,
889
    and constructs the EDP.
890

891
    """
892
    # Find time index
893
    UT = dtime.hour + dtime.minute / 60.0 + dtime.second / 3600.0
×
894
    it = np.where(aUT == UT)[0]
×
895

896
    # Find IRTAM parameters
897
    IRTAM_f2 = IRTAM_density(dtime, alon, alat, modip, TOV, irtam_dir,
×
898
                             use_subdirs)
899

900
    # Create empty arrays with needed shape for 1 time frame
901
    # to fill with updated values
902
    shape = IRTAM_f2['fo'].shape
×
903
    NmF1 = np.zeros((shape))
×
904
    NmE = np.zeros((shape))
×
905
    hmE = np.zeros((shape))
×
906
    B_F2_top = np.zeros((shape))
×
907
    B_E_bot = np.zeros((shape))
×
908
    B_E_top = np.zeros((shape))
×
909
    P_F1 = np.zeros((shape))
×
910
    NmEs = np.zeros((shape))
×
911
    hmEs = np.zeros((shape))
×
912
    B_Es_bot = np.zeros((shape))
×
913
    B_Es_top = np.zeros((shape))
×
914
    hmF1 = np.zeros((shape))
×
915
    B_F2_top = np.zeros((shape))
×
916

917
    # Fill with PyIRI parameters that will not
918
    # need to be updated
919
    NmE[:, :] = e_peak['Nm'][it, :]
×
920
    hmE[:, :] = e_peak['hm'][it, :]
×
921
    B_F2_top[:, :] = f2['B_top'][it, :]
×
922
    B_E_bot[:, :] = f1['P'][it, :]
×
923
    B_E_top[:, :] = e_peak['B_top'][it, :]
×
924
    P_F1[:, :] = f1['P'][it, :]
×
925
    NmEs[:, :] = es_peak['Nm'][it, :]
×
926
    hmEs[:, :] = es_peak['hm'][it, :]
×
927
    B_Es_bot[:, :] = es_peak['B_bot'][it, :]
×
928
    B_Es_top[:, :] = es_peak['B_top'][it, :]
×
929
    B_F2_top[:, :] = f2['B_top'][it, :]
×
930

931
    # UPDATING PARAMETERS that depend of NmF2, hmF2, and thickness:
932
    # Convert critical frequency to the electron density (m-3)
933
    NmF2 = IRTAM_freq_to_Nm(IRTAM_f2['fo'])
×
934

935
    # --------------------------------------------------------------------------
936
    # Find F1 layer based on the P and F2 layer
937
    (NmF1,
×
938
     foF1,
939
     hmF1,
940
     B_F1_bot) = sh.derive_dependent_F1_parameters(
941
         P_F1,
942
         NmF2,
943
         IRTAM_f2['hm'],
944
         IRTAM_f2['B0'],
945
         IRTAM_f2['B1'],
946
         hmE)
947

948
    # combine parameters from PyIRI and IRTAM to merged dictionary
949
    F2_result = {'Nm': NmF2,
×
950
                 'hm': IRTAM_f2['hm'],
951
                 'B_top': B_F2_top,
952
                 'B0': IRTAM_f2['B0'],
953
                 'B1': IRTAM_f2['B1']}
954
    F1_result = {'Nm': NmF1,
×
955
                 'hm': hmF1,
956
                 'B_bot': B_F1_bot}
957
    E_result = {'Nm': NmE,
×
958
                'hm': hmE,
959
                'B_bot': B_E_bot,
960
                'B_top': B_E_top}
961
    Es_result = {'Nm': NmEs,
×
962
                 'hm': hmEs,
963
                 'B_bot': B_Es_bot,
964
                 'B_top': B_Es_top}
965

NEW
966
    EDP_result = sh.EDP_builder_continuous(
×
967
        F2_result, F1_result, E_result, aalt)
968

969
    return F2_result, F1_result, E_result, Es_result, EDP_result
×
970

971

972
def run_PyIRTAM(year, month, day, aUT, alon, alat, aalt, F107, irtam_dir='',
7✔
973
                use_subdirs=True, download=False):
974
    """Update parameters and build EDP for IRTAM for one time frame.
975

976
    Parameters
977
    ----------
978
    year : int
979
        Year.
980
    mth : int
981
        Month of year.
982
    aUT : array-like
983
        Array of universal time (UT) in hours. Must be Numpy array of any size
984
        [N_T].
985
    alon : array-like
986
        Flattened array of geographic longitudes in degrees. Must be Numpy
987
        array of any size [N_G].
988
    alat : array-like
989
        Flattened array of geographic latitudes in degrees. Must be Numpy array
990
        of any size [N_G].
991
    aalt : array-like
992
        Array of altitudes in km. Must be Numpy array of any size [N_V].
993
    F107 : float
994
        User provided F10.7 solar flux index in SFU.
995
    irtam_dir : str
996
        Directory with IRTAM coefficients, or '' to use package directory.
997
        (default='')
998
    use_subdirs : bool
999
        If True, adds YYYY/MMDD subdirectories to the filename path, if False
1000
        assumes that the entire path to the coefficient directory is provided
1001
        by `irtam_dir` (default=True)
1002
    download : bool
1003
        If True, downloads coefficient files (default=False)
1004

1005
    Returns
1006
    -------
1007
    f2_b : dict
1008
        F2 parameters form PyIRI
1009
        'Nm' is peak density of F2 region in m-3.
1010
        'fo' is critical frequency of F2 region in MHz.
1011
        'M3000' is the obliquity factor for a distance of 3,000 km.
1012
        Defined as refracted in the ionosphere, can be received at a distance
1013
        of 3,000 km, unitless.
1014
        'hm' is height of the F2 peak in km.
1015
        'B_topi is top thickness of the F2 region in km.
1016
        'B_bot' is bottom thickness of the F2 region in km.
1017
        Shape [N_T, N_G, 2].
1018
    f1_b : dict
1019
        F1 parameters form PyIRI
1020
        'Nm' is peak density of F1 region in m-3.
1021
        'fo' is critical frequency of F1 region in MHz.
1022
        'P' is the probability occurrence of F1 region, unitless.
1023
        'hm' is height of the F1 peak in km.
1024
        'B_bot' is bottom thickness of the F1 region in km.
1025
        Shape [N_T, N_G, 2].
1026
    e_b : dict
1027
        E parameters form PyIRI
1028
        'Nm' is peak density of E region in m-3.
1029
        'fo' is critical frequency of E region in MHz.
1030
        'hm' is height of the E peak in km.
1031
        'B_top' is bottom thickness of the E region in km.
1032
        'B_bot' is bottom thickness of the E region in km.
1033
        Shape [N_T, N_G, 2].
1034
    es_b : dict
1035
        Es parameters form PyIRI
1036
        'Nm' is peak density of Es region in m-3.
1037
        'fo' is critical frequency of Es region in MHz.
1038
        'hm' is height of the Es peak in km.
1039
        'B_top' is bottom thickness of the Es region in km.
1040
        'B_bot' is bottom thickness of the Es region in km.
1041
        Shape [N_T, N_G, 2].
1042
    sun : dict
1043
        'lon' is longitude of subsolar point in degrees.
1044
        'lat' is latitude of subsolar point in degrees.
1045
        Shape [N_G].
1046
    mag : dict
1047
        'inc' is inclination of the magnetic field in degrees.
1048
        'modip' is modified dip angle in degrees.
1049
        'mag_dip_lat' is magnetic dip latitude in degrees.
1050
        Shape [N_G].
1051
    edp_b : array-like
1052
        Electron density profiles from PyIRI in m-3 with shape
1053
        [N_T, N_V, N_G]
1054
    f2_day : dict
1055
        F2 parameters form PyIRTAM
1056
        'Nm' is peak density of F2 region in m-3.
1057
        'hm' is height of the F2 peak in km.
1058
        'B_top is top thickness of the F2 region in km.
1059
        'B0' is bottom thickness parameter of the F2 region in km.
1060
        'B1' is bottom thickness parameter of the F2 region in km.
1061
        Shape [N_T, N_G].
1062
    f1_day : dict
1063
        F1 parameters form PyIRTAM
1064
        'Nm' is peak density of F1 region in m-3.
1065
        'hm' is height of the F1 peak in km.
1066
        'B_bot' is bottom thickness of the F1 region in km.
1067
        Shape [N_T, N_G].
1068
    e_day : dict
1069
        E parameters form PyIRTAM
1070
        'Nm' is peak density of E region in m-3.
1071
        'hm' is height of the E peak in km.
1072
        'B_top' is bottom thickness of the E region in km.
1073
        'B_bot' is bottom thickness of the E region in km.
1074
        Shape [N_T, N_G].
1075
    es_day : dict
1076
        Es parameters form PyIRTAM
1077
        'Nm' is peak density of Es region in m-3.
1078
        'hm' is height of the Es peak in km.
1079
        'B_top' is bottom thickness of the Es region in km.
1080
        'B_bot' is bottom thickness of the Es region in km.
1081
        Shape [N_T, N_G].
1082
    edp_day : array-like
1083
        Electron density profile from PyIRTAM in m-3. Shape
1084
        [N_T, N_V, N_G].
1085

1086
    Notes
1087
    -----
1088
    This function runs PyIRTAM for a day of interest.
1089

1090
    """
1091
    # First, determine the standard PyIRI parameters for the day of interest
1092
    # It is better to do it in the beginning (outside the time loop),
1093
    # so that PyIRI is called only once for the whole day.
1094

1095
    # Use CCIR (not URSI) since IRTAM uses CCIR models.
1096
    ccir_or_ursi = 0  # 0 = CCIR, 1 = URSI
×
1097

1098
    # Run PyIRI
1099
    f2_b, f1_b, e_b, es_b, sun, mag, edp_b = ml_up.IRI_density_1day(
×
1100
        year, month, day, aUT, alon, alat, aalt, F107, PyIRI.coeff_dir,
1101
        ccir_or_ursi)
1102

1103
    # Create empty dictionaries to store daily parameters.
1104
    empt = np.array([])
×
1105
    f2_day = {'Nm': empt, 'hm': empt, 'B0': empt, 'B1': empt, 'B_top': empt}
×
1106
    f1_day = {'Nm': empt, 'hm': empt, 'B_bot': empt}
×
1107
    e_day = {'Nm': empt, 'hm': empt, 'B_bot': empt, 'B_top': empt}
×
1108
    es_day = {'Nm': empt, 'hm': empt, 'B_bot': empt, 'B_top': empt}
×
1109
    edp_day = edp_b * 0.
×
1110
    f1_day['P'] = f1_b['P']
×
1111

1112
    for it in range(0, aUT.size):
×
1113
        # Dtime for one time frame
1114
        hour = int(np.fix(aUT[it]))
×
1115
        minute = int((aUT[it] - hour) * 60.)
×
1116
        dtime = dt.datetime(year, month, day, hour, minute, 0)
×
1117

1118
        # If the user specifies, the coefficients are
1119
        # downloaded form the UMass Lowell data base
1120
        if download:
×
1121
            for param in ['B0', 'B1', 'hmF2', 'foF2']:
×
1122
                coeff.download_irtam_coeffs(dtime, param, irtam_dir=irtam_dir)
×
1123

1124
        # Call PyIRTAM:
1125
        F2, F1, E, Es, EDP = call_IRTAM_PyIRI(aUT, dtime, alon, alat, aalt,
×
1126
                                              f2_b, f1_b, e_b, es_b,
1127
                                              mag['modip'], aUT[it], irtam_dir,
1128
                                              use_subdirs=use_subdirs)
1129
        # Save results.
1130
        if it == 0:
×
1131
            for key in F2:
×
1132
                f2_day[key] = F2[key][:]
×
1133
            for key in F1:
×
1134
                f1_day[key] = F1[key][:]
×
1135
            for key in E:
×
1136
                e_day[key] = E[key][:]
×
1137
            for key in Es:
×
1138
                es_day[key] = Es[key][:]
×
1139
        else:
1140
            for key in F2:
×
1141
                f2_day[key] = np.concatenate((f2_day[key], F2[key][:]), axis=0)
×
1142
            for key in F1:
×
1143
                f1_day[key] = np.concatenate((f1_day[key], F1[key][:]), axis=0)
×
1144
            for key in E:
×
1145
                e_day[key] = np.concatenate((e_day[key], E[key][:]), axis=0)
×
1146
            for key in Es:
×
1147
                es_day[key] = np.concatenate((es_day[key], Es[key][:]), axis=0)
×
1148
        edp_day[it, :, :] = EDP
×
1149

1150
    return (f2_b, f1_b, e_b, es_b, sun, mag, edp_b, f2_day, f1_day, e_day,
×
1151
            es_day, edp_day)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc