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 

2import healpy as hp 

3from lsst.sims.utils import _hpid2RaDec, Site, _angularSeparation, _xyz_from_ra_dec 

4import matplotlib.pylab as plt 

5from lsst.sims.featureScheduler.basis_functions import Base_basis_function 

6from lsst.sims.featureScheduler.utils import hp_in_lsst_fov, int_rounded 

7 

8 

9__all__ = ['Zenith_mask_basis_function', 'Zenith_shadow_mask_basis_function', 

10 'Moon_avoidance_basis_function', 'Map_cloud_basis_function', 

11 'Planet_mask_basis_function', 'Mask_azimuth_basis_function', 

12 'Solar_elongation_mask_basis_function', 'Area_check_mask_basis_function'] 

13 

14 

15 

16class Area_check_mask_basis_function(Base_basis_function): 

17 """Take a list of other mask basis functions, and do an additional check for area available 

18 """ 

19 def __init__(self, bf_list, nside=32, min_area=1000.): 

20 super(Area_check_mask_basis_function, self).__init__(nside=nside) 

21 self.bf_list = bf_list 

22 self.result = np.zeros(hp.nside2npix(self.nside), dtype=float) 

23 self.min_area = min_area 

24 

25 def check_feasibility(self, conditions): 

26 result = True 

27 for bf in self.bf_list: 

28 if not bf.check_feasibility(conditions): 

29 return False 

30 

31 area_map = self.result.copy() 

32 for bf in self.bf_list: 

33 area_map *= bf(conditions) 

34 

35 good_pix = np.where(area_map == 0)[0] 

36 if hp.nside2pixarea(self.nside, degrees=True)*good_pix.size < self.min_area: 

37 result = False 

38 return result 

39 

40 def _calc_value(self, conditions, **kwargs): 

41 result = self.result.copy() 

42 for bf in self.bf_list: 

43 result *= bf(conditions) 

44 return result 

45 

46 

47 

48class Solar_elongation_mask_basis_function(Base_basis_function): 

49 """Mask things at various solar elongations 

50 

51 Parameters 

52 ---------- 

53 min_elong : float (0) 

54 The minimum solar elongation to consider (degrees). 

55 max_elong : float (60.) 

56 The maximum solar elongation to consider (degrees). 

57 """ 

58 

59 def __init__(self, min_elong=0., max_elong=60., nside=None, penalty=np.nan): 

60 super(Solar_elongation_mask_basis_function, self).__init__(nside=nside) 

61 self.min_elong = np.radians(min_elong) 

62 self.max_elong = np.radians(max_elong) 

63 self.penalty = penalty 

64 self.result = np.empty(hp.nside2npix(self.nside), dtype=float) 

65 self.result.fill(self.penalty) 

66 

67 def _calc_value(self, conditions, indx=None): 

68 result = self.result.copy() 

69 in_range = np.where((int_rounded(conditions.solar_elongation) >= int_rounded(self.min_elong)) & 

70 (int_rounded(conditions.solar_elongation) <= int_rounded(self.max_elong)))[0] 

71 result[in_range] = 1 

72 return result 

73 

74 

75class Zenith_mask_basis_function(Base_basis_function): 

76 """Just remove the area near zenith. 

77 

78 Parameters 

79 ---------- 

80 min_alt : float (20.) 

81 The minimum possible altitude (degrees) 

82 max_alt : float (82.) 

83 The maximum allowed altitude (degrees) 

84 """ 

85 def __init__(self, min_alt=20., max_alt=82., nside=None): 

86 super(Zenith_mask_basis_function, self).__init__(nside=nside) 

87 self.update_on_newobs = False 

88 self.min_alt = np.radians(min_alt) 

89 self.max_alt = np.radians(max_alt) 

90 self.result = np.empty(hp.nside2npix(self.nside), dtype=float).fill(self.penalty) 

91 

92 def _calc_value(self, conditions, indx=None): 

93 

94 result = self.result.copy() 

95 alt_limit = np.where((int_rounded(conditions.alt) > int_rounded(self.min_alt)) & 

96 (int_rounded(conditions.alt) < int_rounded(self.max_alt)))[0] 

97 result[alt_limit] = 1 

98 return result 

99 

100 

101class Planet_mask_basis_function(Base_basis_function): 

102 """Mask the bright planets 

103 

104 Parameters 

105 ---------- 

106 mask_radius : float (3.5) 

107 The radius to mask around a planet (degrees). 

108 planets : list of str (None) 

109 A list of planet names to mask. Defaults to ['venus', 'mars', 'jupiter']. Not including 

110 Saturn because it moves really slow and has average apparent mag of ~0.4, so fainter than Vega. 

111 

112 """ 

113 def __init__(self, mask_radius=3.5, planets=None, nside=None, scale=1e5): 

114 super(Planet_mask_basis_function, self).__init__(nside=nside) 

115 if planets is None: 

116 planets = ['venus', 'mars', 'jupiter'] 

117 self.planets = planets 

118 self.mask_radius = np.radians(mask_radius) 

119 self.result = np.zeros(hp.nside2npix(nside)) 

120 # set up a kdtree. Could maybe use healpy.query_disc instead. 

121 self.in_fov = hp_in_lsst_fov(nside=nside, fov_radius=mask_radius, scale=scale) 

122 

123 def _calc_value(self, conditions, indx=None): 

124 result = self.result.copy() 

125 for pn in self.planets: 

126 indices = self.in_fov(conditions.planet_positions[pn+'_RA'], conditions.planet_positions[pn+'_dec']) 

127 result[indices] = np.nan 

128 

129 return result 

130 

131 

132class Zenith_shadow_mask_basis_function(Base_basis_function): 

133 """Mask the zenith, and things that will soon pass near zenith. Useful for making sure 

134 observations will not be too close to zenith when they need to be observed again (e.g. for a pair). 

135 

136 Parameters 

137 ---------- 

138 min_alt : float (20.) 

139 The minimum alititude to alow. Everything lower is masked. (degrees) 

140 max_alt : float (82.) 

141 The maximum altitude to alow. Everything higher is masked. (degrees) 

142 shadow_minutes : float (40.) 

143 Mask anything that will pass through the max alt in the next shadow_minutes time. (minutes) 

144 """ 

145 def __init__(self, nside=None, min_alt=20., max_alt=82., 

146 shadow_minutes=40., penalty=np.nan, site='LSST'): 

147 super(Zenith_shadow_mask_basis_function, self).__init__(nside=nside) 

148 self.update_on_newobs = False 

149 

150 self.penalty = penalty 

151 

152 self.min_alt = np.radians(min_alt) 

153 self.max_alt = np.radians(max_alt) 

154 self.ra, self.dec = _hpid2RaDec(nside, np.arange(hp.nside2npix(nside))) 

155 self.shadow_minutes = np.radians(shadow_minutes/60. * 360./24.) 

156 # Compute the declination band where things could drift into zenith 

157 self.decband = np.zeros(self.dec.size, dtype=float) 

158 self.zenith_radius = np.radians(90.-max_alt)/2. 

159 site = Site(name=site) 

160 self.lat_rad = site.latitude_rad 

161 self.lon_rad = site.longitude_rad 

162 self.decband[np.where((int_rounded(self.dec) < int_rounded(self.lat_rad+self.zenith_radius)) & 

163 (int_rounded(self.dec) > int_rounded(self.lat_rad-self.zenith_radius)))] = 1 

164 

165 self.result = np.empty(hp.nside2npix(self.nside), dtype=float) 

166 self.result.fill(self.penalty) 

167 

168 def _calc_value(self, conditions, indx=None): 

169 

170 result = self.result.copy() 

171 alt_limit = np.where((int_rounded(conditions.alt) > int_rounded(self.min_alt)) & 

172 (int_rounded(conditions.alt) < int_rounded(self.max_alt)))[0] 

173 result[alt_limit] = 1 

174 to_mask = np.where((int_rounded(conditions.HA) > int_rounded(2.*np.pi-self.shadow_minutes-self.zenith_radius)) & 

175 (self.decband == 1)) 

176 result[to_mask] = np.nan 

177 return result 

178 

179 

180class Moon_avoidance_basis_function(Base_basis_function): 

181 """Avoid looking too close to the moon. 

182 

183 Parameters 

184 ---------- 

185 moon_distance: float (30.) 

186 Minimum allowed moon distance. (degrees) 

187 

188 XXX--TODO: This could be a more complicated function of filter and moon phase. 

189 """ 

190 def __init__(self, nside=None, moon_distance=30.): 

191 super(Moon_avoidance_basis_function, self).__init__(nside=nside) 

192 self.update_on_newobs = False 

193 

194 self.moon_distance = int_rounded(np.radians(moon_distance)) 

195 self.result = np.ones(hp.nside2npix(self.nside), dtype=float) 

196 

197 def _calc_value(self, conditions, indx=None): 

198 result = self.result.copy() 

199 

200 angular_distance = _angularSeparation(conditions.az, conditions.alt, 

201 conditions.moonAz, 

202 conditions.moonAlt) 

203 

204 result[int_rounded(angular_distance) < self.moon_distance] = np.nan 

205 

206 return result 

207 

208 

209class Bulk_cloud_basis_function(Base_basis_function): 

210 """Mark healpixels on a map if their cloud values are greater than 

211 the same healpixels on a maximum cloud map. 

212 

213 Parameters 

214 ---------- 

215 nside: int (default_nside) 

216 The healpix resolution. 

217 max_cloud_map : numpy array (None) 

218 A healpix map showing the maximum allowed cloud values for all points on the sky 

219 out_of_bounds_val : float (10.) 

220 Point value to give regions where there are no observations requested 

221 """ 

222 

223 def __init__(self, nside=None, max_cloud_map=None, max_val=0.7, 

224 out_of_bounds_val=np.nan): 

225 super(Bulk_cloud_basis_function, self).__init__(nside=nside) 

226 self.update_on_newobs = False 

227 

228 if max_cloud_map is None: 

229 self.max_cloud_map = np.zeros(hp.nside2npix(nside), dtype=float) + max_val 

230 else: 

231 self.max_cloud_map = max_cloud_map 

232 self.out_of_bounds_area = np.where(self.max_cloud_map > 1.)[0] 

233 self.out_of_bounds_val = out_of_bounds_val 

234 self.result = np.ones(hp.nside2npix(self.nside)) 

235 

236 def _calc_value(self, conditions, indx=None): 

237 """ 

238 Parameters 

239 ---------- 

240 indx : list (None) 

241 Index values to compute, if None, full map is computed 

242 Returns 

243 ------- 

244 Healpix map where pixels with a cloud value greater than the max_cloud_map 

245 value are marked as unseen. 

246 """ 

247 

248 result = self.result.copy() 

249 

250 clouded = np.where(self.max_cloud_map <= conditions.bulk_cloud) 

251 result[clouded] = self.out_of_bounds_val 

252 

253 return result 

254 

255 

256class Map_cloud_basis_function(Base_basis_function): 

257 """Mark healpixels on a map if their cloud values are greater than 

258 the same healpixels on a maximum cloud map. Currently a placeholder for 

259 when the telemetry stream can include a full sky cloud map. 

260 

261 Parameters 

262 ---------- 

263 nside: int (default_nside) 

264 The healpix resolution. 

265 max_cloud_map : numpy array (None) 

266 A healpix map showing the maximum allowed cloud values for all points on the sky 

267 out_of_bounds_val : float (10.) 

268 Point value to give regions where there are no observations requested 

269 """ 

270 

271 def __init__(self, nside=None, max_cloud_map=None, max_val=0.7, 

272 out_of_bounds_val=np.nan): 

273 super(Bulk_cloud_basis_function, self).__init__(nside=nside) 

274 self.update_on_newobs = False 

275 

276 if max_cloud_map is None: 

277 self.max_cloud_map = np.zeros(hp.nside2npix(nside), dtype=float) + max_val 

278 else: 

279 self.max_cloud_map = max_cloud_map 

280 self.out_of_bounds_area = np.where(self.max_cloud_map > 1.)[0] 

281 self.out_of_bounds_val = out_of_bounds_val 

282 self.result = np.ones(hp.nside2npix(self.nside)) 

283 

284 def _calc_value(self, conditions, indx=None): 

285 """ 

286 Parameters 

287 ---------- 

288 indx : list (None) 

289 Index values to compute, if None, full map is computed 

290 Returns 

291 ------- 

292 Healpix map where pixels with a cloud value greater than the max_cloud_map 

293 value are marked as unseen. 

294 """ 

295 

296 result = self.result.copy() 

297 

298 clouded = np.where(self.max_cloud_map <= conditions.bulk_cloud) 

299 result[clouded] = self.out_of_bounds_val 

300 

301 return result 

302 

303 

304class Mask_azimuth_basis_function(Base_basis_function): 

305 """Mask pixels based on azimuth 

306 """ 

307 def __init__(self, nside=None, out_of_bounds_val=np.nan, az_min=0., az_max=180.): 

308 super(Mask_azimuth_basis_function, self).__init__(nside=nside) 

309 self.az_min = int_rounded(np.radians(az_min)) 

310 self.az_max = int_rounded(np.radians(az_max)) 

311 self.out_of_bounds_val = out_of_bounds_val 

312 self.result = np.ones(hp.nside2npix(self.nside)) 

313 

314 def _calc_value(self, conditions, indx=None): 

315 to_mask = np.where((int_rounded(conditions.az) > self.az_min) & (int_rounded(conditions.az) < self.az_max))[0] 

316 result = self.result.copy() 

317 result[to_mask] = self.out_of_bounds_val 

318 

319 return result 

320