Coverage for python/lsst/sims/featureScheduler/schedulers/core_scheduler.py : 12%

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
10import logging
13__all__ = ['Core_scheduler']
16class Core_scheduler(object):
17 """Core scheduler that takes completed observations and observatory status and requests observations
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 """
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()
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]
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)
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))
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]
86 def add_observation(self, observation):
87 """
88 Record a completed observation and update features accordingly.
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 """
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)
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
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.
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
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
147 def request_observation(self, mjd=None):
148 """
149 Ask the scheduler what it wants to observe next
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.
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()
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
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 """
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)))
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
217 if len(self.queue) == 0:
218 self.log.warning('Failed to fill queue')