Skip to content

Commit

Permalink
adding alt,az limits to conditions object
Browse files Browse the repository at this point in the history
  • Loading branch information
yoachim committed Jan 18, 2024
1 parent 0e1e46c commit e37b007
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
72 changes: 72 additions & 0 deletions rubin_scheduler/scheduler/basis_functions/mask_basis_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"MaskAzimuthBasisFunction",
"SolarElongationMaskBasisFunction",
"AreaCheckMaskBasisFunction",
"AltAzShadowMaskBasisFunction",
)

import healpy as hp
Expand Down Expand Up @@ -194,6 +195,77 @@ def _calc_value(self, conditions, indx=None):
return result


class AltAzShadowMaskBasisFunction(BaseBasisFunction):
"""Mask any out of range altitudes and azimuths, then extend the
mask so if observations are taken in pairs, the second in the pair will
not have moved into a masked region.
"""

def __init__(
self,
nside=None,
min_alt=20.0,
max_alt=82.0,
shadow_minutes=40.0,
):
super(AltAzShadowMaskBasisFunction, self).__init__(nside=nside)
self.min_alt = np.radians(min_alt)
self.max_alt = np.radians(max_alt)
self.shadow_time = shadow_minutes / 60.0 / 24.0 # To days
self.result = np.zeros(hp.nside2npix(self.nside), dtype=float)
self.in_range = np.zeros(hp.nside2npix(self.nside), dtype=int)

def _calc_value(self, conditions, indx=None):
# Mask everything to start
result = self.result.copy() + np.nan

in_range_alt = self.in_range.copy()
in_range_az = self.in_range.copy()

# Compute the alt,az values in the future. Use the conditions object
# so the results are cached and can be used by other surveys is needed.
# Technically this could fail if the masked region is very narrow or shadow time
# is very large.
future_alt, future_az = conditions.future_alt_az(np.max(conditions.mjd + self.shadow_time))

# apply limits from the conditions object
for limits in conditions.alt_limits:
good = np.where(
(IntRounded(conditions.alt) >= IntRounded(np.min(limits)))
& (IntRounded(conditions.alt) <= IntRounded(np.max(limits)))
)[0]
in_range_alt[good] += 1
good = np.where(
(IntRounded(future_alt) >= IntRounded(np.min(limits)))
& (IntRounded(future_alt) <= IntRounded(np.max(limits)))
)[0]
in_range_alt[good] += 1

for limits in conditions.az_limits:
good = np.where(
(IntRounded(conditions.az) >= IntRounded(np.min(limits)))
& (IntRounded(conditions.az) <= IntRounded(np.max(limits)))
)[0]
in_range_az[good] += 1
good = np.where(
(IntRounded(future_az) >= IntRounded(np.min(limits)))
& (IntRounded(future_az) <= IntRounded(np.max(limits)))
)[0]
in_range_az[good] += 1

passed_all = np.where((in_range_alt > 1) & (in_range_az > 1))[0]
result[passed_all] = 0

# Apply additional alt constraint in case we want to be more conservative than the limit
result[np.where(IntRounded(conditions.alt) < IntRounded(self.min_alt))] = np.nan
result[np.where(IntRounded(conditions.alt) > IntRounded(self.max_alt))] = np.nan

result[np.where(IntRounded(future_alt) < IntRounded(self.min_alt))] = np.nan
result[np.where(IntRounded(future_alt) > IntRounded(self.max_alt))] = np.nan

return result


class ZenithShadowMaskBasisFunction(BaseBasisFunction):
"""Mask the zenith, and things that will soon pass near zenith. Useful for making sure
observations will not be too close to zenith when they need to be observed again (e.g. for a pair).
Expand Down
31 changes: 31 additions & 0 deletions rubin_scheduler/scheduler/features/conditions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__all__ = ("Conditions",)

import functools
import warnings
from io import StringIO

Expand Down Expand Up @@ -169,6 +170,12 @@ def __init__(
Dictionary of planet name and coordinate e.g., 'venus_RA', 'mars_dec'
scheduled_observations : np.array
A list of MJD times when there are scheduled observations. Defaults to empty array.
tel_az_limits : list of float pairs
A list of lists giving valid azimuth ranges. e.g., [0, 2*np.pi] would
mean all azimuth values are valid, while [[0, np.pi/2], [3*np.pi/2, 2*np.pi]]
would mean anywhere in the south is invalid. Radians.
tel_alt_limits : list of float pairs
A list of lists giving valid altitude ranges. Radians.
Attributes (calculated on demand and cached)
------------------------------------------
Expand Down Expand Up @@ -283,6 +290,10 @@ def _init_attributes(self):
self.tel_az = None
self.cumulative_azimuth_rad = None

# Telescope limits
self.tel_az_limits = None
self.tel_alt_limits = None

# Full sky cloud map
self._cloud_map = None
self._HA = None
Expand Down Expand Up @@ -387,6 +398,26 @@ def calc_alt_az(self):
self._mjd,
)

@functools.lru_cache(maxsize=10)
def future_alt_az(self, mjd):
"""Compute the altitude and azimuth for a future time.
Returns
-------
altitude : `np.array`
The altutude of each healpix at MJD (radians)
azimuth : : `np.array`
The azimuth of each healpix at MJD (radians)
"""
alt, az = _approx_ra_dec2_alt_az(
self.ra,
self.dec,
self.site.latitude_rad,
self.site.longitude_rad,
mjd,
)
return alt, az

@property
def mjd(self):
return self._mjd
Expand Down
6 changes: 6 additions & 0 deletions rubin_scheduler/scheduler/model_observatory/kinem_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ def setup_telescope(
azimuth_accel=7.0,
azimuth_decel=7.0,
settle_time=3.0,
az_limits=None,
alt_limits=None,
):
"""Parameters to define the TELESCOPE movement and position.
Expand Down Expand Up @@ -240,6 +242,10 @@ def setup_telescope(
self.telaz_accel_rad = np.radians(azimuth_accel)
self.telaz_decel_rad = np.radians(azimuth_decel)
self.mount_settletime = settle_time
if alt_limits is None:
self.alt_limits = [[self.telalt_minpos_rad, self.telalt_maxpos_rad]]
if az_limits is None:
self.az_limits = [[0, 2.0 * np.pi]]

def setup_optics(self, ol_slope=1.0 / 3.5, cl_delay=[0.0, 36.0], cl_altlimit=[0.0, 9.0, 90.0]):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ def return_conditions(self):

self.conditions.mjd_start = self.mjd_start

# Telescope limits
self.conditions.az_limits = self.observatory.az_limits
self.conditions.alt_limits = self.observatory.alt_limits

# Planet positions from almanac
self.conditions.planet_positions = self.almanac.get_planet_positions(self.mjd)

Expand Down
16 changes: 16 additions & 0 deletions rubin_scheduler/scheduler/surveys/scripted_surveys.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,22 @@ def _check_alts_ha(self, observation, conditions):
& ((HA > observation["HA_max"]) | (HA < observation["HA_min"]))
& (conditions.sun_alt < observation["sun_alt_max"])
)[0]

# Also check the alt,az limits given by the conditions object
count = in_range * 0
for limits in conditions.alt_limits:
ir = np.where((alt[in_range] >= np.min(limits)) & (alt[in_range] <= np.max(limits)))[0]
count[ir] += 1
good = np.where(count > 0)[0]
in_range = in_range[good]

count = in_range * 0
for limits in conditions.az_limits:
ir = np.where((az[in_range] >= np.min(limits)) & (az[in_range] <= np.max(limits)))[0]
count[ir] += 1
good = np.where(count > 0)[0]
in_range = in_range[good]

return in_range

def _check_list(self, conditions):
Expand Down

0 comments on commit e37b007

Please sign in to comment.