Coverage for python/lsst/sims/featureScheduler/basis_functions/basis_functions.py : 14%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
'Avoid_Fast_Revists', 'Visit_repeat_basis_function', 'M5_diff_basis_function', 'Strict_filter_basis_function', 'Goal_Strict_filter_basis_function', 'Filter_change_basis_function', 'Slewtime_basis_function', 'Aggressive_Slewtime_basis_function', 'Skybrightness_limit_basis_function', 'CableWrap_unwrap_basis_function', 'Cadence_enhance_basis_function', 'Azimuth_basis_function', 'Az_modulo_basis_function', 'Dec_modulo_basis_function', 'Template_generate_basis_function', 'Footprint_nvis_basis_function', 'Third_observation_basis_function']
"""Class that takes features and computes a reward function when called. """
# Set if basis function needs to be recalculated if there is a new observation self.update_on_newobs = True # Set if basis function needs to be recalculated if conditions change self.update_on_mjd = True # Dict to hold all the features we want to track self.survey_features = {} # Keep track of the last time the basis function was called. If mjd doesn't change, use cached value self.mjd_last = None self.value = 0 # list the attributes to compare to check if basis functions are equal. self.attrs_to_compare = [] # Do we need to recalculate the basis function self.recalc = True # Basis functions don't technically all need an nside, but so many do might as well set it here if nside is None: self.nside = utils.set_default_nside() else: self.nside = nside
self.filtername = filtername
""" Parameters ---------- observation : np.array An array with information about the input observation indx : np.array The indices of the healpix map that the observation overlaps with """ for feature in self.survey_features: self.survey_features[feature].add_observation(observation, indx=indx) if self.update_on_newobs: self.recalc = True
"""If there is logic to decide if something is feasible (e.g., only if moon is down), it can be calculated here. Helps prevent full __call__ from being called more than needed. """ return True
self.value = 0 # Update the last time we had an mjd self.mjd_last = conditions.mjd + 0 self.recalc = False return self.value
# XXX--to work on if we need to make a registry of basis functions. pass
pass
""" Parameters ---------- conditions : lsst.sims.featureScheduler.features.conditions object Object that has attributes for all the current conditions.
Return a reward healpix map or a reward scalar. """ # If we are not feasible, return -inf if not self.check_feasibility(conditions): return -np.inf if self.recalc: self.value = self._calc_value(conditions, **kwargs) if self.update_on_mjd: if conditions.mjd != self.mjd_last: self.value = self._calc_value(conditions, **kwargs) return self.value
"""Just add a constant """ return 1
"""Basis function that tracks number of observations and tries to match a specified spatial distribution
Parameters ---------- filtername: (string 'r') The name of the filter for this target map. nside: int (default_nside) The healpix resolution. target_map : numpy array (None) A healpix map showing the ratio of observations desired for all points on the sky norm_factor : float (0.00010519) for converting target map to number of observations. Should be the area of the camera divided by the area of a healpixel divided by the sum of all your goal maps. Default value assumes LSST foV has 1.75 degree radius and the standard goal maps. If using mulitple filters, see lsst.sims.featureScheduler.utils.calc_norm_factor for a utility that computes norm_factor. out_of_bounds_val : float (-10.) Reward value to give regions where there are no observations requested (unitless). """ norm_factor=None, out_of_bounds_val=-10.):
super(Target_map_basis_function, self).__init__(nside=nside, filtername=filtername)
if norm_factor is None: warnings.warn('No norm_factor set, use utils.calc_norm_factor if using multiple filters.') self.norm_factor = 0.00010519 else: self.norm_factor = norm_factor
self.survey_features = {} # Map of the number of observations in filter self.survey_features['N_obs'] = features.N_observations(filtername=filtername, nside=self.nside) # Count of all the observations self.survey_features['N_obs_count_all'] = features.N_obs_count(filtername=None) if target_map is None: self.target_map = utils.generate_goal_map(filtername=filtername, nside=self.nside) else: self.target_map = target_map self.out_of_bounds_area = np.where(self.target_map == 0)[0] self.out_of_bounds_val = out_of_bounds_val self.result = np.zeros(hp.nside2npix(self.nside), dtype=float) self.all_indx = np.arange(self.result.size)
""" Parameters ---------- indx : list (None) Index values to compute, if None, full map is computed Returns ------- Healpix reward map """
result = self.result.copy() if indx is None: indx = self.all_indx
# Find out how many observations we want now at those points goal_N = self.target_map[indx] * self.survey_features['N_obs_count_all'].feature * self.norm_factor
result[indx] = goal_N - self.survey_features['N_obs'].feature[indx] result[self.out_of_bounds_area] = self.out_of_bounds_val
return result
"""Basis function to drive observations of a given footprint. Good to target of opportunity targets where one might want to observe a region 3 times.
Parameters ---------- footprint : np.array A healpix array (1 for desired, 0 for not desired) of the target footprint. nvis : int (1) The number of visits to try and gather """ nvis=1, out_of_bounds_val=np.nan): super(Footprint_nvis_basis_function, self).__init__(nside=nside, filtername=filtername) self.footprint = footprint self.nvis = nvis
# Have a feature that tracks how many observations we have self.survey_features = {} # Map of the number of observations in filter self.survey_features['N_obs'] = features.N_observations(filtername=filtername, nside=self.nside) self.result = np.zeros(hp.nside2npix(nside)) self.result.fill(out_of_bounds_val) self.out_of_bounds_val = out_of_bounds_val
result = self.result.copy() diff = self.footprint*self.nvis - self.survey_features['N_obs'].feature
result[np.where(diff > 0)] = 1
# Any spot where we have enough visits is out of bounds now. result[np.where(diff <= 0)] = self.out_of_bounds_val return result
"""If there have been observations in two filters long enough ago, go for a third
Parameters ---------- gap_min : float (40.) The minimum time gap to consider a pixel good (minutes) gap_max : float (120) The maximum time to consider going for a pair (minutes) """
super(Third_observation_basis_function, self).__init__(nside=nside) self.filtername1 = filtername1 self.filtername2 = filtername2 self.gap_min = gap_min/60./24. self.gap_max = gap_max/60./24.
self.survey_features = {} self.survey_features['last_obs_f1'] = features.Last_observed(filtername=filtername1, nside=nside) self.survey_features['last_obs_f2'] = features.Last_observed(filtername=filtername2, nside=nside) self.result = np.empty(hp.nside2npix(self.nside)) self.result.fill(np.nan)
result = self.result.copy() d1 = conditions.mjd - self.survey_features['last_obs_f1'].feature d2 = conditions.mjd - self.survey_features['last_obs_f2'].feature good = np.where((d1 > self.gap_min) & (d1 < self.gap_max) & (d2 > self.gap_min) & (d2 < self.gap_max)) result[good] = 1 return result
"""Marks targets as unseen if they are in a specified time window in order to avoid fast revisits.
Parameters ---------- filtername: (string 'r') The name of the filter for this target map. gap_min : float (25.) Minimum time for the gap (minutes). nside: int (default_nside) The healpix resolution. penalty_val : float (np.nan) The reward value to use for regions to penalize. Will be masked if set to np.nan (default). """ penalty_val=np.nan): super(Avoid_Fast_Revists, self).__init__(nside=nside, filtername=filtername)
self.filtername = filtername self.penalty_val = penalty_val
self.gap_min = gap_min/60./24. self.nside = nside
self.survey_features = dict() self.survey_features['Last_observed'] = features.Last_observed(filtername=filtername, nside=nside)
result = np.ones(hp.nside2npix(self.nside), dtype=float) if indx is None: indx = np.arange(result.size) diff = conditions.mjd - self.survey_features['Last_observed'].feature[indx] bad = np.where(diff < self.gap_min)[0] result[indx[bad]] = self.penalty_val return result
""" Basis function to reward re-visiting an area on the sky. Looking for Solar System objects.
Parameters ---------- gap_min : float (15.) Minimum time for the gap (minutes) gap_max : float (45.) Maximum time for a gap filtername : str ('r') The filter(s) to count with pairs npairs : int (1) The number of pairs of observations to attempt to gather """ filtername='r', nside=None, npairs=1):
super(Visit_repeat_basis_function, self).__init__(nside=nside, filtername=filtername)
self.gap_min = gap_min/60./24. self.gap_max = gap_max/60./24. self.npairs = npairs
self.survey_features = {} # Track the number of pairs that have been taken in a night self.survey_features['Pair_in_night'] = features.Pair_in_night(filtername=filtername, gap_min=gap_min, gap_max=gap_max, nside=nside) # When was it last observed # XXX--since this feature is also in Pair_in_night, I should just access that one! self.survey_features['Last_observed'] = features.Last_observed(filtername=filtername, nside=nside)
result = np.zeros(hp.nside2npix(self.nside), dtype=float) if indx is None: indx = np.arange(result.size) diff = conditions.mjd - self.survey_features['Last_observed'].feature[indx] good = np.where((diff >= self.gap_min) & (diff <= self.gap_max) & (self.survey_features['Pair_in_night'].feature[indx] < self.npairs))[0] result[indx[good]] += 1. return result
"""Basis function based on the 5-sigma depth. Look up the best depth a healpixel achieves, and compute the limiting depth difference given current conditions """
super(M5_diff_basis_function, self).__init__(nside=nside, filtername=filtername) # Need to look up the deepest m5 values for all the healpixels m5p = M5percentiles() self.dark_map = m5p.dark_map(filtername=filtername, nside_out=self.nside)
# No way to get the sign on this right the first time. result = conditions.M5Depth[self.filtername] - self.dark_map return result
"""Remove the bonus for staying in the same filter if certain conditions are met.
If the moon rises/sets or twilight starts/ends, it makes a lot of sense to consider a filter change. This basis function rewards if it matches the current filter, the moon rises or sets, twilight starts or stops, or there has been a large gap since the last observation.
Paramters --------- time_lag : float (10.) If there is a gap between observations longer than this, let the filter change (minutes) twi_change : float (-18.) The sun altitude to consider twilight starting/ending (degrees) note_free : str ('DD') No penalty for changing filters if the last observation note field includes string. Useful for giving a free filter change after deep drilling sequence """
super(Strict_filter_basis_function, self).__init__(filtername=filtername)
self.time_lag = time_lag/60./24. # Convert to days self.twi_change = np.radians(twi_change)
self.survey_features = {} self.survey_features['Last_observation'] = features.Last_observation() self.note_free = note_free
# Did the moon set or rise since last observation? moon_changed = conditions.moonAlt * self.survey_features['Last_observation'].feature['moonAlt'] < 0
# Are we already in the filter (or at start of night)? in_filter = (conditions.current_filter == self.filtername) | (conditions.current_filter is None)
# Has enough time past? time_past = (conditions.mjd - self.survey_features['Last_observation'].feature['mjd']) > self.time_lag
# Did twilight start/end? twi_changed = (conditions.sunAlt - self.twi_change) * (self.survey_features['Last_observation'].feature['sunAlt']- self.twi_change) < 0
# Did we just finish a DD sequence wasDD = self.note_free in self.survey_features['Last_observation'].feature['note']
# Is the filter mounted? mounted = self.filtername in conditions.mounted_filters
if (moon_changed | in_filter | time_past | twi_changed | wasDD) & mounted: result = 1. else: result = 0.
return result
"""Remove the bonus for staying in the same filter if certain conditions are met.
If the moon rises/sets or twilight starts/ends, it makes a lot of sense to consider a filter change. This basis function rewards if it matches the current filter, the moon rises or sets, twilight starts or stops, or there has been a large gap since the last observation.
Parameters --------- time_lag_min: Minimum time after a filter change for which a new filter change will receive zero reward, or be denied at all (see unseen_before_lag). time_lag_max: Time after a filter change where the reward for changing filters achieve its maximum. time_lag_boost: Time after a filter change to apply a boost on the reward. boost_gain: A multiplier factor for the reward after time_lag_boost. unseen_before_lag: If True will make it impossible to switch filter before time_lag has passed. filtername: The filter for which this basis function will be used. tag: When using filter proportion use only regions with this tag to count for observations. twi_change: Switch reward on when twilight changes. proportion: The expected filter proportion distribution. aways_available: If this is true the basis function will aways be computed regardless of the feasibility. If False a more detailed feasibility check is performed. When set to False, it may speed up the computation process by avoiding to compute the reward functions paired with this bf, when observation is not feasible.
"""
time_lag_boost=60., boost_gain=2.0, unseen_before_lag=False, filtername='r', tag=None, twi_change=-18., proportion=1.0, aways_available=False):
super(Goal_Strict_filter_basis_function, self).__init__(filtername=filtername)
self.time_lag_min = time_lag_min / 60. / 24. # Convert to days self.time_lag_max = time_lag_max / 60. / 24. # Convert to days self.time_lag_boost = time_lag_boost / 60. / 24. self.boost_gain = boost_gain self.unseen_before_lag = unseen_before_lag
self.twi_change = np.radians(twi_change) self.proportion = proportion self.aways_available = aways_available
self.survey_features = {} self.survey_features['Last_observation'] = features.Last_observation() self.survey_features['Last_filter_change'] = features.LastFilterChange() self.survey_features['N_obs_all'] = features.N_obs_count(filtername=None) self.survey_features['N_obs'] = features.N_obs_count(filtername=filtername, tag=tag)
lag_min = self.time_lag_min lag_max = self.time_lag_max
a = 1. / (lag_max - lag_min) b = -a * lag_min
bonus = a * time + b # How far behind we are with respect to proportion? nobs = self.survey_features['N_obs'].feature nobs_all = self.survey_features['N_obs_all'].feature goal = self.proportion # need = 1. - nobs / nobs_all + goal if nobs_all > 0 else 1. + goal need = goal / nobs * nobs_all if nobs > 0 else 1. # need /= goal if hasattr(time, '__iter__'): before_lag = np.where(time <= lag_min) bonus[before_lag] = -np.inf if self.unseen_before_lag else 0. after_lag = np.where(time >= lag_max) bonus[after_lag] = 1. if time < self.time_lag_boost else self.boost_gain elif time <= lag_min: return -np.inf if self.unseen_before_lag else 0. elif time >= lag_max: return 1. if time < self.time_lag_boost else self.boost_gain
return bonus * need
""" This method makes a pre-check of the feasibility of this basis function. If a basis function return False on the feasibility check, it won't computed at all.
:return: """
# Make a quick check about the feasibility of this basis function. If current filter is none, telescope # is parked and we could, in principle, switch to any filter. If this basis function computes reward for # the current filter, then it is also feasible. At last we check for an "aways_available" flag. Meaning, we # force this basis function to be aways be computed. if conditions.current_filter is None or conditions.current_filter == self.filtername or self.aways_available: return True
# If we arrive here, we make some extra checks to make sure this bf is feasible and should be computed.
# Did the moon set or rise since last observation? moon_changed = conditions.moonAlt * self.survey_features['Last_observation'].feature['moonAlt'] < 0
# Are we already in the filter (or at start of night)? not_in_filter = (conditions.current_filter != self.filtername)
# Has enough time past? lag = conditions.mjd - self.survey_features['Last_filter_change'].feature['mjd'] time_past = lag > self.time_lag_min
# Did twilight start/end? twi_changed = (conditions.sunAlt - self.twi_change) * \ (self.survey_features['Last_observation'].feature['sunAlt'] - self.twi_change) < 0
# Did we just finish a DD sequence wasDD = self.survey_features['Last_observation'].feature['note'] == 'DD'
# Is the filter mounted? mounted = self.filtername in conditions.mounted_filters
if (moon_changed | time_past | twi_changed | wasDD) & mounted & not_in_filter: return True else: return False
if conditions.current_filter is None: return 0. # no bonus if no filter is mounted # elif self.condition_features['Current_filter'].feature == self.filtername: # return 0. # no bonus if on the filter already
# Did the moon set or rise since last observation? moon_changed = conditions.moonAlt * \ self.survey_features['Last_observation'].feature['moonAlt'] < 0
# Are we already in the filter (or at start of night)? # not_in_filter = (self.condition_features['Current_filter'].feature != self.filtername)
# Has enough time past? lag = conditions.mjd - self.survey_features['Last_filter_change'].feature['mjd'] time_past = lag > self.time_lag_min
# Did twilight start/end? twi_changed = (conditions.sunAlt - self.twi_change) * ( self.survey_features['Last_observation'].feature['sunAlt'] - self.twi_change) < 0
# Did we just finish a DD sequence wasDD = self.survey_features['Last_observation'].feature['note'] == 'DD'
# Is the filter mounted? mounted = self.filtername in conditions.mounted_filters
if (moon_changed | time_past | twi_changed | wasDD) & mounted: result = self.filter_change_bonus(lag) if time_past else 0. else: result = -100. if self.unseen_before_lag else 0.
return result
"""Reward staying in the current filter. """ super(Filter_change_basis_function, self).__init__(filtername=filtername)
if (conditions.current_filter == self.filtername) | (conditions.current_filter is None): result = 1. else: result = 0. return result
"""Reward slews that take little time
Parameters ---------- max_time : float (135) The estimated maximum slewtime (seconds). Used to normalize so the basis function spans ~ -1-0 in reward units. """ super(Slewtime_basis_function, self).__init__(nside=nside, filtername=filtername)
self.maxtime = max_time self.nside = nside self.filtername = filtername self.result = np.zeros(hp.nside2npix(nside), dtype=float)
# No tracking of observations in this basis function. Purely based on conditions. pass
# If we are in a different filter, the Filter_change_basis_function will take it if conditions.current_filter != self.filtername: result = 0 else: # Need to make sure smaller slewtime is larger reward. if np.size(conditions.slewtime) > 1: result = self.result.copy() good = ~np.isnan(conditions.slewtime) result[good] = -conditions.slewtime[good]/self.maxtime else: result = -conditions.slewtime/self.maxtime return result
"""Reward slews that take little time
XXX--not sure how this is different from Slewtime_basis_function? Looks like it's checking the slewtime to the field position rather than the healpix maybe? """
super(Aggressive_Slewtime_basis_function, self).__init__(nside=nside, filtername=filtername)
self.maxtime = max_time self.hard_max = hard_max self.order = order self.result = np.zeros(hp.nside2npix(nside), dtype=float)
# If we are in a different filter, the Filter_change_basis_function will take it if conditions.current_filter != self.filtername: result = 0. else: # Need to make sure smaller slewtime is larger reward. if np.size(self.condition_features['slewtime'].feature) > 1: result = self.result.copy() result.fill(np.nan)
good = np.where(np.bitwise_and(conditions.slewtime > 0., conditions.slewtime < self.maxtime)) result[good] = ((self.maxtime - conditions.slewtime[good]) / self.maxtime) ** self.order if self.hard_max is not None: not_so_good = np.where(conditions.slewtime > self.hard_max) result[not_so_good] -= 10. fields = np.unique(conditions.hp2fields[good]) for field in fields: hp_indx = np.where(conditions.hp2fields == field) result[hp_indx] = np.min(result[hp_indx]) else: result = (self.maxtime - conditions.slewtime) / self.maxtime return result
"""Mask regions that are outside a sky brightness limit
XXX--TODO: This should probably go to the mask basis functions.
Parameters ---------- min : float (20.) The minimum sky brightness (mags). max : float (30.) The maximum sky brightness (mags).
""" super(Skybrightness_limit_basis_function, self).__init__(nside=nside, filtername=filtername)
self.min = min self.max = max self.result = np.empty(hp.nside2npix(self.nside), dtype=float) self.result.fill(np.nan)
result = self.result.copy()
good = np.where(np.bitwise_and(conditions.skybrightness[self.filtername] > self.min, conditions.skybrightness[self.filtername] < self.max)) result[good] = 1.0
return result
""" Parameters ---------- minAz : float (20.) The minimum azimuth to activate bf (degrees) maxAz : float (82.) The maximum azimuth to activate bf (degrees) unwrap_until: float (90.) The window in which the bf is activated (degrees) """ activate_tol=20., delta_unwrap=1.2, unwrap_until=70., max_duration=30.): super(CableWrap_unwrap_basis_function, self).__init__(nside=nside)
self.minAz = np.radians(minAz) self.maxAz = np.radians(maxAz)
self.activate_tol = np.radians(activate_tol) self.delta_unwrap = np.radians(delta_unwrap) self.unwrap_until = np.radians(unwrap_until)
self.minAlt = np.radians(minAlt) self.maxAlt = np.radians(maxAlt) # Convert to half-width for convienence self.nside = nside self.active = False self.unwrap_direction = 0. # either -1., 0., 1. self.max_duration = max_duration/60./24. # Convert to days self.activation_time = None self.result = np.zeros(hp.nside2npix(self.nside), dtype=float)
result = self.result.copy()
current_abs_rad = np.radians(conditions.az) unseen = np.where(np.bitwise_or(conditions.alt < self.minAlt, conditions.alt > self.maxAlt)) result[unseen] = np.nan
if (self.minAz + self.activate_tol < current_abs_rad < self.maxAz - self.activate_tol) and not self.active: return result elif self.active and self.unwrap_direction == 1 and current_abs_rad > self.minAz+self.unwrap_until: self.active = False self.unwrap_direction = 0. self.activation_time = None return result elif self.active and self.unwrap_direction == -1 and current_abs_rad < self.maxAz-self.unwrap_until: self.active = False self.unwrap_direction = 0. self.activation_time = None return result elif (self.activation_time is not None and conditions.mjd - self.activation_time > self.max_duration): self.active = False self.unwrap_direction = 0. self.activation_time = None return result
if not self.active: self.activation_time = conditions.mjd if current_abs_rad < 0.: self.unwrap_direction = 1 # clock-wise unwrap else: self.unwrap_direction = -1 # counter-clock-wise unwrap
self.active = True
max_abs_rad = self.maxAz min_abs_rad = self.minAz
TWOPI = 2.*np.pi
# Compute distance and accumulated az. norm_az_rad = np.divmod(conditions.az - min_abs_rad, TWOPI)[1] + min_abs_rad distance_rad = divmod(norm_az_rad - current_abs_rad, TWOPI)[1] get_shorter = np.where(distance_rad > np.pi) distance_rad[get_shorter] -= TWOPI accum_abs_rad = current_abs_rad + distance_rad
# Compute wrap regions and fix distances mask_max = np.where(accum_abs_rad > max_abs_rad) distance_rad[mask_max] -= TWOPI mask_min = np.where(accum_abs_rad < min_abs_rad) distance_rad[mask_min] += TWOPI
# Step-2: Repeat but now with compute reward to unwrap using specified delta_unwrap unwrap_current_abs_rad = current_abs_rad - (np.abs(self.delta_unwrap) if self.unwrap_direction > 0 else -np.abs(self.delta_unwrap)) unwrap_distance_rad = divmod(norm_az_rad - unwrap_current_abs_rad, TWOPI)[1] unwrap_get_shorter = np.where(unwrap_distance_rad > np.pi) unwrap_distance_rad[unwrap_get_shorter] -= TWOPI unwrap_distance_rad = np.abs(unwrap_distance_rad)
if self.unwrap_direction < 0: mask = np.where(accum_abs_rad > unwrap_current_abs_rad) else: mask = np.where(accum_abs_rad < unwrap_current_abs_rad)
# Finally build reward map result = (1. - unwrap_distance_rad/np.max(unwrap_distance_rad))**2. result[mask] = 0. result[unseen] = np.nan
return result
"""Drive a certain cadence Parameters ---------- filtername : str ('gri') The filter(s) that should be grouped together supress_window : list of float The start and stop window for when observations should be repressed (days) apply_area : healpix map The area over which to try and drive the cadence. Good values as 1, no candece drive 0. Probably works as a bool array too.""" supress_window=[0, 1.8], supress_val=-0.5, enhance_window=[2.1, 3.2], enhance_val=1., apply_area=None): super(Cadence_enhance_basis_function, self).__init__(nside=nside, filtername=filtername)
self.supress_window = np.sort(supress_window) self.supress_val = supress_val self.enhance_window = np.sort(enhance_window) self.enhance_val = enhance_val
self.survey_features = {} self.survey_features['last_observed'] = features.Last_observed(filtername=filtername)
self.empty = np.zeros(hp.nside2npix(self.nside), dtype=float) # No map, try to drive the whole area if apply_area is None: self.apply_indx = np.arange(self.empty.size) else: self.apply_indx = np.where(apply_area != 0)[0]
# copy an empty array result = self.empty.copy() if indx is not None: ind = np.intersect1d(indx, self.apply_indx) else: ind = self.apply_indx if np.size(ind) == 0: result = 0 else: mjd_diff = conditions.mjd - self.survey_features['last_observed'].feature[ind] to_supress = np.where((mjd_diff > self.supress_window[0]) & (mjd_diff < self.supress_window[1])) result[ind[to_supress]] = self.supress_val to_enhance = np.where((mjd_diff > self.enhance_window[0]) & (mjd_diff < self.enhance_window[1])) result[ind[to_enhance]] = self.enhance_val return result
"""Reward staying in the same azimuth range. Possibly better than using slewtime, especially when selecting a large area of sky.
Parameters ----------
"""
super(Azimuth_basis_function, self).__init__(nside=nside)
az_dist = conditions.az - conditions.telAz az_dist = az_dist % (2.*np.pi) over = np.where(az_dist > np.pi) az_dist[over] = 2. * np.pi - az_dist[over] # Normalize sp between 0 and 1 result = az_dist/np.pi return result
"""Try to replicate the Rothchild et al cadence forcing by only observing on limited az ranges per night.
Parameters ---------- az_limits : list of float pairs (None) The azimuth limits (degrees) to use. """ super(Az_modulo_basis_function, self).__init__(nside=nside) self.result = np.ones(hp.nside2npix(self.nside)) if az_limits is None: spread = 100./2. self.az_limits = np.radians([[360-spread, spread], [90.-spread, 90.+spread], [180.-spread, 180.+spread]]) else: self.az_limits = np.radians(az_limits) self.mod_val = len(self.az_limits) self.out_of_bounds_val = out_of_bounds_val
result = self.result.copy() az_lim = self.az_limits[np.max(conditions.night) % self.mod_val]
if az_lim[0] < az_lim[1]: out_pix = np.where((conditions.az < az_lim[0]) | (conditions.az > az_lim[1])) else: out_pix = np.where((conditions.az < az_lim[0]) | (conditions.az > az_lim[1]))[0] result[out_pix] = self.out_of_bounds_val return result
"""Emphasize dec bands on a nightly varying basis
Parameters ---------- dec_limits : list of float pairs (None) The azimuth limits (degrees) to use. """ super(Dec_modulo_basis_function, self).__init__(nside=nside)
npix = hp.nside2npix(nside) hpids = np.arange(npix) ra, dec = _hpid2RaDec(nside, hpids)
self.results = []
if dec_limits is None: self.dec_limits = np.radians([[-90., -32.8], [-32.8, -12.], [-12., 35.]]) else: self.dec_limits = np.radians(dec_limits) self.mod_val = len(self.dec_limits) self.out_of_bounds_val = out_of_bounds_val
for limits in self.dec_limits: good = np.where((dec >= limits[0]) & (dec < limits[1]))[0] tmp = np.zeros(npix) tmp[good] = 1 self.results.append(tmp)
night_index = np.max(conditions.night % self.mod_val) result = self.results[night_index]
return result
"""Emphasize areas that have not been observed in a long time
Parameters ---------- day_gap : float (250.) How long to wait before boosting the reward (days) footprint : np.array (None) The indices of the healpixels to apply the boost to. Uses the default footprint if None """ super(Template_generate_basis_function, self).__init__(nside=nside) self.day_gap = day_gap self.filtername = filtername self.survey_features = {} self.survey_features['Last_observed'] = features.Last_observed(filtername=filtername) self.result = np.zeros(hp.nside2npix(self.nside)) if footprint is None: fp = utils.standard_goals(nside=nside)[filtername] else: fp = footprint self.out_of_bounds = np.where(fp == 0)
result = self.result.copy() overdue = np.where((conditions.mjd - self.survey_features['Last_observed'].feature) > self.day_gap) result[overdue] = 1 result[self.out_of_bounds] = 0
return result |