Source code for sense.soil

from dataclasses import dataclass, field
from typing import Optional

import numpy as np

from .dielectric import Dobson85
from .util import f2lam


[docs] @dataclass class Soil: """Class specifying a soil. Parameters ---------- surface: string name of used RT-model for surface contribution eps : complex relative permitivity, if this is not given, then mv needs to be given s : float surface rms height [m] mv : float volumetric soil moisture [m**3/m**3]; either eps or mv needs to be given f : float frequency [GHz] l : float optional: autocorrelation length acl : str identifier for shape of autocorrelation function G = Gaussian E = Exponential clay : float optional fractional clay content sand : float optional fraction sand content bulk : float bulk density [g/cm**3] C_hh : float empirical parameter (Water Cloud Model) D_hh : float empirical parameter (Water Cloud Model) C_vv : float empirical parameter (Water Cloud Model) D_vv : float empirical parameter (Water Cloud Model) C_hv : float empirical parameter (Water Cloud Model) D_hv : float empirical parameter (Water Cloud Model) V2 : float parameter specifying the vegetation (Water Cloud Model) """ # Soil parameters for different models surface: Optional[str] = None eps: Optional[complex] = None mv: Optional[float] = None f: Optional[float] = None s: Optional[float] = None l: Optional[float] = None acl: Optional[str] = None clay: Optional[float] = None sand: Optional[float] = None bulk: Optional[float] = 1.65 debye: Optional[float] = None dc_model: str = "Dobson85" # Soil parameters for Water Cloud model C_hh: Optional[float] = None D_hh: Optional[float] = None C_vv: Optional[float] = None D_vv: Optional[float] = None C_hv: Optional[float] = None D_hv: Optional[float] = None V2: Optional[float] = None # Computed fields (not set by user) k: Optional[float] = field(init=False, default=None) ks: Optional[float] = field(init=False, default=None) kl: Optional[float] = field(init=False, default=None) def __post_init__(self): """Run checks and compute dependent parameters after initialization.""" self._check() # Convert between eps and mv if needed if self.eps is not None: self._convert_eps2mv() if self.mv is not None and self.surface != 'WaterCloud': self._convert_mv2eps() # Roughness parameters (only if not WaterCloud) if self.surface != 'WaterCloud': self.k = 2.0 * np.pi / f2lam(self.f) # Wavenumber in m⁻¹ (not cm⁻¹) self.ks = self.s * self.k self.kl = self.k * self.l if self.l is not None else None def _convert_mv2eps(self): """Convert mv to eps using dielectric model.""" if (self.clay is None) or (self.sand is None): self.eps = None print("WARNING: Permittivity cannot be calculated due to missing soil texture!") return if self.dc_model == 'Dobson85': DC = Dobson85( clay=self.clay, sand=self.sand, mv=self.mv, freq=self.f, debye=self.debye, bulk=self.bulk ) else: raise ValueError(f"Invalid DC model! {self.dc_model}") self.eps = DC.eps def _convert_eps2mv(self): """This routine converts soil moisture into dielectric properties and vice versa. future implementations will comprise e.g. the Dobson model and others ... """ assert self.eps is not None, ( "Currently conversion not implemented yet; " "you need to provide the DC directly!" ) def _check(self): """Basic parameter validation.""" if self.acl is not None: assert self.acl in ['G', 'E'], ( "Invalid form of autocorrelation function specified" ) if self.surface != 'WaterCloud': assert self.s is not None, "Surface RMS height (s) is required" if self.eps is None: assert self.mv is not None, "Either eps or mv needs to be given!" if self.mv is None: assert self.eps is not None, "Either eps or mv needs to be given!"