Source code for across.tools.visibility.constraints.airmass

from typing import Literal

import astropy.units as u  # type: ignore[import-untyped]
import numpy as np
import numpy.typing as npt
from astropy.coordinates import AltAz, SkyCoord  # type: ignore[import-untyped]
from astropy.time import Time  # type: ignore[import-untyped]
from pydantic import Field

from ...core.enums import ConstraintType
from ...ephemeris import Ephemeris
from .base import ConstraintABC, get_slice


[docs] class AirmassConstraint(ConstraintABC): """ Constraint based on airmass (zenith angle) for ground-based telescopes. Airmass affects the quality of astronomical observations. Higher airmass means more atmospheric absorption and poorer image quality. This constraint ensures observations are conducted at acceptable airmass values. Parameters ---------- max_air_mass : float Maximum allowed airmass. Observations with higher airmass will be constrained. Typical values: 1.5-2.0 for good quality, up to 3.0 for some surveys. Methods ------- __call__(time, ephemeris, coordinate) Checks if the airmass is too high for the given coordinate and time. """
[docs] name: Literal[ConstraintType.AIRMASS] = ConstraintType.AIRMASS
[docs] short_name: Literal["Airmass"] = "Airmass"
[docs] max_air_mass: float = Field(default=2.0, gt=1.0, description="Maximum allowed airmass for observations")
[docs] def __call__(self, time: Time, ephemeris: Ephemeris, coordinate: SkyCoord) -> npt.NDArray[np.bool_]: """ Check if the airmass is too high for observation. Parameters ---------- time : Time The time of observation. ephemeris : Ephemeris The ephemeris (used to get Earth location). coordinate : SkyCoord The coordinate to check. Returns ------- npt.NDArray[np.bool_] Boolean array where True indicates the airmass is too high (constraint violated). """ if ephemeris.earth_location is None: raise ValueError("Earth location required for airmass calculations") # Find the slice of ephemeris data we need i = get_slice(time, ephemeris) # Convert coordinate to AltAz for the given time and location self.computed_values.alt_az = coordinate.transform_to( AltAz(obstime=time[i], location=ephemeris.earth_location) ) # Calculate airmass using Kasten-Young formula for high accuracy h_deg = self.computed_values.alt_az.alt.to_value(u.deg) # Clip altitudes to valid range for airmass calculation to avoid # unphysical values when below horizon or at zenith. This also prevents # division by zero in the formula. h_deg = np.clip(h_deg, 0.0, 90.0) self.computed_values.air_mass = 1 / ( np.sin(np.radians(h_deg)) + 0.50572 * (h_deg + 6.07995) ** (-1.6364) ) # Constrain observations with airmass above the maximum in_constraint: npt.NDArray[np.bool_] = self.computed_values.air_mass > self.max_air_mass return in_constraint