Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/GRIPS-code/pyLBL

Python line-by-line radiative transfer model
https://github.com/GRIPS-code/pyLBL

Last synced: about 2 months ago
JSON representation

Python line-by-line radiative transfer model

Awesome Lists containing this project

README

        

pyLBL: Python Line-by-line Radiative Transfer Model
===================================================

|docs|

This application aims to provide a simple, flexible framework for calculating line-by-line
spectra.

Quickstart - calculating your first spectra
-------------------------------------------
Let's jump right in with a very simple example. After running through the installation
steps detailed below, spectra for a point in an atmosphere can be calculated by:

.. code-block:: python

from numpy import arange
from pyLBL import Database, HitranWebApi, Spectroscopy
from xarray import Dataset

# Download the line paramters and store them in a local sqlite database.
# You should only have to create the database once. Subsequent runs can
# then re-use the already created database.
webapi = HitranWebApi("")
database = Database("")
database.create(webapi) # This step will take a long time.

# An xarray dataset describing the atmosphere to simulate is required. This can be a
# cf-compliant netcdf file or explicitly defined (as below).
atmosphere = Dataset(
data_vars={
"play": (["z",], [98388.,], {"units": "Pa", "standard_name": "air_pressure"}),
"tlay": (["z",], [288.99,], {"units": "K", "standard_name": "air_temperature"}),
"xh2o": (["z",], [0.006637074,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_water_vapor_in_air"}),
"xco2": (["z",], [0.0003599889,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_carbon_dioxide_in_air"}),
"xo3": (["z",], [6.859128e-08,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_ozone_in_air"}),
"xn2o": (["z",], [3.199949e-07,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_nitrous_oxide_in_air"}),
"xco": (["z",], [1.482969e-07,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_carbon_monoxide_in_air"}),
"xch4": (["z",], [1.700002e-06,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_methane_in_air"}),
"xo2": (["z",], [0.208996,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_oxygen_in_air"}),
"xn2": (["z",], [0.781,],
{"units": "mol mol-1", "standard_name": "mole_fraction_of_nitrogen_in_air"}),
},
)

# A wavenumber grid in units of [cm-1] is also required.
grid = arange(1., 5000., 0.1)

# A Spectroscopy object controls the absorption coefficient calculation.
s = Spectroscopy(atmosphere, grid, database)
spectra = s.compute_absorption()
spectra.to_netcdf("spectra.nc")

Here your HITRAN api key can be located by registering/logging into HITRAN_ and
looking at your profile page. More information detailing each of the above steps are located
in the sections below.

Installation
------------

Installing using pip
~~~~~~~~~~~~~~~~~~~~

Make sure that you have the most recent version of :code:`pip`, then run
the following command in the base directory of the repository:

.. code-block:: bash

pip install --upgrade pip # If you need to upgrade pip.
pip install .

This command should install the model and the following dependencies:

* matplotlib
* netCDF4
* numpy
* scipy
* SQLAlchemy
* xarray

High-level API
--------------

This application aims to improve on existing line-by-line radiative transfer models
by separating the data management and calculation. Data management is handled by
a :code:`Database` object, which allows the user to construct a local database
of up-to-date line parameters, continuum coefficients, and cross sections
without having to explicitly interact with the ascii/data files typically
needed when using existing line-by-line models. Absorption spectra calculation
is handled by a :code:`Spectroscopy` object, which allows the user to specify which molecular
lines, continua, and cross section models they would like to use. The details of
each of these objects are discussed further in the next sections.

Spectral database management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A :code:`Database` object provides an interface to the current HITRAN_ database of molecular
line parameters. To create a database object of transitions for a specific set of molecules, run:

.. code-block:: python

from pyLBL import Database

# Make a connection to a database. If the database already exists and you want to
# just to re-use it, this is the only step you need.
database = Database("")

# If however you have not already populated the database, the data can be downloaded
# and inserted by running:
from pyLBL import HitranWebApi
webapi = HitranWebApi("")
database.create(webapi) # Note that this step will take a long time.

You must create an account on the HITRAN_ website in order to get
an api key. It is included as part of your profile on the webite.

Absorption calculation
~~~~~~~~~~~~~~~~~~~~~~

A :code:`Spectroscopy` object allow users to choose which models are used to calculate the
molecular lines, various molecular continua, and absorption cross sections. Currently,
the supported models are as follows:

============== ==============
Component Models
============== ==============
lines pyLBL_
continua mt_ckd_
cross sections arts-crossfit_
============== ==============

For example, to create a :code:`Spectroscopy` object using the native pure python spectral
lines model and the MT-CKD continuum, use:

.. code-block:: python

from pyLBL import Spectroscopy

spectroscopy = Spectroscopy(atmosphere, grid, database, mapping=mapping,
lines_backend="pyLBL", continua_backend="mt_ckd")

Here the :code:`database` argument is a :code:`Database` object as described above. The
:code:`atmosphere`, :code:`mapping`, and :code:`grid` inputs are described in the
following section.

User atmospheric inputs
~~~~~~~~~~~~~~~~~~~~~~~
Atmospheric inputs should be passed in as an xarray :code:`Dataset` object. As an example,
the surface layer of the first CIRC case can be described by:

.. code-block:: python

def variable(data, units, standard_name):
return (["z",], data, {"units": units, "standard_name": standard_name})

def create_circ_xarray_dataset():
from xarray import Dataset
temperature = [288.99,] # [K].
pressure = [98388.,] # [Pa].
xh2o = [0.006637074,] # [mol mol-1].
xco2 = [0.0003599889,] # [mol mol-1].
xo3 = [6.859128e-08,] # [mol mol-1].
xn2o = [3.199949e-07,] # [mol mol-1].
xco = [1.482969e-07,] # [mol mol-1].
xch4 = [1.700002e-06,] # [mol mol-1].
xo2 = [0.208996,] # [mol mol-1].
xn2 = [0.781,] # [mol mol-1].
return Dataset(
data_vars={
"p": variable(pressure, "Pa", "air_pressure"),
"t": variable(temperature, "K", "air_temperature"),
"xh2o": variable(xh2o, "mol mol-1", "mole_fraction_of_water_vapor_in_air"),
"xco2": variable(xco2, "mol mol-1", "mole_fraction_of_carbon_dioxide_in_air"),
"xo3": variable(xo3, "mol mol-1", "mole_fraction_of_ozone_in_air"),
"xn2o": variable(xn2o, "mol mol-1", "mole_fraction_of_nitrous_oxide_in_air"),
"xco": variable(xco, "mol mol-1", "mole_fraction_of_carbon_monoxide_in_air"),
"xch4": variable(xch4, "mol mol-1", "mole_fraction_of_methane_in_air"),
"xo2": variable(xo2, "mol mol-1", "mole_fraction_of_oxygen_in_air"),
"xn2": variable(xn2, "mol mol-1", "mole_fraction_of_nitrogen_in_air"),
},
coords={
"layer": (["z",], [1,]),
},
)

As shown in this example, the units of presure must be Pa, temperature must be K,
and mole fraction must be mol mol-1. Users may define a dictionary specifying which
variables in the dataset should be read:

.. code-block:: python

mapping = {
"play": "p", # name of pressure variable in dataset.
"tlay": "t", # name of temperature variable in dataset.
"mole_fraction": {
"H2O" : "xh2o", # name of water vapor mole fraction variable in dataset.
"CO2" : "xco2", # name of carbon dioxided mole fraction variable in dataset.
# et cetera
},
}

If this dictionary is not provided, the application attempts to "discover" the variables
in the dataset using their CF :code:`standard_name` attributes:

============================= ===============================
Variable standard_name Attribute
============================= ===============================
pressure `"air_pressure"`
temperature `"air_temperature"`
mole fraction of molecule xxx `"mole_fraction_of_xxx_in_air"`
============================= ===============================

For a full list of valid :code:`standard_name` attributes, go here_

Spectral grid input should in wavenumber [cm-1] and be defined as a numpy
array, for example:

.. code-block:: python

from numpy import arange
grid = arange(1., 5001., 0.1)

Also as of now, the wavenumber grid resolution should be one divided by an integer. This
requirement may be relaxed in the future.

Absorption output
~~~~~~~~~~~~~~~~~

Absorption coefficients can be calculated using the :code:`Spectroscopy` object described
above by running:

.. code-block:: python

absorption = spectroscopy.compute_absorption(output_format="all")

# Optional: convert dataset to netcdf.
absorption.to_netcdf("")

The output is returned as an xarray :code:`Dataset`. The exact format of the output data
depends on the value of the :code:`output_format` argument. When set to :code:`"all"` (which is
currently the default), the dataset will return the spectra split up by molecule
and mechansim (lines, continuum, cross_section). An example viewed in netCDF format
would look like this:

.. code-block:: none

netcdf absorption {
dimensions:
wavenumber = 49990 ;
mechanism = 2 ;
z = 1 ;
variables:
double wavenumber(wavenumber) ;
wavenumber:_FillValue = NaN ;
wavenumber:units = "cm-1" ;
string mechanism(mechanism) ;
double H2O_absorption(z, mechanism, wavenumber) ;
H2O_absorption:_FillValue = NaN ;
H2O_absorption:units = "m-1" ;
double CO2_absorption(z, mechanism, wavenumber) ;
CO2_absorption:_FillValue = NaN ;
CO2_absorption:units = "m-1" ;
double O3_absorption(z, mechanism, wavenumber) ;
O3_absorption:_FillValue = NaN ;
O3_absorption:units = "m-1" ;
double N2O_absorption(z, mechanism, wavenumber) ;
N2O_absorption:_FillValue = NaN ;
N2O_absorption:units = "m-1" ;
double CO_absorption(z, mechanism, wavenumber) ;
CO_absorption:_FillValue = NaN ;
CO_absorption:units = "m-1" ;
double CH4_absorption(z, mechanism, wavenumber) ;
CH4_absorption:_FillValue = NaN ;
CH4_absorption:units = "m-1" ;
double O2_absorption(z, mechanism, wavenumber) ;
O2_absorption:_FillValue = NaN ;
O2_absorption:units = "m-1" ;
double N2_absorption(z, mechanism, wavenumber) ;
N2_absorption:_FillValue = NaN ;
N2_absorption:units = "m-1" ;
data:
mechanism = "lines", "continuum" ;

If the :code:`output_format` argument is instead set to :code:`"gas"`, the spectra for
the different mechanims will be summed for each molecule, yielding output that looks
like this (in netCDF format):

.. code-block:: none

netcdf absorption {
dimensions:
wavenumber = 49990 ;
z = 1 ;
variables:
double wavenumber(wavenumber) ;
wavenumber:_FillValue = NaN ;
wavenumber:units = "cm-1" ;
double H2O_absorption(z, wavenumber) ;
H2O_absorption:_FillValue = NaN ;
H2O_absorption:units = "m-1" ;
double CO2_absorption(z, wavenumber) ;
CO2_absorption:_FillValue = NaN ;
CO2_absorption:units = "m-1" ;
double O3_absorption(z, wavenumber) ;
O3_absorption:_FillValue = NaN ;
O3_absorption:units = "m-1" ;
double N2O_absorption(z, wavenumber) ;
N2O_absorption:_FillValue = NaN ;
N2O_absorption:units = "m-1" ;
double CO_absorption(z, wavenumber) ;
CO_absorption:_FillValue = NaN ;
CO_absorption:units = "m-1" ;
double CH4_absorption(z, wavenumber) ;
CH4_absorption:_FillValue = NaN ;
CH4_absorption:units = "m-1" ;
double O2_absorption(z, wavenumber) ;
O2_absorption:_FillValue = NaN ;
O2_absorption:units = "m-1" ;
double N2_absorption(z, wavenumber) ;
N2_absorption:_FillValue = NaN ;
N2_absorption:units = "m-1" ;
}

Lastly, if the :code:`output_format` argument is set to any other value, only the total
absorption spectra (summed over all molecules) will be returned. In netCDF format, the
resulting dataset will appear like this:

.. code-block:: none

netcdf absorption {
dimensions:
wavenumber = 49990 ;
z = 1 ;
variables:
double wavenumber(wavenumber) ;
wavenumber:_FillValue = NaN ;
wavenumber:units = "cm-1" ;
double absorption(z, wavenumber) ;
absorption:_FillValue = NaN ;
absorption:units = "m-1" ;
}

.. _HITRAN: https://hitran.org
.. _pyLBL: https://github.com/GRIPS-code/pyLBL/blob/new_db/pyLBL/c_lib/gas_optics.py
.. _mt_ckd: https://github.com/GRIPS-code/MT_CKD/tree/fortran-90-and-python
.. _arts-crossfit: https://github.com/menzel-gfdl/arts-crossfit/tree/make-package
.. _here: http://cfconventions.org/Data/cf-standard-names/77/build/cf-standard-name-table.html

.. |docs| image:: https://readthedocs.org/projects/pylbl-1/badge/?version=latest
:target: https://pylbl-1.readthedocs.io/en/latest