Absorption cross sections

Lines

"""
This file will showcase how you can load line data from arts-cat-data into the
workspace and do the required setups to perform a simple forward calculations
using this data.
"""

import os

import matplotlib.pyplot as plt
import numpy as np
import pyarts3 as pyarts

# Download catalogs
pyarts.data.download()

# Initialize ARTS
ws = pyarts.workspace.Workspace()

"""
Set ws.abs_species to the species tags that you wish to use.  Pure line-by-line
calculations sets the species by an expanded form of the AFGL tag system in
ARTS.  This example sets the isotopologue to O16-O16, "O2-66", to include all
lines of said isotopologue into the absorption species tag list

There are many ways to customize this tag.  The following things are examples
of what is possible:
1) Change "O2-66" to "O2".  Consequence: Not just the O2-66 isotopologue is
    included in the absorption species tag list but all molecular oxygen lines
    are included.  Note that "O2-*" is the same thing as "O2".
2) Change "O2-66" into "O2-66,O2-68".  Consequence: Not just O2-66 but also
    the O16-O18 isotopologue is included as the first species tag.
    Note that for forward calculation purposes, writing ["O2-66,O2-68"] or
    ["O2-66", "O2-68"] includes the same lines but changes the layout of
    ws.abs_lines_per_species
3) Change "O2-66" into "O2-66-40e9-120e9".  Consequence:  Still only the O2-66
    isotopologue is considered, but all lines below 40 GHz and all lines above
    120 GHz are considered part of another species tag.  Note that you can
    write your list as ["O2-66-40e9-120e9,O2-66"] to still include all lines,
    though this would in this particular example be a trivial waste of time
4) Change "O2-66" to "O2-Z-66".  Consequence:  You will activate Zeeman effect
    calculations for O2-66.  Warning: Zeeman effect calculations are slow
    because they require several times more calls to core line-by-line
    functions.  Note that one way to speed up these calculations is to combine
    the tags with one of the examples above.  If you write your list of species
    tags as ["O2-Z-66-110e9-120e9", "O2-66"], only absorption lines between
    110 and 120 GHz will be treated as Zeeman-affected, but the rest of the
    lines are still included in the calculations.
5) Change ["O2-66"] to ["O2-66", "H2O-161"].  Consequence: The water
    isotopologue H1-O16-H1 is added to your list of line-by-line absorption
    species.  Note that you can add as many species as you wish.  Also note
    that you are not allowed to write "O2-66,H2O-161" but must separate this
    as written at the top of this listitem.
5) Change ["O2-66"] to ["O2-66", "O2"].  Consequence: All oxygen lines are
    in your list of line-by-line absorption species.  Note that you can
    add as many species as you wish.  Also note that you are allowed to
    write "O2-66,O2".

"""
ws.absorption_speciesSet(species=["O2-66"])

"""

Load the line data of the absorption tags defined in ws.abs_species into the
ARTS line catalog at ws.abs_lines_per_species

The line data file is expected to be named as a line-by-line species tag.  So
for our species tag of "O2-66" above, the reading routine will look for the file
name "lines/O2-66.xml".  The search paths for these files prefer paths relative
to the current working directory above this available elsewhere on the system.
However, "lines/O2-66.xml" does exist in a fully up-to-date version of
arts-cat-data (if you ARTS version is recent enough) so it is likely that this
is the file that is selected for reading.

The resulting ws.abs_lines_per_species will have outer size 1,
len(ws.abs_lines_per_species.value) == 1, after running this file as provided
because the size and shape of ws.abs_species is linked to the size and shape
of ws.abs_lines_per_species.

If you change your tags following one of the examples above, the following
are the consequences:
1) Change "O2-66" to "O2".  ws.abs_lines_per_species will now contain not just
    O2-66 lines but also other isotopologues.  The len of
    ws.abs_lines_per_species will not change.
2) Change "O2-66" into "O2-66,O2-68".  ws.abs_lines_per_species will now
    contain not just O2-66 lines but also lines of O2-68.  If written as
    ["O2-66,O2-68"] the len of ws.abs_lines_per_species will not change.  If
    written as ["O2-66", "O2-68"] the len of ws.abs_lines_per_species is now 2.
3) Change "O2-66" into "O2-66-40e9-120e9".  This will simply limit the number
    of lines in the line catalog.
4) Change "O2-66" to "O2-Z-66".  The line catalog will look exactly the same
    but the calculations inside will change significantly
5) Change ["O2-66"] to ["O2-66", "H2O-161"].  The len of
    ws.abs_lines_per_species is now 2 as the first entry are lines of O2-66 and
    the second entry are lines of H2O-161
6) Change ["O2-66"] to ["O2-66", "O2"].  The len of
    ws.abs_lines_per_species is now 2 as the first entry are lines of O2-66 and
    the second entry are all other lines of O2.  Note therefore that ["O2", "O2-66"]
    also has the len 2, but that all lines are now in the first entry.

"""
ws.absorption_bandsReadSpeciesSplitCatalog(basename="lines/")

"""

You should generally always call this after you are done setting up your
ws.abs_species and ws.abs_lines_per_species.  It will deal with the internal
ARTS setup for you.  Note that the flag use_abs_lookup=1 can be passed to this
method call to set up the agenda for USING the the lookup-table.  Without the
flag, ARTS should be configured correctly either 1) to compute the lookup-table,
or to 2) compute the absorption on-the-fly

"""
ws.propagation_matrix_agendaAuto()

"""
Compute absorption

Now we can use the propagation_matrix_agenda to compute the absorption of O2-66.
We can also use this agenda in more complicated setups that might require
absorption calculations, but that is for other examples

To just execute the agenda we need to still define its both its inputs and the
inputs required to initialize the propagation matrix
"""

ws.jacobian_targets = pyarts.arts.JacobianTargets()
ws.frequency_grid = np.linspace(40e9, 120e9, 11)  # Frequencies between 40 and 120 GHz
ws.ray_path_point  # No particular POSLOS
ws.atmospheric_pointInit()
ws.atmospheric_point.temperature = 295  # At room temperature
ws.atmospheric_point.pressure = 1e5  # At 1 bar
ws.atmospheric_point["O2"] = 0.21  # At 21% atmospheric Oxygen

# Call the agenda with inputs above
ws.propagation_matrix_agendaExecute()

# Plot the absorption of this example
fig, ax = plt.subplots()
ax.semilogy(ws.frequency_grid.value / 1e9, ws.propagation_matrix)
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("Absorption [1/m]")
ax.set_title("O2-66 absorption from examples/arts-cat-data/lines/lines.py")

if "ARTS_HEADLESS" not in os.environ:
    plt.show()

"""
That's it!  You are done and have reached the end of this example.  Everything
below here is just to ensure that ARTS does not break in the future.  It can
be safely ignored
"""
# Save test results
# ws.propagation_matrix.savexml("lines_test_result.xml", type="ascii")

# test that we are still OK
assert np.allclose(
    [
        8.92664373e-06,
        2.88107577e-05,
        1.55470464e-03,
        1.55360514e-03,
        5.57640699e-05,
        2.51926728e-05,
        1.72272988e-05,
        1.43453057e-05,
        1.46251762e-05,
        2.71389841e-05,
        1.97382384e-04,
    ],
    ws.propagation_matrix[:, 0],
), "O2 Absorption has changed"

(Source code, svg, pdf)

_images/1-lines.svg

Cia

"""

This file will showcase how you can load collision-induced absorption (CIA)
data from arts-cat-data into the workspace and do the required setups to
perform a simple forward calculations using this data

Note that this example presumes that you have set the environment variable
ARTS_DATA_PATH to contain a path to a local copy of both arts-cat-data and
arts-xml-data before you import pyarts3.  Please check that this is the case
if the example does not work for you.  You can easily check if this path is
set by adding the following two lines at the top of this pyarts-controlfile:

```
import os
print(os.environ.get("ARTS_DATA_PATH"))
```

"""

import os

import matplotlib.pyplot as plt
import numpy as np
import pyarts3 as pyarts

# Download catalogs
pyarts.data.download()

# Initialize ARTS
ws = pyarts.workspace.Workspace()

"""

Set ws.abs_species to the species tags that you wish to use.  CIA tags are
structured so that the main and secondary species are separated by the word
"CIA"

This example sets up self CIA by molecular oxygen

CIA is not just self-induced but can occur between two different molecules.  In
this case you can change the tag to read something like "O2-CIA-N2", though
you must ensure that there also exists another N2 species in your ws.abs_species
as this is the only way to pass the volume mixing ratio of the atmosphere into
ARTS

"""
ws.absorption_speciesSet(species=["O2-CIA-O2"])

"""

Loads all CIA data from a given folder.  This command expects the file
"cia/O2-CIA-O2.xml" to be found either by relative local path or in the
user-defined search paths.

"""
ws.absorption_cia_dataReadSpeciesSplitCatalog(basename="cia/")


"""

This example does not deal with line-by-line absorption at all.  We must still
ensure that the line-by-line catalog has the correct size and that it has been
set so that our automatic agenda routine can do its work

"""
ws.absorption_bands = {}

"""

You should generally always call this after you are done setting up your
ws.abs_species and ws.abs_lines_per_species.  It will deal with the internal
ARTS setup for you.  Note that the flag use_abs_lookup=1 can be passed to this
method call to set up the agenda for USING the the lookup-table.  Without the
flag, ARTS should be configured correctly to either COMPUTE the lookup-table
or to compute the absorption on-the-fly

In this case, it turns out that the temparature extrapolation is not enough
for a tropical atmosphere scenario below, so we extend it a small bit.  Play
with this "T_extrapolfac" value to see the relevant error message

"""
ws.propagation_matrix_agendaAuto(T_extrapolfac=1)

"""

Compute absorption

Now we can use the propagation_matrix_agenda to compute the absorption of O2-66.
We can also use this agenda in more complicated setups that might require
absorption calculations, but that is for other examples

To just execute the agenda we need to still define its both its inputs and the
inputs required to initialize the propagation matrix

"""

ws.jacobian_targets = pyarts.arts.JacobianTargets()
ws.frequency_grid = pyarts.arts.convert.wavelen2freq(
    np.linspace(6900e-9, 5900e-9, 1001)
)
ws.atmospheric_point.temperature = 295  # At room temperature
ws.atmospheric_point.pressure = 1e5  # At 1 bar
ws.atmospheric_point["O2"] = 0.21  # At 21% atmospheric Oxygen
ws.ray_path_point  # No particular POSLOS

# Call the agenda with inputs above
ws.propagation_matrix_agendaExecute()

# Plot the absorption of this example
fig, ax = plt.subplots()
ax.plot(
    1e9 * pyarts.arts.convert.freq2wavelen(ws.frequency_grid.value),
    ws.propagation_matrix[:, 0],
)
ax.set_xlabel("Wavelength [nm]")
ax.set_ylabel("Absorption [1/m]")
ax.set_title("O2-CIA-O2 absorption from examples/arts-cat-data/cia/cia.py")

if "ARTS_HEADLESS" not in os.environ:
    plt.show()

"""
That's it!  You are done and have reached the end of this example.  Everything
below here is just to ensure that ARTS does not break in the future.  It can
be safely ignored

"""
# Save test results
# ws.propagation_matrix.savexml("cia_test_result.xml", type="ascii")

# test that we are still OK
propagation_matrix_agenda = pyarts.arts.PropmatVector.fromxml("cia_test_result.xml")
assert np.allclose(propagation_matrix_agenda, ws.propagation_matrix), (
    "O2 Absorption has changed"
)

(Source code, svg, pdf)

_images/2-cia.svg

Single species recipe

"""Absorption by a single species"""

import os

import matplotlib.pyplot as plt
import numpy as np
import pyarts3 as pyarts

# Download catalogs
pyarts.data.download()

# %% Select absorption species
species = "O2-66"  # Main isotope of O2

# %% Activate the recipe for this species
#
# See [SingleSpeciesAbsorption](pyarts3.recipe.rst#pyarts3.recipe.SingleSpeciesAbsorption).
absorption = pyarts.recipe.SingleSpeciesAbsorption(species=species)

# %% Select a single temperature, a VMR value, and a range of pressures
atm = pyarts.arts.AtmPoint()
atm.set_species_vmr("O2", 0.2095)
atm.temperature = 273
ps = np.logspace(5, -2, 8)

# %% Select frequency range
line_f0 = 118750348044.712  # Lowest energy absorption line
f = np.linspace(-500e6, 500e6, 1001) + line_f0  # Some range around it

# %% Use the recipe and convert the results to cross-sections
xsec = []
for p in ps:
    atm.pressure = p
    xsec.append(absorption(f, atm) / atm.number_density(species))
xsec = np.array(xsec)

# %% Plot the results
fig, ax = plt.subplots()
ax.semilogy((f - line_f0) / 1e6, xsec.T)
ax.set_xlabel("Frequency offset [MHz]")
ax.set_ylabel("Cross-section [$m^2$]")
ax.set_title("Cross-section of O$_2$ 16-16")
ax.set_ylim(ymin=1e-3 * np.min(xsec))
ax.legend(
    [f"P: $10^{'{'}{round(np.log10(x))}{'}'}$" for x in ps],
    ncols=4,
    loc="lower center",
)

if "ARTS_HEADLESS" not in os.environ:
    plt.show()

# %% Integration test by ensuring some statistics look good
assert np.isclose(6.792977548868407e-28 / xsec.mean(), 1)
assert np.isclose(5.43981642113382e-24 / xsec.sum(), 1)
assert np.isclose(1.3359834491781882e-24 / xsec.max(), 1)
assert np.isclose(2.537911691540087e-26 / xsec.std(), 1)
assert np.isclose(8.236637542411964e-35 / xsec.min(), 1)

(Source code, svg, pdf)

_images/3-single-species-recipe.svg