2
2
3
3
# Authors of this code:
4
4
# Daniel A. Brandt, Ph.D., Michigan Tech Research Institute, daabrand@mtu.edu
5
+ # Aaron L. Bukowski, Ph.D., University of Michigan, abukowski@umich.edu
5
6
# Aaron J. Ridley, Ph.D., University of Michigan, ridley@umich.edu
6
7
7
8
# This file contains a suite of tools that do the following:
14
15
import numpy as np
15
16
from datetime import datetime , timedelta
16
17
import pathlib
17
- import os
18
- from urllib . request import urlretrieve
18
+ import os , sys
19
+ import pooch
19
20
from netCDF4 import Dataset
20
21
import scipy .integrate as integ
21
22
22
23
# Directory management:
23
24
here = pathlib .Path (__file__ ).parent .resolve ()
24
- saveLoc = here .joinpath ('. ' )
25
+ euvDir = here .parent . joinpath ('share/run/UA/inputs ' )
25
26
26
27
# Physical constants:
27
28
h = 6.62607015e-34 # Planck's constant in SI units of J s
28
29
c = 299792458 # Speed of light in m s^-1
29
30
30
31
# Helper Functions:
31
- def getFism (dateStart , dateEnd , saveLoc , stanBands = False ):
32
+ def getFism2 (dateStart , dateEnd , source , downloadDir = None ):
32
33
"""
33
- Download FISM2 daily data, either in the Standard Bands or not. Default is to download the data NOT in the
34
- Standard Bands. Note that this function downloads the data to a pre-determined location. The download is also
35
- 'smart'. If the data already exists (over the exact same time period). It is NOT overwitten. If the requested data
36
- covers times OUTSIDE the time period of existing data, the NEW data is simply appended or prepended to a file
37
- containing the existing data.
38
- Args:
39
- dateStart: str
40
- The start date in 'YYYY-MM-DD' format.
41
- dateEnd: str
42
- The end date in 'YYYY-MM-DD' format.
43
- saveLoc: str/path
44
- The path to save data to
45
- stanBands: bool
46
- If True, downloads FISM2 data in the STAN BANDS. Default is False.
47
- Returns:
48
- fismFile: str
49
- The location of the downloaded FISM data.
34
+ Given a starting date and an ending date, automatically download irradiance data from LISIRD for a specific source,
35
+ including FISM2 daily or FISM2 in the Standard Bands.
36
+ :param dateStart: str
37
+ The starting date for the data in YYYY-MM-DD format.
38
+ :param dateEnd: str
39
+ The ending date for the data in YYYY-MM-DD format.
40
+ :param source: str
41
+ The type of data to be obtained. Valid inputs are:
42
+ - FISM2 (for daily averages of FISM2 data)
43
+ - FISM2S (for daily averages of FISM2 standard bands, according to Solomon and Qian 2005)
44
+ :return times: ndarray
45
+ Datetime values for each spectrum.
46
+ :return wavelengths: ndarray
47
+ Wavelength bins (bin boundaries) for the spectral data.
48
+ :return irradiance: ndarray
49
+ A 2D array where each row is a spectrum at a particular time, and the columns are wavelength bands.
50
50
"""
51
+ # Converting the input time strings to datetimes:
51
52
dateStartDatetime = datetime .strptime (dateStart , "%Y-%m-%d" )
52
53
dateEndDatetime = datetime .strptime (dateEnd , "%Y-%m-%d" )
53
54
54
- # Helper function to manage the obtaining of data, given a URL:
55
- def urlObtain (url , fname ):
56
- if os .path .isfile (fname ) == True :
57
- print ('File already exists (loading in data) ' + str (fname ))
58
- else :
59
- urlretrieve (url , fname )
60
-
61
- URL = 'https://lasp.colorado.edu/eve/data_access/eve_data/fism/daily_bands/daily_bands.nc'
62
- fname = 'FISM2_daily.nc'
63
- urlObtain (URL , fname )
64
-
65
- if stanBands :
66
- datetimes , wavelengths , irradiance = readFism (fname , stanBands = True )
55
+ # Check if the user has asked for a source that can be obtained:
56
+ validSources = ['FISM2' , 'FISM2S' ]
57
+ if source not in validSources :
58
+ raise ValueError ("Variable 'source' must be either 'FISM2' or 'FISM2S." )
59
+
60
+ # If the download directory is not specified, set it to the top directory that the package is in:
61
+ if downloadDir is None :
62
+ downloadDir = os .getcwd ()
63
+
64
+ # Download the most recent file for the corresponding source and read it in:
65
+ if source == 'FISM2' :
66
+ url = 'https://lasp.colorado.edu/eve/data_access/eve_data/fism/daily_hr_data/daily_data.nc'
67
+ fname = 'FISM2_daily_data.nc'
68
+ urlObtain (url , loc = downloadDir , fname = fname ) # hash='dbee404e1c75689b47691b8a4a733236bb66abbdc0f01b8cbd8236f69fe9d469'
69
+ datetimes , wavelengths , irradiance , uncertainties = obtainFism2 (downloadDir + '/' + fname )
67
70
else :
68
- datetimes , wavelengths , irradiance = readFism (fname , stanBands = False )
71
+ url = 'https://lasp.colorado.edu/eve/data_access/eve_data/fism/daily_bands/daily_bands.nc'
72
+ fname = 'FISM2_daily_bands.nc'
73
+ urlObtain (url , loc = downloadDir , fname = fname ) # hash='27e3183f8ad6b289de191a63d3feada64c9d3f6b2973315ceda4a42c41638465'
74
+ datetimes , wavelengths , irradiance , uncertainties = obtainFism2 (downloadDir + '/' + fname , bands = True )
75
+
76
+ # Subset the data according to user demands:
77
+ validInds = np .where ((datetimes >= dateStartDatetime ) & (datetimes <= dateEndDatetime ))[0 ]
78
+ times = datetimes [validInds ]
79
+ if source == 'FISM2S' :
80
+ irradiance = irradiance [- 1 , validInds , :]
81
+ else :
82
+ irradiance = irradiance [validInds , :]
69
83
70
- # Subset the data in time, and save the subset data to a relative path:
71
- subset_inds = np .where ((datetimes >= dateStartDatetime ) & (datetimes <= dateEndDatetime ))[0 ]
72
- subset_times = datetimes [subset_inds ]
73
- subset_irradiance = irradiance [subset_inds , :]
84
+ # Return the resulting data:
85
+ return times , wavelengths , irradiance
74
86
75
- return subset_times , wavelengths , subset_irradiance
87
+ def obtainFism2 (myFism2File , bands = False ):
88
+ """
89
+ Load in spectrum data from a FISM2 file.
90
+ :param myFism2File: str
91
+ The location of the NETCDF4 file.
92
+ :param bands: bool
93
+ If True, loads in the data segmented into the Solomon and Qian 2005 standard bands.
94
+ :return datetimes: ndarray
95
+ An array of datetimes for each TIMED/SEE spectra.
96
+ :return wavelengths: ndarray
97
+ A one-dimensional array of wavelengths at which there are irradiance values.
98
+ :return irradiances: ndarray
99
+ A two-dimensional array of irradiance values at each time.
100
+ :return uncertainties: ndarray
101
+ A two-dimensional array of irradiance uncertainty values at each time.
102
+ """
103
+ fism2Data = Dataset (myFism2File )
104
+ wavelengths = np .asarray (fism2Data .variables ["wavelength" ])
105
+ if bands == True : # STANDARD BANDS
106
+ flux = np .asarray (fism2Data .variables ["ssi" ]) # photons/cm2/second
107
+ # bandwidths = np.asarray(fism2Data.variables['band_width'])
108
+ pFlux = flux * 1.0e4 # photons/m2/second
109
+ # Convert fluxes to irradiances:
110
+ irr = np .zeros_like (flux )
111
+ for i in range (flux .shape [1 ]):
112
+ irr [:, i ] = spectralIrradiance (pFlux [:, i ], wavelengths [i ] * 10.0 ) # W/m^2
113
+ irradiance = np .array ([flux , irr ])
114
+ uncertainties = np .full_like (irradiance , fill_value = np .nan ) # TODO: Replace with an estimation of uncertainty
115
+ else : # NATIVE DATA
116
+ irradiance = np .asarray (fism2Data .variables ["irradiance" ]) # W/m^2/nm
117
+ uncertainties = np .asarray (fism2Data .variables ["uncertainty" ])
118
+ dates = fism2Data .variables ["date" ]
119
+ datetimes = []
120
+ for i in range (len (dates )):
121
+ year = dates [i ][:4 ]
122
+ day = dates [i ][4 :]
123
+ currentDatetime = (
124
+ datetime (int (year ), 1 , 1 )
125
+ + timedelta (int (day ) - 1 )
126
+ + timedelta (hours = 12 )
127
+ )
128
+ datetimes .append (currentDatetime )
129
+ datetimes = np .asarray (datetimes )
130
+ return datetimes , wavelengths , irradiance , uncertainties
76
131
77
- def rebin (fism_out , saveLoc , binning_scheme = 'EUVAC' , zero = True ):
132
+ def rebin (fism_out , saveLoc = os . getcwd () , binning_scheme = 'EUVAC' , zero = True ):
78
133
"""
79
134
Takes the output of getFism and rebins the data into whatever format the user desires.
80
135
Args:
81
136
fism_out: arraylike
82
- The output of getFism . Contains 4 elements: (1) datetime values for the FISM2 spectra, (2) the wavelengths
137
+ The output of getFism2 . Contains 4 elements: (1) datetime values for the FISM2 spectra, (2) the wavelengths
83
138
of the spectrum, (3) the actual FISM2 irradiance spectra.
84
139
saveLoc: path
85
- Path to save data files to.
140
+ Path to save data files to. Defaults to the current working directory.
86
141
binning_scheme: str
87
142
Determines the binning scheme to be used. Valid arguments include the following:
88
- 'EUVAC': Uses the 37 wavelength band scheme described in Richards, et al. 1994; doi.org/10.1029/94JA00518
89
- 'NEUVAC': Uses the 59 wavelength band scheme described in Brandt and Ridley, 2024; doi.org/10.1029/2024SW004043
143
+ 'EUVAC' or 'Euvac' or 'euvac' : Uses the 37 wavelength band scheme described in Richards, et al. 1994; doi.org/10.1029/94JA00518
144
+ 'NEUVAC' or 'Neuvac' or 'neuvac' : Uses the 59 wavelength band scheme described in Brandt and Ridley, 2024; doi.org/10.1029/2024SW004043
90
145
'HFG': Uses the 23 wavelength band scheme described in Solomon and Qian, 2005; https://doi.org/10.1029/2005JA011160
91
- 'SOLOMON': Same situation as for argument 'HFG'.
92
- NOTE: If 'HFG' or 'SOLOMON' is chosen, the values of fism_out must correspond to getFism being run with the
93
- argument stanBands = True . If this IS NOT the case, an error will be thrown.
146
+ 'SOLOMON' or 'Solomon' or 'solomon' : Same situation as for argument 'HFG'.
147
+ NOTE: If 'HFG' or 'SOLOMON' is chosen, the values of fism_out must correspond to getFism2 being run with the
148
+ argument source='FISM2' . If this IS NOT the case, an error will be thrown.
94
149
zero: bool
95
150
Controls whether singular (bright) wavelength lines are set to a value of zero after they are extracted.
96
151
Default is True.
@@ -107,15 +162,15 @@ def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
107
162
# nativeResolution = np.concatenate((np.diff(wavelengths), np.array([np.diff(wavelengths)[-1]])), axis=0)
108
163
nativeWavelengths = wavelengths .copy ()
109
164
110
- if binning_scheme != 'HFG' and binning_scheme != 'SOLOMON' :
111
- if binning_scheme == 'EUVAC' :
165
+ if binning_scheme != 'HFG' and binning_scheme != 'SOLOMON' and binning_scheme != 'Solomon' and binning_scheme != 'solomon' :
166
+ if binning_scheme == 'EUVAC' or binning_scheme == 'Euvac' or binning_scheme == 'euvac' :
112
167
# Grab the euv_37.csv file:
113
- fileStr = str (saveLoc .joinpath ('euv.csv' ))
168
+ fileStr = str (euvDir .joinpath ('euv.csv' ))
114
169
bin_bounds = read_euv_csv_file (fileStr )
115
170
tag = '_37'
116
- elif binning_scheme == 'NEUVAC' :
171
+ elif binning_scheme == 'NEUVAC' or binning_scheme == 'Neuvac' or binning_scheme == 'neuvac' :
117
172
# Grab the euv_59.csv file:
118
- fileStr = str (saveLoc .joinpath ('euv_59.csv' ))
173
+ fileStr = str (euvDir .joinpath ('euv_59.csv' ))
119
174
bin_bounds = read_euv_csv_file (fileStr )
120
175
tag = '_59'
121
176
else :
@@ -172,7 +227,7 @@ def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
172
227
except :
173
228
fism2_data [:, iWave ] = integ .trapezoid (myData [iStart :iEnd ], wavelengths [iStart :iEnd ])
174
229
175
- elif binning_scheme == 'HFG' or binning_scheme == 'SOLOMON' :
230
+ elif binning_scheme == 'HFG' or binning_scheme == 'SOLOMON' or binning_scheme == 'Solomon' or binning_scheme == 'solomon' :
176
231
# Determine whether the supplied data already conforms to the Solomon and Qian binning scheme.
177
232
tag = '_solomon'
178
233
if fism_out [2 ].shape [1 ] != 23 :
@@ -186,7 +241,7 @@ def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
186
241
raise ValueError ("Invalid value for argument 'binning_scheme'. Must be 'EUVAC', 'NEUVAC', 'HFG', or 'SOLOMON'." )
187
242
188
243
# Save the rebinned data to a relative path (outside the package directory) in the form of a .txt file:
189
- fism2_file = saveLoc .joinpath ('fism2_file' + tag + '.txt' )
244
+ fism2_file = pathlib . Path ( os . getcwd ()) .joinpath ('fism2_file' + tag + '.txt' )
190
245
saveFism (fism2_data , datetimes , fism2_file )
191
246
192
247
return fism2_file , fism2_data
@@ -246,46 +301,6 @@ def safe_open_w(path):
246
301
os .system ('readlink -f ' + str (filename ))
247
302
return
248
303
249
- def readFism (fism_file , stanBands = False ):
250
- """
251
- Load in spectrum data from a FISM2 file.
252
- Args:
253
- fism_file: str
254
- The location of a FISM2 NETCDF4 file.
255
- stanBands: bool
256
- If True, expects the data to be read in to be in the STAN BANDS. Default is False.
257
- Returns:
258
- datetimes: numpy.ndarray
259
- An array of datetimes for reach FISM2 spectrum.
260
- wavelengths: numpy.ndarray
261
- A one-dimensional array of wavelengths at which there are irradiance values.
262
- irradiances: numpy.ndarray
263
- A two-dimensional array of irradiance values in each wavelength bin.
264
- uncertainties: numpy.ndarray
265
- A two-dimensional array of irradiance uncertainty values at each bin. Only returned if stanBands is False.
266
- """
267
- fism2Data = Dataset (fism_file )
268
- wavelengths = np .asarray (fism2Data .variables ['wavelength' ])
269
- if stanBands :
270
- flux = np .asarray (fism2Data .variables ['ssi' ]) # photons/cm^2/second
271
- pFlux = flux * 1e4 # photons/m^2/second
272
- # Convert fluxes to irradiances:
273
- irradiance = np .zeros_like (flux )
274
- for i in range (flux .shape [1 ]):
275
- irradiance [:, i ] = spectralIrradiance (pFlux [:, i ], wavelengths [i ] * 10. )# W/m^2
276
- else :
277
- irradiance = np .asarray (fism2Data .variables ['ssi' ])
278
- dates = fism2Data .variables ['date' ]
279
- datetimes = []
280
- for j in range (len (dates )):
281
- year = dates [j ][:4 ]
282
- day = dates [j ][4 :]
283
- currentDatetime = datetime (int (year ), 1 , 1 ) + timedelta (int (day ) - 1 ) + timedelta (hours = 12 )
284
- datetimes .append (currentDatetime )
285
- datetimes = np .asarray (datetimes )
286
-
287
- return datetimes , wavelengths , irradiance
288
-
289
304
def spectralIrradiance (photonFlux , wavelength ):
290
305
"""
291
306
Convert the photon flux to the corresponding spectral irradiance, given a specific wavelength.
@@ -354,6 +369,28 @@ def read_euv_csv_file(file):
354
369
'f74113' : f74113 }
355
370
return wavelengths
356
371
372
+ def urlObtain (URL , loc = None , fname = None , hash = None ):
373
+ """
374
+ Helper function that uses Pooch to download files to a location specified by the user.
375
+ :param URL: str
376
+ The location of a file to be downloaded.
377
+ :param loc: str
378
+ The place the file will be downloaded.
379
+ :param fname: str
380
+ The name the file will have once it is downloaded.
381
+ :param hash: str
382
+ A known hash (checksum) of the file. Will be used to verify the download or check if an existing file needs to
383
+ be updated.
384
+ :return:
385
+ """
386
+ if loc is None :
387
+ loc = os .getcwd ()
388
+ if os .path .isfile (str (loc ) + '/' + fname ) is False :
389
+ fname_loc = pooch .retrieve (url = URL , known_hash = hash , fname = fname , path = loc )
390
+ else :
391
+ fname_loc = str (loc ) + '/' + fname
392
+
393
+ return fname_loc
357
394
358
395
def get_args ():
359
396
@@ -378,16 +415,22 @@ def get_args():
378
415
# Download some FISM2 data for the time period stated by the user.
379
416
380
417
args = get_args ()
418
+ dateStart = args .start
419
+ dateEnd = args .end
420
+ binning_scheme = args .binning
421
+
422
+ if binning_scheme == 'HFG' or binning_scheme == 'SOLOMON' or binning_scheme == 'Solomon' or binning_scheme == 'solomon' :
423
+ # SOLOMON (STAN BANDS; b23)
424
+ fism2_out_23 = getFism2 (dateStart , dateEnd , 'FISM2S' , downloadDir = None )
425
+ fism2_file_23 , fism2_data_23 = rebin (fism2_out_23 , binning_scheme = binning_scheme )
426
+ else :
427
+ fism2_out_raw = getFism2 (dateStart , dateEnd , 'FISM2' , downloadDir = None )
428
+ if binning_scheme == 'NEUVAC' or binning_scheme == 'Neuvac' or binning_scheme == 'neuvac' :
429
+ # NEUVAC BINS (b59)
430
+ fism2_file_59 , fism2_data_59 = rebin (fism2_out_raw , binning_scheme = binning_scheme , zero = True )
431
+ else :
432
+ # EUVAC BINS (b37)
433
+ fism2_file_37 , fism2_data_37 = rebin (fism2_out_raw , binning_scheme = binning_scheme , zero = True )
381
434
382
- # fismOut = getFism(dateStart, dateEnd)
383
- # rebinnedFismFile, rebinnedFismData = rebin(fismOut, binning_scheme='EUVAC')
384
- # rebinnedFismFile_N, rebinnedFismData_N = rebin(fismOut, binning_scheme='NEUVAC')
385
-
386
- fismOut_S = getFism (dateStart , dateEnd , saveLoc , stanBands = True )
387
- rebinnedFismFile_S , rebinnedFismData_S = rebin (fismOut_S , saveLoc , binning_scheme = 'SOLOMON' )
388
-
389
- fismOut_N = getFism (dateStart , dateEnd , saveLoc , stanBands = False )
390
- rebinnedFismFile_N , rebinnedFismData_N = rebin (fismOut_N , saveLoc , binning_scheme = 'NEUVAC' )
391
-
392
- fismOut_E = getFism (dateStart , dateEnd , saveLoc , stanBands = True )
393
- rebinnedFismFile_E , rebinnedFismData_E = rebin (fismOut_E , saveLoc , binning_scheme = 'EUVAC' )
435
+ # Exit with a zero error code:
436
+ sys .exit (0 )
0 commit comments