Coverage for python/lsst/sims/featureScheduler/surveys/scripted_surveys.py : 10%

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, _angularSeparation
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):
19 """
20 """
21 if nside is None:
22 nside = set_default_nside()
24 self.extra_features = {}
25 self.nside = nside
26 self.reward_val = reward
27 self.reward = -np.inf
28 super(Scripted_survey, self).__init__(basis_functions=basis_functions,
29 ignore_obs=ignore_obs, nside=nside)
31 def add_observation(self, observation, indx=None, **kwargs):
32 """Check if observation matches a scripted observation
33 """
35 # From base class
36 checks = [io not in str(observation['note']) for io in self.ignore_obs]
37 if all(checks):
38 for feature in self.extra_features:
39 self.extra_features[feature].add_observation(observation, **kwargs)
40 for bf in self.basis_functions:
41 bf.add_observation(observation, **kwargs)
42 for detailer in self.detailers:
43 detailer.add_observation(observation, **kwargs)
44 self.reward_checked = False
46 # was it taken in the right time window, and hasn't already been marked as observed.
47 time_matches = np.where((observation['mjd'] > self.mjd_start) &
48 (observation['mjd'] < self.obs_wanted['flush_by_mjd']) &
49 (~self.obs_wanted['observed']) &
50 (observation['note'] == self.obs_wanted['note']))[0]
51 for match in time_matches:
52 distances = _angularSeparation(self.obs_wanted[match]['RA'],
53 self.obs_wanted[match]['dec'],
54 observation['RA'], observation['dec'])
55 if (distances < self.obs_wanted[match]['dist_tol']) & \
56 (self.obs_wanted[match]['filter'] == observation['filter']):
57 # Log it as observed.
58 self.obs_wanted['observed'][match] = True
59 self.scheduled_obs = self.obs_wanted['mjd'][~self.obs_wanted['observed']]
60 break
62 def calc_reward_function(self, conditions):
63 """If there is an observation ready to go, execute it, otherwise, -inf
64 """
65 observation = self._check_list(conditions)
66 if observation is None:
67 self.reward = -np.inf
68 else:
69 self.reward = self.reward_val
70 return self.reward
72 def _slice2obs(self, obs_row):
73 """take a slice and return a full observation object
74 """
75 observation = empty_observation()
76 for key in ['RA', 'dec', 'filter', 'exptime', 'nexp',
77 'note', 'rotSkyPos', 'flush_by_mjd']:
78 observation[key] = obs_row[key]
79 return observation
81 def _check_alts_HA(self, observation, conditions):
82 """Given scheduled observations, check which ones can be done in current conditions.
84 Parameters
85 ----------
86 observation : np.array
87 An array of scheduled observations. Probably generated with lsst.sims.featureScheduler.utils.scheduled_observation
88 """
89 # Just do a fast ra,dec to alt,az conversion.
90 alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'],
91 conditions.site.latitude_rad, None,
92 conditions.mjd,
93 lmst=conditions.lmst)
94 HA = conditions.lmst - observation['RA']*12./np.pi
95 HA[np.where(HA > 24)] -= 24
96 HA[np.where(HA < 0)] += 24
97 in_range = np.where((alt < observation['alt_max']) & (alt > observation['alt_min']) &
98 ((HA > observation['HA_max']) | (HA < observation['HA_min'])))[0]
99 return in_range
101 def _check_list(self, conditions):
102 """Check to see if the current mjd is good
103 """
105 # Scheduled observations that are in the right time window and have not been executed
106 in_time_window = np.where((self.mjd_start < conditions.mjd) &
107 (self.obs_wanted['flush_by_mjd'] > conditions.mjd) &
108 (~self.obs_wanted['observed']))[0]
110 if np.size(in_time_window) > 0:
111 pass_checks = self._check_alts_HA(self.obs_wanted[in_time_window], conditions)
112 matches = in_time_window[pass_checks]
113 else:
114 matches = []
116 if np.size(matches) > 0:
117 # XXX--could make this a list and just send out all the things that currently match
118 # rather than one at a time
119 observation = self._slice2obs(self.obs_wanted[matches[0]])
120 else:
121 observation = None
122 return observation
124 def set_script(self, obs_wanted):
125 """
126 Parameters
127 ----------
128 obs_wanted : np.array
129 The observations that should be executed. Needs to have columns with dtype names:
130 Should be from lsst.sim.featureScheduler.utils.scheduled_observation
131 mjds : np.array
132 The MJDs for the observaitons, should be same length as obs_list
133 mjd_tol : float (15.)
134 The tolerance to consider an observation as still good to observe (min)
135 """
137 self.obs_wanted = obs_wanted
139 self.obs_wanted.sort(order='mjd')
140 self.mjd_start = self.obs_wanted['mjd'] - self.obs_wanted['mjd_tol']
141 # Here is the atribute that core scheduler checks to broadcast scheduled observations
142 # in the conditions object.
143 self.scheduled_obs = self.obs_wanted['mjd']
145 def generate_observations_rough(self, conditions):
146 observation = self._check_list(conditions)
147 return [observation]
150class Pairs_survey_scripted(Scripted_survey):
151 """Check if incoming observations will need a pair in 30 minutes. If so, add to the queue
152 """
153 def __init__(self, basis_functions, filt_to_pair='griz',
154 dt=40., ttol=10., reward_val=101., note='scripted', ignore_obs='ack',
155 min_alt=30., max_alt=85., lat=-30.2444, moon_distance=30., max_slew_to_pair=15.,
156 nside=None):
157 """
158 Parameters
159 ----------
160 filt_to_pair : str (griz)
161 Which filters to try and get pairs of
162 dt : float (40.)
163 The ideal gap between pairs (minutes)
164 ttol : float (10.)
165 The time tolerance when gathering a pair (minutes)
166 """
167 if nside is None:
168 nside = set_default_nside()
170 super(Pairs_survey_scripted, self).__init__(basis_functions=basis_functions,
171 ignore_obs=ignore_obs, min_alt=min_alt,
172 max_alt=max_alt, nside=nside)
174 self.lat = np.radians(lat)
175 self.note = note
176 self.ttol = ttol/60./24.
177 self.dt = dt/60./24. # To days
178 self.max_slew_to_pair = max_slew_to_pair # in seconds
179 self._moon_distance = np.radians(moon_distance)
181 self.extra_features = {}
182 self.extra_features['Pair_map'] = features.Pair_in_night(filtername=filt_to_pair)
184 self.reward_val = reward_val
185 self.filt_to_pair = filt_to_pair
186 # list to hold observations
187 self.observing_queue = []
188 # make ignore_obs a list
189 if type(self.ignore_obs) is str:
190 self.ignore_obs = [self.ignore_obs]
192 def add_observation(self, observation, indx=None, **kwargs):
193 """Add an observed observation
194 """
195 # self.ignore_obs not in str(observation['note'])
196 to_ignore = np.any([ignore in str(observation['note']) for ignore in self.ignore_obs])
197 log.debug('[Pairs.add_observation]: %s: %s: %s', to_ignore, str(observation['note']), self.ignore_obs)
198 log.debug('[Pairs.add_observation.queue]: %s', self.observing_queue)
199 if not to_ignore:
200 # Update my extra features:
201 for feature in self.extra_features:
202 if hasattr(self.extra_features[feature], 'add_observation'):
203 self.extra_features[feature].add_observation(observation, indx=indx)
204 self.reward_checked = False
206 # Check if this observation needs a pair
207 # XXX--only supporting single pairs now. Just start up another scripted survey
208 # to grab triples, etc? Or add two observations to queue at a time?
209 # keys_to_copy = ['RA', 'dec', 'filter', 'exptime', 'nexp']
210 if ((observation['filter'][0] in self.filt_to_pair) and
211 (np.max(self.extra_features['Pair_map'].feature[indx]) < 1)):
212 obs_to_queue = empty_observation()
213 for key in observation.dtype.names:
214 obs_to_queue[key] = observation[key]
215 # Fill in the ideal time we would like this observed
216 log.debug('Observation MJD: %.4f (dt=%.4f)', obs_to_queue['mjd'], self.dt)
217 obs_to_queue['mjd'] += self.dt
218 self.observing_queue.append(obs_to_queue)
219 log.debug('[Pairs.add_observation.queue.size]: %i', len(self.observing_queue))
220 for obs in self.observing_queue:
221 log.debug('[Pairs.add_observation.queue]: %s', obs)
223 def _purge_queue(self, conditions):
224 """Remove any pair where it's too late to observe it
225 """
226 # Assuming self.observing_queue is sorted by MJD.
227 if len(self.observing_queue) > 0:
228 stale = True
229 in_window = np.abs(self.observing_queue[0]['mjd']-conditions.mjd) < self.ttol
230 log.debug('Purging queue')
231 while stale:
232 # If the next observation in queue is past the window, drop it
233 if (self.observing_queue[0]['mjd'] < conditions.mjd) & (~in_window):
234 log.debug('Past the window: obs_mjd=%.4f (current_mjd=%.4f)',
235 self.observing_queue[0]['mjd'],
236 conditions.mjd)
237 del self.observing_queue[0]
238 # If we are in the window, but masked, drop it
239 elif (in_window) & (~self._check_mask(self.observing_queue[0], conditions)):
240 log.debug('Masked')
241 del self.observing_queue[0]
242 # If in time window, but in alt exclusion zone
243 elif (in_window) & (~self._check_alts(self.observing_queue[0], conditions)):
244 log.debug('in alt exclusion zone')
245 del self.observing_queue[0]
246 else:
247 stale = False
248 # If we have deleted everything, break out of where
249 if len(self.observing_queue) == 0:
250 stale = False
252 def _check_alts(self, observation, conditions):
253 result = False
254 # Just do a fast ra,dec to alt,az conversion. Can use LMST from a feature.
256 alt, az = _approx_RaDec2AltAz(observation['RA'], observation['dec'],
257 self.lat, None,
258 conditions.mjd,
259 lmst=conditions.lmst)
260 in_range = np.where((alt < self.max_alt) & (alt > self.min_alt))[0]
261 if np.size(in_range) > 0:
262 result = True
263 return result
265 def _check_mask(self, observation, conditions):
266 """Check that the proposed observation is not currently masked for some reason on the sky map.
267 True if the observation is good to observe
268 False if the proposed observation is masked
269 """
271 hpid = np.max(_raDec2Hpid(self.nside, observation['RA'], observation['dec']))
272 skyval = conditions.M5Depth[observation['filter'][0]][hpid]
274 if skyval > 0:
275 return True
276 else:
277 return False
279 def calc_reward_function(self, conditions):
280 self._purge_queue(conditions)
281 result = -np.inf
282 self.reward = result
283 log.debug('Pair - calc_reward_func')
284 for indx in range(len(self.observing_queue)):
286 check = self._check_observation(self.observing_queue[indx], conditions)
287 log.debug('%s: %s', check, self.observing_queue[indx])
288 if check[0]:
289 result = self.reward_val
290 self.reward = self.reward_val
291 break
292 elif not check[1]:
293 break
295 self.reward_checked = True
296 return result
298 def _check_observation(self, observation, conditions):
300 delta_t = observation['mjd'] - conditions.mjd
301 log.debug('Check_observation: obs_mjd=%.4f (current_mjd=%.4f, delta=%.4f, tol=%.4f)',
302 observation['mjd'],
303 conditions.mjd,
304 delta_t,
305 self.ttol)
306 obs_hp = _raDec2Hpid(self.nside, observation['RA'], observation['dec'])
307 slewtime = conditions.slewtime[obs_hp[0]]
308 in_slew_window = slewtime <= self.max_slew_to_pair or delta_t < 0.
309 in_time_window = np.abs(delta_t) < self.ttol
311 if conditions.current_filter is None:
312 infilt = True
313 else:
314 infilt = conditions.current_filter in self.filt_to_pair
316 is_observable = self._check_mask(observation, conditions)
317 valid = in_time_window & infilt & in_slew_window & is_observable
318 log.debug('Pair - observation: %s ' % observation)
319 log.debug('Pair - check[%s]: in_time_window[%s] infilt[%s] in_slew_window[%s] is_observable[%s]' %
320 (valid, in_time_window, infilt, in_slew_window, is_observable))
322 return (valid,
323 in_time_window,
324 infilt,
325 in_slew_window,
326 is_observable)
328 def generate_observations(self, conditions):
329 # Toss anything in the queue that is too old to pair up:
330 self._purge_queue(conditions)
331 # Check for something I want a pair of
332 result = []
333 # if len(self.observing_queue) > 0:
334 log.debug('Pair - call')
335 for indx in range(len(self.observing_queue)):
337 check = self._check_observation(self.observing_queue[indx], conditions)
339 if check[0]:
340 result = self.observing_queue.pop(indx)
341 result['note'] = 'pair(%s)' % self.note
342 # Make sure we don't change filter if we don't have to.
343 if conditions.current_filter is not None:
344 result['filter'] = conditions.current_filter
345 # Make sure it is observable!
346 # if self._check_mask(result):
347 result = [result]
348 break
349 elif not check[1]:
350 # If this is not in time window and queue is chronological, none will be...
351 break
353 return result