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 __future__ import absolute_import 

2from builtins import object 

3import numpy as np 

4import healpy as hp 

5from lsst.sims.utils import _hpid2RaDec 

6from lsst.sims.featureScheduler.utils import hp_in_lsst_fov, set_default_nside, hp_in_comcam_fov, int_rounded 

7from lsst.sims.utils import _approx_RaDec2AltAz, _approx_altaz2pa 

8import logging 

9 

10 

11__all__ = ['Core_scheduler'] 

12 

13 

14class Core_scheduler(object): 

15 """Core scheduler that takes completed observations and observatory status and requests observations 

16 

17 Parameters 

18 ---------- 

19 surveys : list (or list of lists) of lsst.sims.featureScheduler.survey objects 

20 A list of surveys to consider. If multiple surveys return the same highest 

21 reward value, the survey at the earliest position in the list will be selected. 

22 Can also be a list of lists to make heirarchical priorities. 

23 nside : int 

24 A HEALpix nside value. 

25 camera : str ('LSST') 

26 Which camera to use for computing overlapping HEALpixels for an observation. 

27 Can be 'LSST' or 'comcam' 

28 conditions : a lsst.sims.featureScheduler.features.Conditions object (None) 

29 An object that hold the current conditions and derived values (e.g., 5-sigma depth). Will 

30 generate a default if set to None. 

31 """ 

32 

33 def __init__(self, surveys, nside=None, camera='LSST', rotator_limits=[85., 275.], log=None): 

34 """ 

35 Parameters 

36 ---------- 

37 surveys : list (or list of lists) of lsst.sims.featureScheduler.survey objects 

38 A list of surveys to consider. If multiple surveys return the same highest 

39 reward value, the survey at the earliest position in the list will be selected. 

40 Can also be a list of lists to make heirarchical priorities. 

41 nside : int 

42 A HEALpix nside value. 

43 camera : str ('LSST') 

44 Which camera to use for computing overlapping HEALpixels for an observation. 

45 Can be 'LSST' or 'comcam' 

46 rotator_limits : sequence of floats 

47 """ 

48 if nside is None: 

49 nside = set_default_nside() 

50 

51 if log is None: 

52 self.log = logging.getLogger(type(self).__name__) 

53 else: 

54 self.log = log.getChild(type(self).__name__) 

55 

56 # initialize a queue of observations to request 

57 self.queue = [] 

58 # The indices of self.survey_lists that provided the last addition(s) to the queue 

59 self.survey_index = [None, None] 

60 

61 # If we have a list of survey objects, convert to list-of-lists 

62 if isinstance(surveys[0], list): 

63 self.survey_lists = surveys 

64 else: 

65 self.survey_lists = [surveys] 

66 self.nside = nside 

67 hpid = np.arange(hp.nside2npix(nside)) 

68 self.ra_grid_rad, self.dec_grid_rad = _hpid2RaDec(nside, hpid) 

69 # Should just make camera a class that takes a pointing and returns healpix indices 

70 if camera == 'LSST': 

71 self.pointing2hpindx = hp_in_lsst_fov(nside=nside) 

72 elif camera == 'comcam': 

73 self.pointing2hpindx = hp_in_comcam_fov(nside=nside) 

74 else: 

75 raise ValueError('camera %s not implamented' % camera) 

76 

77 # keep track of how many observations get flushed from the queue 

78 self.flushed = 0 

79 self.rotator_limits = np.sort(np.radians(rotator_limits)) 

80 

81 def flush_queue(self): 

82 """" 

83 Like it sounds, clear any currently queued desired observations. 

84 """ 

85 self.queue = [] 

86 self.survey_index = [None, None] 

87 

88 def add_observation(self, observation): 

89 """ 

90 Record a completed observation and update features accordingly. 

91 

92 Parameters 

93 ---------- 

94 observation : dict-like 

95 An object that contains the relevant information about a 

96 completed observation (e.g., mjd, ra, dec, filter, rotation angle, etc) 

97 """ 

98 

99 # Find the healpixel centers that are included in an observation 

100 indx = self.pointing2hpindx(observation['RA'], observation['dec'], 

101 rotSkyPos=observation['rotSkyPos']) 

102 for surveys in self.survey_lists: 

103 for survey in surveys: 

104 survey.add_observation(observation, indx=indx) 

105 

106 def update_conditions(self, conditions_in): 

107 """ 

108 Parameters 

109 ---------- 

110 conditions : dict-like 

111 The current conditions of the telescope (pointing position, loaded filters, cloud-mask, etc) 

112 """ 

113 # Add the current queue and scheduled queue to the conditions 

114 self.conditions = conditions_in 

115 # put the local queue in the conditions 

116 self.conditions.queue = self.queue 

117 

118 # Check if any surveys have upcomming scheduled observations. Note that we are accumulating 

119 # all of the possible scheduled observations, so it's up to the user to make sure things don't 

120 # collide. The ideal implementation would be to have all the scheduled observations in a 

121 # single survey objects, presumably at the highest tier of priority. 

122 

123 all_scheduled = [] 

124 for sl in self.survey_lists: 

125 for sur in sl: 

126 scheduled = sur.get_scheduled_obs() 

127 if scheduled is not None: 

128 all_scheduled.append(scheduled) 

129 if len(all_scheduled) == 0: 

130 self.conditions.scheduled_observations = [] 

131 else: 

132 all_scheduled = np.sort(np.array(all_scheduled).ravel()) 

133 # In case the surveys have not been removing executed observations 

134 all_scheduled = all_scheduled[np.where(all_scheduled >= self.conditions.mjd)] 

135 self.conditions.scheduled_observations = all_scheduled 

136 

137 def _check_queue_mjd_only(self, mjd): 

138 """ 

139 Check if there are things in the queue that can be executed using only MJD and not full conditions. 

140 This is primarly used by sim_runner to reduce calls calculating updated conditions when they are not 

141 needed. 

142 """ 

143 result = False 

144 if len(self.queue) > 0: 

145 if (int_rounded(mjd) < int_rounded(self.queue[0]['flush_by_mjd'])) | (self.queue[0]['flush_by_mjd'] == 0): 

146 result = True 

147 return result 

148 

149 def request_observation(self, mjd=None): 

150 """ 

151 Ask the scheduler what it wants to observe next 

152 

153 Paramters 

154 --------- 

155 mjd : float (None) 

156 The Modified Julian Date. If None, it uses the MJD from the conditions from the last conditions update. 

157 

158 Returns 

159 ------- 

160 observation object (ra,dec,filter,rotangle) 

161 Returns None if the queue fails to fill 

162 """ 

163 if mjd is None: 

164 mjd = self.conditions.mjd 

165 if len(self.queue) == 0: 

166 self._fill_queue() 

167 

168 if len(self.queue) == 0: 

169 return None 

170 else: 

171 # If the queue has gone stale, flush and refill. Zero means no flush_by was set. 

172 if (int_rounded(mjd) > int_rounded(self.queue[0]['flush_by_mjd'])) & (self.queue[0]['flush_by_mjd'] != 0): 

173 self.flushed += len(self.queue) 

174 self.flush_queue() 

175 self._fill_queue() 

176 if len(self.queue) == 0: 

177 return None 

178 observation = self.queue.pop(0) 

179 # If we are limiting the camera rotator 

180 if self.rotator_limits is not None: 

181 alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'], self.conditions.site.latitude_rad, 

182 self.conditions.site.longitude_rad, mjd) 

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

184 rotTelPos_expected = (obs_pa - observation['rotSkyPos']) % (2.*np.pi) 

185 if (int_rounded(rotTelPos_expected) > int_rounded(self.rotator_limits[0])) & (int_rounded(rotTelPos_expected) < int_rounded(self.rotator_limits[1])): 

186 diff = np.abs(self.rotator_limits - rotTelPos_expected) 

187 limit_indx = np.min(np.where(diff == np.min(diff))[0]) 

188 observation['rotSkyPos'] = (obs_pa - self.rotator_limits[limit_indx]) % (2.*np.pi) 

189 return observation 

190 

191 def _fill_queue(self): 

192 """ 

193 Compute reward function for each survey and fill the observing queue with the 

194 observations from the highest reward survey. 

195 """ 

196 

197 rewards = None 

198 for ns, surveys in enumerate(self.survey_lists): 

199 rewards = np.zeros(len(surveys)) 

200 for i, survey in enumerate(surveys): 

201 rewards[i] = np.nanmax(survey.calc_reward_function(self.conditions)) 

202 # If we have a good reward, break out of the loop 

203 if np.nanmax(rewards) > -np.inf: 

204 self.survey_index[0] = ns 

205 break 

206 if (np.nanmax(rewards) == -np.inf) | (np.isnan(np.nanmax(rewards))): 

207 self.flush_queue() 

208 else: 

209 to_fix = np.where(np.isnan(rewards) == True) 

210 rewards[to_fix] = -np.inf 

211 # Take a min here, so the surveys will be executed in the order they are 

212 # entered if there is a tie. 

213 self.survey_index[1] = np.min(np.where(rewards == np.nanmax(rewards))) 

214 # Survey return list of observations 

215 result = self.survey_lists[self.survey_index[0]][self.survey_index[1]].generate_observations(self.conditions) 

216 

217 self.queue = result 

218 

219 if len(self.queue) == 0: 

220 self.log.warning('Failed to fill queue')