Surface¶
Surfaces give the bidirectional reflectance factor R(μ₀, μ) a solver
reflects, behind the Surface protocol. v0 has Lambertian, R = A;
PolynomialAlbedo makes A a Legendre series in wavenumber; RossLi is the
MODIS kernel BRDF (RossThick and LiSparse-Reciprocal). RPV and Cox–Munk come
later in the roadmap.
surface
¶
Surface reflectance behind the Surface protocol: Lambertian in v0
(radtrax#35), a spectral (Legendre-series) albedo (radtrax#106) and the
Ross–Li kernel BRDF (radtrax#105).
lambertian¶
The Surface protocol and the Lambertian surface.
A surface turns the viewing geometry into a bidirectional reflectance
factor R (dimensionless): the reflected radiance relative to that of a
white Lambertian surface under the same illumination, so the radiance
leaving the surface is L = E₀ μ₀ R / π. A Lambertian surface reflects
the same in every direction, R = A, its albedo (radtrax · 3). Kernel
BRDFs (Ross–Li, RPV) and Cox–Munk arrive with v1 of the roadmap; they use
the relative azimuth in :class:~radtrax.atmosphere.ViewingGeometry and,
for a spectral albedo model, the wavenumber grid.
Shapes: G wavenumbers; *G means a scalar or (G,).
Surface
¶
Bases: Module
Protocol: the surface's bidirectional reflectance factor.
An abstract Equinox module, so filter_jit compiles each concrete
surface and a solver can take any of them.
reflectance
abstractmethod
¶
reflectance(geometry: ViewingGeometry, nu_cm: Float[Array, ' G']) -> Float[Array, '*G']
Bidirectional reflectance factor R (dimensionless).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
ViewingGeometry
|
Sun and sensor directions. |
required |
nu_cm
|
Float[Array, ' G']
|
Wavenumber grid [cm⁻¹], |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '*G']
|
|
Source code in src/radtrax/surface/lambertian.py
Lambertian
¶
Bases: Surface
A Lambertian surface: R = A in every direction.
Attributes:
| Name | Type | Description |
|---|---|---|
albedo |
Float[Array, '*G']
|
Surface albedo |
Examples:
>>> import jax.numpy as jnp
>>> from radtrax.atmosphere import ViewingGeometry
>>> surface = Lambertian(0.25)
>>> geometry = ViewingGeometry.from_angles(35.0, 0.0)
>>> float(surface.reflectance(geometry, jnp.array([4300.0])))
0.25
Source code in src/radtrax/surface/lambertian.py
reflectance
¶
reflectance(geometry: ViewingGeometry, nu_cm: Float[Array, ' G']) -> Float[Array, '*G']
R = A, whatever the geometry and wavenumber.
polynomial¶
A spectral albedo: a low-order Legendre series in wavenumber.
Real SWIR albedo has a slope across a fitting window, and a slope aliases into a methane band (plumax#98: a 10 % slope biases the column). Optimal estimation retrieves it as nuisance state, which needs a few well-conditioned coefficients rather than one value per wavenumber:
with P_k the Legendre polynomials (geonnax.basis.legendre_polynomials)
on the window [ν_min, ν_max], where they are orthogonal. The surface is
Lambertian at each wavenumber: R(ν) = A(ν) in every direction.
Shapes: G wavenumbers; K coefficients (degree K − 1).
PolynomialAlbedo
¶
Bases: Surface
A Lambertian surface whose albedo is a Legendre series in wavenumber.
A(ν) = Σ_k a_k P_k(x) with x the wavenumber mapped onto
[−1, 1] across nu_range_cm. With one coefficient it is
Lambertian(a_0). Outside the window the series extrapolates; keep
the model grid inside it. The window is checked to increase when it is
concrete; a window traced under jit is not checked.
Attributes:
| Name | Type | Description |
|---|---|---|
coefficients |
Float[Array, ' K']
|
Legendre coefficients |
nu_range_cm |
Float[Array, ' 2']
|
The window |
Examples:
>>> import jax.numpy as jnp
>>> from radtrax.atmosphere import ViewingGeometry
>>> surface = PolynomialAlbedo(
... jnp.array([0.25, 0.05]), nu_range_cm=jnp.array([4200.0, 4400.0])
... )
>>> geometry = ViewingGeometry.from_angles(35.0, 0.0)
>>> nu = jnp.array([4200.0, 4300.0, 4400.0])
>>> [round(v, 6) for v in surface.reflectance(geometry, nu).tolist()]
[0.2, 0.25, 0.3]
Source code in src/radtrax/surface/polynomial.py
albedo
¶
A(ν) on the grid (dimensionless), (G,).
Source code in src/radtrax/surface/polynomial.py
reflectance
¶
reflectance(geometry: ViewingGeometry, nu_cm: Float[Array, ' G']) -> Float[Array, '*G']
ross_li¶
The Ross–Li kernel BRDF: RossThick and LiSparse-Reciprocal.
The MODIS kernel-driven model (Lucht et al. 2000) writes the bidirectional reflectance factor as a linear combination of an isotropic term and two kernels of the geometry:
- RossThick (volume scattering by a dense canopy), with the phase angle
ξ,cos ξ = cos θ₀ cos θ + sin θ₀ sin θ cos φ:K_vol = ((π/2 − ξ) cos ξ + sin ξ) / (cos θ₀ + cos θ) − π/4. - LiSparse-Reciprocal (geometric-optical shadowing by sparse crowns)
with MODIS's crown shape
h/b = 2,b/r = 1(soθ' = θ):K_geo = O − sec θ₀ − sec θ + ½ (1 + cos ξ) sec θ₀ sec θ, with the overlapO = (t − sin t cos t)(sec θ₀ + sec θ) / πandcos t = (h/b) √(D² + (tan θ₀ tan θ sin φ)²) / (sec θ₀ + sec θ), clipped to[−1, 1],D² = tan² θ₀ + tan² θ − 2 tan θ₀ tan θ cos φ.
φ is :class:~radtrax.atmosphere.ViewingGeometry's relative azimuth,
sensor minus sun, so φ = 0 with θ₀ = θ is the hotspot (backscatter).
Both kernels are 0 at nadir/nadir and symmetric in θ₀ ↔ θ.
First-order gradients are finite everywhere, nadir and the hotspot included (second derivatives are not: the Hessian at exact nadir is NaN):
(π/2 − ξ) cos ξ + sin ξisc · arcsin c + √(1 − c²)inc = cos ξ, whose derivative isarcsin c; andt − sin t cos tisarccos u − u √(1 − u²)inu = cos t, whose derivative is−2 √(1 − u²). Both are written with that derivative (custom_jvp), wherearccosdifferentiated naively gives0 · ∞at±1.√(D² + …)vanishes at nadir and at the hotspot, where the kernel has a kink (a distance); the double-where there gives the symmetric (sub)gradient 0, which is what a central difference measures.
Shapes: G wavenumbers; *G means a scalar or (G,).
RossLi
¶
Bases: Surface
The Ross–Li kernel BRDF: R = f_iso + f_vol K_vol + f_geo K_geo.
With f_vol = f_geo = 0 it is Lambertian(f_iso).
Attributes:
| Name | Type | Description |
|---|---|---|
f_iso |
Float[Array, '*G']
|
Isotropic weight (dimensionless), a scalar or |
f_vol |
Float[Array, '*G']
|
RossThick (volume) weight, a scalar or |
f_geo |
Float[Array, '*G']
|
LiSparse-Reciprocal (geometric) weight, a scalar or |
Examples:
>>> import jax.numpy as jnp
>>> from radtrax.atmosphere import ViewingGeometry
>>> surface = RossLi(0.3, 0.1, 0.05)
>>> nadir = ViewingGeometry(1.0, 1.0)
>>> float(surface.reflectance(nadir, jnp.array([4300.0])))
0.3
Source code in src/radtrax/surface/ross_li.py
reflectance
¶
reflectance(geometry: ViewingGeometry, nu_cm: Float[Array, ' G']) -> Float[Array, '*G']
R = f_iso + f_vol K_vol + f_geo K_geo; the kernels do not depend on ν.
Source code in src/radtrax/surface/ross_li.py
ross_thick
¶
ross_thick(geometry: ViewingGeometry) -> Float[Array, '']
The RossThick volume-scattering kernel K_vol (dimensionless).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
ViewingGeometry
|
Sun and sensor directions. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
|
Examples:
>>> from radtrax.atmosphere import ViewingGeometry
>>> round(float(ross_thick(ViewingGeometry(1.0, 1.0))), 12)
0.0
Source code in src/radtrax/surface/ross_li.py
li_sparse_reciprocal
¶
li_sparse_reciprocal(geometry: ViewingGeometry) -> Float[Array, '']
The LiSparse-Reciprocal geometric kernel K_geo (dimensionless), MODIS's shape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry
|
ViewingGeometry
|
Sun and sensor directions. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
|
Examples:
>>> from radtrax.atmosphere import ViewingGeometry
>>> round(float(li_sparse_reciprocal(ViewingGeometry(1.0, 1.0))), 12)
0.0