Hide keyboard shortcuts

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 

5 

6 

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 

15 

16class Filter_loaded_basis_function(Base_basis_function): 

17 """Check that the filter(s) needed are loaded 

18 

19 Parameters 

20 ---------- 

21 filternames : str or list of str 

22 The filternames that need to be mounted to execute. 

23 """ 

24 def __init__(self, filternames='r'): 

25 super(Filter_loaded_basis_function, self).__init__() 

26 if type(filternames) is not list: 

27 filternames = [filternames] 

28 self.filternames = filternames 

29 

30 def check_feasibility(self, conditions): 

31 

32 for filtername in self.filternames: 

33 result = filtername in conditions.mounted_filters 

34 if result is False: 

35 return result 

36 return result 

37 

38 

39class Night_modulo_basis_function(Base_basis_function): 

40 """Only return true on certain nights 

41 """ 

42 def __init__(self, pattern=None): 

43 super(Night_modulo_basis_function, self).__init__() 

44 if pattern is None: 

45 pattern = [True, False] 

46 self.pattern = pattern 

47 self.mod_val = len(self.pattern) 

48 

49 def check_feasibility(self, conditions): 

50 indx = int(conditions.night % self.mod_val) 

51 result = self.pattern[indx] 

52 return result 

53 

54 

55class Time_in_twilight_basis_function(Base_basis_function): 

56 """Make sure there is some time left in twilight. 

57 

58 Parameters 

59 ---------- 

60 time_needed : float (5) 

61 The time needed remaining in twilight (minutes) 

62 """ 

63 def __init__(self, time_needed=5.): 

64 super(Time_in_twilight_basis_function, self).__init__() 

65 self.time_needed = time_needed/60./24. # To days 

66 

67 def check_feasibility(self, conditions): 

68 result = False 

69 time1 = conditions.sun_n18_setting - conditions.mjd 

70 time2 = conditions.sun_n12_rising - conditions.mjd 

71 

72 if time1 > self.time_needed: 

73 result = True 

74 else: 

75 if conditions.sunAlt > np.radians(-18.): 

76 if time2 > self.time_needed: 

77 result = True 

78 return result 

79 

80 

81 

82class Time_to_twilight_basis_function(Base_basis_function): 

83 """Make sure there is enough time before twilight. Useful 

84 if you want to check before starting a long sequence of observations. 

85 

86 Parameters 

87 ---------- 

88 time_needed : float (30.) 

89 The time needed to run a survey (mintues). 

90 alt_limit : int (18) 

91 The sun altitude limit to use. Must be 12 or 18 

92 """ 

93 def __init__(self, time_needed=30., alt_limit=18): 

94 super(Time_to_twilight_basis_function, self).__init__() 

95 self.time_needed = time_needed/60./24. # To days 

96 self.alt_limit = str(alt_limit) 

97 

98 def check_feasibility(self, conditions): 

99 available_time = getattr(conditions, 'sun_n' + self.alt_limit + '_rising') - conditions.mjd 

100 result = available_time > self.time_needed 

101 return result 

102 

103 

104class Not_twilight_basis_function(Base_basis_function): 

105 def __init__(self, sun_alt_limit=-18): 

106 """ 

107 # Should be -18 or -12 

108 """ 

109 self.sun_alt_limit = str(int(sun_alt_limit)).replace('-', 'n') 

110 super(Not_twilight_basis_function, self).__init__() 

111 

112 def check_feasibility(self, conditions): 

113 result = True 

114 if conditions.mjd < getattr(conditions, 'sun_'+self.sun_alt_limit+'_setting'): 

115 result = False 

116 if conditions.mjd > getattr(conditions, 'sun_'+self.sun_alt_limit+'_rising'): 

117 result = False 

118 return result 

119 

120 

121class Force_delay_basis_function(Base_basis_function): 

122 """Keep a survey from executing to rapidly. 

123 

124 Parameters 

125 ---------- 

126 days_delay : float (2) 

127 The number of days to force a gap on. 

128 """ 

129 def __init__(self, days_delay=2., survey_name=None): 

130 super(Force_delay_basis_function, self).__init__() 

131 self.days_delay = days_delay 

132 self.survey_name = survey_name 

133 self.survey_features['last_obs_self'] = features.Last_observation(survey_name=self.survey_name) 

134 

135 def check_feasibility(self, conditions): 

136 result = True 

137 if conditions.mjd - self.survey_features['last_obs_self'].feature['mjd'] < self.days_delay: 

138 result = False 

139 return result 

140 

141 

142class Soft_delay_basis_function(Base_basis_function): 

143 """Like Force_delay, but go ahead and let things catch up if they fall far behind. 

144 

145 Parameters 

146 ---------- 

147 

148 """ 

149 def __init__(self, fractions=[0.000, 0.009, 0.017], delays=[0., 0.5, 1.5], survey_name=None): 

150 if len(fractions) != len(delays): 

151 raise ValueError('fractions and delays must be same length') 

152 super(Soft_delay_basis_function, self).__init__() 

153 self.delays = delays 

154 self.survey_name = survey_name 

155 self.survey_features['last_obs_self'] = features.Last_observation(survey_name=self.survey_name) 

156 self.fractions = fractions 

157 self.survey_features['Ntot'] = features.N_obs_survey() 

158 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name) 

159 

160 def check_feasibility(self, conditions): 

161 result = True 

162 current_ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature 

163 indx = np.searchsorted(self.fractions, current_ratio) 

164 if indx == len(self.fractions): 

165 indx -= 1 

166 delay = self.delays[indx] 

167 if conditions.mjd - self.survey_features['last_obs_self'].feature['mjd'] < delay: 

168 result = False 

169 return result 

170 

171 

172class Hour_Angle_limit_basis_function(Base_basis_function): 

173 """Only execute a survey in limited hour angle ranges. Useful for 

174 limiting Deep Drilling Fields. 

175 

176 Parameters 

177 ---------- 

178 RA : float (0.) 

179 RA of the target (degrees). 

180 ha_limits : list of lists 

181 limits for what hour angles are acceptable (hours). e.g., 

182 to give 4 hour window around RA=0, ha_limits=[[22,24], [0,2]] 

183 """ 

184 def __init__(self, RA=0., ha_limits=None): 

185 super(Hour_Angle_limit_basis_function, self).__init__() 

186 self.ra_hours = RA/360.*24. 

187 self.HA_limits = np.array(ha_limits) 

188 

189 def check_feasibility(self, conditions): 

190 target_HA = (conditions.lmst - self.ra_hours) % 24 

191 # Are we in any of the possible windows 

192 result = False 

193 for limit in self.HA_limits: 

194 lres = limit[0] <= target_HA < limit[1] 

195 result = result or lres 

196 

197 return result 

198 

199 

200class Moon_down_basis_function(Base_basis_function): 

201 """Demand the moon is down """ 

202 def check_feasibility(self, conditions): 

203 result = True 

204 if conditions.moonAlt > 0: 

205 result = False 

206 return result 

207 

208 

209class Fraction_of_obs_basis_function(Base_basis_function): 

210 """Limit the fraction of all observations that can be labled a certain 

211 survey name. Useful for keeping DDFs from exceeding a given fraction of the 

212 total survey. 

213 

214 Parameters 

215 ---------- 

216 frac_total : float 

217 The fraction of total observations that can be of this survey 

218 survey_name : str 

219 The name of the survey 

220 """ 

221 def __init__(self, frac_total, survey_name=''): 

222 super(Fraction_of_obs_basis_function, self).__init__() 

223 self.survey_name = survey_name 

224 self.frac_total = frac_total 

225 self.survey_features['Ntot'] = features.N_obs_survey() 

226 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name) 

227 

228 def check_feasibility(self, conditions): 

229 # If nothing has been observed, fine to go 

230 result = True 

231 if self.survey_features['Ntot'].feature == 0: 

232 return result 

233 ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature 

234 if ratio > self.frac_total: 

235 result = False 

236 return result 

237 

238 

239class Look_ahead_ddf_basis_function(Base_basis_function): 

240 """Look into the future to decide if it's a good time to observe or block. 

241 

242 Parameters 

243 ---------- 

244 frac_total : float 

245 The fraction of total observations that can be of this survey 

246 aggressive_fraction : float 

247 If the fraction of observations drops below ths value, be more aggressive in scheduling. 

248 e.g., do not wait for conditions to improve, execute as soon as possible. 

249 time_needed : float (30.) 

250 Estimate of the amount of time needed to execute DDF sequence (minutes). 

251 RA : float (0.) 

252 The RA of the DDF 

253 ha_limits : list of lists (None) 

254 limits for what hour angles are acceptable (hours). e.g., 

255 to give 4 hour window around HA=0, ha_limits=[[22,24], [0,2]] 

256 survey_name : str ('') 

257 The name of the survey 

258 time_jump : float (44.) 

259 The amount of time to assume will jump ahead if another survey executes (minutes) 

260 sun_alt_limit : float (-18.) 

261 The limit to assume twilight starts (degrees) 

262 """ 

263 def __init__(self, frac_total, aggressive_fraction, time_needed=30., RA=0., 

264 ha_limits=None, survey_name='', time_jump=44., sun_alt_limit=-18.): 

265 super(Look_ahead_ddf_basis_function, self).__init__() 

266 if aggressive_fraction > frac_total: 

267 raise ValueError('aggressive_fraction should be less than frac_total') 

268 self.survey_name = survey_name 

269 self.frac_total = frac_total 

270 self.ra_hours = RA/360.*24. 

271 self.HA_limits = np.array(ha_limits) 

272 self.sun_alt_limit = str(int(sun_alt_limit)).replace('-', 'n') 

273 self.time_jump = time_jump / 60. # To hours 

274 self.time_needed = time_needed / 60. # To hours 

275 self.aggressive_fraction = aggressive_fraction 

276 self.survey_features['Ntot'] = features.N_obs_survey() 

277 self.survey_features['N_survey'] = features.N_obs_survey(note=self.survey_name) 

278 

279 def check_feasibility(self, conditions): 

280 result = True 

281 target_HA = (conditions.lmst - self.ra_hours) % 24 

282 ratio = self.survey_features['N_survey'].feature / self.survey_features['Ntot'].feature 

283 available_time = getattr(conditions, 'sun_' + self.sun_alt_limit + '_rising') - conditions.mjd 

284 # If it's more that self.time_jump to hour angle zero 

285 # See if there will be enough time to twilight in the future 

286 if (target_HA > 12) & (target_HA < 24.-self.time_jump): 

287 if available_time > (self.time_needed + self.time_jump): 

288 result = False 

289 # If the survey has fallen far behind, be agressive and observe anytime it's up. 

290 if ratio < self.aggressive_fraction: 

291 result = True 

292 return result 

293 

294 

295class Clouded_out_basis_function(Base_basis_function): 

296 def __init__(self, cloud_limit=0.7): 

297 super(Clouded_out_basis_function, self).__init__() 

298 self.cloud_limit = cloud_limit 

299 

300 def check_feasibility(self, conditions): 

301 result = True 

302 if conditions.bulk_cloud > self.cloud_limit: 

303 result = False 

304 return result 

305 

306 

307class Rising_more_basis_function(Base_basis_function): 

308 """Say a spot is not available if it will rise substatially before twilight. 

309 

310 Parameters 

311 ---------- 

312 RA : float 

313 The RA of the point in the sky (degrees) 

314 pad : float 

315 When to start observations if there's plenty of time before twilight (minutes) 

316 """ 

317 def __init__(self, RA, pad=30.): 

318 super(Rising_more_basis_function, self).__init__() 

319 self.RA_hours = RA * 24 / 360. 

320 self.pad = pad/60. # To hours 

321 

322 def check_feasibility(self, conditions): 

323 result = True 

324 hour_angle = conditions.lmst - self.RA_hours 

325 # If it's rising, and twilight is well beyond when it crosses the meridian 

326 time_to_twi = (conditions.sun_n18_rising - conditions.mjd)*24. 

327 if (hour_angle < -self.pad) & (np.abs(hour_angle) < (time_to_twi - self.pad)): 

328 result = False 

329 return result 

330 

331 

332class Sun_alt_limit_basis_function(Base_basis_function): 

333 """Don't try unless the sun is below some limit 

334 """ 

335 

336 def __init__(self, alt_limit=-12.1): 

337 super(Sun_alt_limit_basis_function, self).__init__() 

338 self.alt_limit = np.radians(alt_limit) 

339 

340 def check_feasibility(self, conditions): 

341 result = True 

342 if conditions.sunAlt > self.alt_limit: 

343 result = False 

344 return result 

345 

346 

347## XXX--TODO: Can include checks to see if downtime is coming, clouds are coming, moon rising, or surveys in a higher tier  

348# Have observations they want to execute soon.