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

17 

18 

19class Filter_loaded_basis_function(Base_basis_function): 

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

21 

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 

32 

33 def check_feasibility(self, conditions): 

34 

35 for filtername in self.filternames: 

36 result = filtername in conditions.mounted_filters 

37 if result is False: 

38 return result 

39 return result 

40 

41 

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) 

49 

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 

55 

56 

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) 

66 

67 def check_feasibility(self, conditions): 

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

69 result = self.pattern[indx] 

70 return result 

71 

72 

73class Time_in_twilight_basis_function(Base_basis_function): 

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

75 

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 

84 

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 

89 

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 

97 

98 

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) 

106 

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 

111 

112 

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. 

116 

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) 

128 

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 

133 

134 

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. 

138 

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 

147 

148 def check_feasibility(self, conditions): 

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

150 return True 

151 

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

153 result = available_time > self.time_needed 

154 return result 

155 

156 

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

164 

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 

172 

173 

174class Force_delay_basis_function(Base_basis_function): 

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

176 

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) 

187 

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 

193 

194 

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. 

197 

198 Parameters 

199 ---------- 

200 

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) 

212 

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 

223 

224 

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. 

228 

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) 

241 

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 

249 

250 return result 

251 

252 

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 

260 

261 

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. 

266 

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) 

280 

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 

290 

291 

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. 

294 

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) 

331 

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 

353 

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 

358 

359 

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 

364 

365 def check_feasibility(self, conditions): 

366 result = True 

367 if conditions.bulk_cloud > self.cloud_limit: 

368 result = False 

369 return result 

370 

371 

372class Rising_more_basis_function(Base_basis_function): 

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

374 

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 

386 

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 

395 

396 

397class Sun_alt_limit_basis_function(Base_basis_function): 

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

399 """ 

400 

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) 

404 

405 def check_feasibility(self, conditions): 

406 result = True 

407 if conditions.sunAlt > self.alt_limit: 

408 result = False 

409 return result 

410 

411 

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.