Source code for sasktran2.constituent.lambertiansurface
import numpy as np
from sasktran2 import Atmosphere
from sasktran2.atmosphere import (
NativeGridDerivative,
SurfaceDerivativeMapping,
)
from sasktran2.util.interpolation import linear_interpolating_matrix
from .base import Constituent
[docs]
class LambertianSurface(Constituent):
def __init__(
self,
albedo: np.array,
wavelengths_nm: np.array = None,
wavenumbers_cminv: np.array = None,
out_of_bounds_mode="zero",
) -> None:
"""
A Lambertian surface that is defined by albedo at discrete grid points.
This can either operate in a "scalar" mode where the albedo is constant in wavelength,
a "native" mode where the albedo is defined on the same grid as the atmosphere, or
an "interpolated" mode where the albedo is interpolated either in wavenumber or wavelength
Parameters
----------
albedo : np.array
Surface albedo. Can be a scalar to indicate it is constant in wavelength. If set to an
array it must either match the atmosphere wavelength grid dimension, or one of
wavelengths_nm or wavenumbers_cminv must be set.
wavelengths_nm : np.array, optional
Wavelengths in [nm] that the albedo is specified at, by default None
wavenumbers_cminv : np.array, optional
Wavenumbers in inverse cm that the albedo is specified at, by default None
out_of_bounds_mode : str, optional
One of ["extend" or "zero"], "extend" will extend the last/first value if we are
interpolating outside the grid. "zero" will set the albedo to 0 outside of the
grid boundaries, by default "zero"
"""
super().__init__()
self._out_of_bounds_mode = out_of_bounds_mode
self._albedo = np.atleast_1d(albedo)
if wavelengths_nm is not None:
self._x = np.atleast_1d(wavelengths_nm)
self._interp_var = "wavelengths_nm"
elif wavenumbers_cminv is not None:
self._x = np.atleast_1d(wavenumbers_cminv)
self._interp_var = "wavenumbers_cminv"
else:
if len(self._albedo) == 1:
self._interp_var = "constant"
else:
self._interp_var = "native"
@property
def albedo(self) -> np.array:
return self._albedo
@albedo.setter
def albedo(self, albedo: np.array):
self._albedo = np.atleast_1d(albedo)
def _interpolating_matrix(self, atmo: Atmosphere):
if self._interp_var == "constant":
# Don't have to interpolate, just assign the constant value
return np.ones_like(atmo.surface.albedo).reshape(-1, 1)
if self._interp_var == "native":
# Also can just assign the user value, but first make sure that it is the correct length
if len(self._albedo) != len(atmo.surface.albedo):
msg = "The number of albedo values must match the number of wavelengths in the atmosphere"
raise ValueError(msg)
return np.eye(len(self._albedo))
# Now we have to interpolate
grid_x = getattr(atmo, self._interp_var)
if grid_x is None:
msg = f"The atmosphere does not have {self._interp_var} defined, cannot interpolate the user albedo"
raise ValueError(msg)
return linear_interpolating_matrix(self._x, grid_x, self._out_of_bounds_mode)
def add_to_atmosphere(self, atmo: Atmosphere):
interp_matrix = self._interpolating_matrix(atmo)
atmo.surface.albedo[:] = interp_matrix @ self._albedo
def register_derivative(self, atmo: Atmosphere, name: str):
# Start by constructing the interpolation matrix
interp_matrix = self._interpolating_matrix(atmo)
derivs = {}
derivs["albedo"] = SurfaceDerivativeMapping(
NativeGridDerivative(d_albedo=np.ones_like(atmo.surface.albedo)),
interpolating_matrix=interp_matrix,
interp_dim="wavelength",
result_dim=f"{name}_wavelength",
)
return derivs