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 'End_of_evening_basis_function'] 

15 

16 

17class Filter_loaded_basis_function(Base_basis_function): 

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

19 

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 

30 

31 def check_feasibility(self, conditions): 

32 

33 for filtername in self.filternames: 

34 result = filtername in conditions.mounted_filters 

35 if result is False: 

36 return result 

37 return result 

38 

39 

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) 

49 

50 def check_feasibility(self, conditions): 

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

52 result = self.pattern[indx] 

53 return result 

54 

55 

56class Time_in_twilight_basis_function(Base_basis_function): 

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

58 

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 

67 

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 

72 

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 

80 

81 

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) 

89 

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 

94 

95 

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. 

99 

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) 

111 

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 

116 

117 

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__() 

125 

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 

133 

134 

135class Force_delay_basis_function(Base_basis_function): 

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

137 

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) 

148 

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 

154 

155 

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. 

158 

159 Parameters 

160 ---------- 

161 

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) 

173 

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 

184 

185 

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. 

189 

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) 

202 

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 

210 

211 return result 

212 

213 

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 

221 

222 

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. 

227 

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) 

241 

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 

251 

252 

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. 

255 

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) 

292 

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 

314 

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 

319 

320 

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 

325 

326 def check_feasibility(self, conditions): 

327 result = True 

328 if conditions.bulk_cloud > self.cloud_limit: 

329 result = False 

330 return result 

331 

332 

333class Rising_more_basis_function(Base_basis_function): 

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

335 

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 

347 

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 

356 

357 

358class Sun_alt_limit_basis_function(Base_basis_function): 

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

360 """ 

361 

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) 

365 

366 def check_feasibility(self, conditions): 

367 result = True 

368 if conditions.sunAlt > self.alt_limit: 

369 result = False 

370 return result 

371 

372 

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.