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 

5from lsst.sims.featureScheduler.utils import int_rounded 

6 

7 

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 

17 

18class Filter_loaded_basis_function(Base_basis_function): 

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

20 

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 

31 

32 def check_feasibility(self, conditions): 

33 

34 for filtername in self.filternames: 

35 result = filtername in conditions.mounted_filters 

36 if result is False: 

37 return result 

38 return result 

39 

40 

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) 

50 

51 def check_feasibility(self, conditions): 

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

53 result = self.pattern[indx] 

54 return result 

55 

56 

57class Time_in_twilight_basis_function(Base_basis_function): 

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

59 

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 

68 

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 

73 

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 

81 

82 

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) 

90 

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 

95 

96 

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. 

100 

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) 

112 

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 

117 

118 

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. 

122 

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 

131 

132 def check_feasibility(self, conditions): 

133 if len(conditions.scheduled_observations) == 0: 

134 return True 

135 

136 available_time = np.min(conditions.scheduled_observations) - conditions.mjd 

137 result = available_time > self.time_needed 

138 return result 

139 

140 

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

148 

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 

156 

157 

158class Force_delay_basis_function(Base_basis_function): 

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

160 

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) 

171 

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 

177 

178 

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. 

181 

182 Parameters 

183 ---------- 

184 

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) 

196 

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 

207 

208 

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. 

212 

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) 

225 

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 

233 

234 return result 

235 

236 

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 

244 

245 

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. 

250 

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) 

264 

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 

274 

275 

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. 

278 

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) 

315 

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 

337 

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 

342 

343 

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 

348 

349 def check_feasibility(self, conditions): 

350 result = True 

351 if conditions.bulk_cloud > self.cloud_limit: 

352 result = False 

353 return result 

354 

355 

356class Rising_more_basis_function(Base_basis_function): 

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

358 

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 

370 

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 

379 

380 

381class Sun_alt_limit_basis_function(Base_basis_function): 

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

383 """ 

384 

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) 

388 

389 def check_feasibility(self, conditions): 

390 result = True 

391 if conditions.sunAlt > self.alt_limit: 

392 result = False 

393 return result 

394 

395 

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.