Source code for sasktran2.solar.model

from __future__ import annotations

import numpy as np
import xarray as xr
from scipy.integrate import cumulative_trapezoid

import sasktran2 as sk


[docs] class SolarModel:
[docs] def __init__( self, source="solar_irradiance_hsrs_2022_11_30_extended", ds=None, mode="sample", resolution=None, resolution_in_wavelength=True, ): """ Loads in a solar model for use inside SASKTRAN2. By default the HSRS extended solar model is loaded in that has a useful wavelength range covering 115 nm to 200 microns and intgrates to 1362.8 W/m^2. Coddington, O. M., Richard, E. C., Harber, D., Pilewskie, P., Woods, T. N., Snow, M., et al. (2023). Version 2 of the TSIS-1 Hybrid Solar Reference Spectrum and Extension to the Full Spectrum. Earth and Space Science, 10, e2022EA002637. https://doi.org/10.1029/2022EA002637 Optionally a manually specified dataset can be used, which must contain the variables "wavelength" and "irradiance". It is assumed that "wavelength" is in units of nm and "irradiance" has spectral units of /nm. Parameters ---------- source: str, optional sasktran2 database source identifier. Currently only "solar_irradiance_hsrs_2022_11_30_extended" is supported and is the default ds: xr.Dataset, optional If set, then the dataset is used instead of the source. The dataset must contain the variables "wavelength" and "irradiance" default is None mode: str, optional The mode of the solar model. Options are "integrate", "average", "sample". Default is "sample". If "integrate" is selected then the solar spectrum is integrated over the wavelength range of interest. If "average" is selected then the solar spectrum is averaged over the wavelength range of interest. If "sample" is selected then the solar spectrum is sampled at the wavelengths of interest. The "integrate" and "average" modes are useful for calculating the solar irradiance at a specific wavelength range and the "sample" mode is useful for calculating the solar irradiance at specific wavelengths. resolution: float, optional The resolution to integrate the solar spectrum at. If None, then the resolution is determined by the wavelength grid. Default is None resolution_in_wavelength: bool, optional If True, then the resolution is in wavelength units. If False, then the resolution is in wavenumber units. Default is True """ if ds is not None: self._ds = ds else: self._ds = sk.database.StandardDatabase().path(f"solar/{source}.nc") self._ds = xr.open_dataset(self._ds) self._wv = self._ds["wavelength"].to_numpy() self._irrad = self._ds["irradiance"].to_numpy() self._ds.close() self._mode = mode.lower() if self._mode == "integrate" or self._mode == "average": self._integral = cumulative_trapezoid( self._ds["irradiance"].values, self._ds["wavelength"].values ) elif self._mode == "sample": self._integral = None else: msg = "Invalid mode" raise ValueError(msg) self._resolution = resolution self._resolution_in_wavelength = resolution_in_wavelength
[docs] def irradiance( self, wavelengths: np.ndarray, solardistance: float | None = None ) -> np.array: """ Calculates the solar irradiance at the specified wavelengths in nm. Default units are in W/m^2/nm, but can be different if a manual database is used instead. If the mode is set to "integrate" then the units will be W/m^2. The solar distance factor can be used to scale the solar irradiance to a different distance. The default is None indicating that the solar irradiance is at 1 AU. Parameters ---------- wavelengths : np.ndarray Wavelengths in [nm] to calculate the solar irradiance at solardistance : float, optional Solar distance to scale by in AU., by default None Returns ------- np.array The solar irradiance at the specified wavelengths """ solar_distance_factor = ( 1 / (solardistance**2) if solardistance is not None else 1 ) if self._mode == "sample": return np.interp(wavelengths, self._wv, self._irrad) * solar_distance_factor # Have to calculate the wavelength intervals # and integrate the spectrum over the intervals if self._resolution is not None: if self._resolution_in_wavelength: left_intervals = wavelengths - self._resolution / 2 right_intervals = wavelengths + self._resolution / 2 else: left_intervals = 1e7 / (1e7 / wavelengths + self._resolution / 2) right_intervals = 1e7 / (1e7 / wavelengths - self._resolution / 2) else: left_intervals = np.concat( ([wavelengths[0]], np.diff(wavelengths) / 2 + wavelengths[:-1]) ) right_intervals = np.concat( (np.diff(wavelengths) / 2 + wavelengths[:-1], [wavelengths[-1]]) ) lidx = np.searchsorted(self._ds["wavelength"].values, left_intervals) - 1 ridx = np.searchsorted(self._ds["wavelength"].values, right_intervals) - 1 left_vals = np.interp(left_intervals, self._wv, self._irrad) right_vals = np.interp(right_intervals, self._wv, self._irrad) # Cumlative integral to left side I_left = self._integral[lidx] + 0.5 * (self._irrad[lidx] + left_vals) * ( left_intervals - self._wv[lidx] ) I_right = self._integral[ridx] + 0.5 * (self._irrad[ridx] + right_vals) * ( right_intervals - self._wv[ridx] ) if self._mode == "integrate": return (I_right - I_left) * solar_distance_factor return ( (I_right - I_left) / (right_intervals - left_intervals) * solar_distance_factor )
# TODO: Implement doppler shift?