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, _angularSeparation 

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): 

19 """ 

20 """ 

21 if nside is None: 

22 nside = set_default_nside() 

23 

24 self.extra_features = {} 

25 self.nside = nside 

26 self.reward_val = reward 

27 self.reward = -np.inf 

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

29 ignore_obs=ignore_obs, nside=nside) 

30 

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

32 """Check if observation matches a scripted observation 

33 """ 

34 

35 # From base class 

36 checks = [io not in str(observation['note']) for io in self.ignore_obs] 

37 if all(checks): 

38 for feature in self.extra_features: 

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

40 for bf in self.basis_functions: 

41 bf.add_observation(observation, **kwargs) 

42 for detailer in self.detailers: 

43 detailer.add_observation(observation, **kwargs) 

44 self.reward_checked = False 

45 

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

47 time_matches = np.where((observation['mjd'] > self.mjd_start) & 

48 (observation['mjd'] < self.obs_wanted['flush_by_mjd']) & 

49 (~self.obs_wanted['observed']) & 

50 (observation['note'] == self.obs_wanted['note']))[0] 

51 for match in time_matches: 

52 distances = _angularSeparation(self.obs_wanted[match]['RA'], 

53 self.obs_wanted[match]['dec'], 

54 observation['RA'], observation['dec']) 

55 if (distances < self.obs_wanted[match]['dist_tol']) & \ 

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

57 # Log it as observed. 

58 self.obs_wanted['observed'][match] = True 

59 self.scheduled_obs = self.obs_wanted['mjd'][~self.obs_wanted['observed']] 

60 break 

61 

62 def calc_reward_function(self, conditions): 

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

64 """ 

65 observation = self._check_list(conditions) 

66 if observation is None: 

67 self.reward = -np.inf 

68 else: 

69 self.reward = self.reward_val 

70 return self.reward 

71 

72 def _slice2obs(self, obs_row): 

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

74 """ 

75 observation = empty_observation() 

76 for key in ['RA', 'dec', 'filter', 'exptime', 'nexp', 

77 'note', 'rotSkyPos', 'flush_by_mjd']: 

78 observation[key] = obs_row[key] 

79 return observation 

80 

81 def _check_alts_HA(self, observation, conditions): 

82 """Given scheduled observations, check which ones can be done in current conditions. 

83 

84 Parameters 

85 ---------- 

86 observation : np.array 

87 An array of scheduled observations. Probably generated with lsst.sims.featureScheduler.utils.scheduled_observation 

88 """ 

89 # Just do a fast ra,dec to alt,az conversion. 

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

91 conditions.site.latitude_rad, None, 

92 conditions.mjd, 

93 lmst=conditions.lmst) 

94 HA = conditions.lmst - observation['RA']*12./np.pi 

95 HA[np.where(HA > 24)] -= 24 

96 HA[np.where(HA < 0)] += 24 

97 in_range = np.where((alt < observation['alt_max']) & (alt > observation['alt_min']) & 

98 ((HA > observation['HA_max']) | (HA < observation['HA_min'])))[0] 

99 return in_range 

100 

101 def _check_list(self, conditions): 

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

103 """ 

104 

105 # Scheduled observations that are in the right time window and have not been executed 

106 in_time_window = np.where((self.mjd_start < conditions.mjd) & 

107 (self.obs_wanted['flush_by_mjd'] > conditions.mjd) & 

108 (~self.obs_wanted['observed']))[0] 

109 

110 if np.size(in_time_window) > 0: 

111 pass_checks = self._check_alts_HA(self.obs_wanted[in_time_window], conditions) 

112 matches = in_time_window[pass_checks] 

113 else: 

114 matches = [] 

115 

116 if np.size(matches) > 0: 

117 # XXX--could make this a list and just send out all the things that currently match 

118 # rather than one at a time 

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

120 else: 

121 observation = None 

122 return observation 

123 

124 def set_script(self, obs_wanted): 

125 """ 

126 Parameters 

127 ---------- 

128 obs_wanted : np.array 

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

130 Should be from lsst.sim.featureScheduler.utils.scheduled_observation 

131 mjds : np.array 

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

133 mjd_tol : float (15.) 

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

135 """ 

136 

137 self.obs_wanted = obs_wanted 

138 

139 self.obs_wanted.sort(order='mjd') 

140 self.mjd_start = self.obs_wanted['mjd'] - self.obs_wanted['mjd_tol'] 

141 # Here is the atribute that core scheduler checks to broadcast scheduled observations 

142 # in the conditions object. 

143 self.scheduled_obs = self.obs_wanted['mjd'] 

144 

145 def generate_observations_rough(self, conditions): 

146 observation = self._check_list(conditions) 

147 return [observation] 

148 

149 

150class Pairs_survey_scripted(Scripted_survey): 

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

152 """ 

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

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

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

156 nside=None): 

157 """ 

158 Parameters 

159 ---------- 

160 filt_to_pair : str (griz) 

161 Which filters to try and get pairs of 

162 dt : float (40.) 

163 The ideal gap between pairs (minutes) 

164 ttol : float (10.) 

165 The time tolerance when gathering a pair (minutes) 

166 """ 

167 if nside is None: 

168 nside = set_default_nside() 

169 

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

171 ignore_obs=ignore_obs, min_alt=min_alt, 

172 max_alt=max_alt, nside=nside) 

173 

174 self.lat = np.radians(lat) 

175 self.note = note 

176 self.ttol = ttol/60./24. 

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

178 self.max_slew_to_pair = max_slew_to_pair # in seconds 

179 self._moon_distance = np.radians(moon_distance) 

180 

181 self.extra_features = {} 

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

183 

184 self.reward_val = reward_val 

185 self.filt_to_pair = filt_to_pair 

186 # list to hold observations 

187 self.observing_queue = [] 

188 # make ignore_obs a list 

189 if type(self.ignore_obs) is str: 

190 self.ignore_obs = [self.ignore_obs] 

191 

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

193 """Add an observed observation 

194 """ 

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

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

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

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

199 if not to_ignore: 

200 # Update my extra features: 

201 for feature in self.extra_features: 

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

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

204 self.reward_checked = False 

205 

206 # Check if this observation needs a pair 

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

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

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

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

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

212 obs_to_queue = empty_observation() 

213 for key in observation.dtype.names: 

214 obs_to_queue[key] = observation[key] 

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

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

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

218 self.observing_queue.append(obs_to_queue) 

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

220 for obs in self.observing_queue: 

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

222 

223 def _purge_queue(self, conditions): 

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

225 """ 

226 # Assuming self.observing_queue is sorted by MJD. 

227 if len(self.observing_queue) > 0: 

228 stale = True 

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

230 log.debug('Purging queue') 

231 while stale: 

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

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

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

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

236 conditions.mjd) 

237 del self.observing_queue[0] 

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

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

240 log.debug('Masked') 

241 del self.observing_queue[0] 

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

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

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

245 del self.observing_queue[0] 

246 else: 

247 stale = False 

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

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

250 stale = False 

251 

252 def _check_alts(self, observation, conditions): 

253 result = False 

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

255 

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

257 self.lat, None, 

258 conditions.mjd, 

259 lmst=conditions.lmst) 

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

261 if np.size(in_range) > 0: 

262 result = True 

263 return result 

264 

265 def _check_mask(self, observation, conditions): 

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

267 True if the observation is good to observe 

268 False if the proposed observation is masked 

269 """ 

270 

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

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

273 

274 if skyval > 0: 

275 return True 

276 else: 

277 return False 

278 

279 def calc_reward_function(self, conditions): 

280 self._purge_queue(conditions) 

281 result = -np.inf 

282 self.reward = result 

283 log.debug('Pair - calc_reward_func') 

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

285 

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

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

288 if check[0]: 

289 result = self.reward_val 

290 self.reward = self.reward_val 

291 break 

292 elif not check[1]: 

293 break 

294 

295 self.reward_checked = True 

296 return result 

297 

298 def _check_observation(self, observation, conditions): 

299 

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

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

302 observation['mjd'], 

303 conditions.mjd, 

304 delta_t, 

305 self.ttol) 

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

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

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

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

310 

311 if conditions.current_filter is None: 

312 infilt = True 

313 else: 

314 infilt = conditions.current_filter in self.filt_to_pair 

315 

316 is_observable = self._check_mask(observation, conditions) 

317 valid = in_time_window & infilt & in_slew_window & is_observable 

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

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

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

321 

322 return (valid, 

323 in_time_window, 

324 infilt, 

325 in_slew_window, 

326 is_observable) 

327 

328 def generate_observations(self, conditions): 

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

330 self._purge_queue(conditions) 

331 # Check for something I want a pair of 

332 result = [] 

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

334 log.debug('Pair - call') 

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

336 

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

338 

339 if check[0]: 

340 result = self.observing_queue.pop(indx) 

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

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

343 if conditions.current_filter is not None: 

344 result['filter'] = conditions.current_filter 

345 # Make sure it is observable! 

346 # if self._check_mask(result): 

347 result = [result] 

348 break 

349 elif not check[1]: 

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

351 break 

352 

353 return result