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 

8from lsst.sims.featureScheduler.utils import approx_altaz2pa 

9 

10 

11import logging 

12 

13 

14__all__ = ['Core_scheduler'] 

15 

16 

17class Core_scheduler(object): 

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

19 

20 Parameters 

21 ---------- 

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

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

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

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

26 nside : int 

27 A HEALpix nside value. 

28 camera : str ('LSST') 

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

30 Can be 'LSST' or 'comcam' 

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

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

33 generate a default if set to None. 

34 """ 

35 

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

37 """ 

38 Parameters 

39 ---------- 

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

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

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

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

44 nside : int 

45 A HEALpix nside value. 

46 camera : str ('LSST') 

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

48 Can be 'LSST' or 'comcam' 

49 rotator_limits : sequence of floats 

50 """ 

51 if nside is None: 

52 nside = set_default_nside() 

53 

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

55 # initialize a queue of observations to request 

56 self.queue = [] 

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

58 self.survey_index = [None, None] 

59 

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

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

62 self.survey_lists = surveys 

63 else: 

64 self.survey_lists = [surveys] 

65 self.nside = nside 

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

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

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

69 if camera == 'LSST': 

70 self.pointing2hpindx = hp_in_lsst_fov(nside=nside) 

71 elif camera == 'comcam': 

72 self.pointing2hpindx = hp_in_comcam_fov(nside=nside) 

73 else: 

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

75 

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

77 self.flushed = 0 

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

79 

80 def flush_queue(self): 

81 """" 

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

83 """ 

84 self.queue = [] 

85 self.survey_index = [None, None] 

86 

87 def add_observation(self, observation): 

88 """ 

89 Record a completed observation and update features accordingly. 

90 

91 Parameters 

92 ---------- 

93 observation : dict-like 

94 An object that contains the relevant information about a 

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

96 """ 

97 

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

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

100 rotSkyPos=observation['rotSkyPos']) 

101 for surveys in self.survey_lists: 

102 for survey in surveys: 

103 survey.add_observation(observation, indx=indx) 

104 

105 def update_conditions(self, conditions_in): 

106 """ 

107 Parameters 

108 ---------- 

109 conditions : dict-like 

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

111 """ 

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

113 self.conditions = conditions_in 

114 # put the local queue in the conditions 

115 self.conditions.queue = self.queue 

116 

117 # XXX---TODO: Could potentially put more complicated info from all 

118 # the surveys in the conditions object here. e.g., when a DDF plans to next request 

119 # observations. 

120 

121 def _check_queue_mjd_only(self, mjd): 

122 """ 

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

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

125 needed. 

126 """ 

127 result = False 

128 if len(self.queue) > 0: 

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

130 result = True 

131 return result 

132 

133 def request_observation(self, mjd=None): 

134 """ 

135 Ask the scheduler what it wants to observe next 

136 

137 Paramters 

138 --------- 

139 mjd : float (None) 

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

141 

142 Returns 

143 ------- 

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

145 Returns None if the queue fails to fill 

146 """ 

147 if mjd is None: 

148 mjd = self.conditions.mjd 

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

150 self._fill_queue() 

151 

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

153 return None 

154 else: 

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

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

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

158 self.flush_queue() 

159 self._fill_queue() 

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

161 return None 

162 observation = self.queue.pop(0) 

163 # If we are limiting the camera rotator 

164 if self.rotator_limits is not None: 

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

166 self.conditions.site.longitude_rad, mjd) 

167 obs_pa = approx_altaz2pa(alt, az, self.conditions.site.latitude_rad) 

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

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

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

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

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

173 return observation 

174 

175 def _fill_queue(self): 

176 """ 

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

178 observations from the highest reward survey. 

179 """ 

180 

181 rewards = None 

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

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

184 for i, survey in enumerate(surveys): 

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

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

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

188 self.survey_index[0] = ns 

189 break 

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

191 self.flush_queue() 

192 else: 

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

194 rewards[to_fix] = -np.inf 

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

196 # entered if there is a tie. 

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

198 

199 # Survey return list of observations 

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

201 self.queue = result 

202 

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

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