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

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