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

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
1import numpy as np
2from lsst.sims.featureScheduler import features
3import matplotlib.pylab as plt
4from lsst.sims.featureScheduler.basis_functions import Base_basis_function
5from lsst.sims.featureScheduler.utils import int_rounded
8__all__ = ['Filter_loaded_basis_function', 'Time_to_twilight_basis_function',
9 'Not_twilight_basis_function', 'Force_delay_basis_function',
10 'Hour_Angle_limit_basis_function', 'Moon_down_basis_function',
11 'Fraction_of_obs_basis_function', 'Clouded_out_basis_function',
12 'Rising_more_basis_function', 'Soft_delay_basis_function',
13 'Look_ahead_ddf_basis_function', 'Sun_alt_limit_basis_function',
14 'Time_in_twilight_basis_function', 'Night_modulo_basis_function',
15 'End_of_evening_basis_function']
18class Filter_loaded_basis_function(Base_basis_function):
19 """Check that the filter(s) needed are loaded
21 Parameters
22 ----------
23 filternames : str or list of str
24 The filternames that need to be mounted to execute.
25 """
26 def __init__(self, filternames='r'):
27 super(Filter_loaded_basis_function, self).__init__()
28 if type(filternames) is not list:
29 filternames = [filternames]
30 self.filternames = filternames
32 def check_feasibility(self, conditions):
34 for filtername in self.filternames:
35 result = filtername in conditions.mounted_filters
36 if result is False:
37 return result
38 return result
41class Night_modulo_basis_function(Base_basis_function):
42 """Only return true on certain nights
43 """
44 def __init__(self, pattern=None):
45 super(Night_modulo_basis_function, self).__init__()
46 if pattern is None:
47 pattern = [True, False]
48 self.pattern = pattern
49 self.mod_val = len(self.pattern)
51 def check_feasibility(self, conditions):
52 indx = int(conditions.night % self.mod_val)
53 result = self.pattern[indx]
54 return result
57class Time_in_twilight_basis_function(Base_basis_function):
58 """Make sure there is some time left in twilight.
60 Parameters
61 ----------
62 time_needed : float (5)
63 The time needed remaining in twilight (minutes)
64 """
65 def __init__(self, time_needed=5.):
66 super(Time_in_twilight_basis_function, self).__init__()
67 self.time_needed = time_needed/60./24. # To days
69 def check_feasibility(self, conditions):
70 result = False
71 time1 = conditions.sun_n18_setting - conditions.mjd
72 time2 = conditions.sun_n12_rising - conditions.mjd
74 if time1 > self.time_needed:
75 result = True
76 else:
77 if conditions.sunAlt > np.radians(-18.):
78 if time2 > self.time_needed:
79 result = True
80 return result
83class End_of_evening_basis_function(Base_basis_function):
84 """Only let observations happen in a limited time before twilight
85 """
86 def __init__(self, time_remaining=30., alt_limit=18):
87 super(End_of_evening_basis_function, self).__init__()
88 self.time_remaining = int_rounded(time_remaining/60./24.)
89 self.alt_limit = str(alt_limit)
91 def check_feasibility(self, conditions):
92 available_time = getattr(conditions, 'sun_n' + self.alt_limit + '_rising') - conditions.mjd
93 result = int_rounded(available_time) < self.time_remaining
94 return result
97class Time_to_twilight_basis_function(Base_basis_function):
98 """Make sure there is enough time before twilight. Useful
99 if you want to check before starting a long sequence of observations.
101 Parameters
102 ----------
103 time_needed : float (30.)
104 The time needed to run a survey (mintues).
105 alt_limit : int (18)
106 The sun altitude limit to use. Must be 12 or 18
107 """
108 def __init__(self, time_needed=30., alt_limit=18):
109 super(Time_to_twilight_basis_function, self).__init__()
110 self.time_needed = time_needed/60./24. # To days
111 self.alt_limit = str(alt_limit)
113 def check_feasibility(self, conditions):
114 available_time = getattr(conditions, 'sun_n' + self.alt_limit + '_rising') - conditions.mjd
115 result = available_time > self.time_needed
116 return result
119class Not_twilight_basis_function(Base_basis_function):
120 def __init__(self, sun_alt_limit=-18):
121 """
122 # Should be -18 or -12
123 """
124 self.sun_alt_limit = str(int(sun_alt_limit)).replace('-', 'n')
125 super(Not_twilight_basis_function, self).__init__()
127 def check_feasibility(self, conditions):
128 result = True
129 if conditions.mjd < getattr(conditions, 'sun_'+self.sun_alt_limit+'_setting'):
130 result = False
131 if conditions.mjd > getattr(conditions, 'sun_'+self.sun_alt_limit+'_rising'):
132 result = False
133 return result
136class Force_delay_basis_function(Base_basis_function):
137 """Keep a survey from executing to rapidly.
139 Parameters
140 ----------
141 days_delay : float (2)
142 The number of days to force a gap on.
143 """
144 def __init__(self, days_delay=2., survey_name=None):
145 super(Force_delay_basis_function, self).__init__()
146 self.days_delay = days_delay
147 self.survey_name = survey_name
148 self.survey_features['last_obs_self'] = features.Last_observation(survey_name=self.survey_name)
150 def check_feasibility(self, conditions):
151 result = True
152 if conditions.mjd - self.survey_features['last_obs_self'].feature['mjd'] < self.days_delay:
153 result = False
154 return result
157class Soft_delay_basis_function(Base_basis_function):
158 """Like Force_delay, but go ahead and let things catch up if they fall far behind.
160 Parameters
161 ----------
163 """
164 def __init__(self, fractions=[0.000, 0.009, 0.017], delays=[0., 0.5, 1.5], survey_name=None):
165 if len(fractions) != len(delays):
166 raise ValueError('fractions and delays must be same length')
167 super(Soft_delay_basis_function, self).__init__()
168 self.delays = delays
169 self.survey_name = survey_name
170 self.survey_features['last_obs_self'] = features.Last_observation(survey_name=self.survey_name)
171 self.fractions = fractions
172 self.survey_features['Ntot'] = features.N_obs_survey()
173 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name)
175 def check_feasibility(self, conditions):
176 result = True
177 current_ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature
178 indx = np.searchsorted(self.fractions, current_ratio)
179 if indx == len(self.fractions):
180 indx -= 1
181 delay = self.delays[indx]
182 if conditions.mjd - self.survey_features['last_obs_self'].feature['mjd'] < delay:
183 result = False
184 return result
187class Hour_Angle_limit_basis_function(Base_basis_function):
188 """Only execute a survey in limited hour angle ranges. Useful for
189 limiting Deep Drilling Fields.
191 Parameters
192 ----------
193 RA : float (0.)
194 RA of the target (degrees).
195 ha_limits : list of lists
196 limits for what hour angles are acceptable (hours). e.g.,
197 to give 4 hour window around RA=0, ha_limits=[[22,24], [0,2]]
198 """
199 def __init__(self, RA=0., ha_limits=None):
200 super(Hour_Angle_limit_basis_function, self).__init__()
201 self.ra_hours = RA/360.*24.
202 self.HA_limits = np.array(ha_limits)
204 def check_feasibility(self, conditions):
205 target_HA = (conditions.lmst - self.ra_hours) % 24
206 # Are we in any of the possible windows
207 result = False
208 for limit in self.HA_limits:
209 lres = limit[0] <= target_HA < limit[1]
210 result = result or lres
212 return result
215class Moon_down_basis_function(Base_basis_function):
216 """Demand the moon is down """
217 def check_feasibility(self, conditions):
218 result = True
219 if conditions.moonAlt > 0:
220 result = False
221 return result
224class Fraction_of_obs_basis_function(Base_basis_function):
225 """Limit the fraction of all observations that can be labled a certain
226 survey name. Useful for keeping DDFs from exceeding a given fraction of the
227 total survey.
229 Parameters
230 ----------
231 frac_total : float
232 The fraction of total observations that can be of this survey
233 survey_name : str
234 The name of the survey
235 """
236 def __init__(self, frac_total, survey_name=''):
237 super(Fraction_of_obs_basis_function, self).__init__()
238 self.survey_name = survey_name
239 self.frac_total = frac_total
240 self.survey_features['Ntot'] = features.N_obs_survey()
241 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name)
243 def check_feasibility(self, conditions):
244 # If nothing has been observed, fine to go
245 result = True
246 if self.survey_features['Ntot'].feature == 0:
247 return result
248 ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature
249 if ratio > self.frac_total:
250 result = False
251 return result
254class Look_ahead_ddf_basis_function(Base_basis_function):
255 """Look into the future to decide if it's a good time to observe or block.
257 Parameters
258 ----------
259 frac_total : float
260 The fraction of total observations that can be of this survey
261 aggressive_fraction : float
262 If the fraction of observations drops below ths value, be more aggressive in scheduling.
263 e.g., do not wait for conditions to improve, execute as soon as possible.
264 time_needed : float (30.)
265 Estimate of the amount of time needed to execute DDF sequence (minutes).
266 RA : float (0.)
267 The RA of the DDF
268 ha_limits : list of lists (None)
269 limits for what hour angles are acceptable (hours). e.g.,
270 to give 4 hour window around HA=0, ha_limits=[[22,24], [0,2]]
271 survey_name : str ('')
272 The name of the survey
273 time_jump : float (44.)
274 The amount of time to assume will jump ahead if another survey executes (minutes)
275 sun_alt_limit : float (-18.)
276 The limit to assume twilight starts (degrees)
277 """
278 def __init__(self, frac_total, aggressive_fraction, time_needed=30., RA=0.,
279 ha_limits=None, survey_name='', time_jump=44., sun_alt_limit=-18.):
280 super(Look_ahead_ddf_basis_function, self).__init__()
281 if aggressive_fraction > frac_total:
282 raise ValueError('aggressive_fraction should be less than frac_total')
283 self.survey_name = survey_name
284 self.frac_total = frac_total
285 self.ra_hours = RA/360.*24.
286 self.HA_limits = np.array(ha_limits)
287 self.sun_alt_limit = str(int(sun_alt_limit)).replace('-', 'n')
288 self.time_jump = time_jump / 60. / 24. # To days
289 self.time_needed = time_needed / 60. / 24. # To days
290 self.aggressive_fraction = aggressive_fraction
291 self.survey_features['Ntot'] = features.N_obs_survey()
292 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name)
294 def check_feasibility(self, conditions):
295 result = True
296 target_HA = (conditions.lmst - self.ra_hours) % 24
297 ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature
298 available_time = getattr(conditions, 'sun_' + self.sun_alt_limit + '_rising') - conditions.mjd
299 # If it's more that self.time_jump to hour angle zero
300 # See if there will be enough time to twilight in the future
301 if (int_rounded(target_HA) > int_rounded(12)) & (int_rounded(target_HA) < int_rounded(24.-self.time_jump)):
302 if int_rounded(available_time) > int_rounded(self.time_needed + self.time_jump):
303 result = False
304 # If we paused for better conditions, but the moon will rise, turn things back on.
305 if int_rounded(conditions.moonAlt) < int_rounded(0):
306 if int_rounded(conditions.moonrise) > int_rounded(conditions.mjd):
307 if int_rounded(conditions.moonrise - conditions.mjd) > int_rounded(self.time_jump):
308 result = True
309 # If the moon is up and will set soon, pause
310 if int_rounded(conditions.moonAlt) > int_rounded(0):
311 time_after_moonset = getattr(conditions, 'sun_' + self.sun_alt_limit + '_rising') - conditions.moonset
312 if int_rounded(conditions.moonset) > int_rounded(self.time_jump):
313 if int_rounded(time_after_moonset) > int_rounded(self.time_needed):
314 result = False
316 # If the survey has fallen far behind, be agressive and observe anytime it's up.
317 if int_rounded(ratio) < int_rounded(self.aggressive_fraction):
318 result = True
319 return result
322class Clouded_out_basis_function(Base_basis_function):
323 def __init__(self, cloud_limit=0.7):
324 super(Clouded_out_basis_function, self).__init__()
325 self.cloud_limit = cloud_limit
327 def check_feasibility(self, conditions):
328 result = True
329 if conditions.bulk_cloud > self.cloud_limit:
330 result = False
331 return result
334class Rising_more_basis_function(Base_basis_function):
335 """Say a spot is not available if it will rise substatially before twilight.
337 Parameters
338 ----------
339 RA : float
340 The RA of the point in the sky (degrees)
341 pad : float
342 When to start observations if there's plenty of time before twilight (minutes)
343 """
344 def __init__(self, RA, pad=30.):
345 super(Rising_more_basis_function, self).__init__()
346 self.RA_hours = RA * 24 / 360.
347 self.pad = pad/60. # To hours
349 def check_feasibility(self, conditions):
350 result = True
351 hour_angle = conditions.lmst - self.RA_hours
352 # If it's rising, and twilight is well beyond when it crosses the meridian
353 time_to_twi = (conditions.sun_n18_rising - conditions.mjd)*24.
354 if (hour_angle < -self.pad) & (np.abs(hour_angle) < (time_to_twi - self.pad)):
355 result = False
356 return result
359class Sun_alt_limit_basis_function(Base_basis_function):
360 """Don't try unless the sun is below some limit
361 """
363 def __init__(self, alt_limit=-12.1):
364 super(Sun_alt_limit_basis_function, self).__init__()
365 self.alt_limit = np.radians(alt_limit)
367 def check_feasibility(self, conditions):
368 result = True
369 if conditions.sunAlt > self.alt_limit:
370 result = False
371 return result
374## XXX--TODO: Can include checks to see if downtime is coming, clouds are coming, moon rising, or surveys in a higher tier
375# Have observations they want to execute soon.