LEGO Blocks Aerosol API

pyRadtran models aerosols as composable blocks. A species block carries mass-normalized optics (e.g. MieSpecies); a vertical profile places mass in the column; a PlacedBlock binds the two. Any number of blocks are externally mixed into one CompositeAerosol, written as a single explicit .master / .LAYER file set.

Vertical profiles

Three concrete profiles implement the VerticalProfile protocol:

from pyradtran.models.blocks import MassProfile, ExponentialProfile, TabulatedProfile

mass = MassProfile(kg_m3_per_layer=(1e-7, 2e-7, 1e-7, 1e-8))      # explicit per-layer kg/m^3
expo = ExponentialProfile(rho0_kg_m3=1e-6, scale_height_km=2.0)   # rho0 * exp(-z/H)
tab = TabulatedProfile(z_km=(0.0, 2.0, 4.0, 8.0), kg_m3=(1e-6, 5e-7, 1e-7, 0.0))
print(type(mass).__name__, type(expo).__name__, type(tab).__name__)

Building and running a composite

Build a Mie species, invert a target optical depth into a mass profile with od_to_mass_profile(), wrap it in a PlacedBlock, assemble the composite, attach it, and run DISORT. Each composite needs the wavelength grid (µm), the altitude grid (km, descending), and a writable output_dir:

from pyradtran import Scene, Runner
from pyradtran.models.aerosol_composite import (
    CompositeAerosol, IntegrationConfig, MieSpecies, RefractiveIndex, SizeDistribution,
)
from pyradtran.models.blocks import PlacedBlock, od_to_mass_profile

altitude_km = [8.0, 6.0, 4.0, 2.0, 0.0]
ri = RefractiveIndex(wavelength_um=[0.40, 0.55, 0.70], n_real=[1.53, 1.53, 1.53], k_imag=[0.008, 0.008, 0.008])
sd = SizeDistribution(kind="lognormal", params={"r_g_um": 0.50, "sigma_g": 2.2})
dust = MieSpecies(refractive_index=ri, size_distribution=sd,
                  particle_density_kg_m3=2600.0, integration_config=IntegrationConfig(), name="dust")

profile = od_to_mass_profile(dust, tau_ref=0.20, ref_nm=550.0,
                             altitude_km=altitude_km, scale_height_km=3.0)
aerosol = CompositeAerosol(
    pieces=[PlacedBlock(block=dust, profile=profile)],
    wavelength_grid_um=[0.50, 0.55, 0.60],
    altitude_grid_km=altitude_km,
    n_legendre=32,
    output_dir=".",
)

scene = (
    Scene().set_atmosphere(profile="us")
    .set_source_solar(sza=30.0)
    .set_wavelength(500.0, 600.0)
    .set_solver(method="disort", streams=16, disort_intcor="moments")
    .set_output(quantities=["lambda", "edir", "edn", "eup"], format="ascii", zout=[0, 2, "toa"])
    .set_aerosol(aerosol)
)
result = Runner.execute(scene, data_path=None)
print("surface edir:", float(result.edir.isel(zout=0).mean()))

Mix several blocks by adding more PlacedBlock items to pieces; they are combined with scattering-optical-depth weighting.

Direct (pre-computed) layer files

For pre-computed explicit aerosol files, skip the profile entirely and use DirectLayerOpticsBlock, which parses a .master / .LAYER set directly (the requested wavelength grid must match the file’s grid):

from pyradtran.models.blocks import DirectLayerOpticsBlock
# block = DirectLayerOpticsBlock(master_path="my_aerosol.master", name="explicit")