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.detailers import Base_detailer 

3from lsst.sims.utils import _approx_RaDec2AltAz, _approx_altaz2pa 

4 

5 

6__all__ = ["Dither_detailer", "Camera_rot_detailer", "Euclid_dither_detailer"] 

7 

8 

9def gnomonic_project_toxy(ra, dec, raCen, decCen): 

10 """Calculate x/y projection of RA1/Dec1 in system with center at RAcen, Deccenp. 

11 Input radians. Returns x/y.""" 

12 # also used in Global Telescope Network website 

13 if (len(ra) != len(dec)): 

14 raise Exception("Expect RA and Dec arrays input to gnomonic projection to be same length.") 

15 cosc = np.sin(decCen) * np.sin(dec) + np.cos(decCen) * np.cos(dec) * np.cos(ra-raCen) 

16 x = np.cos(dec) * np.sin(ra-raCen) / cosc 

17 y = (np.cos(decCen)*np.sin(dec) - np.sin(decCen)*np.cos(dec)*np.cos(ra-raCen)) / cosc 

18 return x, y 

19 

20 

21def gnomonic_project_tosky(x, y, raCen, decCen): 

22 """Calculate RA/Dec on sky of object with x/y and RA/Cen of field of view. 

23 Returns Ra/Dec in radians.""" 

24 denom = np.cos(decCen) - y * np.sin(decCen) 

25 ra = raCen + np.arctan2(x, denom) 

26 dec = np.arctan2(np.sin(decCen) + y * np.cos(decCen), np.sqrt(x*x + denom*denom)) 

27 return ra, dec 

28 

29 

30class Dither_detailer(Base_detailer): 

31 """ 

32 make a uniform dither pattern. Offset by a maximum radius in a random direction. 

33 Mostly intended for DDF pointings, the BaseMarkovDF_survey class includes dithering 

34 for large areas. 

35 

36 Parameters 

37 ---------- 

38 max_dither : float (0.7) 

39 The maximum dither size to use (degrees). 

40 per_night : bool (True) 

41 If true, us the same dither offset for an entire night 

42 

43 

44 """ 

45 def __init__(self, max_dither=0.7, seed=42, per_night=True): 

46 self.survey_features = {} 

47 

48 self.current_night = -1 

49 self.max_dither = np.radians(max_dither) 

50 self.per_night = per_night 

51 np.random.seed(seed=seed) 

52 self.offset = None 

53 

54 def _generate_offsets(self, n_offsets, night): 

55 if self.per_night: 

56 if night != self.current_night: 

57 self.current_night = night 

58 self.offset = (np.random.random((1, 2))-0.5) * 2.*self.max_dither 

59 angle = np.random.random(1)*2*np.pi 

60 radius = self.max_dither * np.sqrt(np.random.random(1)) 

61 self.offset = np.array([radius*np.cos(angle), radius*np.sin(angle)]) 

62 offsets = np.tile(self.offset, (n_offsets, 1)) 

63 else: 

64 angle = np.random.random(n_offsets)*2*np.pi 

65 radius = self.max_dither * np.sqrt(np.random.random(n_offsets)) 

66 offsets = np.array([radius*np.cos(angle), radius*np.sin(angle)]) 

67 

68 return offsets 

69 

70 def __call__(self, observation_list, conditions): 

71 

72 # Generate offsets in RA and Dec 

73 offsets = self._generate_offsets(len(observation_list), conditions.night) 

74 

75 obs_array = np.concatenate(observation_list) 

76 newRA, newDec = gnomonic_project_tosky(offsets[0, :], offsets[1, :], obs_array['RA'], obs_array['dec']) 

77 for i, obs in enumerate(observation_list): 

78 observation_list[i]['RA'] = newRA[i] 

79 observation_list[i]['dec'] = newDec[i] 

80 return observation_list 

81 

82 

83def bearing(lon1, lat1, lon2, lat2): 

84 """Bearing between two points 

85 """ 

86 

87 delta_l = lon2 - lon1 

88 X = np.cos(lat2) * np.sin(delta_l) 

89 Y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(delta_l) 

90 theta = np.arctan2(X, Y) 

91 

92 return theta 

93 

94 

95def dest(dist, bearing, lat1, lon1): 

96 

97 lat2 = np.arcsin(np.sin(lat1)*np.cos(dist)+np.cos(lat1)*np.sin(dist)*np.cos(bearing)) 

98 lon2 = lon1 + np.arctan2(np.sin(bearing)*np.sin(dist)*np.cos(lat1), np.cos(dist)-np.sin(lat1)*np.sin(lat2)) 

99 return lat2, lon2 

100 

101 

102class Euclid_dither_detailer(Base_detailer): 

103 """Directional dithering for Euclid DDFs 

104 

105 Parameters 

106 ---------- 

107 XXX--fill in docstring 

108 

109 """ 

110 def __init__(self, dither_bearing_dir=[-0.25, 1], dither_bearing_perp=[-0.25, 0.25], 

111 seed=42, per_night=True, ra_a=58.90, 

112 dec_a=-49.315, ra_b=63.6, dec_b=-47.60): 

113 self.survey_features = {} 

114 

115 self.ra_a = np.radians(ra_a) 

116 self.ra_b = np.radians(ra_b) 

117 self.dec_a = np.radians(dec_a) 

118 self.dec_b = np.radians(dec_b) 

119 

120 self.dither_bearing_dir = np.radians(dither_bearing_dir) 

121 self.dither_bearing_perp = np.radians(dither_bearing_perp) 

122 

123 self.bearing_atob = bearing(self.ra_a, self.dec_a, self.ra_b, self.dec_b) 

124 self.bearing_btoa = bearing(self.ra_b, self.dec_b, self.ra_a, self.dec_a) 

125 

126 self.current_night = -1 

127 

128 self.per_night = per_night 

129 np.random.seed(seed=seed) 

130 self.shifted_ra_a = None 

131 self.shifted_dec_a = None 

132 self.shifted_ra_b = None 

133 self.shifted_dec_b = None 

134 

135 def _generate_offsets(self, n_offsets, night): 

136 if self.per_night: 

137 if night != self.current_night: 

138 self.current_night = night 

139 bearing_mag = np.random.uniform(low=self.dither_bearing_dir.min(), high=self.dither_bearing_dir.max()) 

140 perp_mag = np.random.uniform(low=self.dither_bearing_perp.min(), high=self.dither_bearing_perp.max()) 

141 # Move point a along the bearings 

142 self.shifted_dec_a, self.shifted_ra_a = dest(bearing_mag, self.bearing_atob, self.dec_a, self.ra_a) 

143 self.shifted_dec_a, self.shifted_ra_a = dest(perp_mag, self.bearing_atob+np.pi/2., 

144 self.shifted_dec_a, self.shifted_ra_a) 

145 

146 # Shift the second position 

147 bearing_mag = np.random.uniform(low=self.dither_bearing_dir.min(), high=self.dither_bearing_dir.max()) 

148 perp_mag = np.random.uniform(low=self.dither_bearing_perp.min(), high=self.dither_bearing_perp.max()) 

149 

150 self.shifted_dec_b, self.shifted_ra_b = dest(bearing_mag, self.bearing_btoa, self.dec_b, self.ra_b) 

151 self.shifted_dec_b, self.shifted_ra_b = dest(perp_mag, self.bearing_btoa+np.pi/2., 

152 self.shifted_dec_b, self.shifted_ra_b) 

153 else: 

154 # XXX--not implamented 

155 ValueError('not implamented') 

156 

157 return self.shifted_ra_a, self.shifted_dec_a, self.shifted_ra_b, self.shifted_dec_b 

158 

159 def __call__(self, observation_list, conditions): 

160 # Generate offsets in RA and Dec 

161 ra_a, dec_a, ra_b, dec_b = self._generate_offsets(len(observation_list), conditions.night) 

162 

163 for i, obs in enumerate(observation_list): 

164 if obs[0]['note'][-1] == 'a': 

165 observation_list[i]['RA'] = ra_a 

166 observation_list[i]['dec'] = dec_a 

167 elif obs[0]['note'][-1] == 'b': 

168 observation_list[i]['RA'] = ra_b 

169 observation_list[i]['dec'] = dec_b 

170 else: 

171 ValueError('observation note does not end in a or b.') 

172 return observation_list 

173 

174 

175class Camera_rot_detailer(Base_detailer): 

176 """ 

177 Randomly set the camera rotation, either for each exposure, or per night. 

178 

179 Parameters 

180 ---------- 

181 max_rot : float (90.) 

182 The maximum amount to offset the camera (degrees) 

183 min_rot : float (90) 

184 The minimum to offset the camera (degrees) 

185 per_night : bool (True) 

186 If True, only set a new offset per night. If False, randomly rotates every observation. 

187 """ 

188 def __init__(self, max_rot=90., min_rot=-90., per_night=True, seed=42): 

189 self.survey_features = {} 

190 

191 self.current_night = -1 

192 self.max_rot = np.radians(max_rot) 

193 self.min_rot = np.radians(min_rot) 

194 self.range = self.max_rot - self.min_rot 

195 self.per_night = per_night 

196 np.random.seed(seed=seed) 

197 self.offset = None 

198 

199 def _generate_offsets(self, n_offsets, night): 

200 if self.per_night: 

201 if night != self.current_night: 

202 self.current_night = night 

203 self.offset = np.random.random(1) * self.range + self.min_rot 

204 offsets = np.ones(n_offsets) * self.offset 

205 else: 

206 offsets = np.random.random(n_offsets) * self.range + self.min_rot 

207 

208 offsets = offsets % (2.*np.pi) 

209 

210 return offsets 

211 

212 def __call__(self, observation_list, conditions): 

213 

214 # Generate offsets in camamera rotator 

215 offsets = self._generate_offsets(len(observation_list), conditions.night) 

216 

217 for i, obs in enumerate(observation_list): 

218 alt, az = _approx_RaDec2AltAz(obs['RA'], obs['dec'], conditions.site.latitude_rad, 

219 conditions.site.longitude_rad, conditions.mjd) 

220 obs_pa = _approx_altaz2pa(alt, az, conditions.site.latitude_rad) 

221 obs['rotSkyPos'] = (offsets[i] - obs_pa) % (2.*np.pi) 

222 obs['rotTelPos'] = offsets[i] 

223 

224 return observation_list