3 · Layer Optical Properties — the bridge
For: anyone reading the RT basics arc top-to-bottom. This is the page that connects "physics inputs" (gas absorption, Mie scattering) to "what the solver does." Centerpiece of the thread.
Prev: 2 · Vector RTE & Discretization · Next: 3a · Gas Absorption
Concepts/02 ended on a four-array abstraction:
Every atmospheric layer reduces to four arrays
For each Fourier moment
| Symbol | Meaning | Shape |
|---|---|---|
| optical depth | (nSpec,) | |
| single-scattering albedo | (nSpec,) | |
| forward-direction phase matrix | (NquadN, NquadN, nSpec) | |
| backscatter phase matrix | (NquadN, NquadN, nSpec) |
These are also the three core RT variables the linearized solver differentiates against (
How the four arrays are built (the operator chain)
The Julia type that carries them is CoreScatteringOpticalProperties, and the constructor is constructCoreOpticalProperties in src/CoreRT/LayerOpticalProperties/compEffectiveLayerProperties.jl:11–65. The build happens once per band, per Fourier moment, before the layer loop:
Gas opacity τ_abs ─┐
(HITRAN LBL) │
│
Rayleigh ─────────┐ │
(greek + ω̃_Cab) │ │
▼ ▼
Aerosol_1 ──→ constructCoreOpticalProperties ──→ Per-layer
(GreekCoefs, (τ, ϖ₀,
ω̃, k, fᵗ) Z⁺⁺, Z⁻⁺)
▲ │
│ │
Aerosol_2 ────────┘ ▼
MOM solverThe build proceeds in five steps (the relevant lines from compEffectiveLayerProperties.jl:11–65):
Pick the Rayleigh greek source. For pure-elastic runs (
noRS), use the full Rayleigh greek — rotational Raman is rolled into the effective depolarization. For Raman-aware runs (RRS,VS_*), use the Cabannes greek and handle rotational Raman explicitly. Selector at lines 8–9. See Concepts/08.Build the Rayleigh layer's
from the chosen greek and the quadrature points via Scattering.compute_Z_moments(pol_type, μ, greek, m).For each aerosol, run
compute_Z_momentsagain (with that aerosol'sGreekCoefs), wrap in aCoreScatteringOpticalPropertiesviacreateAero(which also applies δ-M truncation — see Concepts/03c), and mix it into the layer with+.Add gas absorption to the total optical depth:
, . Coded ascombo + CoreAbsorptionOpticalProperties(τ_abs)(line 58).Stack layers vertically with
*(line 62):prod([band_layer_props[i][iz] for i = 1:length(iBand)]). The result is aVector{CoreScatteringOpticalProperties}, one entry per atmospheric layer, ready for Concepts/04.
The walkthrough in Concepts/03c goes through the body of the loop in detail, including the scattering-vs-total τ split that makes the doubling step cheap.
The two operators on CoreScatteringOpticalProperties
Mixing scatterers in a layer (src/CoreRT/types.jl:1063–1093):
function Base.:+(x::CoreScatteringOpticalProperties, y::CoreScatteringOpticalProperties)
τ = x.τ .+ y.τ # additive optical depth
wx = x.τ .* x.ϖ # scattering-weight from each
wy = y.τ .* y.ϖ
w = wx .+ wy
ϖ = w ./ τ # τϖ-weighted SSA
Z⁺⁺ = (wx .* xZ⁺⁺ .+ wy .* yZ⁺⁺) ./ w # τϖ-weighted phase matrix
Z⁻⁺ = (wx .* xZ⁻⁺ .+ wy .* yZ⁻⁺) ./ w
CoreScatteringOpticalProperties(τ, ϖ, Z⁺⁺, Z⁻⁺)
endThe weighting is
Stacking layers vertically (src/CoreRT/types.jl:1096+):
function Base.:*(x::CoreScatteringOpticalProperties, y::CoreScatteringOpticalProperties)
arr_type = array_type(architecture(x.τ))
x = expandOpticalProperties(x, arr_type)
y = expandOpticalProperties(y, arr_type)
CoreScatteringOpticalProperties(
[x.τ; y.τ], [x.ϖ; y.ϖ],
cat(x.Z⁺⁺, y.Z⁺⁺, dims=3),
cat(x.Z⁻⁺, y.Z⁻⁺, dims=3))
endThis stacks the spectral axis (cat(..., dims=3)) for Vector{CoreScatteringOpticalProperties}) — it's a multi-band concatenation along the spectral axis, used when several bands share the same atmospheric column.
A worked example
Consider an OCO-2-like O₂ A-band layer at altitude iz, with one Rayleigh contribution and one fine-mode aerosol:
# inside constructCoreOpticalProperties, conceptually:
# (1) Rayleigh: τ_rayl from molecular cross-section, ϖ_Cab ≈ 1 for pure scattering
rayl = CoreScatteringOpticalProperties(τ_rayl, ϖ_Cab, RaylZ⁺⁺, RaylZ⁻⁺)
# (2) Aerosol fine mode, with δ-M applied via createAero
aer = createAero(τ_aer_fine, aer_optics_fine, AerZ⁺⁺, AerZ⁻⁺)
# → modifies (τ, ϖ) for forward-peak truncation per SS2015
# (3) Mix scatterers in the layer
combo = rayl + aer
# → τ, ϖ, Z⁺⁺, Z⁻⁺ are τϖ-weighted averages
# (4) Add gas absorption (τ_abs is per-wavelength)
layer_optics = combo + CoreAbsorptionOpticalProperties(τ_abs)
# → τ_λ = τ_abs + τ_scat
# ϖ_λ = τ_scat·ϖ / τ_λ
# Z⁺⁺, Z⁻⁺ unchangedAfter all Nz layers are built, rt_kernel! consumes them one at a time from TOA to BOA inside the Fourier-moment loop (Concepts/04).
What's next
To understand how each ingredient is computed:
3a · Gas Absorption — how
is built (HITRAN line-by-line, Voigt line shapes). 3b · Mie & Rayleigh — how
, , , are built (Mie series, GreekCoefs, NAI-2 vs PCW). 3c · Mixing & δ-M Truncation — how the ingredients combine, and the scattering-vs-total τ split that makes the doubling tractable.
If you only care about what the solver does with these arrays, skip to Concepts/04.
Code anchors
| Concept | Source |
|---|---|
| Build per-layer (τ, ϖ, Z) | src/CoreRT/LayerOpticalProperties/compEffectiveLayerProperties.jl:11–65 |
Aerosol δ-M wrapper (createAero) | compEffectiveLayerProperties.jl:67–72 |
| Cabannes vs Rayleigh greek | compEffectiveLayerProperties.jl:8–9 |
Mix scatterers (operator +) | src/CoreRT/types.jl:1063–1093 |
Stack layers (operator *) | src/CoreRT/types.jl:1096+ |
CoreScatteringOpticalProperties type | src/CoreRT/types.jl::CoreScatteringOpticalProperties |
| Gas absorption type | src/CoreRT/types.jl::CoreAbsorptionOpticalProperties |
Hands-on tutorials
Runnable examples with Plotly figures:
References
Sanghavi et al. (2014), JQSRT 133:412–433, doi:10.1016/j.jqsrt.2013.09.004. §2 (layer-averaged optics, Eqs. C.22–C.24 for the forward-only
definitions used here). Sanghavi & Frankenberg (2023), JQSRT 311:108791, doi:10.1016/j.jqsrt.2023.108791. §3.2 — separating
from for the constant- N_doubldesign (more in Concepts/03c).Crib sheet:
docs/dev_notes/theory_references.md§F.