Creating spectroscopic look-up tables
ArchNEMESIS includes functionality to create line-by-line or correlated-k look-up tables compatible with both archNEMESIS and NEMESIS. These programs are based on the spectroscopic calculations made with the LineData class, so we also encourage the users to also explore this tutorial to better understand how the calculations are performed.
In this notebook, we provide some examples showing how archNEMESIS can be used to create these look-up tables.
[1]:
import archnemesis as ans
import matplotlib.pyplot as plt
import numpy as np
from archnemesis.Data.path_data import archnemesis_path
1. Creating a line-by-line look-up table
We are going to create a look-up table for the main isotope of CO (ID=5, ISO=1) using spectroscopic line data from HITRAN2024, together with the partition sums from TIPS2025.
The main function for creating these look-up tables is called calc_lbltable() and is stored in the Spectroscopy_0.py file. A description of the input parameters is defined below:
[2]:
help(ans.calc_lbltable)
Help on function calc_lbltable in module archnemesis.Spectroscopy_0:
calc_lbltable(outname, gasID, isoID, npress, p0, pn, ntemp, t0, tn, ispace, nwave, wavemin, delwave, iproc, wn_calc_window, wn_approx_window, self_frac, line_database, pf_database='/home/alday/Documents/Projects/archnemesis-dist/archnemesis/Data/partition_functions/tips2025.h5', cont_database=None, include_pressure_shift=True, n_chunks=1, n_cores=1)
Calculate a line-by-line look-up table for a given gas
at specified pressure and temperature levels
Input parameters
-----------------
@param gasID: int
Nemesis gas identifier
@param isoID: int
Nemesis isotopologue identifier
@param npress: int
Number of pressure levels
@param p0: float
Minimum pressure level (atm)
@param pn: float
Maximum pressure level (atm)
@param ntemp: int
Number of temperature levels
@param t0: float
Minimum temperature level (K)
@param tn: float
Maximum temperature level (K)
@param ispace: int
Spectral unit (0 - wavenumbers (cm-1); 1 - wavelength (um))
@param nwave: int
Number of wavenumber/wavelength points
@param wavemin: float
Minimum wavenumber/wavelength (cm-1/um)
@param delwave: float
Wavenumber/wavelength step (cm-1/um)
@param iproc: int
Lineshape identifier (see possible cases in SpectroscopicLineProfileEnum)
@param wn_calc_window: float
Wavenumber window for lineshape calculation (cm-1)
@param wn_calc_window: float
Wavenumber window for lineshape wing approximation (cm-1)
@param self_frac: float
Self-broadening fraction (0 - complete self-broadening, 1 - complete air broadening)
@param line_database: str
Path to archnemesis spectroscopic database to use ('HITRAN','GEISA', etc.)
@param pf_database: str
Path to archnemesis partition function database (default = TIPS2025)
@param cont_database: str
Path to archnemesis pseudo-continuum database (default = None). If None, it is assumed
it is the same as the line_database
@param include_pressure_shift: bool
If True, include pressure shift in the waveumber of the lines
@param n_chunks: int
Number of chunks to split the wavenumber grid into for the calculations (default = 1, i.e. no splitting)
@param n_cores: int
Number of cores to use in the calculations (if >1, parallel processes are used. maximum value is n_chunks)
The calculations of the line-by-line cross sections include some flags that may be turned on or off for optimisation of the calculations.
The parameter n_chunks defines the number of chunks into which the spectral range is divided to perform the calculations, which may or not be calculated in parallel. In some occassions, if the spectral range we are defining for the calculations is too wide, the arrays may become too large and may require a lot of memory. Splitting the calculations into smaller chunks alleviates this requirement.
In some cases, it may be desirable to split the spectral range into n_chunks, but then perform the calculation of each chunk in parallel, to speed up the calculations. This is defined by the parameter n_cores, which defines the number of parallel processes that may be performed. The parameter n_cores must therefore be between 1 and n_chunks.
[5]:
#lbl-table inputs
################################################################################
#CO
gasID = 5 ; isoID = 1
outname = "example_co"
npress = 2 ; p0 = 1.0e-6 ; pn = 1.0e-2
ntemp = 2 ; t0 = 130. ; tn = 250.
ispace = 0 #wavenumber (cm-1)
vmin = 1900. ; delv = 0.001 ; nwave = 500001
iproc = 0 #Voigt lineshape
wn_calc_window = 25. #Caclculation window (cm-1)
wn_approx_window = 250. #Caclculation window (cm-1)
self_frac = 0.5 #fraction of self-broadening
line_base = archnemesis_path()+"/tests/files/linedata/CO_1_ambient_AIR.h5"
pf_base = archnemesis_path()+'/archnemesis/Data/partition_functions/tips2025.h5'
#Performing calculations
###################################################################################
#Calculating lbl-table
ans.calc_lbltable(outname, #Name of the output .lta file
gasID,isoID, #Gas information
npress,p0,pn, #Pressure grid
ntemp,t0,tn, #Temperature grid
ispace,nwave,vmin,delv, #Wavenumber grid
iproc, #Lineshape identifier
wn_calc_window, #Wavenumber calculation window (cm-1)
wn_approx_window, #Wavenumber window at which an approximation for the wings is applied (cm-1)
self_frac, #Self-broadening fraction
line_base, #Database
pf_database=pf_base, #Partition function database (default = TIPS2025)
cont_database=None, #Pseudo-continuum database (If not None, it will use the same as the line_database)
include_pressure_shift=True, #Flag to include pressure shift in the waveumbers
n_chunks = 2,
n_cores = 1,
)
INFO :: read_tables :: Spectroscopy_0.py-1465 :: Reading tables
INFO :: read_tables :: Spectroscopy_0.py-1472 :: RUNTIME calculation is loading desired wavenumber range from databases
INFO :: read_tables :: Spectroscopy_0.py-1478 :: Reading table self.LOCATION_LD[igas]='/home/alday/Documents/Projects/archnemesis-dist/tests/files/linedata/CO_1_ambient_AIR.h5' wavemin=1900.0 wavemax=2400.0
INFO :: calc_lbltable_chunk :: Spectroscopy_0.py-3312 :: Calculating absorption cross-sections for wavenumber range 1900.00 - 2150.00 cm-1
INFO :: calc_lbltable_chunk :: Spectroscopy_0.py-3316 :: (1/2) Pressure level 1.00e-06 atm
INFO :: calc_klbl_online :: Spectroscopy_0.py-2111 :: Gas 5, Isotope 1 - Calculating line-by-line cross sections at runtime...
INFO :: calc_lbltable_chunk :: Spectroscopy_0.py-3316 :: (2/2) Pressure level 1.00e-02 atm
INFO :: calc_klbl_online :: Spectroscopy_0.py-2111 :: Gas 5, Isotope 1 - Calculating line-by-line cross sections at runtime...
INFO :: calc_lbltable_chunk :: Spectroscopy_0.py-3312 :: Calculating absorption cross-sections for wavenumber range 2150.00 - 2400.00 cm-1
INFO :: calc_lbltable_chunk :: Spectroscopy_0.py-3316 :: (1/2) Pressure level 1.00e-06 atm
INFO :: calc_klbl_online :: Spectroscopy_0.py-2111 :: Gas 5, Isotope 1 - Calculating line-by-line cross sections at runtime...
INFO :: calc_lbltable_chunk :: Spectroscopy_0.py-3316 :: (2/2) Pressure level 1.00e-02 atm
INFO :: calc_klbl_online :: Spectroscopy_0.py-2111 :: Gas 5, Isotope 1 - Calculating line-by-line cross sections at runtime...
[6]:
#Reading the table
#Initialising spectroscopy class with ILBL = 2 (line-by-line)
Spectroscopy = ans.Spectroscopy_0(ILBL=2)
Spectroscopy.NGAS = 1 #We define two gases in the class
Spectroscopy.LOCATION = [outname+'.lta']
#Reading the header information
Spectroscopy.read_header()
#Printing summary information
Spectroscopy.summary_info()
WARNING :: read_header :: Spectroscopy_0.py-1279 :: self.NGAS=1 self.LOCATION=PathRedirectList(['example_co.lta'], redirects = {})
INFO :: summary_info :: Spectroscopy_0.py-459 ::
#===== SUMMARY =====#
Spectroscopy_0 instance at memory location 138099509459600
Calculation type ILBL :: (<SpectralCalculationModeEnum.LINE_BY_LINE_TABLES: 2>, ' (line-by-line)')
Number of radiatively-active gaseous species :: 1
Gaseous species :: ['CO (1)']
Number of spectral points :: 500001
Wavelength range :: (np.float64(1900.0), '-', np.float64(2400.0))
Step size :: 0.0009999999999763531
Number of temperature levels :: 2
Temperature range :: (np.float32(130.0), '-', np.float32(250.0))
Number of pressure levels :: 2
Pressure range :: (np.float32(1e-06), '-', np.float32(0.01))
#===================#
[7]:
#Reading the information in the lbl-tables in a specified spectral range
Spectroscopy.read_tables()
fig,(ax) = plt.subplots(Spectroscopy.NGAS,1,figsize=(12,5),sharex=True)
ipress = 0 ; itemp = 0
ax.plot(Spectroscopy.WAVE,Spectroscopy.K[:,ipress,itemp,0],label='Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ipress = 0 ; itemp = -1
ax.plot(Spectroscopy.WAVE,Spectroscopy.K[:,ipress,itemp,0],label='Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ipress = -1 ; itemp = 0
ax.plot(Spectroscopy.WAVE,Spectroscopy.K[:,ipress,itemp,0],label='Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ipress = -1 ; itemp = -1
ax.plot(Spectroscopy.WAVE,Spectroscopy.K[:,ipress,itemp,0],label='Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ax.set_yscale('log')
ax.set_ylabel('Absorption cross section (cm$^2$)')
ax.legend()
ax.grid()
ax.set_facecolor('lightgray')
ax.set_xlabel('Wavenumber (cm$^{-1}$)')
plt.tight_layout()
INFO :: read_tables :: Spectroscopy_0.py-1465 :: Reading tables
INFO :: read_tables :: Spectroscopy_0.py-1522 :: Reading table self.LOCATION[igas]='example_co.lta' wavemin=0.0 wavemax=10000000000.0
3. Comparing both look-up tables
In this part, we are just going to show a comparison of the line-by-line and correlated-k look-up tables to see how they look.
[12]:
#Reading the table
#Initialising spectroscopy class with ILBL = 2 (line-by-line)
Spectroscopy_LBL = ans.Spectroscopy_0(ILBL=2)
Spectroscopy_LBL.NGAS = 1 #We define two gases in the class
Spectroscopy_LBL.LOCATION = [outname+'.lta']
#Reading the header information
Spectroscopy_LBL.read_header()
#Printing summary information
Spectroscopy_LBL.summary_info()
WARNING :: read_header :: Spectroscopy_0.py-1279 :: self.NGAS=1 self.LOCATION=PathRedirectList(['example_co.lta'], redirects = {})
INFO :: summary_info :: Spectroscopy_0.py-459 ::
#===== SUMMARY =====#
Spectroscopy_0 instance at memory location 138099510705936
Calculation type ILBL :: (<SpectralCalculationModeEnum.LINE_BY_LINE_TABLES: 2>, ' (line-by-line)')
Number of radiatively-active gaseous species :: 1
Gaseous species :: ['CO (1)']
Number of spectral points :: 500001
Wavelength range :: (np.float64(1900.0), '-', np.float64(2400.0))
Step size :: 0.0009999999999763531
Number of temperature levels :: 2
Temperature range :: (np.float32(130.0), '-', np.float32(250.0))
Number of pressure levels :: 2
Pressure range :: (np.float32(1e-06), '-', np.float32(0.01))
#===================#
[13]:
#Reading the information in the lbl-tables in a specified spectral range
Spectroscopy.read_tables()
Spectroscopy_LBL.read_tables()
fig,(ax) = plt.subplots(Spectroscopy.NGAS,1,figsize=(12,5),sharex=True)
c = "tab:blue"
ipress = 0 ; itemp = 0
k = np.matmul(Spectroscopy.K[:,:,ipress,itemp,0],Spectroscopy.DELG)
ax.plot(Spectroscopy.WAVE,k,label="Correlated-k approximation",c=c)
ax.plot(Spectroscopy_LBL.WAVE,Spectroscopy_LBL.K[:,ipress,itemp,0],linestyle="--",c=c,linewidth=0.5,label="Line-by-line calculations")
ax.set_title('Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ax.set_yscale('log')
ax.set_ylabel('Absorption cross section (cm$^2$)')
ax.legend()
ax.grid()
ax.set_facecolor('lightgray')
ax.set_xlabel('Wavenumber (cm$^{-1}$)')
plt.tight_layout()
fig,(ax) = plt.subplots(Spectroscopy.NGAS,1,figsize=(12,5),sharex=True)
c = "tab:orange"
ipress = 0 ; itemp = -1
k = np.matmul(Spectroscopy.K[:,:,ipress,itemp,0],Spectroscopy.DELG)
ax.plot(Spectroscopy.WAVE,k,label="Correlated-k approximation",c=c)
ax.plot(Spectroscopy_LBL.WAVE,Spectroscopy_LBL.K[:,ipress,itemp,0],linestyle="--",c=c,linewidth=0.5,label="Line-by-line calculations")
ax.set_title('Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ax.set_yscale('log')
ax.set_ylabel('Absorption cross section (cm$^2$)')
ax.legend()
ax.grid()
ax.set_facecolor('lightgray')
ax.set_xlabel('Wavenumber (cm$^{-1}$)')
plt.tight_layout()
fig,(ax) = plt.subplots(Spectroscopy.NGAS,1,figsize=(12,5),sharex=True)
c = "tab:green"
ipress = -1 ; itemp = 0
k = np.matmul(Spectroscopy.K[:,:,ipress,itemp,0],Spectroscopy.DELG)
ax.plot(Spectroscopy.WAVE,k,label="Correlated-k approximation",c=c)
ax.plot(Spectroscopy_LBL.WAVE,Spectroscopy_LBL.K[:,ipress,itemp,0],linestyle="--",c=c,linewidth=0.5,label="Line-by-line calculations")
ax.set_title('Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ax.set_yscale('log')
ax.set_ylabel('Absorption cross section (cm$^2$)')
ax.legend()
ax.grid()
ax.set_facecolor('lightgray')
ax.set_xlabel('Wavenumber (cm$^{-1}$)')
plt.tight_layout()
fig,(ax) = plt.subplots(Spectroscopy.NGAS,1,figsize=(12,5),sharex=True)
c = "tab:red"
ipress = -1 ; itemp = -1
k = np.matmul(Spectroscopy.K[:,:,ipress,itemp,0],Spectroscopy.DELG)
ax.plot(Spectroscopy.WAVE,k,label="Correlated-k approximation",c=c)
ax.plot(Spectroscopy_LBL.WAVE,Spectroscopy_LBL.K[:,ipress,itemp,0],linestyle="--",c=c,linewidth=0.5,label="Line-by-line calculations")
ax.set_title('Pressure :: '+str(Spectroscopy.PRESS[ipress])+' atm - Temperature :: '+str(Spectroscopy.TEMP[itemp])+' K')
ax.set_yscale('log')
ax.set_ylabel('Absorption cross section (cm$^2$)')
ax.legend()
ax.grid()
ax.set_facecolor('lightgray')
ax.set_xlabel('Wavenumber (cm$^{-1}$)')
plt.tight_layout()
INFO :: read_tables :: Spectroscopy_0.py-1465 :: Reading tables
INFO :: read_tables :: Spectroscopy_0.py-1513 :: Reading table self.LOCATION[igas]='example_co.kta' wavemin=0.0 wavemax=10000000000.0
INFO :: read_tables :: Spectroscopy_0.py-1465 :: Reading tables
INFO :: read_tables :: Spectroscopy_0.py-1522 :: Reading table self.LOCATION[igas]='example_co.lta' wavemin=0.0 wavemax=10000000000.0