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', 'Time_to_scheduled_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 Time_to_scheduled_basis_function(Base_basis_function):
120 """Make sure there is enough time before next scheduled observation. Useful
121 if you want to check before starting a long sequence of observations.
123 Parameters
124 ----------
125 time_needed : float (30.)
126 The time needed to run a survey (mintues).
127 """
128 def __init__(self, time_needed=30.):
129 super(Time_to_scheduled_basis_function, self).__init__()
130 self.time_needed = time_needed/60./24. # To days
132 def check_feasibility(self, conditions):
133 if len(conditions.scheduled_observations) == 0:
134 return True
136 available_time = np.min(conditions.scheduled_observations) - conditions.mjd
137 result = available_time > self.time_needed
138 return result
141class Not_twilight_basis_function(Base_basis_function):
142 def __init__(self, sun_alt_limit=-18):
143 """
144 # Should be -18 or -12
145 """
146 self.sun_alt_limit = str(int(sun_alt_limit)).replace('-', 'n')
147 super(Not_twilight_basis_function, self).__init__()
149 def check_feasibility(self, conditions):
150 result = True
151 if conditions.mjd < getattr(conditions, 'sun_'+self.sun_alt_limit+'_setting'):
152 result = False
153 if conditions.mjd > getattr(conditions, 'sun_'+self.sun_alt_limit+'_rising'):
154 result = False
155 return result
158class Force_delay_basis_function(Base_basis_function):
159 """Keep a survey from executing to rapidly.
161 Parameters
162 ----------
163 days_delay : float (2)
164 The number of days to force a gap on.
165 """
166 def __init__(self, days_delay=2., survey_name=None):
167 super(Force_delay_basis_function, self).__init__()
168 self.days_delay = days_delay
169 self.survey_name = survey_name
170 self.survey_features['last_obs_self'] = features.Last_observation(survey_name=self.survey_name)
172 def check_feasibility(self, conditions):
173 result = True
174 if conditions.mjd - self.survey_features['last_obs_self'].feature['mjd'] < self.days_delay:
175 result = False
176 return result
179class Soft_delay_basis_function(Base_basis_function):
180 """Like Force_delay, but go ahead and let things catch up if they fall far behind.
182 Parameters
183 ----------
185 """
186 def __init__(self, fractions=[0.000, 0.009, 0.017], delays=[0., 0.5, 1.5], survey_name=None):
187 if len(fractions) != len(delays):
188 raise ValueError('fractions and delays must be same length')
189 super(Soft_delay_basis_function, self).__init__()
190 self.delays = delays
191 self.survey_name = survey_name
192 self.survey_features['last_obs_self'] = features.Last_observation(survey_name=self.survey_name)
193 self.fractions = fractions
194 self.survey_features['Ntot'] = features.N_obs_survey()
195 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name)
197 def check_feasibility(self, conditions):
198 result = True
199 current_ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature
200 indx = np.searchsorted(self.fractions, current_ratio)
201 if indx == len(self.fractions):
202 indx -= 1
203 delay = self.delays[indx]
204 if conditions.mjd - self.survey_features['last_obs_self'].feature['mjd'] < delay:
205 result = False
206 return result
209class Hour_Angle_limit_basis_function(Base_basis_function):
210 """Only execute a survey in limited hour angle ranges. Useful for
211 limiting Deep Drilling Fields.
213 Parameters
214 ----------
215 RA : float (0.)
216 RA of the target (degrees).
217 ha_limits : list of lists
218 limits for what hour angles are acceptable (hours). e.g.,
219 to give 4 hour window around RA=0, ha_limits=[[22,24], [0,2]]
220 """
221 def __init__(self, RA=0., ha_limits=None):
222 super(Hour_Angle_limit_basis_function, self).__init__()
223 self.ra_hours = RA/360.*24.
224 self.HA_limits = np.array(ha_limits)
226 def check_feasibility(self, conditions):
227 target_HA = (conditions.lmst - self.ra_hours) % 24
228 # Are we in any of the possible windows
229 result = False
230 for limit in self.HA_limits:
231 lres = limit[0] <= target_HA < limit[1]
232 result = result or lres
234 return result
237class Moon_down_basis_function(Base_basis_function):
238 """Demand the moon is down """
239 def check_feasibility(self, conditions):
240 result = True
241 if conditions.moonAlt > 0:
242 result = False
243 return result
246class Fraction_of_obs_basis_function(Base_basis_function):
247 """Limit the fraction of all observations that can be labled a certain
248 survey name. Useful for keeping DDFs from exceeding a given fraction of the
249 total survey.
251 Parameters
252 ----------
253 frac_total : float
254 The fraction of total observations that can be of this survey
255 survey_name : str
256 The name of the survey
257 """
258 def __init__(self, frac_total, survey_name=''):
259 super(Fraction_of_obs_basis_function, self).__init__()
260 self.survey_name = survey_name
261 self.frac_total = frac_total
262 self.survey_features['Ntot'] = features.N_obs_survey()
263 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name)
265 def check_feasibility(self, conditions):
266 # If nothing has been observed, fine to go
267 result = True
268 if self.survey_features['Ntot'].feature == 0:
269 return result
270 ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature
271 if ratio > self.frac_total:
272 result = False
273 return result
276class Look_ahead_ddf_basis_function(Base_basis_function):
277 """Look into the future to decide if it's a good time to observe or block.
279 Parameters
280 ----------
281 frac_total : float
282 The fraction of total observations that can be of this survey
283 aggressive_fraction : float
284 If the fraction of observations drops below ths value, be more aggressive in scheduling.
285 e.g., do not wait for conditions to improve, execute as soon as possible.
286 time_needed : float (30.)
287 Estimate of the amount of time needed to execute DDF sequence (minutes).
288 RA : float (0.)
289 The RA of the DDF
290 ha_limits : list of lists (None)
291 limits for what hour angles are acceptable (hours). e.g.,
292 to give 4 hour window around HA=0, ha_limits=[[22,24], [0,2]]
293 survey_name : str ('')
294 The name of the survey
295 time_jump : float (44.)
296 The amount of time to assume will jump ahead if another survey executes (minutes)
297 sun_alt_limit : float (-18.)
298 The limit to assume twilight starts (degrees)
299 """
300 def __init__(self, frac_total, aggressive_fraction, time_needed=30., RA=0.,
301 ha_limits=None, survey_name='', time_jump=44., sun_alt_limit=-18.):
302 super(Look_ahead_ddf_basis_function, self).__init__()
303 if aggressive_fraction > frac_total:
304 raise ValueError('aggressive_fraction should be less than frac_total')
305 self.survey_name = survey_name
306 self.frac_total = frac_total
307 self.ra_hours = RA/360.*24.
308 self.HA_limits = np.array(ha_limits)
309 self.sun_alt_limit = str(int(sun_alt_limit)).replace('-', 'n')
310 self.time_jump = time_jump / 60. / 24. # To days
311 self.time_needed = time_needed / 60. / 24. # To days
312 self.aggressive_fraction = aggressive_fraction
313 self.survey_features['Ntot'] = features.N_obs_survey()
314 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name)
316 def check_feasibility(self, conditions):
317 result = True
318 target_HA = (conditions.lmst - self.ra_hours) % 24
319 ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature
320 available_time = getattr(conditions, 'sun_' + self.sun_alt_limit + '_rising') - conditions.mjd
321 # If it's more that self.time_jump to hour angle zero
322 # See if there will be enough time to twilight in the future
323 if (int_rounded(target_HA) > int_rounded(12)) & (int_rounded(target_HA) < int_rounded(24.-self.time_jump)):
324 if int_rounded(available_time) > int_rounded(self.time_needed + self.time_jump):
325 result = False
326 # If we paused for better conditions, but the moon will rise, turn things back on.
327 if int_rounded(conditions.moonAlt) < int_rounded(0):
328 if int_rounded(conditions.moonrise) > int_rounded(conditions.mjd):
329 if int_rounded(conditions.moonrise - conditions.mjd) > int_rounded(self.time_jump):
330 result = True
331 # If the moon is up and will set soon, pause
332 if int_rounded(conditions.moonAlt) > int_rounded(0):
333 time_after_moonset = getattr(conditions, 'sun_' + self.sun_alt_limit + '_rising') - conditions.moonset
334 if int_rounded(conditions.moonset) > int_rounded(self.time_jump):
335 if int_rounded(time_after_moonset) > int_rounded(self.time_needed):
336 result = False
338 # If the survey has fallen far behind, be agressive and observe anytime it's up.
339 if int_rounded(ratio) < int_rounded(self.aggressive_fraction):
340 result = True
341 return result
344class Clouded_out_basis_function(Base_basis_function):
345 def __init__(self, cloud_limit=0.7):
346 super(Clouded_out_basis_function, self).__init__()
347 self.cloud_limit = cloud_limit
349 def check_feasibility(self, conditions):
350 result = True
351 if conditions.bulk_cloud > self.cloud_limit:
352 result = False
353 return result
356class Rising_more_basis_function(Base_basis_function):
357 """Say a spot is not available if it will rise substatially before twilight.
359 Parameters
360 ----------
361 RA : float
362 The RA of the point in the sky (degrees)
363 pad : float
364 When to start observations if there's plenty of time before twilight (minutes)
365 """
366 def __init__(self, RA, pad=30.):
367 super(Rising_more_basis_function, self).__init__()
368 self.RA_hours = RA * 24 / 360.
369 self.pad = pad/60. # To hours
371 def check_feasibility(self, conditions):
372 result = True
373 hour_angle = conditions.lmst - self.RA_hours
374 # If it's rising, and twilight is well beyond when it crosses the meridian
375 time_to_twi = (conditions.sun_n18_rising - conditions.mjd)*24.
376 if (hour_angle < -self.pad) & (np.abs(hour_angle) < (time_to_twi - self.pad)):
377 result = False
378 return result
381class Sun_alt_limit_basis_function(Base_basis_function):
382 """Don't try unless the sun is below some limit
383 """
385 def __init__(self, alt_limit=-12.1):
386 super(Sun_alt_limit_basis_function, self).__init__()
387 self.alt_limit = np.radians(alt_limit)
389 def check_feasibility(self, conditions):
390 result = True
391 if conditions.sunAlt > self.alt_limit:
392 result = False
393 return result
396## XXX--TODO: Can include checks to see if downtime is coming, clouds are coming, moon rising, or surveys in a higher tier
397# Have observations they want to execute soon.