Coverage for python/lsst/sims/featureScheduler/surveys/scripted_surveys.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
1import numpy as np
2from lsst.sims.featureScheduler.utils import (empty_observation, set_default_nside)
3import lsst.sims.featureScheduler.features as features
4from lsst.sims.featureScheduler.surveys import BaseSurvey
5from lsst.sims.utils import _approx_RaDec2AltAz, _raDec2Hpid
6import logging
8log = logging.getLogger(__name__)
10__all__ = ['Scripted_survey', 'Pairs_survey_scripted']
13class Scripted_survey(BaseSurvey):
14 """
15 Take a set of scheduled observations and serve them up.
16 """
17 def __init__(self, basis_functions, reward=1e6, ignore_obs='dummy',
18 nside=None, min_alt=30., max_alt=85.):
19 """
20 min_alt : float (30.)
21 The minimum altitude to attempt to chace a pair to (degrees). Default of 30 = airmass of 2.
22 max_alt : float(85.)
23 The maximum altitude to attempt to chase a pair to (degrees).
25 """
26 if nside is None:
27 nside = set_default_nside()
29 self.extra_features = {}
31 self.min_alt = np.radians(min_alt)
32 self.max_alt = np.radians(max_alt)
33 self.nside = nside
34 self.reward_val = reward
35 self.reward = -reward
36 super(Scripted_survey, self).__init__(basis_functions=basis_functions,
37 ignore_obs=ignore_obs, nside=nside)
39 def add_observation(self, observation, indx=None, **kwargs):
40 """Check if this matches a scripted observation
41 """
42 # From base class
43 if self.ignore_obs not in observation['note']:
44 for feature in self.extra_features:
45 self.extra_features[feature].add_observation(observation, **kwargs)
46 self.reward_checked = False
48 dt = self.obs_wanted['mjd'] - observation['mjd']
49 # was it taken in the right time window, and hasn't already been marked as observed.
50 time_matches = np.where((np.abs(dt) < self.mjd_tol) & (~self.obs_log))[0]
51 for match in time_matches:
52 # Might need to change this to an angular distance calc and add another tolerance?
53 if (self.obs_wanted[match]['RA'] == observation['RA']) & \
54 (self.obs_wanted[match]['dec'] == observation['dec']) & \
55 (self.obs_wanted[match]['filter'] == observation['filter']):
56 self.obs_log[match] = True
57 break
59 def calc_reward_function(self, conditions):
60 """If there is an observation ready to go, execute it, otherwise, -inf
61 """
62 observation = self._check_list()
63 if observation is None:
64 self.reward = -np.inf
65 else:
66 self.reward = self.reward_val
67 return self.reward
69 def _slice2obs(self, obs_row):
70 """take a slice and return a full observation object
71 """
72 observation = empty_observation()
73 for key in ['RA', 'dec', 'filter', 'exptime', 'nexp', 'note', 'field_id']:
74 observation[key] = obs_row[key]
75 return observation
77 def _check_alts(self, indices):
78 """Check the altitudes of potential matches.
79 """
80 # This is kind of a kludgy low-resolution way to convert ra,dec to alt,az, but should be really fast.
81 # XXX--should I stick the healpixel value on when I set the script? Might be faster.
82 # XXX not sure this really needs to be it's own method
83 hp_ids = _raDec2Hpid(self.nside, self.obs_wanted[indices]['RA'], self.obs_wanted[indices]['dec'])
84 alts = self.extra_features['altaz'].feature['alt'][hp_ids]
85 in_range = np.where((alts < self.max_alt) & (alts > self.min_alt))
86 indices = indices[in_range]
87 return indices
89 def _check_list(self, conditions):
90 """Check to see if the current mjd is good
91 """
92 dt = self.obs_wanted['mjd'] - conditions.mjd
93 # Check for matches with the right requested MJD
94 matches = np.where((np.abs(dt) < self.mjd_tol) & (~self.obs_log))[0]
95 # Trim down to ones that are in the altitude limits
96 matches = self._check_alts(matches)
97 if matches.size > 0:
98 observation = self._slice2obs(self.obs_wanted[matches[0]])
99 else:
100 observation = None
101 return observation
103 def set_script(self, obs_wanted, mjd_tol=15.):
104 """
105 Parameters
106 ----------
107 obs_wanted : np.array
108 The observations that should be executed. Needs to have columns with dtype names:
109 XXX
110 mjds : np.array
111 The MJDs for the observaitons, should be same length as obs_list
112 mjd_tol : float (15.)
113 The tolerance to consider an observation as still good to observe (min)
114 """
115 self.mjd_tol = mjd_tol/60./24. # to days
116 self.obs_wanted = obs_wanted
117 # Set something to record when things have been observed
118 self.obs_log = np.zeros(obs_wanted.size, dtype=bool)
120 def add_to_script(self, observation, mjd_tol=15.):
121 """
122 Parameters
123 ----------
124 observation : observation object
125 The observation one would like to add to the scripted surveys
126 mjd_tol : float (15.)
127 The time tolerance on the observation (minutes)
128 """
129 self.mjd_tol = mjd_tol/60./24. # to days
130 self.obs_wanted = np.concatenate((self.obs_wanted, observation))
131 self.obs_log = np.concatenate((self.obs_log, np.zeros(1, dtype=bool)))
132 # XXX--could do a sort on mjd here if I thought that was a good idea.
133 # XXX-note, there's currently nothing that flushes this, so adding
134 # observations can pile up nonstop. Should prob flush nightly or something
136 def generate_observations(self, conditions):
137 observation = self._check_list(conditions)
138 return [observation]
141class Pairs_survey_scripted(Scripted_survey):
142 """Check if incoming observations will need a pair in 30 minutes. If so, add to the queue
143 """
144 def __init__(self, basis_functions, filt_to_pair='griz',
145 dt=40., ttol=10., reward_val=101., note='scripted', ignore_obs='ack',
146 min_alt=30., max_alt=85., lat=-30.2444, moon_distance=30., max_slew_to_pair=15.,
147 nside=None):
148 """
149 Parameters
150 ----------
151 filt_to_pair : str (griz)
152 Which filters to try and get pairs of
153 dt : float (40.)
154 The ideal gap between pairs (minutes)
155 ttol : float (10.)
156 The time tolerance when gathering a pair (minutes)
157 """
158 if nside is None:
159 nside = set_default_nside()
161 super(Pairs_survey_scripted, self).__init__(basis_functions=basis_functions,
162 ignore_obs=ignore_obs, min_alt=min_alt,
163 max_alt=max_alt, nside=nside)
165 self.lat = np.radians(lat)
166 self.note = note
167 self.ttol = ttol/60./24.
168 self.dt = dt/60./24. # To days
169 self.max_slew_to_pair = max_slew_to_pair # in seconds
170 self._moon_distance = np.radians(moon_distance)
172 self.extra_features = {}
173 self.extra_features['Pair_map'] = features.Pair_in_night(filtername=filt_to_pair)
175 self.reward_val = reward_val
176 self.filt_to_pair = filt_to_pair
177 # list to hold observations
178 self.observing_queue = []
179 # make ignore_obs a list
180 if type(self.ignore_obs) is str:
181 self.ignore_obs = [self.ignore_obs]
183 def add_observation(self, observation, indx=None, **kwargs):
184 """Add an observed observation
185 """
186 # self.ignore_obs not in str(observation['note'])
187 to_ignore = np.any([ignore in str(observation['note']) for ignore in self.ignore_obs])
188 log.debug('[Pairs.add_observation]: %s: %s: %s', to_ignore, str(observation['note']), self.ignore_obs)
189 log.debug('[Pairs.add_observation.queue]: %s', self.observing_queue)
190 if not to_ignore:
191 # Update my extra features:
192 for feature in self.extra_features:
193 if hasattr(self.extra_features[feature], 'add_observation'):
194 self.extra_features[feature].add_observation(observation, indx=indx)
195 self.reward_checked = False
197 # Check if this observation needs a pair
198 # XXX--only supporting single pairs now. Just start up another scripted survey
199 # to grab triples, etc? Or add two observations to queue at a time?
200 # keys_to_copy = ['RA', 'dec', 'filter', 'exptime', 'nexp']
201 if ((observation['filter'][0] in self.filt_to_pair) and
202 (np.max(self.extra_features['Pair_map'].feature[indx]) < 1)):
203 obs_to_queue = empty_observation()
204 for key in observation.dtype.names:
205 obs_to_queue[key] = observation[key]
206 # Fill in the ideal time we would like this observed
207 log.debug('Observation MJD: %.4f (dt=%.4f)', obs_to_queue['mjd'], self.dt)
208 obs_to_queue['mjd'] += self.dt
209 self.observing_queue.append(obs_to_queue)
210 log.debug('[Pairs.add_observation.queue.size]: %i', len(self.observing_queue))
211 for obs in self.observing_queue:
212 log.debug('[Pairs.add_observation.queue]: %s', obs)
214 def _purge_queue(self, conditions):
215 """Remove any pair where it's too late to observe it
216 """
217 # Assuming self.observing_queue is sorted by MJD.
218 if len(self.observing_queue) > 0:
219 stale = True
220 in_window = np.abs(self.observing_queue[0]['mjd']-conditions.mjd) < self.ttol
221 log.debug('Purging queue')
222 while stale:
223 # If the next observation in queue is past the window, drop it
224 if (self.observing_queue[0]['mjd'] < conditions.mjd) & (~in_window):
225 log.debug('Past the window: obs_mjd=%.4f (current_mjd=%.4f)',
226 self.observing_queue[0]['mjd'],
227 conditions.mjd)
228 del self.observing_queue[0]
229 # If we are in the window, but masked, drop it
230 elif (in_window) & (~self._check_mask(self.observing_queue[0], conditions)):
231 log.debug('Masked')
232 del self.observing_queue[0]
233 # If in time window, but in alt exclusion zone
234 elif (in_window) & (~self._check_alts(self.observing_queue[0], conditions)):
235 log.debug('in alt exclusion zone')
236 del self.observing_queue[0]
237 else:
238 stale = False
239 # If we have deleted everything, break out of where
240 if len(self.observing_queue) == 0:
241 stale = False
243 def _check_alts(self, observation, conditions):
244 result = False
245 # Just do a fast ra,dec to alt,az conversion. Can use LMST from a feature.
247 alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'],
248 self.lat, None,
249 conditions.mjd,
250 lmst=conditions.lmst)
251 in_range = np.where((alt < self.max_alt) & (alt > self.min_alt))[0]
252 if np.size(in_range) > 0:
253 result = True
254 return result
256 def _check_mask(self, observation, conditions):
257 """Check that the proposed observation is not currently masked for some reason on the sky map.
258 True if the observation is good to observe
259 False if the proposed observation is masked
260 """
262 hpid = np.max(_raDec2Hpid(self.nside, observation['RA'], observation['dec']))
263 skyval = conditions.M5Depth[observation['filter'][0]][hpid]
265 if skyval > 0:
266 return True
267 else:
268 return False
270 def calc_reward_function(self, conditions):
271 self._purge_queue(conditions)
272 result = -np.inf
273 self.reward = result
274 log.debug('Pair - calc_reward_func')
275 for indx in range(len(self.observing_queue)):
277 check = self._check_observation(self.observing_queue[indx], conditions)
278 log.debug('%s: %s', check, self.observing_queue[indx])
279 if check[0]:
280 result = self.reward_val
281 self.reward = self.reward_val
282 break
283 elif not check[1]:
284 break
286 self.reward_checked = True
287 return result
289 def _check_observation(self, observation, conditions):
291 delta_t = observation['mjd'] - conditions.mjd
292 log.debug('Check_observation: obs_mjd=%.4f (current_mjd=%.4f, delta=%.4f, tol=%.4f)',
293 observation['mjd'],
294 conditions.mjd,
295 delta_t,
296 self.ttol)
297 obs_hp = _raDec2Hpid(self.nside, observation['RA'], observation['dec'])
298 slewtime = conditions.slewtime[obs_hp[0]]
299 in_slew_window = slewtime <= self.max_slew_to_pair or delta_t < 0.
300 in_time_window = np.abs(delta_t) < self.ttol
302 if conditions.current_filter is None:
303 infilt = True
304 else:
305 infilt = conditions.current_filter in self.filt_to_pair
307 is_observable = self._check_mask(observation, conditions)
308 valid = in_time_window & infilt & in_slew_window & is_observable
309 log.debug('Pair - observation: %s ' % observation)
310 log.debug('Pair - check[%s]: in_time_window[%s] infilt[%s] in_slew_window[%s] is_observable[%s]' %
311 (valid, in_time_window, infilt, in_slew_window, is_observable))
313 return (valid,
314 in_time_window,
315 infilt,
316 in_slew_window,
317 is_observable)
319 def generate_observations(self, conditions):
320 # Toss anything in the queue that is too old to pair up:
321 self._purge_queue(conditions)
322 # Check for something I want a pair of
323 result = []
324 # if len(self.observing_queue) > 0:
325 log.debug('Pair - call')
326 for indx in range(len(self.observing_queue)):
328 check = self._check_observation(self.observing_queue[indx], conditions)
330 if check[0]:
331 result = self.observing_queue.pop(indx)
332 result['note'] = 'pair(%s)' % self.note
333 # Make sure we don't change filter if we don't have to.
334 if conditions.current_filter is not None:
335 result['filter'] = conditions.current_filter
336 # Make sure it is observable!
337 # if self._check_mask(result):
338 result = [result]
339 break
340 elif not check[1]:
341 # If this is not in time window and queue is chronological, none will be...
342 break
344 return result