## 1. Introduction

Radiative transfer is an important aspect in various branches of physics, esp. in the atmospheric sciences, both for Earth and (exo-)planets [

1,

2,

3]. Many radiative transfer models have been developed in recent decades (see [

4], for an early review and

https://en.wikipedia.org/wiki/Atmospheric_radiative_transfer_codes for an up-to-date listing (All links in this paper were checked and active 4 April 2019.)), ranging from simple approximations to complex, often highly optimized codes and usually tailored to a specific spectral domain.

An essential prerequisite for the analysis of data recorded by atmospheric remote sensing instruments as well as for theoretical investigations such as retrieval assessments is a flexible, yet efficient and reliable radiative transfer model (RTM). Furthermore, as the retrieval of atmospheric parameters is in general a nonlinear optimization problem (inverse problem), the retrieval code must be closely connected to the radiative transfer code (forward model).

Radiative transfer depends on the state of the atmosphere (pressure, temperature, composition), the optical properties of the atmospheric constituents (molecules and particles), geometry, and spectral range. Because of the numerous parameters the setup of a radiative transfer calculation can be cumbersome and error prone, and several interfaces have been developed to ease the application. MODO ([

5], see also

http://www.rese.ch/products/modo/) is a “graphical front-end” to the MODTRAN [

6] radiative transfer code, and a Python interface to MODTRAN’s predecessor, the low-resolution band model LOWTRAN, is available at

https://pypi.org/project/lowtran/. Wilson [

7], see also

http://www.py6s.rtwilson.com/, has developed a Python interface to the widely used 6S (Second Simulation of the Satellite Signal in the Solar Spectrum, [

8]) model.

In the infrared and microwave region, line-by-line (lbl) modeling of molecular absorption is mandatory esp. for high spectral resolution. Furthermore, lbl models are required for the setup and verification of fast parameterized models, e.g., band, k-distribution, or exponential sum fitting models [

6,

9,

10]. Moreover, lbl models are ideal for radiative transfer in planetary atmospheres, where fast models often parameterized for Earth-like conditions are not suitable [

11,

12].

An essential input for lbl radiative transfer codes are the molecules’ optical properties compiled in databases such as HITRAN [

13], HITEMP [

14], GEISA [

15], the JPL and CDMS catalogs [

16,

17,

18], or ExoMol [

19] that list line parameters (center frequency, line strength, broadening parameters etc.) for millions to billions of lines of some dozen molecules. Lbl models are computationally challenging because of the large number of spectral lines to be considered and the large number of wavenumber grid points required for adequate sampling. Nevertheless, lbl models are available for almost half a century, e.g., FASCODE or 4A (Fast Atmospheric Signature Code, Automatized Atmospheric Absorption Atlas, [

20,

21]). Thanks to the increased computational power (and because of the increased number of high-resolution Earth and planetary spectra) a variety of codes has been developed since these early efforts, e.g., ARTS (Atmospheric Radiative Transfer Simulator, [

22,

23,

24]), GARLIC (Generic Atmospheric Radiation Line-by-line Code, [

25]), GenLN2 [

26], KOPRA (Karlsruhe Optimized & Precise Radiative transfer Algorithm, [

27]), LblRTM (Line-by-line Radiative Transfer Model, [

28]), LinePak [

29], RFM (Reference Forward Model, [

30]),

$\sigma $-IASI [

31], and VSTAR (Versatile Software for Transfer of Atmospheric Radiation, [

32]).

Several web sites offer an Internet access to lbl databases and/or lbl models: “HITRANonline” [

33] at

http://hitran.org/ allows the downloading of line and other data from the most recent HITRAN version. With the “Information System HITRAN on the Web (HotW)” at

http://hitran.tsu.ru (see also

http://spectra.iao.ru/) several types of spectra can be simulated using HITRAN data. The HITRAN Application Programming Interface (HAPI) [

34] is “a set of routines in Python which aims to provide remote access to functionality and data given by the HITRANonline”. A similar service (visualization etc.) for the GEISA database is offered at

https://cds-espri.ipsl.upmc.fr/geisa/. The “wavelength search engine” SpectraPlot ([

35], see also

http://www.spectraplot.com/) is a “web-based application for simulating spectra of atomic and molecular gases” employing, among others, HITRAN and HITEMP.

http://www.spectralcalc.com/ provides a web interface to the widely used LinePak [

29] model (with extended functionality for subscribed users). Goddard’s Planetary Spectrum Generator (PSG,

https://psg.gsfc.nasa.gov/, Villanueva et al. [

36]) can be used to generate high-resolution spectra of planetary bodies (e.g., planets, moons, comets, exoplanets). Knowledge of the atmospheric transmission is also important for ground-based astronomy observations, and some tools for the correction of telluric absorption are available, e.g., MolecFit ([

37], see also

https://www.uibk.ac.at/eso/software/molecfit.html.en) and TAPAS (Transmissions Atmosphériques Personnalisées Pour l’AStronomie, [

38], online at

http://cds-espri.ipsl.fr/tapas/) (both codes use LblRTM). An early attempt to a “virtual lab” providing a web interface to MIRART (the Fortran 77 predecessor of GARLIC, [

39]) and FASCODE is described in Ernst et al. [

40].

In general, RTMs incl. lbl models work as a kind of “black-box”. Given a set of specifications on spectral range, atmospheric conditions, geometry etc. compiled in an input file (or configuration file etc., e.g., the

`TAPE5` used by LOWTRAN, MODTRAN, and FASCODE), the program is executed reading this input file (and probably some further auxiliary files, e.g., the lbl database) and one or several spectra (transmission, radiance, …) are returned (e.g., saved in file(s)). This approach is clearly advantageous when numerous transmission and/or radiance spectra must be generated, e.g., for the operational analysis of thousands or millions of observed spectra. However, to better understand the physics of radiative transfer, inspection of intermediate variables can provide useful insight. For example, the selection of an appropriate spectral range or measurement geometry is crucial for the design and setup of a new measurement system, and analysis and visualization of line parameters, absorption cross sections and coefficients, layer optical depths, etc. can be useful. Methods and tools have been developed for the “automated” definition of “microwindows”, i.e., spectral intervals optimized for retrieval of temperature or molecular concentrations from thermal emission infrared observations (e.g., [

41,

42,

43,

44,

45,

46]). Despite these and similar efforts interactive and flexible RTMs with an easy access to all kind of intermediate variables (“step-by-step”, similar to a debugger) appear to be useful, also for pedagogical purposes.

Py4CAtS—Python for Computational Atmospheric Spectroscopy—has been developed with this intention. Since its first release in the mid-1990s [

47,

48] Python has become increasingly popular for scientific computing [

49,

50]. Thanks to its numerous extension packages, in particular NumPy/SciPy [

51], the “Python-based ecosystem” ([

52],

https://scipy.org/) is widely considered to be the language of choice in many areas, e.g., atmospheric science and astrophysics [

53,

54,

55].

Originally, the development of Py4CAtS was motivated by the idea of computational steering [

56], i.e., numerically time-consuming tasks such as the lbl evaluation of molecular absorption cross sections are performed by a code (e.g., GARLIC subroutines) written in compiled languages such as Fortran (or C, C++, …) that is made accessible from Python (or another scripting language) using a wrapper such as PyFort [

57] or F2Py [

58]. More precisely, the intention was to call subroutines of GARLIC from Python. Unfortunately porting the wrapper code from one machine to another turned out to be difficult and time-consuming with early versions of PyFort. However, thanks to the increased performance of NumPy a Python implementation of lbl cross sections became feasible, i.e., the interface to GARLIC’s subroutines became less important. Accordingly, the further development of GARLIC and Py4CAtS became largely independent, and Py4CAtS is now a full lbl radiative transfer tool kit delivering absorption cross sections and coefficients, optical depths, transmissions, weighting functions, and radiances.

The paper is organized as follows: After a brief review of the basic facts of lbl infrared and microwave (for brevity simply IR in the following) radiative transfer in the following section some numerical and implementation aspects are discussed in

Section 2.2. Usage of the code as well as some implementation details are presented in

Section 3. The paper continues with a discussion of several aspects in

Section 4 before the final conclusion in

Section 5. Implementation aspects are described in the

Appendix A.

## 3. The Package: Usage and Implementation

In Py4CAtS the individual steps of an IR radiative transfer computation are implemented in a series of modules and functions, see

Figure 1:

All Python source files along with

supplementary files are delivered as a single tarball. Unpacking this file will create four directories with the source files in

`src`, data in

`data`, documentation in

`doc`, and executable scripts in

`bin`. Actually, the files in the

`bin` directory are just links to the scripts in the

`src` directory (e.g.,

`bin/lbl2xs` is a link to

`src/lbl2xs.py`), and in the following subsection it is assumed that the

`bin` directory is included in the shell’s search path for executables (

`PATH` environment variable for Unix-like systems). When Py4CAtS is used inside the IPython interpreter (see

Section 3.2), all functions etc. should have been imported using a statement such as

`%run /home/schreier/py4cats/src/py4cats.py`. Importing Py4CAtS can be easily done automatically by an appropriate modification of the (I)Python configuration file, e.g., by adding this file to the list of files to run at IPython startup. (In IPython 6 this list is defined by

`c.InteractiveShellApp.exec_files` in

`.ipython/profile_default/iypthon_config.py`.)

#### 3.1. Running Py4CAtS in the Classical Console

A typical session in a Unix/Linux environment (console/terminal) is shown in

Figure 2. After creating a new directory, the first step is to extract the relevant lines from the HITRAN or GEISA data base using

`higstract`. (Data are available at

hitran.org and

http://cds-espri.ipsl.upmc.fr/. Py4CAtS can read the original data file as is, i.e., the 160-byte fixed-width format used since HITRAN 2004 or the 100-byte format of the older HITRAN versions. Regarding GEISA, the format of a single record (corresponding to a “physical line”) has changed slightly from version to version, and Py4CAtS parses the file name to select the appropriate reader.) Please note that for this command at least one option is mandatory, i.e., the spectral range (here 50 to

$60\phantom{\rule{0.166667em}{0ex}}{\mathrm{cm}}^{-1}$) or a molecule must be specified (for convenience,

`higstract -m main …` can be used to extract line data for the first seven molecules (

${\mathrm{H}}_{2}\mathrm{O}$,

${\mathrm{CO}}_{2}$, …,

${\mathrm{O}}_{2}$) of HITRAN or GEISA.)

`higstract` saves the data in several tabular ASCII files, one file per molecule. By default, each file has five columns for line position

$\widehat{\nu}$, strengths

S, energy

E, air-broadening parameter

${\gamma}_{\mathrm{L}}^{0}$, and the exponent

n and is named as

`H2O.vSEan` etc., where the extension indicates the columns (further parameters, e.g., for self-broadening, can be requested with the format option

`-f`).

The next step is to compute absorption cross sections according to Equation (

1). Without any option, cross sections are computed for the reference pressure and temperature used in the line parameter database, i.e.,

${p}_{0}=1013.25\phantom{\rule{0.166667em}{0ex}}\mathrm{mb}$ and

${T}_{0}=296\phantom{\rule{0.166667em}{0ex}}\mathrm{K}$ for HITRAN and GEISA. Alternatively, pressure(s) and temperature(s) can be specified with the

`-p` and

`-T` option. For atmospheric applications it is more convenient to read the data from a file (e.g., “midlatitude summer” (mls) data assuming the first three column list altitudes, pressure, and temperature). Again, the cross sections will be saved in files, one per molecule, using NumPy’s pickle format or alternatively ASCII tabular or HITRAN formatted files.

The computation continues to absorption coefficients by summing all cross sections (level-by-level) scaled with the molecular concentrations, cf. Equation (

6), and integrating these to the layer (or delta) optical depth, Equation (

7). Finally, the radiance seen by an observer at ground and by a spaceborne observer (actually an observer at top of atmosphere, ToA) is computed.

In each step of the calculation the results are written to file(s), and read in for the next step. Obviously, this can be quite time-consuming esp. for ASCII files (reading NumPy pickled files is surprisingly fast). In particular, the cross section files can be very large (depending on the spectral region, the size of the spectral interval, the number of molecules, and atmospheric levels, etc.). For many applications some of the intermediate quantities are not of interest, and the file transfer operations can be omitted, e.g., (see the dashed arrows in

Figure 2)

`lbl2ac` compute lbl cross sections and combine to absorption coefficients;

`lbl2od` compute lbl cross sections and absorption coefficients, then integrate to optical depth.

`xs2od` multiply cross sections with densities, sum over molecules, and integrate to optical depth.

In addition to these scripts there are several further Python files that contain functions used by these “main” scripts (e.g., to read or write the data), but can also be executed stand-alone. (Technically, the very first line of all executable scripts is something like

`#!/usr/bin/env python` and the last segment of the module starts with

`if __name__==’__main__’:`.) The scripts

`lines.py`,

`xSection.py`,

`absCo.py`,

`oDepth.py` can be used to read, extract subsets in the spectral or altitude regime, summarize, plot, or reformat the respective data.

`molecules.py` collects properties (e.g., mass) of the IR relevant molecules and the ID numbers used in the HITRAN and GEISA database.

`atmos1D.py` is useful to handle the atmospheric data (

p,

T, VMRs, …). The functions in

`hitran.py` and

`geisa.py` are used by

`higstract.py`, but can be used as a “low-level” interface to the respective dataset. Unit conversions are implemented in the

`cgsUnit.py` module (note that internally Py4CAtS uses cgs units consistently, see

Appendix A.6). Furthermore,

`radiance2radiance.py` and

`radiance2Kelvin.py` allow the conversion of radiance spectra, e.g., wavenumber ⟷ wavelength or to equivalent brightness temperature using the inverse of Planck’s function (

11).

The `lbl2ac`, `lbl2od`, or `xs2od` scripts clearly help to avoid the time-consuming reading and writing of some of the intermediate quantities, however, these scripts turn Py4CAtS back into the “black-box” radiative transfer discussed above.

When executing the series of individual steps, the subsequent steps must re-read the data saved in the previous step(s) from file(s) and check the consistency of the data. For example, the `xs2ac.py` script will test if all cross sections cover the same spectral interval, and if the pressures and temperatures match those in the atmospheric data file. Accordingly, a substantial part of the code is devoted to implement appropriate tests. A prerequisite for these tests is that all relevant attributes are saved in the file along with the spectra; for example, a single cross section is saved along with its pressure, temperature, and wavenumber grid specification.

#### 3.2. Py4CAtS Used within the (I)Python and Jupyter Shell

Access to intermediate quantities is especially useful for visualization. In the (I)Python interpreter, graphics packages such as Matplotlib [

77] allow the plotting of any kind of array. For plotting as well as for the subsequent processing steps it is important to have all attributes of a spectrum available. However, a function returning an array for the spectrum together with its attributes (e.g., with a call such as

`lines_h2o, pRef, tRef = higstract(’/data/hitran/86/lines’, ’H2O’)`) would be cumbersome, and collecting the return values in a dictionary would not significantly improve this. In Py4CAtS this problem is solved by means of subclassed and/or structured NumPy arrays that will be briefly described (in Appendices

Appendix A.4 and

Appendix A.5) after a presentation of the typical workflow illustrated in

Figure 3.

#### 3.2.1. Atmospheric Data

Knowledge of the atmospheric state, i.e., pressure, temperature, and molecular composition as a function of altitude, is indispensable for lbl modeling and radiative transfer. The Py4CAtS scripts called from the console automatically read these data from a file (along with the line data, cross section data, etc.) This is essentially accomplished by the function

`atmRead` of the

`atmos1D.py` module, e.g., the command

`mls = atmRead(’/data/atmos/20/mls.xy’)` returns the data of the “midlatitude summer” atmosphere of the AFGL dataset [

78] regridded to 20 levels (see the very first input labelled

`In[1]:` in

Figure 3).

`atmRead` essentially expects a tabular ascii file with rows corresponding to the atmospheric levels and columns for altitudes, pressure (and/or air density), temperature, and molecular concentrations. Two comment lines are mandatory:

`#what:` followed by the column identifiers and

`#units:` followed by the physical units (e.g., “km”).

`mls` is an example of a structured array (see

Appendix A.5), where the individual profiles can be accessed by their name, e.g.,

`mls[’T’]` or

`mls[’H2O’]`, and the data for a specific level

l are given by the row index, e.g.,

`mls[0]` or

`mls[-1]` for the bottom and top level, respectively. Please note that “rows” and “columns” can be specified in two ways; for example, the BoA temperature is

`mls[’T’][0] == mls[0][’T’]`.

All data are stored internally in cgs units, e.g., pressure is converted to $\mathrm{dyn}/{\mathrm{cm}}^{2}=\mathrm{g}/\mathrm{cm}/{\mathrm{s}}^{2}$. Molecular concentrations are stored as number densities (i.e., if the data file contains volume mixing ratios (VMR), these are converted to densities by multiplication with the air number density $n=p/\left(kT\right)$). VMR can be obtained with the `vmr` function, e.g., `vmr(mls)`. Further convenience functions of the `atmos1D.py` module include `vcd` to integrate the (molecular and air) number densities along a vertical path through the atmosphere to the “Vertical Column Density” ($N=\int n\left(z\right)\phantom{\rule{0.166667em}{0ex}}\mathrm{d}z$ with default limits given by top and bottom of atmosphere, ${z}_{\mathrm{ToA}}$ and ${z}_{\mathrm{BoA}}$), `cmr` for the “Column Mixing Ratio”, i.e., the ratio of the molecular VCDs and the air VCD.

The `atmRegrid (mls, zNew, …)` function allows interpolation of the atmospheric data to a new altitude grid. If the limits of the new grid exceed the old limits, `atmRegrid` prints a warning. The actual results are depending on the chosen interpolation scheme; in case of the default linear interpolation NumPy’s `interp` function simply repeats the very first or last value at BoA or ToA, respectively.

Data from different files can be combined with the `atmMerge` function, e.g.,

`combiAtm = atmMerge (mlw, traceGases)` (here the second data set “`traceGases`” is likely comprising only concentration profiles that can be read with the `vmrRead` function.) If the two data sets are given on different altitude grids, profiles from the second set are interpolated to the grid of the first set. If a profile is defined in both data, then by default the second is ignored unless the flag `replace` is “switched on”.

The data can be easily visualized using the standard Matplotlib [

77] functions (e.g.,

`plot(mls[’T’],mls[’z’])`), but for convenience Py4CAtS provides a function

`atmPlot` that expects a single structured array or a list thereof as first argument. (For some general remarks on visualization see

Appendix A.2.) For example,

`atmPlot(mls)` shows temperature vs. altitude, and

`atmPlot([mls,mlw], ’O3’, ’mb’)` compares ozone profiles of the midlatitude summer and winter atmospheres with pressure as vertical axis. Finally, the function

`atmSave` can be used to write the data to file (see also

Appendix A.1).

#### 3.2.2. Line Data

For the following presentation assume that you are interested in carbon monoxide (CO) retrievals from the thermal IR observations as done by IASI (e.g., [

79,

80]) or AIRS [

81].

`llDict = higstract(’/data/hitran/2012/lines’, (2100,2150))` returns about 26 thousand lines in a dictionary of 22 line lists, more precisely structured arrays, one array for each molecule with transitions in this spectral range around the CO fundamental band around $2143\phantom{\rule{0.166667em}{0ex}}{\mathrm{cm}}^{-1}$. Actually, these arrays are subclassed NumPy arrays (with type `lineArray`) holding some extra information as attributes, e.g., `llDict[’CO’].p` and `llDict[’CO’].t` gives HITRAN’s reference pressure $1013.25\times {10}^{3}\phantom{\rule{0.166667em}{0ex}}\mathrm{dyn}/{\mathrm{cm}}^{2}$ and temperature $296.0\phantom{\rule{0.166667em}{0ex}}\mathrm{K}$, respectively. If lines of a single molecule are extracted (e.g., `higstract(’/data/geisa/2003/lines’, molecule=’CO’)`), a single `lineArray` is returned. The option `molecule=’main’` returns the line parameters of the “main” molecules only, i.e., the molecules with ID numbers $\le 7$ of the HITRAN and GEISA database.

Similar to the atmospheric data, the line data can be plotted using Matplotlib’s functions, or, more conveniently, with Py4CAtS’ function, e.g.,

`atlas(llDict)`. The

`atlas` function (named after Park et al. [

82]) can be used with a single

`lineArray` or a list or dictionary of

`lineArray`s and displays line strength vs. position by default.

#### 3.2.3. Cross Sections

In the next step the line parameters can be used to compute molecular cross sections, see the

`In[3]:` block in

Figure 3.

`xs = lbl2xs(llDict[’CO’])` returns the cross section of CO in the spectral range around

$2140\phantom{\rule{0.166667em}{0ex}}{\mathrm{cm}}^{-1}$ for the database (here HITRAN) reference pressure and temperature. A single cross section is stored in a subclassed NumPy array (i.e.,

`type(xs) → xsArray`) with “attributes” such as lower and upper wavenumber bound, pressure, temperature, and molecule stored in further items, e.g.,

`xs.x` or

`xs.p`. As a default, the Voigt profile is considered. Please note that the cross sections are evaluated on a uniform wavenumber grid (with spacing defined by the mean line width, see

Section 2.2.1), so it is sufficient (and more memory efficient) to save the very first and very last grid point only.

Cross sections for different pressures and temperatures are obtained by specifying the second or third function argument of

`lbl2xs`. Please note that the data type returned by

`lbl2xs` in these examples is different, i.e., the type is depending on the number of

$p,T$ pairs and the type of the line data (a single or a dictionary of

`lineArray`, essentially the number of molecules). In the very first example (CO and one

$p,T$) in

Figure 3 a single subclassed NumPy array

`xsArray` is returned, whereas a list of

`xsArray`’s is returned for a list of

$p,T$ pairs and a single molecule. Finally, the last example gives a dictionary of lists of

`xsArray`’s, each list for a single molecule and a dictionary entry for each molecule.

The

`xSection.py` module has a function

`xsPlot` to visualize the cross sections, e.g.,

`xsPlot(xss)`. This function works recursively (cf.

Appendix A.3), i.e., it can be called with a single

`xsArray`, a list thereof, or a dictionary of (lists of)

`xsArray`’s.

Figure 4 demonstrates the combined use of the

`atlas` and

`xsPlot` functions exploiting Matplotlib’s function

`twinx` to share the common wavenumber axis. The functions

`xsInfo` and

`xsSave` can be used with a single cross section array, a list of cross sections, or a dictionary of lists to summarize the cross sections’ properties or to write the data to file(s); Reading cross section data from file(s) is possible with the

`xsRead` function.

#### 3.2.4. Absorption Coefficients

Given cross sections of some molecules on a set of

$p,T$ levels along with the atmospheric data, in particular the molecular number densities, the absorption coefficient (

8) for all levels are generated with

`acList = xs2ac (mls, xssDict)`. The list contains a spectrum for each atmospheric

$p,T$ level, where each spectrum is stored in a subclassed NumPy array:

`type(acList[0]) → acArray` similar to the cross sections, i.e., with attributes stored as, e.g.,

`ac.x` and

`ac.z` for the wavenumber range and altitude, respectively. Please note that the number of levels in the atmospheric data set (here

`mls`) and the lengths of the cross section lists in the

`xssDict` dictionary must be identical. Furthermore, all molecules with cross section data must be contained in the atmospheric data (but there can be some “unused” molecules in the atmospheric data set).

The absorption coefficients can be plotted with the standard Matplotlib functions, but Py4CAtS also has a function to make this easier: `acPlot(acList)`. The function `acInfo(acList)` prints essential information about the absorption coefficients (Actually it is a loop calling the corresponding `info` method of `acArray`, i.e., `for ac in acList: ac.info()`).

The data can be saved to file (tabular ascii) with the standard NumPy `savetxt` or Py4CAtS’ `awrite` function. The `acSave` function automatically saves the absorption coefficients along with the atmospheric data, and the `acRead` function allows reading of the data (incl. the associated atmosphere) back from file, e.g., `absCo = acRead(acFile)`. Both `acSave` and `acRead` also support HITRAN formatted files or Python/NumPy’s internal pickle format.

#### 3.2.5. Optical Depths

The next step is to integrate the absorption coefficients along the (vertical) path through the atmosphere using the function

`ac2dod` (see

`In[4]` in

Figure 3). Similar to cross sections and absorption coefficients this returns a list of

`(nLevels-1)` subclassed NumPy arrays

`odArray`, where each list member is essentially the delta / differential / layer optical depth spectrum along with its attributes lower and upper altitudes, pressures, and temperatures (and the wavenumber interval, too).

The optical depths instances can be combined by addition or subtraction, e.g., the delta optical depths of the first (bottom) two layers can be added by

`dodList[0]+dodList[1]` (see

Figure 5). The main purpose of the

`__add__` special method is to combine the optical depths from neighboring layers, but it can also be used to sum the optical depths of different molecules in one layer.

Please note that the `ac2dod` function (and the `xs2dod`, `lbl2od`, … function discussed below) only return delta optical depths (dod), further functions can be used to convert to cumulative or total optical depth. Summation of all layer optical depths with the `dod2tod` function delivers the total optical depth and `dod2cod` returns the (ac)cumulated optical depth. By default, the accumulation is starting with the very first layer (usually at BoA) and the very last element of the generated list should be the total optical depth, whereas `cod = dod2cod(dodList,True)` starts accumulating with the very last layer and the very first `cod[0]` corresponds to the total optical depth.

A quick-look of optical depth(s) can be generated with the `odPlot` function, and the data (incl. the attributes) can be saved to file using `odSave` using ASCII, netcdf, or pickle format. Later, the optical depth data can be read from file into a (new) IPython session with `oDepth = odRead (odFile)`.

The `oDepthOne` function returns the distance ${s}_{1}$ from the (uplooking or downlooking) observer to the point, where the optical depth is one, $\tau (\nu ,{s}_{1})=1.0$, corresponding to a transmission that has decreased to $\mathcal{T}=1/e$. This distance should roughly correspond to the location of the weighting function maximum.

#### 3.2.6. Weighting Functions

`wgtFct = ac2wf(acList[, angle, zObs])` computes weighting functions according to Equation (

13) for an observer at altitude

`zObs` looking in direction

`angle` (default

${180}^{\circ}$) and returns a subclassed 2D NumPy array of type

`wfArray` (with

`wgtFct.shape = (len(vGrid), len(sGrid))`). By default, the observer is assumed to be at ToA or BoA for viewing angles larger or smaller than

${90}^{\circ}$. The attributes define the wavenumber interval, path distance grid

`sGrid` (relative to the observer (in cm), i.e., from ToA to BoA in case of a downlooking nadir view), observer altitude, and viewing angle. Optionally

`ac2wf` also allows the treatment of finite field-of-view effects with an extra argument

`FoV` to set the type and width (HWHM, in degree) (e.g.,

`FoV=’Gauss 7.5’`).

Alternatively, given the delta/layer optical depths the weighting functions can be approximated by finite differencing using the

`dod2wf(dodList,zObs,angle)` function, but starting from the absorption coefficient is much more reliable. For weighting functions of a horizontal path (zenith angle

$\theta ={90}^{\circ}$) see the first remark in

Section 4.2.

The function

`wfSave(wgtFct, …)` and

`wfRead(wfFile, …)` can be used to save and read the data, and

`wfPlot(wgtFct[, wavenumber, …])` provides a simple visualization tool. If (a) specific wavenumber(s) are given, a 2D plot is presented, otherwise a contour plot is generated. An example of weighting functions for microwave temperature sounding in the region of the oxygen rotation band is given in

Figure 6.

#### 3.2.7. Radiance/Intensity

The

`dod2ri` function evaluates the Schwarzschild integral (

9) and returns the radiance or intensity, again a subclassed NumPy array

`riArray` with attributes for wavenumber interval, altitude, pressure, and temperature minimum/maximum, observer zenith angle, and background temperature, see the last block in

Figure 3. Without optional arguments, the radiance seen by an uplooking observer at the surface (BoA) is computed, whereas

`dod2ri (dodList, 180.0, mls[’T’][0])` gives the radiance for a nadir-viewing observer looking down from ToA with an angle of

${180.0}^{\circ}$ (relative to the zenith angle) to Earth; the third argument specifies the surface temperature

${T}_{\mathrm{b}}$ (here the BoA temperature of the midlatitude summer (mls) atmosphere) that is used to evaluate a Planck background contribution in (

9) with

${I}_{\mathrm{b}}\left(\nu \right)=B(\nu ,{T}_{\mathrm{b}})$.

Please note that

`dod2ri` does not have any argument to specify the observer altitude, i.e., it computes the radiance at BoA or ToA for an angle smaller or larger than

${90}^{\circ}$ (a horizontal path with angle

${90}^{\circ}$ is not implemented). If you want to model the radiance, say, for an airborne observer downlooking from

$10\phantom{\rule{0.166667em}{0ex}}\mathrm{km}$ and have a list of layer optical depths for an atmosphere with a uniform altitude grid of

$1\phantom{\rule{0.166667em}{0ex}}\mathrm{km}$ (hence layer thickness

$1\phantom{\rule{0.166667em}{0ex}}\mathrm{km}$), supply a list of the first ten optical depths only, i.e.,

`dod2ri(dodList[:10],180)`. (See also the remark on limitations in

Section 4.6.)

A further Boolean optional argument can be given to switch to the “B exponential-in

$\tau $” approximation instead of the default “B linear-in

$\tau $”, see

Section 2.2.2.

To plot and save the radiance spectrum (along with the wavenumber grid) in a file use the

`riPlot` and

`riSave` functions, respectively. To convolve the radiance spectrum

I with a spectral response function according to (

12), a special method

`convolve` has been implemented, e.g.,

`radBox1 = radiance.convolve()` uses the default “box” with a half width

$1.0\phantom{\rule{0.166667em}{0ex}}{\mathrm{cm}}^{-1}$. Likewise,

`radGauss2 = radiance.convolve(2.0,’G’)` uses a Gaussian response function with HWHM

$2.0\phantom{\rule{0.166667em}{0ex}}{\mathrm{cm}}^{-1}$. The

`convolve` special method is also available for the

`odArray` and

`wfArray` classes (in the first case the convolution operates on the corresponding transmission).

#### 3.2.8. Shortcuts

If some of the intermediate quantities are not required, it is possible to go directly from line and atmospheric data to optical depths using `dodList = lbl2od (mls, llDict)`. Likewise, cross sections and absorption coefficients can be bypassed with the `lbl2ac` and `xs2dod` functions. Please note that `lbl2ac` and `lbl2od` “inherit” most options accepted by `lbl2xs` or `xs2ac`.

## 5. Conclusions

Py4CAtS, a Python package developed for “computational atmospheric spectroscopy”, has been presented and its usage in a Unix-like console or within the (I)Python interpreter/notebook (recommended) has been demonstrated. Starting with line data from HITRAN or GEISA and atmospheric data, the scripts and functions allow the computation of cross sections, absorption coefficients, optical depths, weighting functions, and radiances. In particular, they also provide easy access to all intermediate quantities esp. for visualization.

When the development of Py4CAtS started, lbl modeling with Python appeared to be quite ambitious. However, thanks to NumPy and its dramatic performance improvements, atmospheric radiative transfer modeling with Python can now be done with reasonable speed. In fact, our recent benchmark tests of Voigt and complex error function algorithms indicated that NumPy implementations are not significantly slower than Fortran implementations [

102].

As emphasized above, the intention for Py4CAtS has not been a highly efficient and accurate lbl radiative transfer code, and the package is not considered to be a competitor for the codes mentioned in the introduction. Nevertheless, despite the speed limitations of interpreters such as Python and the omission of limb geometry, continuum, or scattering Py4CAtS is believed to be attractive because of the flexibility and ease of use. The future development of Py4CAtS will certainly be driven by further optimizations and extensions of its functionality.