Coverage for python/lsst/sims/featureScheduler/detailers/detailer.py : 16%

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 lsst.sims.utils import _raDec2Hpid, _approx_RaDec2AltAz, _angularSeparation, _approx_altaz2pa
2import numpy as np
3from lsst.sims.featureScheduler.utils import int_rounded
4import copy
6__all__ = ["Base_detailer", "Zero_rot_detailer", "Comcam_90rot_detailer", "Close_alt_detailer",
7 "Take_as_pairs_detailer", "Twilight_triple_detailer", "Spider_rot_detailer", "Flush_for_sched_detailer"]
10class Base_detailer(object):
11 """
12 A Detailer is an object that takes a list of proposed observations and adds "details" to them. The
13 primary purpose is that the Markov Decision Process does an excelent job selecting RA,Dec,filter
14 combinations, but we may want to add additional logic such as what to set the camera rotation angle
15 to, or what to use for an exposure time. We could also modify the order of the proposed observations.
16 For Deep Drilling Fields, a detailer could be useful for computing dither positions and modifying
17 the exact RA,Dec positions.
18 """
20 def __init__(self, nside=32):
21 """
22 """
23 # Dict to hold all the features we want to track
24 self.survey_features = {}
25 self.nside = nside
27 def add_observation(self, observation, indx=None):
28 """
29 Parameters
30 ----------
31 observation : np.array
32 An array with information about the input observation
33 indx : np.array
34 The indices of the healpix map that the observation overlaps with
35 """
36 for feature in self.survey_features:
37 self.survey_features[feature].add_observation(observation, indx=indx)
39 def __call__(self, observation_list, conditions):
40 """
41 Parameters
42 ----------
43 observation_list : list of observations
44 The observations to detail.
45 conditions : lsst.sims.featureScheduler.conditions object
47 Returns
48 -------
49 List of observations.
50 """
52 return observation_list
55class Zero_rot_detailer(Base_detailer):
56 """
57 Detailer to set the camera rotation to be apporximately zero in rotTelPos.
58 Because it can never be written too many times:
59 rotSkyPos = rotTelPos - ParallacticAngle
60 But, wait, what? Is it really the other way?
61 """
63 def __call__(self, observation_list, conditions):
65 # XXX--should I convert the list into an array and get rid of this loop?
66 for obs in observation_list:
67 alt, az = _approx_RaDec2AltAz(obs['RA'], obs['dec'], conditions.site.latitude_rad,
68 conditions.site.longitude_rad, conditions.mjd)
69 obs_pa = _approx_altaz2pa(alt, az, conditions.site.latitude_rad)
70 obs['rotSkyPos'] = obs_pa
72 return observation_list
75class Spider_rot_detailer(Base_detailer):
76 """
77 Set the camera rotation to +/- 45 degrees so diffraction spikes align along chip rows
78 and columns
79 """
81 def __call__(self, observation_list, conditions):
82 indx = int(conditions.night % 2)
83 rotTelPos = np.radians([45., 315.][indx])
85 for obs in observation_list:
86 obs['rotSkyPos'] = np.nan
87 obs['rotTelPos'] = rotTelPos
89 return observation_list
92class Comcam_90rot_detailer(Base_detailer):
93 """
94 Detailer to set the camera rotation so rotSkyPos is 0, 90, 180, or 270 degrees. Whatever
95 is closest to rotTelPos of zero.
96 """
98 def __call__(self, observation_list, conditions):
99 favored_rotSkyPos = np.radians([0., 90., 180., 270., 360.]).reshape(5, 1)
100 obs_array =np.concatenate(observation_list)
101 alt, az = _approx_RaDec2AltAz(obs_array['RA'], obs_array['dec'], conditions.site.latitude_rad,
102 conditions.site.longitude_rad, conditions.mjd)
103 parallactic_angle = _approx_altaz2pa(alt, az, conditions.site.latitude_rad)
104 # If we set rotSkyPos to parallactic angle, rotTelPos will be zero. So, find the
105 # favored rotSkyPos that is closest to PA to keep rotTelPos as close as possible to zero.
106 ang_diff = np.abs(parallactic_angle - favored_rotSkyPos)
107 min_indxs = np.argmin(ang_diff, axis=0)
108 # can swap 360 and zero if needed?
109 final_rotSkyPos = favored_rotSkyPos[min_indxs]
110 # Set all the observations to the proper rotSkyPos
111 for rsp, obs in zip(final_rotSkyPos, observation_list):
112 obs['rotSkyPos'] = rsp
114 return observation_list
117class Close_alt_detailer(Base_detailer):
118 """
119 re-order a list of observations so that the closest in altitude to the current pointing is first.
121 Parameters
122 ----------
123 alt_band : float (10)
124 The altitude band to try and stay in (degrees)
125 """
126 def __init__(self, alt_band=10.):
127 super(Close_alt_detailer, self).__init__()
128 self.alt_band = int_rounded(np.radians(alt_band))
130 def __call__(self, observation_list, conditions):
131 obs_array = np.concatenate(observation_list)
132 alt, az = _approx_RaDec2AltAz(obs_array['RA'], obs_array['dec'], conditions.site.latitude_rad,
133 conditions.site.longitude_rad, conditions.mjd)
134 alt_diff = np.abs(alt - conditions.telAlt)
135 in_band = np.where(int_rounded(alt_diff) <= self.alt_band)[0]
136 if in_band.size == 0:
137 in_band = np.arange(alt.size)
139 # Find the closest in angular distance of the points that are in band
140 ang_dist = _angularSeparation(az[in_band], alt[in_band], conditions.telAz, conditions.telAlt)
141 good = np.min(np.where(ang_dist == ang_dist.min())[0])
142 indx = in_band[good]
143 result = observation_list[indx:] + observation_list[:indx]
144 return result
147class Flush_for_sched_detailer(Base_detailer):
148 """Update the flush-by MJD to be before any scheduled observations
150 Parameters
151 ----------
152 tol : float
153 How much before to flush (minutes)
154 """
155 def __init__(self, tol=2.5):
156 super(Flush_for_sched_detailer, self).__init__()
157 self.tol = tol/24./60. # To days
159 def __call__(self, observation_list, conditions):
160 if np.size(conditions.scheduled_observations) > 0:
161 new_flush = np.min(conditions.scheduled_observations) - self.tol
162 for obs in observation_list:
163 if obs['flush_by_mjd'] > new_flush:
164 obs['flush_by_mjd'] = new_flush
165 return observation_list
168class Take_as_pairs_detailer(Base_detailer):
169 def __init__(self, filtername='r', exptime=None, nexp_dict=None):
170 """
171 """
172 super(Take_as_pairs_detailer, self).__init__()
173 self.filtername = filtername
174 self.exptime = exptime
175 self.nexp_dict = nexp_dict
177 def __call__(self, observation_list, conditions):
178 paired = copy.deepcopy(observation_list)
179 if self.exptime is not None:
180 for obs in paired:
181 obs['exptime'] = self.exptime
182 for obs in paired:
183 obs['filter'] = self.filtername
184 if self.nexp_dict is not None:
185 obs['nexp'] = self.nexp_dict[self.filtername]
186 if conditions.current_filter == self.filtername:
187 for obs in paired:
188 obs['note'] = obs['note'][0] + ', a'
189 for obs in observation_list:
190 obs['note'] = obs['note'][0] + ', b'
191 result = paired + observation_list
192 else:
193 for obs in paired:
194 obs['note'] = obs['note'][0] + ', b'
195 for obs in observation_list:
196 obs['note'] = obs['note'][0] + ', a'
197 result = observation_list + paired
198 # XXX--maybe a temp debugging thing, label what part of sequence each observation is.
199 for i, obs in enumerate(result):
200 obs['survey_id'] = i
201 return result
204class Twilight_triple_detailer(Base_detailer):
205 def __init__(self, slew_estimate=5.0, n_repeat=3):
206 super(Twilight_triple_detailer, self).__init__()
207 self.slew_estimate = slew_estimate
208 self.n_repeat = n_repeat
210 def __call__(self, observation_list, conditions):
212 obs_array = np.concatenate(observation_list)
214 # Estimate how much time is left in the twilgiht block
215 potential_times = np.array([conditions.sun_n18_setting - conditions.mjd,
216 conditions.sun_n12_rising - conditions.mjd])
218 potential_times = np.min(potential_times[np.where(potential_times > 0)]) * 24.*3600.
220 # How long will observations take?
221 cumulative_slew = np.arange(obs_array.size) * self.slew_estimate
222 cumulative_expt = np.cumsum(obs_array['exptime'])
223 cumulative_time = cumulative_slew + cumulative_expt
224 # If we are way over, truncate the list before doing the triple
225 if np.max(cumulative_time) > potential_times:
226 max_indx = np.where(cumulative_time/self.n_repeat <= potential_times)[0]
227 if np.size(max_indx) == 0:
228 # Very bad magic number fudge
229 max_indx = 3
230 else:
231 max_indx = np.max(max_indx)
232 if max_indx == 0:
233 max_indx += 1
234 observation_list = observation_list[0:max_indx]
236 # Repeat the observations n times
237 out_obs = []
238 for i in range(self.n_repeat):
239 out_obs.extend(copy.deepcopy(observation_list))
241 return out_obs