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

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 Not_twilight_basis_function(Base_basis_function): 

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

121 """ 

122 # Should be -18 or -12 

123 """ 

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

125 super(Not_twilight_basis_function, self).__init__() 

126 

127 def check_feasibility(self, conditions): 

128 result = True 

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

130 result = False 

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

132 result = False 

133 return result 

134 

135 

136class Force_delay_basis_function(Base_basis_function): 

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

138 

139 Parameters 

140 ---------- 

141 days_delay : float (2) 

142 The number of days to force a gap on. 

143 """ 

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

145 super(Force_delay_basis_function, self).__init__() 

146 self.days_delay = days_delay 

147 self.survey_name = survey_name 

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

149 

150 def check_feasibility(self, conditions): 

151 result = True 

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

153 result = False 

154 return result 

155 

156 

157class Soft_delay_basis_function(Base_basis_function): 

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

159 

160 Parameters 

161 ---------- 

162 

163 """ 

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

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

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

167 super(Soft_delay_basis_function, self).__init__() 

168 self.delays = delays 

169 self.survey_name = survey_name 

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

171 self.fractions = fractions 

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

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

174 

175 def check_feasibility(self, conditions): 

176 result = True 

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

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

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

180 indx -= 1 

181 delay = self.delays[indx] 

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

183 result = False 

184 return result 

185 

186 

187class Hour_Angle_limit_basis_function(Base_basis_function): 

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

189 limiting Deep Drilling Fields. 

190 

191 Parameters 

192 ---------- 

193 RA : float (0.) 

194 RA of the target (degrees). 

195 ha_limits : list of lists 

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

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

198 """ 

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

200 super(Hour_Angle_limit_basis_function, self).__init__() 

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

202 self.HA_limits = np.array(ha_limits) 

203 

204 def check_feasibility(self, conditions): 

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

206 # Are we in any of the possible windows 

207 result = False 

208 for limit in self.HA_limits: 

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

210 result = result or lres 

211 

212 return result 

213 

214 

215class Moon_down_basis_function(Base_basis_function): 

216 """Demand the moon is down """ 

217 def check_feasibility(self, conditions): 

218 result = True 

219 if conditions.moonAlt > 0: 

220 result = False 

221 return result 

222 

223 

224class Fraction_of_obs_basis_function(Base_basis_function): 

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

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

227 total survey. 

228 

229 Parameters 

230 ---------- 

231 frac_total : float 

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

233 survey_name : str 

234 The name of the survey 

235 """ 

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

237 super(Fraction_of_obs_basis_function, self).__init__() 

238 self.survey_name = survey_name 

239 self.frac_total = frac_total 

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

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

242 

243 def check_feasibility(self, conditions): 

244 # If nothing has been observed, fine to go 

245 result = True 

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

247 return result 

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

249 if ratio > self.frac_total: 

250 result = False 

251 return result 

252 

253 

254class Look_ahead_ddf_basis_function(Base_basis_function): 

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

256 

257 Parameters 

258 ---------- 

259 frac_total : float 

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

261 aggressive_fraction : float 

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

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

264 time_needed : float (30.) 

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

266 RA : float (0.) 

267 The RA of the DDF 

268 ha_limits : list of lists (None) 

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

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

271 survey_name : str ('') 

272 The name of the survey 

273 time_jump : float (44.) 

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

275 sun_alt_limit : float (-18.) 

276 The limit to assume twilight starts (degrees) 

277 """ 

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

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

280 super(Look_ahead_ddf_basis_function, self).__init__() 

281 if aggressive_fraction > frac_total: 

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

283 self.survey_name = survey_name 

284 self.frac_total = frac_total 

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

286 self.HA_limits = np.array(ha_limits) 

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

288 self.time_jump = time_jump / 60. / 24. # To days 

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

290 self.aggressive_fraction = aggressive_fraction 

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

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

293 

294 def check_feasibility(self, conditions): 

295 result = True 

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

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

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

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

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

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

302 if int_rounded(available_time) > int_rounded(self.time_needed + self.time_jump): 

303 result = False 

304 # If we paused for better conditions, but the moon will rise, turn things back on. 

305 if int_rounded(conditions.moonAlt) < int_rounded(0): 

306 if int_rounded(conditions.moonrise) > int_rounded(conditions.mjd): 

307 if int_rounded(conditions.moonrise - conditions.mjd) > int_rounded(self.time_jump): 

308 result = True 

309 # If the moon is up and will set soon, pause 

310 if int_rounded(conditions.moonAlt) > int_rounded(0): 

311 time_after_moonset = getattr(conditions, 'sun_' + self.sun_alt_limit + '_rising') - conditions.moonset 

312 if int_rounded(conditions.moonset) > int_rounded(self.time_jump): 

313 if int_rounded(time_after_moonset) > int_rounded(self.time_needed): 

314 result = False 

315 

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

317 if int_rounded(ratio) < int_rounded(self.aggressive_fraction): 

318 result = True 

319 return result 

320 

321 

322class Clouded_out_basis_function(Base_basis_function): 

323 def __init__(self, cloud_limit=0.7): 

324 super(Clouded_out_basis_function, self).__init__() 

325 self.cloud_limit = cloud_limit 

326 

327 def check_feasibility(self, conditions): 

328 result = True 

329 if conditions.bulk_cloud > self.cloud_limit: 

330 result = False 

331 return result 

332 

333 

334class Rising_more_basis_function(Base_basis_function): 

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

336 

337 Parameters 

338 ---------- 

339 RA : float 

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

341 pad : float 

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

343 """ 

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

345 super(Rising_more_basis_function, self).__init__() 

346 self.RA_hours = RA * 24 / 360. 

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

348 

349 def check_feasibility(self, conditions): 

350 result = True 

351 hour_angle = conditions.lmst - self.RA_hours 

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

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

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

355 result = False 

356 return result 

357 

358 

359class Sun_alt_limit_basis_function(Base_basis_function): 

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

361 """ 

362 

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

364 super(Sun_alt_limit_basis_function, self).__init__() 

365 self.alt_limit = np.radians(alt_limit) 

366 

367 def check_feasibility(self, conditions): 

368 result = True 

369 if conditions.sunAlt > self.alt_limit: 

370 result = False 

371 return result 

372 

373 

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

375# Have observations they want to execute soon.