Coverage for python/lsst/sims/featureScheduler/surveys/base_survey.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,
3 hp_in_lsst_fov, read_fields, hp_in_comcam_fov,
4 comcamTessellate)
5import healpy as hp
6from lsst.sims.featureScheduler.thomson import xyz2thetaphi, thetaphi2xyz
7from lsst.sims.featureScheduler.detailers import Zero_rot_detailer
9__all__ = ['BaseSurvey', 'BaseMarkovDF_survey']
12class BaseSurvey(object):
13 """A baseclass for survey objects.
15 Parameters
16 ----------
17 basis_functions : list
18 List of basis_function objects
19 extra_features : list XXX--should this be a dict for clarity?
20 List of any additional features the survey may want to use
21 e.g., for computing final dither positions.
22 extra_basis_functions : dict of lsst.sims.featureScheduler.basis_function objects
23 Extra basis function objects. Typically not psased in, but et in the __init__.
24 ignore_obs : list of str (None)
25 If an incoming observation has this string in the note, ignore it. Handy if
26 one wants to ignore DD fields or observations requested by self. Take note,
27 if a survey is called 'mysurvey23', setting ignore_obs to 'mysurvey2' will
28 ignore it because 'mysurvey2' is a substring of 'mysurvey23'.
29 detailers : list of lsst.sims.featureScheduler.detailers objects
30 The detailers to apply to the list of observations.
31 scheduled_obs : np.array
32 An array of MJD values for when observations should execute.
33 """
34 def __init__(self, basis_functions, extra_features=None, extra_basis_functions=None,
35 ignore_obs=None, survey_name='', nside=None, detailers=None,
36 scheduled_obs=None):
37 if nside is None:
38 nside = set_default_nside()
39 if ignore_obs is None:
40 ignore_obs = []
42 if isinstance(ignore_obs, str):
43 ignore_obs = [ignore_obs]
45 self.nside = nside
46 self.survey_name = survey_name
47 self.ignore_obs = ignore_obs
49 self.reward = None
50 self.survey_index = None
52 self.basis_functions = basis_functions
54 if extra_features is None:
55 self.extra_features = {}
56 else:
57 self.extra_features = extra_features
58 if extra_basis_functions is None:
59 self.extra_basis_functions = {}
60 else:
61 self.extra_basis_functions = extra_basis_functions
63 self.reward_checked = False
65 # Attribute to track if the reward function is up-to-date.
66 self.reward_checked = False
68 # If there's no detailers, add one to set rotation to near zero
69 if detailers is None:
70 self.detailers = [Zero_rot_detailer(nside=nside)]
71 else:
72 self.detailers = detailers
74 # Scheduled observations
75 self.scheduled_obs = scheduled_obs
77 def get_scheduled_obs(self):
78 return self.scheduled_obs
80 def add_observation(self, observation, **kwargs):
81 # Check each posible ignore string
82 checks = [io not in str(observation['note']) for io in self.ignore_obs]
83 # ugh, I think here I have to assume observation is an array and not a dict.
84 if all(checks):
85 for feature in self.extra_features:
86 self.extra_features[feature].add_observation(observation, **kwargs)
87 for bf in self.extra_basis_functions:
88 self.extra_basis_functions[bf].add_observation(observation, **kwargs)
89 for bf in self.basis_functions:
90 bf.add_observation(observation, **kwargs)
91 for detailer in self.detailers:
92 detailer.add_observation(observation, **kwargs)
93 self.reward_checked = False
95 def _check_feasibility(self, conditions):
96 """
97 Check if the survey is feasable in the current conditions
98 """
99 for bf in self.basis_functions:
100 result = bf.check_feasibility(conditions)
101 if not result:
102 return result
103 return result
105 def calc_reward_function(self, conditions):
106 """
107 Parameters
108 ----------
109 conditions : lsst.sims.featureScheduler.features.Conditions object
111 Returns
112 -------
113 reward : float (or array)
115 """
116 if self._check_feasability():
117 self.reward = 0
118 else:
119 # If we don't pass feasability
120 self.reward = -np.inf
122 self.reward_checked = True
123 return self.reward
125 def generate_observations_rough(self, conditions):
126 """
127 Returns
128 -------
129 one of:
130 1) None
131 2) A list of observations
132 """
133 # If the reward function hasn't been updated with the
134 # latest info, calculate it
135 if not self.reward_checked:
136 self.reward = self.calc_reward_function(conditions)
137 obs = empty_observation()
138 return [obs]
140 def generate_observations(self, conditions):
141 observations = self.generate_observations_rough(conditions)
142 for detailer in self.detailers:
143 observations = detailer(observations, conditions)
144 return observations
146 def viz_config(self):
147 # XXX--zomg, we should have a method that goes through all the objects and
148 # makes plots/prints info so there can be a little notebook showing the config!
149 pass
152def rotx(theta, x, y, z):
153 """rotate the x,y,z points theta radians about x axis"""
154 sin_t = np.sin(theta)
155 cos_t = np.cos(theta)
156 xp = x
157 yp = y*cos_t+z*sin_t
158 zp = -y*sin_t+z*cos_t
159 return xp, yp, zp
162class BaseMarkovDF_survey(BaseSurvey):
163 """ A Markov Decision Function survey object. Uses Basis functions to compute a
164 final reward function and decide what to observe based on the reward. Includes
165 methods for dithering and defaults to dithering nightly.
167 Parameters
168 ----------
169 basis_function : list of lsst.sims.featureSchuler.basis_function objects
171 basis_weights : list of float
172 Must be same length as basis_function
173 seed : hashable
174 Random number seed, used for randomly orienting sky tessellation.
175 camera : str ('LSST')
176 Should be 'LSST' or 'comcam'
177 """
178 def __init__(self, basis_functions, basis_weights, extra_features=None,
179 smoothing_kernel=None,
180 ignore_obs=None, survey_name='', nside=None, seed=42,
181 dither=True, detailers=None, camera='LSST'):
183 super(BaseMarkovDF_survey, self).__init__(basis_functions=basis_functions,
184 extra_features=extra_features,
185 ignore_obs=ignore_obs, survey_name=survey_name,
186 nside=nside, detailers=detailers)
188 self.basis_weights = basis_weights
189 # Check that weights and basis functions are same length
190 if len(basis_functions) != np.size(basis_weights):
191 raise ValueError('basis_functions and basis_weights must be same length.')
193 self.camera = camera
194 # Load the OpSim field tesselation and map healpix to fields
195 if self.camera == 'LSST':
196 self.fields_init = read_fields()
197 elif self.camera == 'comcam':
198 self.fields_init = comcamTessellate()
199 else:
200 ValueError('camera %s unknown, should be "LSST" or "comcam"' %camera)
201 self.fields = self.fields_init.copy()
202 self.hp2fields = np.array([])
203 self._hp2fieldsetup(self.fields['RA'], self.fields['dec'])
205 if smoothing_kernel is not None:
206 self.smoothing_kernel = np.radians(smoothing_kernel)
207 else:
208 self.smoothing_kernel = None
210 # Start tracking the night
211 self.night = -1
213 # Set the seed
214 np.random.seed(seed)
215 self.dither = dither
217 def _hp2fieldsetup(self, ra, dec, leafsize=100):
218 """Map each healpixel to nearest field. This will only work if healpix
219 resolution is higher than field resolution.
220 """
221 if self.camera == 'LSST':
222 pointing2hpindx = hp_in_lsst_fov(nside=self.nside)
223 elif self.camera == 'comcam':
224 pointing2hpindx = hp_in_comcam_fov(nside=self.nside)
226 self.hp2fields = np.zeros(hp.nside2npix(self.nside), dtype=np.int)
227 for i in range(len(ra)):
228 hpindx = pointing2hpindx(ra[i], dec[i], rotSkyPos=0.)
229 self.hp2fields[hpindx] = i
231 def _spin_fields(self, lon=None, lat=None, lon2=None):
232 """Spin the field tessellation to generate a random orientation
234 The default field tesselation is rotated randomly in longitude, and then the
235 pole is rotated to a random point on the sphere.
237 Parameters
238 ----------
239 lon : float (None)
240 The amount to initially rotate in longitude (radians). Will use a random value
241 between 0 and 2 pi if None (default).
242 lat : float (None)
243 The amount to rotate in latitude (radians).
244 lon2 : float (None)
245 The amount to rotate the pole in longitude (radians).
246 """
247 if lon is None:
248 lon = np.random.rand()*np.pi*2
249 if lat is None:
250 # Make sure latitude points spread correctly
251 # http://mathworld.wolfram.com/SpherePointPicking.html
252 lat = np.arccos(2.*np.random.rand() - 1.)
253 if lon2 is None:
254 lon2 = np.random.rand()*np.pi*2
255 # rotate longitude
256 ra = (self.fields_init['RA'] + lon) % (2.*np.pi)
257 dec = self.fields_init['dec'] + 0
259 # Now to rotate ra and dec about the x-axis
260 x, y, z = thetaphi2xyz(ra, dec+np.pi/2.)
261 xp, yp, zp = rotx(lat, x, y, z)
262 theta, phi = xyz2thetaphi(xp, yp, zp)
263 dec = phi - np.pi/2
264 ra = theta + np.pi
266 # One more RA rotation
267 ra = (ra + lon2) % (2.*np.pi)
269 self.fields['RA'] = ra
270 self.fields['dec'] = dec
271 # Rebuild the kdtree with the new positions
272 # XXX-may be doing some ra,dec to conversions xyz more than needed.
273 self._hp2fieldsetup(ra, dec)
275 def smooth_reward(self):
276 """If we want to smooth the reward function.
277 """
278 if hp.isnpixok(self.reward.size):
279 # Need to swap NaNs to hp.UNSEEN so smoothing doesn't spread mask
280 reward_temp = self.reward + 0
281 mask = np.isnan(reward_temp)
282 reward_temp[mask] = hp.UNSEEN
283 self.reward_smooth = hp.sphtfunc.smoothing(reward_temp,
284 fwhm=self.smoothing_kernel,
285 verbose=False)
286 self.reward_smooth[mask] = np.nan
287 self.reward = self.reward_smooth
288 #good = ~np.isnan(self.reward_smooth)
289 # Round off to prevent strange behavior early on
290 #self.reward_smooth[good] = np.round(self.reward_smooth[good], decimals=4)
292 def calc_reward_function(self, conditions):
293 self.reward_checked = True
294 if self._check_feasibility(conditions):
295 self.reward = 0
296 indx = np.arange(hp.nside2npix(self.nside))
297 for bf, weight in zip(self.basis_functions, self.basis_weights):
298 basis_value = bf(conditions, indx=indx)
299 self.reward += basis_value*weight
301 if np.any(np.isinf(self.reward)):
302 self.reward = np.inf
303 else:
304 # If not feasable, negative infinity reward
305 self.reward = -np.inf
306 return self.reward
307 if self.smoothing_kernel is not None:
308 self.smooth_reward()
310 return self.reward
312 def generate_observations_rough(self, conditions):
314 self.reward = self.calc_reward_function(conditions)
316 # Check if we need to spin the tesselation
317 if self.dither & (conditions.night != self.night):
318 self._spin_fields()
319 self.night = conditions.night.copy()
321 # XXX Use self.reward to decide what to observe.
322 return None