Source code for saltshaker.constraints

"""
SALT-specific observation constraints for astroplan.

This module provides constraint classes that integrate with the `astroplan` 
scheduling ecosystem to enforce SALT's unique tracking and lunar limits.
"""

from astroplan import Constraint
import astropy.units as u
from saltshaker.model import get_model
import numpy as np

[docs] class SaltTrackLengthConstraint(Constraint): """ Constraint on the remaining track length of a target. This constraint ensures that at any given time, the target has a remaining track length of at least `min_track_length`. This is essential for scheduling exposures that must not be interrupted by the tracker reaching its physical limit. Attributes: min_track_length (Quantity): The minimum required track duration. tracking_model (SaltTrackingModel): The singleton model used for calculations. """ def __init__(self, min_track_length=None): """ Initializes the SaltTrackLengthConstraint. Args: min_track_length (Quantity | None): The minimum duration needed. Defaults to 0 seconds. """ self.min_track_length = min_track_length if min_track_length is not None else 0 * u.second self.tracking_model = get_model()
[docs] def compute_constraint(self, times, observer, targets): """ Evaluates the constraint for a set of times and targets. This implementation is fully vectorized for maximum performance. """ if times.isscalar: times_arr = times[None] else: times_arr = times # 1. Calculate LST for all times (Astroplan does this efficiently) lst = observer.local_sidereal_time(times_arr) # 2. Initialize result array (n_targets x n_times) constraint_result = np.zeros((len(targets), len(times_arr)), dtype=bool) for i, target in enumerate(targets): declination = target.dec.to(u.deg).value ra = target.ra # 3. Calculate all hour angles at once (Vectorized) ha = (lst - ra).to(u.hourangle).value # 4. Normalize HAs to [-12, 12] range (Vectorized) ha[ha > 12] -= 24 ha[ha < -12] += 24 # 5. Get all track lengths at once (Vectorized call to optimized model) track_lens = self.tracking_model.track_length(declination, ha) * u.second # 6. Apply constraint (Vectorized) constraint_result[i] = track_lens >= self.min_track_length return constraint_result
[docs] class SaltMoonConstraint(Constraint): """ Constraint on Moon phase and position. This constraint is satisfied if: 1. The Moon's illuminated fraction is less than or equal to `max_illumination`. 2. OR, the Moon is currently below the horizon. Attributes: max_illumination (float): Maximum allowed illuminated fraction (0 to 1). """ def __init__(self, max_illumination=1.0): """ Initializes the SaltMoonConstraint. Args: max_illumination (float): Maximum illuminated fraction. Defaults to 1.0 (no constraint). """ self.max_illumination = max_illumination
[docs] def compute_constraint(self, times, observer, targets): """ Evaluates the constraint for a set of times and targets. """ if times.isscalar: times_arr = times[None] else: times_arr = times from astroplan import moon_illumination illum = moon_illumination(times_arr) illum_ok = illum <= self.max_illumination moon_altaz = observer.moon_altaz(times_arr) moon_down = moon_altaz.alt <= 0 * u.deg # The constraint is satisfied if: # (Moon illumination <= max) OR (Moon is below horizon) satisfied_global = np.logical_or(illum_ok, moon_down) # Expand to (n_targets x n_times) return np.tile(satisfied_global, (len(targets), 1))