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.utils import (empty_observation, set_default_nside) 

3import lsst.sims.featureScheduler.features as features 

4from lsst.sims.featureScheduler.surveys import BaseSurvey 

5from lsst.sims.utils import _approx_RaDec2AltAz, _raDec2Hpid 

6import logging 

7 

8log = logging.getLogger(__name__) 

9 

10__all__ = ['Scripted_survey', 'Pairs_survey_scripted'] 

11 

12 

13class Scripted_survey(BaseSurvey): 

14 """ 

15 Take a set of scheduled observations and serve them up. 

16 """ 

17 def __init__(self, basis_functions, reward=1e6, ignore_obs='dummy', 

18 nside=None, min_alt=30., max_alt=85.): 

19 """ 

20 min_alt : float (30.) 

21 The minimum altitude to attempt to chace a pair to (degrees). Default of 30 = airmass of 2. 

22 max_alt : float(85.) 

23 The maximum altitude to attempt to chase a pair to (degrees). 

24 

25 """ 

26 if nside is None: 

27 nside = set_default_nside() 

28 

29 self.extra_features = {} 

30 

31 self.min_alt = np.radians(min_alt) 

32 self.max_alt = np.radians(max_alt) 

33 self.nside = nside 

34 self.reward_val = reward 

35 self.reward = -reward 

36 super(Scripted_survey, self).__init__(basis_functions=basis_functions, 

37 ignore_obs=ignore_obs, nside=nside) 

38 

39 def add_observation(self, observation, indx=None, **kwargs): 

40 """Check if this matches a scripted observation 

41 """ 

42 # From base class 

43 if self.ignore_obs not in observation['note']: 

44 for feature in self.extra_features: 

45 self.extra_features[feature].add_observation(observation, **kwargs) 

46 self.reward_checked = False 

47 

48 dt = self.obs_wanted['mjd'] - observation['mjd'] 

49 # was it taken in the right time window, and hasn't already been marked as observed. 

50 time_matches = np.where((np.abs(dt) < self.mjd_tol) & (~self.obs_log))[0] 

51 for match in time_matches: 

52 # Might need to change this to an angular distance calc and add another tolerance? 

53 if (self.obs_wanted[match]['RA'] == observation['RA']) & \ 

54 (self.obs_wanted[match]['dec'] == observation['dec']) & \ 

55 (self.obs_wanted[match]['filter'] == observation['filter']): 

56 self.obs_log[match] = True 

57 break 

58 

59 def calc_reward_function(self, conditions): 

60 """If there is an observation ready to go, execute it, otherwise, -inf 

61 """ 

62 observation = self._check_list() 

63 if observation is None: 

64 self.reward = -np.inf 

65 else: 

66 self.reward = self.reward_val 

67 return self.reward 

68 

69 def _slice2obs(self, obs_row): 

70 """take a slice and return a full observation object 

71 """ 

72 observation = empty_observation() 

73 for key in ['RA', 'dec', 'filter', 'exptime', 'nexp', 'note', 'field_id']: 

74 observation[key] = obs_row[key] 

75 return observation 

76 

77 def _check_alts(self, indices): 

78 """Check the altitudes of potential matches. 

79 """ 

80 # This is kind of a kludgy low-resolution way to convert ra,dec to alt,az, but should be really fast. 

81 # XXX--should I stick the healpixel value on when I set the script? Might be faster. 

82 # XXX not sure this really needs to be it's own method 

83 hp_ids = _raDec2Hpid(self.nside, self.obs_wanted[indices]['RA'], self.obs_wanted[indices]['dec']) 

84 alts = self.extra_features['altaz'].feature['alt'][hp_ids] 

85 in_range = np.where((alts < self.max_alt) & (alts > self.min_alt)) 

86 indices = indices[in_range] 

87 return indices 

88 

89 def _check_list(self, conditions): 

90 """Check to see if the current mjd is good 

91 """ 

92 dt = self.obs_wanted['mjd'] - conditions.mjd 

93 # Check for matches with the right requested MJD 

94 matches = np.where((np.abs(dt) < self.mjd_tol) & (~self.obs_log))[0] 

95 # Trim down to ones that are in the altitude limits 

96 matches = self._check_alts(matches) 

97 if matches.size > 0: 

98 observation = self._slice2obs(self.obs_wanted[matches[0]]) 

99 else: 

100 observation = None 

101 return observation 

102 

103 def set_script(self, obs_wanted, mjd_tol=15.): 

104 """ 

105 Parameters 

106 ---------- 

107 obs_wanted : np.array 

108 The observations that should be executed. Needs to have columns with dtype names: 

109 XXX 

110 mjds : np.array 

111 The MJDs for the observaitons, should be same length as obs_list 

112 mjd_tol : float (15.) 

113 The tolerance to consider an observation as still good to observe (min) 

114 """ 

115 self.mjd_tol = mjd_tol/60./24. # to days 

116 self.obs_wanted = obs_wanted 

117 # Set something to record when things have been observed 

118 self.obs_log = np.zeros(obs_wanted.size, dtype=bool) 

119 

120 def add_to_script(self, observation, mjd_tol=15.): 

121 """ 

122 Parameters 

123 ---------- 

124 observation : observation object 

125 The observation one would like to add to the scripted surveys 

126 mjd_tol : float (15.) 

127 The time tolerance on the observation (minutes) 

128 """ 

129 self.mjd_tol = mjd_tol/60./24. # to days 

130 self.obs_wanted = np.concatenate((self.obs_wanted, observation)) 

131 self.obs_log = np.concatenate((self.obs_log, np.zeros(1, dtype=bool))) 

132 # XXX--could do a sort on mjd here if I thought that was a good idea. 

133 # XXX-note, there's currently nothing that flushes this, so adding 

134 # observations can pile up nonstop. Should prob flush nightly or something 

135 

136 def generate_observations(self, conditions): 

137 observation = self._check_list(conditions) 

138 return [observation] 

139 

140 

141class Pairs_survey_scripted(Scripted_survey): 

142 """Check if incoming observations will need a pair in 30 minutes. If so, add to the queue 

143 """ 

144 def __init__(self, basis_functions, filt_to_pair='griz', 

145 dt=40., ttol=10., reward_val=101., note='scripted', ignore_obs='ack', 

146 min_alt=30., max_alt=85., lat=-30.2444, moon_distance=30., max_slew_to_pair=15., 

147 nside=None): 

148 """ 

149 Parameters 

150 ---------- 

151 filt_to_pair : str (griz) 

152 Which filters to try and get pairs of 

153 dt : float (40.) 

154 The ideal gap between pairs (minutes) 

155 ttol : float (10.) 

156 The time tolerance when gathering a pair (minutes) 

157 """ 

158 if nside is None: 

159 nside = set_default_nside() 

160 

161 super(Pairs_survey_scripted, self).__init__(basis_functions=basis_functions, 

162 ignore_obs=ignore_obs, min_alt=min_alt, 

163 max_alt=max_alt, nside=nside) 

164 

165 self.lat = np.radians(lat) 

166 self.note = note 

167 self.ttol = ttol/60./24. 

168 self.dt = dt/60./24. # To days 

169 self.max_slew_to_pair = max_slew_to_pair # in seconds 

170 self._moon_distance = np.radians(moon_distance) 

171 

172 self.extra_features = {} 

173 self.extra_features['Pair_map'] = features.Pair_in_night(filtername=filt_to_pair) 

174 

175 self.reward_val = reward_val 

176 self.filt_to_pair = filt_to_pair 

177 # list to hold observations 

178 self.observing_queue = [] 

179 # make ignore_obs a list 

180 if type(self.ignore_obs) is str: 

181 self.ignore_obs = [self.ignore_obs] 

182 

183 def add_observation(self, observation, indx=None, **kwargs): 

184 """Add an observed observation 

185 """ 

186 # self.ignore_obs not in str(observation['note']) 

187 to_ignore = np.any([ignore in str(observation['note']) for ignore in self.ignore_obs]) 

188 log.debug('[Pairs.add_observation]: %s: %s: %s', to_ignore, str(observation['note']), self.ignore_obs) 

189 log.debug('[Pairs.add_observation.queue]: %s', self.observing_queue) 

190 if not to_ignore: 

191 # Update my extra features: 

192 for feature in self.extra_features: 

193 if hasattr(self.extra_features[feature], 'add_observation'): 

194 self.extra_features[feature].add_observation(observation, indx=indx) 

195 self.reward_checked = False 

196 

197 # Check if this observation needs a pair 

198 # XXX--only supporting single pairs now. Just start up another scripted survey 

199 # to grab triples, etc? Or add two observations to queue at a time? 

200 # keys_to_copy = ['RA', 'dec', 'filter', 'exptime', 'nexp'] 

201 if ((observation['filter'][0] in self.filt_to_pair) and 

202 (np.max(self.extra_features['Pair_map'].feature[indx]) < 1)): 

203 obs_to_queue = empty_observation() 

204 for key in observation.dtype.names: 

205 obs_to_queue[key] = observation[key] 

206 # Fill in the ideal time we would like this observed 

207 log.debug('Observation MJD: %.4f (dt=%.4f)', obs_to_queue['mjd'], self.dt) 

208 obs_to_queue['mjd'] += self.dt 

209 self.observing_queue.append(obs_to_queue) 

210 log.debug('[Pairs.add_observation.queue.size]: %i', len(self.observing_queue)) 

211 for obs in self.observing_queue: 

212 log.debug('[Pairs.add_observation.queue]: %s', obs) 

213 

214 def _purge_queue(self, conditions): 

215 """Remove any pair where it's too late to observe it 

216 """ 

217 # Assuming self.observing_queue is sorted by MJD. 

218 if len(self.observing_queue) > 0: 

219 stale = True 

220 in_window = np.abs(self.observing_queue[0]['mjd']-conditions.mjd) < self.ttol 

221 log.debug('Purging queue') 

222 while stale: 

223 # If the next observation in queue is past the window, drop it 

224 if (self.observing_queue[0]['mjd'] < conditions.mjd) & (~in_window): 

225 log.debug('Past the window: obs_mjd=%.4f (current_mjd=%.4f)', 

226 self.observing_queue[0]['mjd'], 

227 conditions.mjd) 

228 del self.observing_queue[0] 

229 # If we are in the window, but masked, drop it 

230 elif (in_window) & (~self._check_mask(self.observing_queue[0], conditions)): 

231 log.debug('Masked') 

232 del self.observing_queue[0] 

233 # If in time window, but in alt exclusion zone 

234 elif (in_window) & (~self._check_alts(self.observing_queue[0], conditions)): 

235 log.debug('in alt exclusion zone') 

236 del self.observing_queue[0] 

237 else: 

238 stale = False 

239 # If we have deleted everything, break out of where 

240 if len(self.observing_queue) == 0: 

241 stale = False 

242 

243 def _check_alts(self, observation, conditions): 

244 result = False 

245 # Just do a fast ra,dec to alt,az conversion. Can use LMST from a feature. 

246 

247 alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'], 

248 self.lat, None, 

249 conditions.mjd, 

250 lmst=conditions.lmst) 

251 in_range = np.where((alt < self.max_alt) & (alt > self.min_alt))[0] 

252 if np.size(in_range) > 0: 

253 result = True 

254 return result 

255 

256 def _check_mask(self, observation, conditions): 

257 """Check that the proposed observation is not currently masked for some reason on the sky map. 

258 True if the observation is good to observe 

259 False if the proposed observation is masked 

260 """ 

261 

262 hpid = np.max(_raDec2Hpid(self.nside, observation['RA'], observation['dec'])) 

263 skyval = conditions.M5Depth[observation['filter'][0]][hpid] 

264 

265 if skyval > 0: 

266 return True 

267 else: 

268 return False 

269 

270 def calc_reward_function(self, conditions): 

271 self._purge_queue(conditions) 

272 result = -np.inf 

273 self.reward = result 

274 log.debug('Pair - calc_reward_func') 

275 for indx in range(len(self.observing_queue)): 

276 

277 check = self._check_observation(self.observing_queue[indx], conditions) 

278 log.debug('%s: %s', check, self.observing_queue[indx]) 

279 if check[0]: 

280 result = self.reward_val 

281 self.reward = self.reward_val 

282 break 

283 elif not check[1]: 

284 break 

285 

286 self.reward_checked = True 

287 return result 

288 

289 def _check_observation(self, observation, conditions): 

290 

291 delta_t = observation['mjd'] - conditions.mjd 

292 log.debug('Check_observation: obs_mjd=%.4f (current_mjd=%.4f, delta=%.4f, tol=%.4f)', 

293 observation['mjd'], 

294 conditions.mjd, 

295 delta_t, 

296 self.ttol) 

297 obs_hp = _raDec2Hpid(self.nside, observation['RA'], observation['dec']) 

298 slewtime = conditions.slewtime[obs_hp[0]] 

299 in_slew_window = slewtime <= self.max_slew_to_pair or delta_t < 0. 

300 in_time_window = np.abs(delta_t) < self.ttol 

301 

302 if conditions.current_filter is None: 

303 infilt = True 

304 else: 

305 infilt = conditions.current_filter in self.filt_to_pair 

306 

307 is_observable = self._check_mask(observation, conditions) 

308 valid = in_time_window & infilt & in_slew_window & is_observable 

309 log.debug('Pair - observation: %s ' % observation) 

310 log.debug('Pair - check[%s]: in_time_window[%s] infilt[%s] in_slew_window[%s] is_observable[%s]' % 

311 (valid, in_time_window, infilt, in_slew_window, is_observable)) 

312 

313 return (valid, 

314 in_time_window, 

315 infilt, 

316 in_slew_window, 

317 is_observable) 

318 

319 def generate_observations(self, conditions): 

320 # Toss anything in the queue that is too old to pair up: 

321 self._purge_queue(conditions) 

322 # Check for something I want a pair of 

323 result = [] 

324 # if len(self.observing_queue) > 0: 

325 log.debug('Pair - call') 

326 for indx in range(len(self.observing_queue)): 

327 

328 check = self._check_observation(self.observing_queue[indx], conditions) 

329 

330 if check[0]: 

331 result = self.observing_queue.pop(indx) 

332 result['note'] = 'pair(%s)' % self.note 

333 # Make sure we don't change filter if we don't have to. 

334 if conditions.current_filter is not None: 

335 result['filter'] = conditions.current_filter 

336 # Make sure it is observable! 

337 # if self._check_mask(result): 

338 result = [result] 

339 break 

340 elif not check[1]: 

341 # If this is not in time window and queue is chronological, none will be... 

342 break 

343 

344 return result