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

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
11__all__ = ['Core_scheduler']
14class Core_scheduler(object):
15 """Core scheduler that takes completed observations and observatory status and requests observations
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 """
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()
51 if log is None:
52 self.log = logging.getLogger(type(self).__name__)
53 else:
54 self.log = log.getChild(type(self).__name__)
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]
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)
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))
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]
88 def add_observation(self, observation):
89 """
90 Record a completed observation and update features accordingly.
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 """
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)
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
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.
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
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
149 def request_observation(self, mjd=None):
150 """
151 Ask the scheduler what it wants to observe next
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.
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()
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
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 """
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)
217 self.queue = result
219 if len(self.queue) == 0:
220 self.log.warning('Failed to fill queue')