Go to notebook file


[1]:
%reload_ext eradiate.notebook.tutorials

Last updated: 2023-12-18 11:05 (eradiate v0.24.5.dev35+g131452cf.d20231215)

Particle layer basics#

Overview

In this tutorial, we introduce Eradiate’s particle layer simulation features. We also introduce the general heterogeneous atmosphere container.

Prerequisites

What you will learn

  • How to add one or several aerosol layers to a molecular atmosphere.


The particle layer interface#

We start by activating the IPython extension and importing and aliasing a few useful components. We also select the CKD mode.

[2]:
%load_ext eradiate
import eradiate
import numpy as np
import matplotlib.pyplot as plt
from rich.pretty import pprint
from eradiate.notebook.tutorials import plot_sigma_t

eradiate.set_mode("ckd")

We alias the eradiate.scenes module and the unit registry for convenience:

[3]:
import eradiate.scenes as ertsc
from eradiate import unit_registry as ureg

The ParticleLayer class is used to configure particle layers. It is parametrised by:

  • an extent, defined by its bottom and top parameters;

  • a number of layers (n_layers) the particle layer is discretised;

  • a particle spatial density distribution (e.g. uniform or gaussian)

  • an optical thickness value (tau_ref) at a reference wavelength set by its w_ref parameter (by default 550 nm).

We start by creating a particle layer with default parameters:

[4]:
particle_layer_default = ertsc.atmosphere.ParticleLayer()
particle_layer_default
[4]:

ParticleLayer(
    id='atmosphere',
    geometry=PlaneParallelGeometry(
        toa_altitude=120.0 km,
        ground_altitude=0.0 km,
        zgrid=ZGrid(
            levels=[0.0 100.0 200.0 ... 119800.0 119900.0 120000.0] m,
            _layers=[50.0 150.0 250.0 ... 119750.0 119850.0 119950.0] m,
            _layer_height=100.0 m,
            _total_height=120000.0 m
        ),
        width=1000000.0 km
    ),
    scale=None,
    bottom=0.0 km,
    top=1.0 km,
    distribution=UniformParticleDistribution(bounds=array([0., 1.])),
    w_ref=550 nm,
    tau_ref=0.2,
    dataset=<xarray.Dataset | source='/home/leroyv/Documents/src/rayference/rtm/eradiate/resources/data/spectra/particles/govaerts_2021-continental.nc'>,
    has_absorption=True,
    has_scattering=True,
    _phase=TabulatedPhaseFunction(
        id='phase_atmosphere',
        data=<xarray.DataArray | name='phase', dims=['w', 'mu', 'i', 'j'], source='/home/leroyv/Documents/src/rayference/rtm/eradiate/resources/data/spectra/particles/govaerts_2021-continental.nc'>
    )
)

We see that our particle_layer_default variable represents an aerosol layer with:

  • a vertical extent from 0 to 1 km;

  • a uniform particle density distribution within that extent;

  • an optical thickness at 550 nm equal to 0.2.

We also note that the geometry field is set to a default suitable for the simulation of a typical atmospheric profile, ranging from 0 to 120 km with a 100 m grid step.

We can now visualize the extinction coefficient using the following convenience function:

[5]:
plot_sigma_t(particle_layer_default, altitude_extent=[0, 2])
../../_images/tutorials_getting_started_particle_layer_9_1.png

We note that the extinction coefficient drops to 0 above 1 km: the particle layer cover the [0, 1] km region, and the rest of the atmospheric profile consists in a vacuum.

Now, let’s define another particle layer with a different optical thickness:

[6]:
particle_layer_thick = ertsc.atmosphere.ParticleLayer(tau_ref=1.0)
plot_sigma_t(
    particle_layer_default,
    particle_layer_thick,
    labels=["default", "thick"],
    altitude_extent=[0, 2],
)
../../_images/tutorials_getting_started_particle_layer_11_1.png

We can also modify the extent of our particle using the bottom and top parameters:

[7]:
particle_layer_high = ertsc.atmosphere.ParticleLayer(
    tau_ref=0.5, bottom=1 * ureg.km, top=2 * ureg.km
)
plot_sigma_t(
    particle_layer_default,
    particle_layer_thick,
    particle_layer_high,
    labels=["default", "thick", "high"],
    altitude_extent=[0, 3],
)
../../_images/tutorials_getting_started_particle_layer_13_1.png

We can vary the particle density distribution within the defined bounds:

[8]:
particle_layer_gaussian = ertsc.atmosphere.ParticleLayer(
    tau_ref=0.5,
    bottom=0.5 * ureg.km,
    top=1.5 * ureg.km,
    distribution="gaussian",
)

plot_sigma_t(
    particle_layer_default,
    particle_layer_thick,
    particle_layer_high,
    particle_layer_gaussian,
    labels=["default", "thick", "high", "gaussian"],
    altitude_extent=[0, 3],
)
../../_images/tutorials_getting_started_particle_layer_15_1.png

Finally, we can modify the altitude grid to allow for a more accurate extinction distribution profile:

[9]:
geometry = ertsc.geometry.PlaneParallelGeometry(
    zgrid=np.arange(0, 10001, 10) * ureg.m,
    toa_altitude=10 * ureg.km,
)

for particle_layer in [
    particle_layer_default,
    particle_layer_thick,
    particle_layer_high,
    particle_layer_gaussian,
]:
    particle_layer.geometry = geometry

plot_sigma_t(
    particle_layer_default,
    particle_layer_thick,
    particle_layer_high,
    particle_layer_gaussian,
    labels=["default", "thick", "high", "gaussian"],
    altitude_extent=[0, 2.5],
)
../../_images/tutorials_getting_started_particle_layer_17_1.png

Adding a particle layer to a molecular atmosphere#

Although Eradiate can simulate radiative transfer in a single particle layer, realistic use cases rather involve adding a particle layer to a molecular atmosphere. For this, we use the HeterogeneousAtmosphere class. This class acts as a container which can mix a MolecularAtmosphere and an arbitrary number of ParticleLayers.

[10]:
exponential = ertsc.atmosphere.ParticleLayer(
    tau_ref=0.05,
    bottom=0 * ureg.km,
    top=6 * ureg.km,
    distribution="exponential",
)

gaussian = ertsc.atmosphere.ParticleLayer(
    tau_ref=0.02,
    bottom=1 * ureg.km,
    top=2 * ureg.km,
    distribution="gaussian",
)

atmosphere = ertsc.atmosphere.HeterogeneousAtmosphere(
    molecular_atmosphere={"type": "molecular"},
    particle_layers=[exponential, gaussian]
)

We can display the extinction coefficient for the full atmospheric profile and all its components as follows (note that we restrict the view range to the [0, 10] km extent for clarity):

[11]:
plot_sigma_t(
    atmosphere,
    atmosphere.molecular_atmosphere,
    exponential,
    gaussian,
    labels=["atmosphere", "molecular", "exponential", "gaussian"],
    altitude_extent=(0, 10),
)
../../_images/tutorials_getting_started_particle_layer_21_1.png

We see on this plot that the various components of the HeterogeneousAtmosphere container are stacked as expected. We also see that the components are all assumed piecewise-constant on their own grid, then regridded on a fine mesh prior to merge.

Running the simulation#

We can now use this atmosphere definition to run a 1D simulation and compute the top-of-atmosphere BRF:

[12]:
# Show only spectral loop progress
eradiate.config.progress = "spectral_loop"

exp = eradiate.experiments.AtmosphereExperiment(
    surface={"type": "lambertian", "reflectance": 1.0},
    atmosphere=atmosphere,
    illumination={"type": "directional", "zenith": 30.0, "azimuth": 0.0},
    measures={
        "type": "mdistant",
        "construct": "hplane",
        "zeniths": np.arange(-75, 76, 5),
        "azimuth": 0.0,
        "srf": {"type": "multi_delta", "wavelengths": 550.0 * ureg.nm},  # Run the simulation for the 550 nm bin
        "spp": 10000,
    },
)
result = eradiate.run(exp)
result
[12]:
<xarray.Dataset>
Dimensions:     (sza: 1, saa: 1, y_index: 1, x_index: 31, w: 1, wbv: 2)
Coordinates:
  * sza         (sza) float64 30.0
  * saa         (saa) float64 0.0
  * y_index     (y_index) int64 0
    y           (y_index) float64 0.0
  * x_index     (x_index) int64 0 1 2 3 4 5 6 7 8 ... 22 23 24 25 26 27 28 29 30
    x           (x_index) float64 0.0 0.03333 0.06667 0.1 ... 0.9333 0.9667 1.0
  * w           (w) float64 551.0
  * wbv         (wbv) <U5 'lower' 'upper'
    vza         (x_index, y_index) int64 -75 -70 -65 -60 -55 ... 55 60 65 70 75
    vaa         (x_index, y_index) int64 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0
Data variables:
    radiance    (sza, saa, w, y_index, x_index) float64 0.4164 0.4419 ... 0.4227
    spp         (sza, saa, y_index, x_index, w) float64 1e+04 1e+04 ... 1e+04
    wbounds     (sza, saa, wbv, w) float64 549.5 552.5
    irradiance  (sza, saa, w) float64 1.634
    brdf        (sza, saa, w, y_index, x_index) float64 0.2548 0.2704 ... 0.2586
    brf         (sza, saa, w, y_index, x_index) float64 0.8005 0.8495 ... 0.8125
Attributes:
    convention:  CF-1.10
    source:      eradiate, version 0.24.5.dev35+g131452cf.d20231215
    history:     2023-12-18T10:05:35 - data creation - AtmosphereExperiment.p...
    references:
    title:       Top-of-atmosphere simulation results

And we can plot the computed TOA BRF:

[13]:
with plt.rc_context({"lines.marker": ".", "lines.linestyle": ":"}):
    result.brf.squeeze(drop=True).plot(x="vza");
../../_images/tutorials_getting_started_particle_layer_27_1.png

Final words#

This quick introduction provides just an overview of how particle layers are parametrised. In particular, particle radiative property datasets (the dataset parameter of the ParticleLayer constructor) are not (yet) covered by this tutorial. Until this is done, you can have a look in the resources/data/spectra/particles and explore those datasets.

Further reading#