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 

8 

9 

10import logging 

11 

12 

13__all__ = ['Core_scheduler'] 

14 

15 

16class Core_scheduler(object): 

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

18 

19 Parameters 

20 ---------- 

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

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

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

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

25 nside : int 

26 A HEALpix nside value. 

27 camera : str ('LSST') 

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

29 Can be 'LSST' or 'comcam' 

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

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

32 generate a default if set to None. 

33 """ 

34 

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

36 """ 

37 Parameters 

38 ---------- 

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

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

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

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

43 nside : int 

44 A HEALpix nside value. 

45 camera : str ('LSST') 

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

47 Can be 'LSST' or 'comcam' 

48 rotator_limits : sequence of floats 

49 """ 

50 if nside is None: 

51 nside = set_default_nside() 

52 

53 self.log = logging.getLogger("Core_scheduler") 

54 # initialize a queue of observations to request 

55 self.queue = [] 

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

57 self.survey_index = [None, None] 

58 

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

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

61 self.survey_lists = surveys 

62 else: 

63 self.survey_lists = [surveys] 

64 self.nside = nside 

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

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

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

68 if camera == 'LSST': 

69 self.pointing2hpindx = hp_in_lsst_fov(nside=nside) 

70 elif camera == 'comcam': 

71 self.pointing2hpindx = hp_in_comcam_fov(nside=nside) 

72 else: 

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

74 

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

76 self.flushed = 0 

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

78 

79 def flush_queue(self): 

80 """" 

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

82 """ 

83 self.queue = [] 

84 self.survey_index = [None, None] 

85 

86 def add_observation(self, observation): 

87 """ 

88 Record a completed observation and update features accordingly. 

89 

90 Parameters 

91 ---------- 

92 observation : dict-like 

93 An object that contains the relevant information about a 

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

95 """ 

96 

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

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

99 rotSkyPos=observation['rotSkyPos']) 

100 for surveys in self.survey_lists: 

101 for survey in surveys: 

102 survey.add_observation(observation, indx=indx) 

103 

104 def update_conditions(self, conditions_in): 

105 """ 

106 Parameters 

107 ---------- 

108 conditions : dict-like 

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

110 """ 

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

112 self.conditions = conditions_in 

113 # put the local queue in the conditions 

114 self.conditions.queue = self.queue 

115 

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

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

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

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

120 

121 all_scheduled = [] 

122 for sl in self.survey_lists: 

123 for sur in sl: 

124 scheduled = sur.get_scheduled_obs() 

125 if scheduled is not None: 

126 all_scheduled.append(scheduled) 

127 if len(all_scheduled) == 0: 

128 self.conditions.scheduled_observations = [] 

129 else: 

130 all_scheduled = np.sort(np.concatenate(all_scheduled)) 

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

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

133 self.conditions.scheduled_observations = all_scheduled 

134 

135 def _check_queue_mjd_only(self, mjd): 

136 """ 

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

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

139 needed. 

140 """ 

141 result = False 

142 if len(self.queue) > 0: 

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

144 result = True 

145 return result 

146 

147 def request_observation(self, mjd=None): 

148 """ 

149 Ask the scheduler what it wants to observe next 

150 

151 Paramters 

152 --------- 

153 mjd : float (None) 

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

155 

156 Returns 

157 ------- 

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

159 Returns None if the queue fails to fill 

160 """ 

161 if mjd is None: 

162 mjd = self.conditions.mjd 

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

164 self._fill_queue() 

165 

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

167 return None 

168 else: 

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

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

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

172 self.flush_queue() 

173 self._fill_queue() 

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

175 return None 

176 observation = self.queue.pop(0) 

177 # If we are limiting the camera rotator 

178 if self.rotator_limits is not None: 

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

180 self.conditions.site.longitude_rad, mjd) 

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

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

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

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

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

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

187 return observation 

188 

189 def _fill_queue(self): 

190 """ 

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

192 observations from the highest reward survey. 

193 """ 

194 

195 rewards = None 

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

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

198 for i, survey in enumerate(surveys): 

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

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

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

202 self.survey_index[0] = ns 

203 break 

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

205 self.flush_queue() 

206 else: 

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

208 rewards[to_fix] = -np.inf 

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

210 # entered if there is a tie. 

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

212 

213 # Survey return list of observations 

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

215 self.queue = result 

216 

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

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