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

1from lsst.sims.utils import _raDec2Hpid, _approx_RaDec2AltAz, _angularSeparation, _approx_altaz2pa 

2import numpy as np 

3from lsst.sims.featureScheduler.utils import int_rounded 

4import copy 

5 

6__all__ = ["Base_detailer", "Zero_rot_detailer", "Comcam_90rot_detailer", "Close_alt_detailer", 

7 "Take_as_pairs_detailer", "Twilight_triple_detailer", "Spider_rot_detailer", "Flush_for_sched_detailer"] 

8 

9 

10class Base_detailer(object): 

11 """ 

12 A Detailer is an object that takes a list of proposed observations and adds "details" to them. The 

13 primary purpose is that the Markov Decision Process does an excelent job selecting RA,Dec,filter 

14 combinations, but we may want to add additional logic such as what to set the camera rotation angle 

15 to, or what to use for an exposure time. We could also modify the order of the proposed observations. 

16 For Deep Drilling Fields, a detailer could be useful for computing dither positions and modifying 

17 the exact RA,Dec positions. 

18 """ 

19 

20 def __init__(self, nside=32): 

21 """ 

22 """ 

23 # Dict to hold all the features we want to track 

24 self.survey_features = {} 

25 self.nside = nside 

26 

27 def add_observation(self, observation, indx=None): 

28 """ 

29 Parameters 

30 ---------- 

31 observation : np.array 

32 An array with information about the input observation 

33 indx : np.array 

34 The indices of the healpix map that the observation overlaps with 

35 """ 

36 for feature in self.survey_features: 

37 self.survey_features[feature].add_observation(observation, indx=indx) 

38 

39 def __call__(self, observation_list, conditions): 

40 """ 

41 Parameters 

42 ---------- 

43 observation_list : list of observations 

44 The observations to detail. 

45 conditions : lsst.sims.featureScheduler.conditions object 

46 

47 Returns 

48 ------- 

49 List of observations. 

50 """ 

51 

52 return observation_list 

53 

54 

55class Zero_rot_detailer(Base_detailer): 

56 """ 

57 Detailer to set the camera rotation to be apporximately zero in rotTelPos. 

58 Because it can never be written too many times: 

59 rotSkyPos = rotTelPos - ParallacticAngle 

60 But, wait, what? Is it really the other way? 

61 """ 

62 

63 def __call__(self, observation_list, conditions): 

64 

65 # XXX--should I convert the list into an array and get rid of this loop? 

66 for obs in observation_list: 

67 alt, az = _approx_RaDec2AltAz(obs['RA'], obs['dec'], conditions.site.latitude_rad, 

68 conditions.site.longitude_rad, conditions.mjd) 

69 obs_pa = _approx_altaz2pa(alt, az, conditions.site.latitude_rad) 

70 obs['rotSkyPos'] = obs_pa 

71 

72 return observation_list 

73 

74 

75class Spider_rot_detailer(Base_detailer): 

76 """ 

77 Set the camera rotation to +/- 45 degrees so diffraction spikes align along chip rows 

78 and columns 

79 """ 

80 

81 def __call__(self, observation_list, conditions): 

82 indx = int(conditions.night % 2) 

83 rotTelPos = np.radians([45., 315.][indx]) 

84 

85 for obs in observation_list: 

86 obs['rotSkyPos'] = np.nan 

87 obs['rotTelPos'] = rotTelPos 

88 

89 return observation_list 

90 

91 

92class Comcam_90rot_detailer(Base_detailer): 

93 """ 

94 Detailer to set the camera rotation so rotSkyPos is 0, 90, 180, or 270 degrees. Whatever 

95 is closest to rotTelPos of zero. 

96 """ 

97 

98 def __call__(self, observation_list, conditions): 

99 favored_rotSkyPos = np.radians([0., 90., 180., 270., 360.]).reshape(5, 1) 

100 obs_array =np.concatenate(observation_list) 

101 alt, az = _approx_RaDec2AltAz(obs_array['RA'], obs_array['dec'], conditions.site.latitude_rad, 

102 conditions.site.longitude_rad, conditions.mjd) 

103 parallactic_angle = _approx_altaz2pa(alt, az, conditions.site.latitude_rad) 

104 # If we set rotSkyPos to parallactic angle, rotTelPos will be zero. So, find the 

105 # favored rotSkyPos that is closest to PA to keep rotTelPos as close as possible to zero. 

106 ang_diff = np.abs(parallactic_angle - favored_rotSkyPos) 

107 min_indxs = np.argmin(ang_diff, axis=0) 

108 # can swap 360 and zero if needed? 

109 final_rotSkyPos = favored_rotSkyPos[min_indxs] 

110 # Set all the observations to the proper rotSkyPos 

111 for rsp, obs in zip(final_rotSkyPos, observation_list): 

112 obs['rotSkyPos'] = rsp 

113 

114 return observation_list 

115 

116 

117class Close_alt_detailer(Base_detailer): 

118 """ 

119 re-order a list of observations so that the closest in altitude to the current pointing is first. 

120 

121 Parameters 

122 ---------- 

123 alt_band : float (10) 

124 The altitude band to try and stay in (degrees) 

125 """ 

126 def __init__(self, alt_band=10.): 

127 super(Close_alt_detailer, self).__init__() 

128 self.alt_band = int_rounded(np.radians(alt_band)) 

129 

130 def __call__(self, observation_list, conditions): 

131 obs_array = np.concatenate(observation_list) 

132 alt, az = _approx_RaDec2AltAz(obs_array['RA'], obs_array['dec'], conditions.site.latitude_rad, 

133 conditions.site.longitude_rad, conditions.mjd) 

134 alt_diff = np.abs(alt - conditions.telAlt) 

135 in_band = np.where(int_rounded(alt_diff) <= self.alt_band)[0] 

136 if in_band.size == 0: 

137 in_band = np.arange(alt.size) 

138 

139 # Find the closest in angular distance of the points that are in band 

140 ang_dist = _angularSeparation(az[in_band], alt[in_band], conditions.telAz, conditions.telAlt) 

141 good = np.min(np.where(ang_dist == ang_dist.min())[0]) 

142 indx = in_band[good] 

143 result = observation_list[indx:] + observation_list[:indx] 

144 return result 

145 

146 

147class Flush_for_sched_detailer(Base_detailer): 

148 """Update the flush-by MJD to be before any scheduled observations 

149 

150 Parameters 

151 ---------- 

152 tol : float 

153 How much before to flush (minutes) 

154 """ 

155 def __init__(self, tol=2.5): 

156 super(Flush_for_sched_detailer, self).__init__() 

157 self.tol = tol/24./60. # To days 

158 

159 def __call__(self, observation_list, conditions): 

160 if np.size(conditions.scheduled_observations) > 0: 

161 new_flush = np.min(conditions.scheduled_observations) - self.tol 

162 for obs in observation_list: 

163 if obs['flush_by_mjd'] > new_flush: 

164 obs['flush_by_mjd'] = new_flush 

165 return observation_list 

166 

167 

168class Take_as_pairs_detailer(Base_detailer): 

169 def __init__(self, filtername='r', exptime=None, nexp_dict=None): 

170 """ 

171 """ 

172 super(Take_as_pairs_detailer, self).__init__() 

173 self.filtername = filtername 

174 self.exptime = exptime 

175 self.nexp_dict = nexp_dict 

176 

177 def __call__(self, observation_list, conditions): 

178 paired = copy.deepcopy(observation_list) 

179 if self.exptime is not None: 

180 for obs in paired: 

181 obs['exptime'] = self.exptime 

182 for obs in paired: 

183 obs['filter'] = self.filtername 

184 if self.nexp_dict is not None: 

185 obs['nexp'] = self.nexp_dict[self.filtername] 

186 if conditions.current_filter == self.filtername: 

187 for obs in paired: 

188 obs['note'] = obs['note'][0] + ', a' 

189 for obs in observation_list: 

190 obs['note'] = obs['note'][0] + ', b' 

191 result = paired + observation_list 

192 else: 

193 for obs in paired: 

194 obs['note'] = obs['note'][0] + ', b' 

195 for obs in observation_list: 

196 obs['note'] = obs['note'][0] + ', a' 

197 result = observation_list + paired 

198 # XXX--maybe a temp debugging thing, label what part of sequence each observation is. 

199 for i, obs in enumerate(result): 

200 obs['survey_id'] = i 

201 return result 

202 

203 

204class Twilight_triple_detailer(Base_detailer): 

205 def __init__(self, slew_estimate=5.0, n_repeat=3): 

206 super(Twilight_triple_detailer, self).__init__() 

207 self.slew_estimate = slew_estimate 

208 self.n_repeat = n_repeat 

209 

210 def __call__(self, observation_list, conditions): 

211 

212 obs_array = np.concatenate(observation_list) 

213 

214 # Estimate how much time is left in the twilgiht block 

215 potential_times = np.array([conditions.sun_n18_setting - conditions.mjd, 

216 conditions.sun_n12_rising - conditions.mjd]) 

217 

218 potential_times = np.min(potential_times[np.where(potential_times > 0)]) * 24.*3600. 

219 

220 # How long will observations take? 

221 cumulative_slew = np.arange(obs_array.size) * self.slew_estimate 

222 cumulative_expt = np.cumsum(obs_array['exptime']) 

223 cumulative_time = cumulative_slew + cumulative_expt 

224 # If we are way over, truncate the list before doing the triple 

225 if np.max(cumulative_time) > potential_times: 

226 max_indx = np.where(cumulative_time/self.n_repeat <= potential_times)[0] 

227 if np.size(max_indx) == 0: 

228 # Very bad magic number fudge 

229 max_indx = 3 

230 else: 

231 max_indx = np.max(max_indx) 

232 if max_indx == 0: 

233 max_indx += 1 

234 observation_list = observation_list[0:max_indx] 

235 

236 # Repeat the observations n times 

237 out_obs = [] 

238 for i in range(self.n_repeat): 

239 out_obs.extend(copy.deepcopy(observation_list)) 

240 

241 return out_obs