Mie Scattering Databases#

import sasktran2 as sk
import numpy as np

SASKTRAN2 includes the ability to generate scattering properties for Mie scatterers. Generally a Mie scattering atmospheric constituent is described by a particle size distribution, and a refractive index. Since Mie scattering calculations can be slow, SASKTRAN2 does not contain perform Mie calculations alongside the radiative transfer calculation, instead databases are generated using internal tools that can then be re-used.

Let’s start by creating a Mie scattering database for sulfate aerosols following a log-normal particle distribution parameterized by a median radius and mode width.

mie_db = sk.database.MieDatabase(
    sk.mie.distribution.LogNormalDistribution(),
    sk.mie.refractive.H2SO4(),
    wavelengths_nm=np.arange(270, 1000, 50.0),
    median_radius=[100, 150, 200],
    mode_width=[1.5, 1.7],
)

The main class which provides the database functionality is the sasktran2.database.MieDatabase object. When it is created for the first time the local database will be generated. Any subsequent instantiations of the object will re-use the cached database. In creating the database we provided a sasktran2.mie.distribution.LogNormalDistribution object which defines the particle size distribution for the scatterer, as well as a sasktran2.mie.refractive.H2SO4 which in this case is the refractive index function for sulfates. We also specified the wavelengths to create the database at, as well as the particle size parameters that should be used.

We can look at the created database

mie_db.load_ds()
<xarray.Dataset> Size: 278kB
Dimensions:        (median_radius: 3, mode_width: 2, wavelength_nm: 15,
                    legendre: 64)
Coordinates:
  * median_radius  (median_radius) int64 24B 100 150 200
  * mode_width     (mode_width) float64 16B 1.5 1.7
  * wavelength_nm  (wavelength_nm) float64 120B 270.0 320.0 ... 920.0 970.0
Dimensions without coordinates: legendre
Data variables:
    xs_scattering  (wavelength_nm, median_radius, mode_width) float64 720B ...
    xs_total       (wavelength_nm, median_radius, mode_width) float64 720B ...
    lm_a1          (wavelength_nm, legendre, median_radius, mode_width) float64 46kB ...
    lm_a2          (wavelength_nm, legendre, median_radius, mode_width) float64 46kB ...
    lm_a3          (wavelength_nm, legendre, median_radius, mode_width) float64 46kB ...
    lm_a4          (wavelength_nm, legendre, median_radius, mode_width) float64 46kB ...
    lm_b1          (wavelength_nm, legendre, median_radius, mode_width) float64 46kB ...
    lm_b2          (wavelength_nm, legendre, median_radius, mode_width) float64 46kB ...

Which contains the scattering and absorption cross sections, as well as the Legendre expansion moments of the phase function that are necessary for the radiative transfer calculation.

Including Mie Scatterers in the Radiative Transfer Calculation#

The Mie database can be included and used as an optical property with the standard sasktran2.constituent.NumberDensityScatterer and sasktran2.constituent.ExtinctionScatterer constituents.

First we set up the example calculation without aerosol

import matplotlib.pyplot as plt

config = sk.Config()

altitudes_m = np.arange(0, 65001, 1000)

model_geometry = sk.Geometry1D(cos_sza=0.6,
                               solar_azimuth=0,
                               earth_radius_m=6372000,
                               altitude_grid_m=altitudes_m,
                               interpolation_method=sk.InterpolationMethod.LinearInterpolation,
                               geometry_type=sk.GeometryType.Spherical)

viewing_geo = sk.ViewingGeometry()

for alt in [10000, 20000, 30000, 40000]:
    ray = sk.TangentAltitudeSolar(tangent_altitude_m=alt,
                                    relative_azimuth=45*np.pi/180,
                                    observer_altitude_m=200000,
                                    cos_sza=0.6)
    viewing_geo.add_ray(ray)

wavel = np.arange(280.0, 800.0, 1)

atmosphere = sk.Atmosphere(model_geometry, config, wavelengths_nm=wavel)

sk.climatology.us76.add_us76_standard_atmosphere(atmosphere)

atmosphere['rayleigh'] = sk.constituent.Rayleigh()
atmosphere['ozone'] = sk.climatology.mipas.constituent("O3", sk.optical.O3DBM())

engine = sk.Engine(config, model_geometry, viewing_geo)

radiance_no_aerosol = engine.calculate_radiance(atmosphere)

Now we can add in aerosol, let’s set the extinction to be a constant 1e-7 per metre until 30 km, and then zero above that.

aero_ext = np.zeros(len(altitudes_m))
aero_ext[0:30] = 1e-7

atmosphere['aerosol'] = sk.constituent.ExtinctionScatterer(
    mie_db,
    altitudes_m=altitudes_m,
    extinction_per_m=aero_ext,
    extinction_wavelength_nm=745,
    median_radius=np.ones_like(aero_ext)*120,
    mode_width=np.ones_like(aero_ext)*1.6
)

radiance_with_aerosol = engine.calculate_radiance(atmosphere)

And we can plot the results

plt.plot(
    wavel, radiance_no_aerosol['radiance'].isel(los=0, stokes=0),
    wavel, radiance_with_aerosol['radiance'].isel(los=0, stokes=0)
)
[<matplotlib.lines.Line2D at 0x7fed7c9de150>,
 <matplotlib.lines.Line2D at 0x7fed7ca12210>]
../_images/5ef7d62bc0ad6a322a4fdbe15bc908d937bd8ddd8604df6a2d4624fd930522f0.png

If we look at the dataset with aerosol included,

radiance_with_aerosol
<xarray.Dataset> Size: 6MB
Dimensions:                   (wavelength: 520, stokes: 1, altitude: 66,
                               los: 4, ozone_altitude: 50, aerosol_altitude: 66)
Coordinates:
  * wavelength                (wavelength) float64 4kB 280.0 281.0 ... 799.0
  * stokes                    (stokes) <U1 4B 'I'
  * altitude                  (altitude) float64 528B 0.0 1e+03 ... 6.5e+04
Dimensions without coordinates: los, ozone_altitude, aerosol_altitude
Data variables:
    radiance                  (wavelength, los, stokes) float64 17kB 0.000588...
    wf_pressure_pa            (altitude, wavelength, los, stokes) float64 1MB ...
    wf_temperature_k          (altitude, wavelength, los, stokes) float64 1MB ...
    wf_ozone_vmr              (ozone_altitude, wavelength, los, stokes) float64 832kB ...
    wf_aerosol_median_radius  (aerosol_altitude, wavelength, los, stokes) float64 1MB ...
    wf_aerosol_mode_width     (aerosol_altitude, wavelength, los, stokes) float64 1MB ...
    wf_aerosol_extinction     (aerosol_altitude, wavelength, los, stokes) float64 1MB ...

We can see that the model has also calculated the derivatives with respect to the particle size parameters, median_radius and mode_width.