Source code for pyEDITH.observation

import numpy as np
from .units import *
from . import utils
import logging
from pyEDITH import parse_input

logger = logging.getLogger("pyEDITH")


[docs] class Observation: """ A class representing an astronomical observation. This class encapsulates various parameters and methods related to astronomical observations, including target star properties, planet characteristics, observational settings, telescope specifications, instrument details, and detector parameters. Parameters ----------- wavelength : np.ndarray Wavelength array (in microns). nlambd : int Number of wavelength points. SNR : np.ndarray Desired bulk SNR. exptime : ndarray Exposure time per single wavelength datapoint. fullsnr : ndarray Signal-to-noise ratio per single wavelength datapoint. td_limit : float Limit placed on exposure times. """ def __init__(self) -> None: """ Initialize the default parameters of the Observation class. Sets the default exposure time limit (td_limit) to a large value. """ # Misc parameters that probably don't need to be changed self.td_limit = 1e20 * TIME # limit placed on exposure times # scalar
[docs] def load_configuration(self, parameters: dict) -> None: """ Load configuration parameters for the observation from a dictionary. This method initializes various observation parameters from the provided dictionary, including wavelength arrays, signal-to-noise ratios, and aperture settings. For IFS mode, it can calculate or regrid the wavelength grid based on specified parameters. Grid model ---------- This method is the place where the *resolved* wavelength grid is defined, and it is designed to be safely re-callable (e.g. inside a loop that rebins the grid between iterations). To make that safe: * ``parameters["wavelength"]`` is stored verbatim as ``self._input_wavelength`` -- the pristine, pre-regrid source grid. This is the single source of truth used as ``from_wavelength`` and is NEVER overwritten with a regridded array. * Per-wavelength inputs (currently ``snr``) are parsed with ``parse_parameters`` and then aligned onto the current resolved grid via ``regrid_to_grid``, always regridding FROM the input wavelength. Parameters ---------- parameters : dict A dictionary containing observation parameters including wavelengths, SNR values, aperture settings, and observation mode settings. Must include 'observing_mode', 'wavelength', 'snr'. Raises ------ KeyError If required parameters are missing or if regridding is requested without necessary parameters """ parameters = parse_input.parse_parameters(parameters) self.observing_mode = parameters["observing_mode"] # ------------------------------------------------------------------ # Pristine source grid: the single source of truth for from_wavelength. # Store it once, before any rebinning, and never overwrite it with a # regridded array. Everything per-wavelength is regridded FROM this. # ------------------------------------------------------------------ self._input_wavelength = np.asarray(parameters["wavelength"], dtype=np.float64) # -------- INPUTS --------- # Observational parameters if parameters["observing_mode"] == "IMAGER": self.wavelength = ( parameters["wavelength"] * WAVELENGTH ) # wavelength # nlambd array #unit: micron # IMAGER has no meaningful bin widths for regridding; broadcast-only self.delta_wavelength = None elif ( parameters["observing_mode"] == "IFS" and bool(parameters["regrid_wavelength"]) is False ): self.wavelength = ( parameters["wavelength"] * WAVELENGTH ) # wavelength # nlambd array #unit: micron IFS_resolution = self.wavelength / np.gradient( self.wavelength ) # calculate the resolution from the wavelength grid dlam_um = np.gradient(self.wavelength) if ~np.isfinite(IFS_resolution).any(): logger.warning( "Wavelength grid is not valid. Using default spectral resolution of 140." ) IFS_resolution = 140 * np.ones_like( self.wavelength ) # default resolution dlam_um = self.wavelength / IFS_resolution self.delta_wavelength = dlam_um elif ( parameters["observing_mode"] == "IFS" and bool(parameters["regrid_wavelength"]) is True ): logger.info("Calculating a new wavelength grid and re-gridding spectra...") new_lam, new_dlam = utils.regrid_wavelengths( self._input_wavelength, parameters["spectral_resolution"], parameters["lam_low"], parameters["lam_high"], ) self.wavelength = ( new_lam * WAVELENGTH ) # wavelength # nlambd array #unit: micron self.delta_wavelength = new_dlam * WAVELENGTH # ------------------------------------------------------------------ # Length of the current resolved grid. Any per-wavelength parameter is # aligned against this via regrid_to_grid. # ------------------------------------------------------------------ self.nlambd = len(self.wavelength) # Target bin widths (as plain floats) for the regrid branch, if defined. to_delta = ( None if self.delta_wavelength is None else np.asarray(self.delta_wavelength.value, dtype=np.float64) ) # ------------------------------------------------------------------ # SNR: align onto the current resolved grid. # ------------------------------------------------------------------ self.SNR = utils.regrid_to_grid( parameters["snr"] * DIMENSIONLESS, from_wavelength=self._input_wavelength, to_wavelength=self.wavelength.value, to_delta_wavelength=to_delta, name="snr", interpolation="1d", ) # signal to noise # nlambd array self.CRb_multiplier = float(parameters["CRb_multiplier"])
[docs] def set_output_arrays(self): """ Initialize arrays for storing observation results. This method creates and initializes the arrays that will store the calculated exposure times and signal-to-noise ratios for each wavelength point in the observation. """ # Initialize some arrays needed for outputs... self.exptime = np.full((self.nlambd), 0.0) * TIME # only used for snr calculation self.fullsnr = np.full((self.nlambd), 0.0) * DIMENSIONLESS
[docs] def validate_configuration(self): """ Validate that all required observation parameters are present and correctly formatted. This method checks that all mandatory attributes exist on the observation object and that they have the expected types and units. Raises ------ TypeError If an attribute has an incorrect type ValueError If a Quantity attribute has incorrect units """ expected_args = { "wavelength": WAVELENGTH, "nlambd": int, "SNR": DIMENSIONLESS, "CRb_multiplier": float, } utils.validate_attributes(self, expected_args)