Hide keyboard shortcuts

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.surveys import BaseSurvey 

3import copy 

4import lsst.sims.featureScheduler.basis_functions as basis_functions 

5from lsst.sims.featureScheduler.utils import empty_observation 

6from lsst.sims.featureScheduler import features 

7import logging 

8import random 

9 

10 

11__all__ = ['Deep_drilling_survey', 'generate_dd_surveys', 'dd_bfs'] 

12 

13log = logging.getLogger(__name__) 

14 

15 

16class Deep_drilling_survey(BaseSurvey): 

17 """A survey class for running deep drilling fields. 

18 

19 Parameters 

20 ---------- 

21 basis_functions : list of lsst.sims.featureScheduler.basis_function objects 

22 These should be feasibility basis functions. 

23 RA : float 

24 The RA of the field (degrees) 

25 dec : float 

26 The dec of the field to observe (degrees) 

27 sequence : list of observation objects or str (rgizy) 

28 The sequence of observations to take. Can be a string of list of obs objects. 

29 nvis : list of ints 

30 The number of visits in each filter. Should be same length as sequence. 

31 survey_name : str (DD) 

32 The name to give this survey so it can be tracked 

33 reward_value : float (101.) 

34 The reward value to report if it is able to start (unitless). 

35 readtime : float (2.) 

36 Readout time for computing approximate time of observing the sequence. (seconds) 

37 flush_pad : float (30.) 

38 How long to hold observations in the queue after they were expected to be completed (minutes). 

39 """ 

40 

41 def __init__(self, basis_functions, RA, dec, sequence='rgizy', 

42 nvis=[20, 10, 20, 26, 20], 

43 exptime=30., u_exptime=30., nexp=2, ignore_obs=None, survey_name='DD', 

44 reward_value=None, readtime=2., filter_change_time=120., 

45 nside=None, flush_pad=30., seed=42, detailers=None): 

46 super(Deep_drilling_survey, self).__init__(nside=nside, basis_functions=basis_functions, 

47 detailers=detailers, ignore_obs=ignore_obs) 

48 random.seed(a=seed) 

49 

50 self.ra = np.radians(RA) 

51 self.ra_hours = RA/360.*24. 

52 self.dec = np.radians(dec) 

53 self.survey_name = survey_name 

54 self.reward_value = reward_value 

55 self.flush_pad = flush_pad/60./24. # To days 

56 self.filter_sequence = [] 

57 if type(sequence) == str: 

58 self.observations = [] 

59 for num, filtername in zip(nvis, sequence): 

60 for j in range(num): 

61 obs = empty_observation() 

62 obs['filter'] = filtername 

63 if filtername == 'u': 

64 obs['exptime'] = u_exptime 

65 else: 

66 obs['exptime'] = exptime 

67 obs['RA'] = self.ra 

68 obs['dec'] = self.dec 

69 obs['nexp'] = nexp 

70 obs['note'] = survey_name 

71 self.observations.append(obs) 

72 else: 

73 self.observations = sequence 

74 

75 # Let's just make this an array for ease of use 

76 self.observations = np.concatenate(self.observations) 

77 order = np.argsort(self.observations['filter']) 

78 self.observations = self.observations[order] 

79 

80 n_filter_change = np.size(np.unique(self.observations['filter'])) 

81 

82 # Make an estimate of how long a seqeunce will take. Assumes no major rotational or spatial 

83 # dithering slowing things down. 

84 self.approx_time = np.sum(self.observations['exptime']+readtime*self.observations['nexp'])/3600./24. \ 

85 + filter_change_time*n_filter_change/3600./24. # to days 

86 

87 if self.reward_value is None: 

88 self.extra_features['Ntot'] = features.N_obs_survey() 

89 self.extra_features['N_survey'] = features.N_obs_survey(note=self.survey_name) 

90 

91 def check_continue(self, observation, conditions): 

92 # feasibility basis functions? 

93 ''' 

94 This method enables external calls to check if a given observations that belongs to this survey is 

95 feasible or not. This is called once a sequence has started to make sure it can continue. 

96 

97 XXX--TODO: Need to decide if we want to develope check_continue, or instead hold the 

98 sequence in the survey, and be able to check it that way. 

99 ''' 

100 

101 result = True 

102 

103 return result 

104 

105 def calc_reward_function(self, conditions): 

106 result = -np.inf 

107 if self._check_feasibility(conditions): 

108 if self.reward_value is not None: 

109 result = self.reward_value 

110 else: 

111 # XXX This might backfire if we want to have DDFs with different fractions of the 

112 # survey time. Then might need to define a goal fraction, and have the reward be the 

113 # number of observations behind that target fraction. 

114 result = self.extra_features['Ntot'].feature / (self.extra_features['N_survey'].feature+1) 

115 return result 

116 

117 def generate_observations_rough(self, conditions): 

118 result = [] 

119 if self._check_feasibility(conditions): 

120 result = copy.deepcopy(self.observations) 

121 

122 # Set the flush_by 

123 result['flush_by_mjd'] = conditions.mjd + self.approx_time + self.flush_pad 

124 

125 # remove filters that are not mounted 

126 mask = np.isin(result['filter'], conditions.mounted_filters) 

127 result = result[mask] 

128 # Put current loaded filter first 

129 ind1 = np.where(result['filter'] == conditions.current_filter)[0] 

130 ind2 = np.where(result['filter'] != conditions.current_filter)[0] 

131 result = result[ind1.tolist() + (ind2.tolist())] 

132 

133 # convert to list of array. Arglebargle, don't understand why I need a reshape there 

134 final_result = [row.reshape(1,) for row in result] 

135 result = final_result 

136 

137 return result 

138 

139 

140def dd_bfs(RA, dec, survey_name, ha_limits, frac_total=0.0185/2., aggressive_frac=0.011/2., 

141 delays=[0., 0.5, 1.5]): 

142 """ 

143 Convienence function to generate all the feasibility basis functions 

144 """ 

145 sun_alt_limit = -18. 

146 time_needed = 62. 

147 fractions = [0.00, aggressive_frac, frac_total] 

148 bfs = [] 

149 bfs.append(basis_functions.Not_twilight_basis_function(sun_alt_limit=sun_alt_limit)) 

150 bfs.append(basis_functions.Time_to_twilight_basis_function(time_needed=time_needed)) 

151 bfs.append(basis_functions.Hour_Angle_limit_basis_function(RA=RA, ha_limits=ha_limits)) 

152 bfs.append(basis_functions.Moon_down_basis_function()) 

153 bfs.append(basis_functions.Fraction_of_obs_basis_function(frac_total=frac_total, survey_name=survey_name)) 

154 bfs.append(basis_functions.Look_ahead_ddf_basis_function(frac_total, aggressive_frac, 

155 sun_alt_limit=sun_alt_limit, time_needed=time_needed, 

156 RA=RA, survey_name=survey_name, 

157 ha_limits=ha_limits)) 

158 bfs.append(basis_functions.Soft_delay_basis_function(fractions=fractions, delays=delays, 

159 survey_name=survey_name)) 

160 bfs.append(basis_functions.Time_to_scheduled_basis_function(time_needed=time_needed)) 

161 

162 return bfs 

163 

164 

165def generate_dd_surveys(nside=None, nexp=2, detailers=None, euclid_detailers=None, reward_value=100, 

166 frac_total=0.0185/2., aggressive_frac=0.011/2., exptime=30, u_exptime=30, 

167 nvis_master=[8, 20, 10, 20, 26, 20], delays=[0., 0.5, 1.5]): 

168 """Utility to return a list of standard deep drilling field surveys. 

169 

170 XXX-Someone double check that I got the coordinates right! 

171 

172 """ 

173 

174 if euclid_detailers is None: 

175 euclid_detailers = detailers 

176 

177 surveys = [] 

178 

179 # ELAIS S1 

180 RA = 9.45 

181 dec = -44. 

182 survey_name = 'DD:ELAISS1' 

183 ha_limits = ([0., 1.5], [21.5, 24.]) 

184 bfs = dd_bfs(RA, dec, survey_name, ha_limits, frac_total=frac_total, aggressive_frac=aggressive_frac, delays=delays) 

185 surveys.append(Deep_drilling_survey(bfs, RA, dec, sequence='urgizy', 

186 nvis=nvis_master, exptime=exptime, u_exptime=u_exptime, 

187 survey_name=survey_name, reward_value=reward_value, 

188 nside=nside, nexp=nexp, detailers=detailers)) 

189 

190 # XMM-LSS 

191 survey_name = 'DD:XMM-LSS' 

192 RA = 35.708333 

193 dec = -4-45/60. 

194 ha_limits = ([0., 1.5], [21.5, 24.]) 

195 bfs = dd_bfs(RA, dec, survey_name, ha_limits, frac_total=frac_total, aggressive_frac=aggressive_frac, delays=delays) 

196 

197 surveys.append(Deep_drilling_survey(bfs, RA, dec, sequence='urgizy', exptime=exptime, u_exptime=u_exptime, 

198 nvis=nvis_master, survey_name=survey_name, reward_value=reward_value, 

199 nside=nside, nexp=nexp, detailers=detailers)) 

200 

201 # Extended Chandra Deep Field South 

202 RA = 53.125 

203 dec = -28.-6/60. 

204 survey_name = 'DD:ECDFS' 

205 ha_limits = [[0.5, 3.0], [20., 22.5]] 

206 bfs = dd_bfs(RA, dec, survey_name, ha_limits, frac_total=frac_total, aggressive_frac=aggressive_frac, delays=delays) 

207 surveys.append(Deep_drilling_survey(bfs, RA, dec, sequence='urgizy', 

208 nvis=nvis_master, exptime=exptime, u_exptime=u_exptime, 

209 survey_name=survey_name, reward_value=reward_value, nside=nside, 

210 nexp=nexp, detailers=detailers)) 

211 

212 # COSMOS 

213 RA = 150.1 

214 dec = 2.+10./60.+55/3600. 

215 survey_name = 'DD:COSMOS' 

216 ha_limits = ([0., 2.5], [21.5, 24.]) 

217 bfs = dd_bfs(RA, dec, survey_name, ha_limits, frac_total=frac_total, aggressive_frac=aggressive_frac, delays=delays) 

218 surveys.append(Deep_drilling_survey(bfs, RA, dec, sequence='urgizy', 

219 nvis=nvis_master, exptime=exptime, u_exptime=u_exptime, 

220 survey_name=survey_name, reward_value=reward_value, nside=nside, 

221 nexp=nexp, detailers=detailers)) 

222 

223 # Euclid Fields 

224 # I can use the sequence kwarg to do two positions per sequence 

225 filters = 'urgizy' 

226 nviss = nvis_master 

227 survey_name = 'DD:EDFS' 

228 # Note the sequences need to be in radians since they are using observation objects directly 

229 # Coords from jc.cuillandre@cea.fr Oct 15, 2020 

230 RAs = np.radians([58.90, 63.6]) 

231 decs = np.radians([-49.315, -47.60]) 

232 suffixes = [', a', ', b'] 

233 sequence = [] 

234 

235 for filtername, nvis in zip(filters, nviss): 

236 for ra, dec, suffix in zip(RAs, decs, suffixes): 

237 for num in range(nvis): 

238 obs = empty_observation() 

239 obs['filter'] = filtername 

240 if filtername == 'u': 

241 obs['exptime'] = u_exptime 

242 else: 

243 obs['exptime'] = exptime 

244 obs['RA'] = ra 

245 obs['dec'] = dec 

246 obs['nexp'] = nexp 

247 obs['note'] = survey_name + suffix 

248 sequence.append(obs) 

249 

250 ha_limits = ([0., 1.5], [22.5, 24.]) 

251 # And back to degrees for the basis function 

252 bfs = dd_bfs(np.degrees(RAs[0]), np.degrees(decs[0]), survey_name, ha_limits, 

253 frac_total=frac_total, aggressive_frac=aggressive_frac, delays=delays) 

254 surveys.append(Deep_drilling_survey(bfs, RA, dec, sequence=sequence, 

255 survey_name=survey_name, reward_value=reward_value, nside=nside, 

256 nexp=nexp, detailers=euclid_detailers)) 

257 

258 return surveys