Coverage for bilby/gw/detector/geometry.py: 98%

121 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-06 04:57 +0000

1import numpy as np 

2from bilby_cython.geometry import calculate_arm, detector_tensor 

3 

4from .. import utils as gwutils 

5 

6 

7class InterferometerGeometry(object): 

8 def __init__(self, length, latitude, longitude, elevation, xarm_azimuth, yarm_azimuth, 

9 xarm_tilt=0., yarm_tilt=0.): 

10 """ 

11 Instantiate an Interferometer object. 

12 

13 Parameters 

14 ========== 

15 

16 length: float 

17 Length of the interferometer in km. 

18 latitude: float 

19 Latitude North in degrees (South is negative). 

20 longitude: float 

21 Longitude East in degrees (West is negative). 

22 elevation: float 

23 Height above surface in metres. 

24 xarm_azimuth: float 

25 Orientation of the x arm in degrees North of East. 

26 yarm_azimuth: float 

27 Orientation of the y arm in degrees North of East. 

28 xarm_tilt: float, optional 

29 Tilt of the x arm in radians above the horizontal defined by 

30 ellipsoid earth model in LIGO-T980044-08. 

31 yarm_tilt: float, optional 

32 Tilt of the y arm in radians above the horizontal. 

33 """ 

34 

35 self._x_updated = False 

36 self._y_updated = False 

37 self._vertex_updated = False 

38 self._detector_tensor_updated = False 

39 

40 self.length = length 

41 self.latitude = latitude 

42 self.longitude = longitude 

43 self.elevation = elevation 

44 self.xarm_azimuth = xarm_azimuth 

45 self.yarm_azimuth = yarm_azimuth 

46 self.xarm_tilt = xarm_tilt 

47 self.yarm_tilt = yarm_tilt 

48 self._vertex = None 

49 self._x = None 

50 self._y = None 

51 self._detector_tensor = None 

52 

53 def __eq__(self, other): 

54 for attribute in ['length', 'latitude', 'longitude', 'elevation', 

55 'xarm_azimuth', 'yarm_azimuth', 'xarm_tilt', 'yarm_tilt']: 

56 if not getattr(self, attribute) == getattr(other, attribute): 

57 return False 

58 return True 

59 

60 def __repr__(self): 

61 return self.__class__.__name__ + '(length={}, latitude={}, longitude={}, elevation={}, ' \ 

62 'xarm_azimuth={}, yarm_azimuth={}, xarm_tilt={}, yarm_tilt={})' \ 

63 .format(float(self.length), float(self.latitude), float(self.longitude), 

64 float(self.elevation), float(self.xarm_azimuth), float(self.yarm_azimuth), float(self.xarm_tilt), 

65 float(self.yarm_tilt)) 

66 

67 @property 

68 def latitude(self): 

69 """ Saves latitude in rad internally. Updates related quantities if set to a different value. 

70 

71 Returns 

72 ======= 

73 float: The latitude position of the detector in degree 

74 """ 

75 return self._latitude * 180 / np.pi 

76 

77 @latitude.setter 

78 def latitude(self, latitude): 

79 self._latitude = latitude * np.pi / 180 

80 self._x_updated = False 

81 self._y_updated = False 

82 self._vertex_updated = False 

83 

84 @property 

85 def latitude_radians(self): 

86 """ 

87 Returns 

88 ======= 

89 float: The latitude position of the detector in radians 

90 """ 

91 

92 return self._latitude 

93 

94 @property 

95 def longitude(self): 

96 """ Saves longitude in rad internally. Updates related quantities if set to a different value. 

97 

98 Returns 

99 ======= 

100 float: The longitude position of the detector in degree 

101 """ 

102 return self._longitude * 180 / np.pi 

103 

104 @longitude.setter 

105 def longitude(self, longitude): 

106 self._longitude = longitude * np.pi / 180 

107 self._x_updated = False 

108 self._y_updated = False 

109 self._vertex_updated = False 

110 

111 @property 

112 def longitude_radians(self): 

113 """ 

114 Returns 

115 ======= 

116 float: The latitude position of the detector in radians 

117 """ 

118 

119 return self._longitude 

120 

121 @property 

122 def elevation(self): 

123 """ Updates related quantities if set to a different values. 

124 

125 Returns 

126 ======= 

127 float: The height about the surface in meters 

128 """ 

129 return self._elevation 

130 

131 @elevation.setter 

132 def elevation(self, elevation): 

133 self._elevation = elevation 

134 self._vertex_updated = False 

135 

136 @property 

137 def xarm_azimuth(self): 

138 """ Saves the x-arm azimuth in rad internally. Updates related quantities if set to a different values. 

139 

140 Returns 

141 ======= 

142 float: The x-arm azimuth in degrees. 

143 

144 """ 

145 return self._xarm_azimuth * 180 / np.pi 

146 

147 @xarm_azimuth.setter 

148 def xarm_azimuth(self, xarm_azimuth): 

149 self._xarm_azimuth = xarm_azimuth * np.pi / 180 

150 self._x_updated = False 

151 

152 @property 

153 def yarm_azimuth(self): 

154 """ Saves the y-arm azimuth in rad internally. Updates related quantities if set to a different values. 

155 

156 Returns 

157 ======= 

158 float: The y-arm azimuth in degrees. 

159 

160 """ 

161 return self._yarm_azimuth * 180 / np.pi 

162 

163 @yarm_azimuth.setter 

164 def yarm_azimuth(self, yarm_azimuth): 

165 self._yarm_azimuth = yarm_azimuth * np.pi / 180 

166 self._y_updated = False 

167 

168 @property 

169 def xarm_tilt(self): 

170 """ Updates related quantities if set to a different values. 

171 

172 Returns 

173 ======= 

174 float: The x-arm tilt in radians. 

175 

176 """ 

177 return self._xarm_tilt 

178 

179 @xarm_tilt.setter 

180 def xarm_tilt(self, xarm_tilt): 

181 self._xarm_tilt = xarm_tilt 

182 self._x_updated = False 

183 

184 @property 

185 def yarm_tilt(self): 

186 """ Updates related quantities if set to a different values. 

187 

188 Returns 

189 ======= 

190 float: The y-arm tilt in radians. 

191 

192 """ 

193 return self._yarm_tilt 

194 

195 @yarm_tilt.setter 

196 def yarm_tilt(self, yarm_tilt): 

197 self._yarm_tilt = yarm_tilt 

198 self._y_updated = False 

199 

200 @property 

201 def vertex(self): 

202 """ Position of the IFO vertex in geocentric coordinates in meters. 

203 

204 Is automatically updated if related quantities are modified. 

205 

206 Returns 

207 ======= 

208 array_like: A 3D array representation of the vertex 

209 """ 

210 if not self._vertex_updated: 

211 self._vertex = gwutils.get_vertex_position_geocentric(self._latitude, self._longitude, 

212 self.elevation) 

213 self._vertex_updated = True 

214 return self._vertex 

215 

216 @property 

217 def x(self): 

218 """ A unit vector along the x-arm 

219 

220 Is automatically updated if related quantities are modified. 

221 

222 Returns 

223 ======= 

224 array_like: A 3D array representation of a unit vector along the x-arm 

225 

226 """ 

227 if not self._x_updated: 

228 self._x = self.unit_vector_along_arm('x') 

229 self._x_updated = True 

230 self._detector_tensor_updated = False 

231 return self._x 

232 

233 @property 

234 def y(self): 

235 """ A unit vector along the y-arm 

236 

237 Is automatically updated if related quantities are modified. 

238 

239 Returns 

240 ======= 

241 array_like: A 3D array representation of a unit vector along the y-arm 

242 

243 """ 

244 if not self._y_updated: 

245 self._y = self.unit_vector_along_arm('y') 

246 self._y_updated = True 

247 self._detector_tensor_updated = False 

248 return self._y 

249 

250 @property 

251 def detector_tensor(self): 

252 """ 

253 Calculate the detector tensor from the unit vectors along each arm of the detector. 

254 

255 See Eq. B6 of arXiv:gr-qc/0008066 

256 

257 Is automatically updated if related quantities are modified. 

258 

259 Returns 

260 ======= 

261 array_like: A 3x3 array representation of the detector tensor 

262 

263 """ 

264 if not self._x_updated or not self._y_updated: 

265 _, _ = self.x, self.y # noqa 

266 if not self._detector_tensor_updated: 

267 self._detector_tensor = detector_tensor(x=self.x, y=self.y) 

268 self._detector_tensor_updated = True 

269 return self._detector_tensor 

270 

271 def unit_vector_along_arm(self, arm): 

272 """ 

273 Calculate the unit vector pointing along the specified arm in cartesian Earth-based coordinates. 

274 

275 See Eqs. B14-B17 in arXiv:gr-qc/0008066 

276 

277 Parameters 

278 ========== 

279 arm: str 

280 'x' or 'y' (arm of the detector) 

281 

282 Returns 

283 ======= 

284 array_like: 3D unit vector along arm in cartesian Earth-based coordinates 

285 

286 Raises 

287 ====== 

288 ValueError: If arm is neither 'x' nor 'y' 

289 

290 """ 

291 if arm == 'x': 

292 return calculate_arm( 

293 arm_tilt=self._xarm_tilt, 

294 arm_azimuth=self._xarm_azimuth, 

295 longitude=self._longitude, 

296 latitude=self._latitude 

297 ) 

298 elif arm == 'y': 

299 return calculate_arm( 

300 arm_tilt=self._yarm_tilt, 

301 arm_azimuth=self._yarm_azimuth, 

302 longitude=self._longitude, 

303 latitude=self._latitude 

304 ) 

305 else: 

306 raise ValueError("Arm must either be 'x' or 'y'.")