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