"""
====================
emc2.core.instrument
====================
This module stores the Instrument class.
"""
import numpy as np
import os
import xarray as xr
from pint import UnitRegistry
from ..io import load_arm_file
from ..io import load_mie_file, load_scat_file, load_bulk_scat_file
ureg = UnitRegistry()
quantity = ureg.Quantity
[docs]
class Instrument(object):
"""
This is the base class which holds the information needed to contain the instrument parameters for the
simulator.
Attributes
----------
instrument_str: str
The name of the instrument.
instrument_class: str
The class of the instrument. Currently must be one of 'radar,' or 'lidar'.
freq: float
The frequency of the instrument.
wavelength: float
The wavelength of the instrument
beta_p_phase_thresh: list of dicts or None
If a list, each index contains a dictionaly with class name, class integer
value (mask order), LDR value bounds, and the corresponding beta_p threshold
(thresholds are linearly interpolated between LDR values). In order for the
method to operate properly, the list should be arranged from the lowest to
highest beta_p threshold values for a given LDR, that is,
beta_p[i+1 | LDR=x] >= beta_p[i | LDR=x].
ext_OD: float
The optical depth where we have full extinction of the lidar signal.
OD_from_sfc: Bool
If True (default), optical depth will be calculated from the surface. If False,
optical depth will be calculated from the top of the atmosphere.
eta: float
Multiple scattering coefficient.
K_w: float
The index of refraction of water used for Ze calculation.
See the ARM KAZR handbook (Widener et al. 2012)
eps_liq: float
The complex dielectric constant for liquid water.
pt: float
Transmitting power in Watts.
theta: float
3 dB beam width in degrees
gain: float
The antenna gain in linear units.
Z_min_1km: float
The minimum detectable signal at 1 km in dBZ
lr: float
Attenuation based on the the general attributes in the spectra files.
pr_noise_ge: float
Minimum detectable signal in mW.
tau_ge: float
Pulse width in mus.
tau_md: float
Pulse width in mus.
"""
[docs]
def __init__(self, frequency=None, wavelength=None):
self.instrument_str = ""
self.instrument_class = ""
self.freq = np.nan
self.wavelength = np.nan
self.beta_p_phase_thresh = []
self.ext_OD = np.nan
self.OD_from_sfc = True
self.eta = np.nan
self.K_w = np.nan
self.eps_liq = np.nan
self.location_code = ""
self.pt = np.nan
self.theta = np.nan
self.gain = np.nan
self.Z_min_1km = np.nan
self.lr = np.nan
self.pr_noise_ge = np.nan
self.pr_noise_md = np.nan
self.tau_ge = np.nan
self.tau_md = np.nan
self.c = 299792458.0 # m/s
self.R_d = 287.058 # J K^-1 Kg^-1
self.g = 9.80665 # m/s^2
self.rho_i = 917 * ureg.kg / (ureg.m**3) # kg/m^3 (0 C 1013.25 hPa, typical model accuracy)
self.rho_l = 1000 * ureg.kg / (ureg.m**3) # kg/m^3 (typical model accuracy)
self.scatterer = None
if frequency is None and wavelength is None:
raise ValueError("Your instrument must have a frequency or wavelength!")
if frequency is None:
self.freq = self.c / wavelength.to('meter').magnitude
self.wavelength = wavelength.to('micrometer').magnitude
elif wavelength is None:
self.freq = frequency.to('Hz').magnitude
self.wavelength = self.c / self.freq * 1e6
else:
self.freq = frequency.to('Hz').magnitude
self.wavelength = wavelength.to('micrometer').magnitude
self.mie_table = {}
self.scat_table = {} # scattering calculation LUTs (e.g., C6 or m-D, A-D relationships).
self.bulk_table = {}
self.scatterer = {}
self.ds = None
[docs]
def read_arm_netcdf_file(self, filename, **kwargs):
"""
Loads a netCDF file that corresponds to ARM standards.
Parameters
----------
filename: str
Additional keyword arguments are passed into :py:func:`act.io.arm.read_arm_netcdf`
"""
self.ds = load_arm_file(filename, **kwargs)
[docs]
def load_instrument_scat_files(self, supercooled=True, *args, **kwargs):
"""
Load scattering and bulk scattering tables for the specified instrument.
This function loads Mie scattering tables, bulk scattering tables, and other
related data files based on the instrument type (radar or lidar) and specific
configurations. It supports different datasets for various instruments and
conditions, such as supercooled liquid water.
Parameters
==========
supercooled: bool
If True, loading LUTs for Temperature of -10 C. Otherwise 25 c.
*args: dict
Additional arguments for specific configurations.
**kwargs: dict
Additional keyword arguments for specific configurations.
"""
inst = self.instrument_str
is_radar = self.instrument_class == "radar" # if False, assuming lidar
# Load mie tables
data_path = os.path.join(os.path.dirname(__file__), 'mie_tables')
if supercooled & (self.instrument_str not in ["RL", "HSRL"]): # negligible T effect at UV-VIS wavelengths
self.mie_table["cl"] = load_mie_file(data_path + f"/Mie{inst}_liq_c.dat") # Rowe et al. (2020) -10 C
self.mie_table["pl"] = load_mie_file(data_path + f"/Mie{inst}_liq_c.dat")
else:
self.mie_table["cl"] = load_mie_file(data_path + f"/Mie{inst}_liq.dat") # Segelstein (1981) 25 C
self.mie_table["pl"] = load_mie_file(data_path + f"/Mie{inst}_liq.dat")
self.mie_table["ci"] = load_mie_file(data_path + f"/Mie{inst}_ci.dat")
if 'DHARMA' in args:
self.mie_table["pi"] = load_mie_file(
data_path + f"/Mie{inst}_pi1.dat") # pi1 for 100 kg/m^2 (DHARMA)
else:
self.mie_table["pi"] = load_mie_file(data_path + f"/Mie{inst}_pi.dat")
# ModelE3 bulk
data_path = os.path.join(os.path.dirname(__file__), 'c6_tables')
self.scat_table["E3_ice"] = load_scat_file(data_path + f"/C6_{inst}_8col_agg_rough_270K.dat", is_radar)
data_path = os.path.join(os.path.dirname(__file__), "bulk_c6_tables")
self.bulk_table["E3_ice"] = load_bulk_scat_file(
data_path + f"/bulk_{inst}_C6PSD_c6_8col_ice_agg_rough_270K.dat")
if supercooled & (self.instrument_str not in ["RL", "HSRL"]): # negligible T effect at UV-VIS wavelengths
self.bulk_table["E3_liq"] = load_bulk_scat_file(data_path + f"/bulk_{inst}_C6PSD_mie_liq_c.dat")
else:
self.bulk_table["E3_liq"] = load_bulk_scat_file(data_path + f"/bulk_{inst}_C6PSD_mie_liq.dat")
self.bulk_table["mie_ice_E3_PSD"] = load_bulk_scat_file(data_path + f"/bulk_{inst}_C6PSD_mie_ice.dat")
# CESM/E3SM bulk
data_path = os.path.join(os.path.dirname(__file__), "mDAD_tables")
self.scat_table["CESM_ice"] = load_scat_file(data_path + f"/mDAD_{inst}_ice.dat", is_radar, param_type="mDAD")
data_path = os.path.join(os.path.dirname(__file__), "bulk_mDAD_tables")
self.bulk_table["CESM_ice"] = load_bulk_scat_file(
data_path + f"/bulk_{inst}_mDAD_mDAD_ice_263K.dat", param_type="mDAD")
if supercooled & (self.instrument_str not in ["RL", "HSRL"]): # negligible T effect at UV-VIS wavelengths
self.bulk_table["CESM_liq"] = xr.open_dataset(data_path + f"/bulk_{inst}_mDAD_mie_liq_c.nc")
else:
self.bulk_table["CESM_liq"] = xr.open_dataset(data_path + f"/bulk_{inst}_mDAD_mie_liq.nc")
self.bulk_table["mie_ice_CESM_PSD"] = load_bulk_scat_file(data_path + f"/bulk_{inst}_mDAD_mie_ice.dat",
param_type="mDAD")