diff --git a/bilby/gw/detector/geometry.py b/bilby/gw/detector/geometry.py new file mode 100644 index 0000000000000000000000000000000000000000..0bd29e3321288f8e85559198f8016a0ea58183fa --- /dev/null +++ b/bilby/gw/detector/geometry.py @@ -0,0 +1,267 @@ +import numpy as np + +from bilby.gw import utils as gwutils + + +class InterferometerGeometry(object): + def __init__(self, length, latitude, longitude, elevation, xarm_azimuth, yarm_azimuth, + xarm_tilt=0., yarm_tilt=0.): + self._x_updated = False + self._y_updated = False + self._vertex_updated = False + self._detector_tensor_updated = False + + self.length = length + self.latitude = latitude + self.longitude = longitude + self.elevation = elevation + self.xarm_azimuth = xarm_azimuth + self.yarm_azimuth = yarm_azimuth + self.xarm_tilt = xarm_tilt + self.yarm_tilt = yarm_tilt + self._vertex = None + self._x = None + self._y = None + self._detector_tensor = None + + @property + def latitude(self): + """ Saves latitude in rad internally. Updates related quantities if set to a different value. + + Returns + ------- + float: The latitude position of the detector in degree + """ + return self._latitude * 180 / np.pi + + @latitude.setter + def latitude(self, latitude): + self._latitude = latitude * np.pi / 180 + self._x_updated = False + self._y_updated = False + self._vertex_updated = False + + @property + def latitude_radians(self): + """ + Returns + ------- + float: The latitude position of the detector in radians + """ + + return self._latitude + + @property + def longitude(self): + """ Saves longitude in rad internally. Updates related quantities if set to a different value. + + Returns + ------- + float: The longitude position of the detector in degree + """ + return self._longitude * 180 / np.pi + + @longitude.setter + def longitude(self, longitude): + self._longitude = longitude * np.pi / 180 + self._x_updated = False + self._y_updated = False + self._vertex_updated = False + + @property + def longitude_radians(self): + """ + Returns + ------- + float: The latitude position of the detector in radians + """ + + return self._longitude + + @property + def elevation(self): + """ Updates related quantities if set to a different values. + + Returns + ------- + float: The height about the surface in meters + """ + return self._elevation + + @elevation.setter + def elevation(self, elevation): + self._elevation = elevation + self._vertex_updated = False + + @property + def xarm_azimuth(self): + """ Saves the x-arm azimuth in rad internally. Updates related quantities if set to a different values. + + Returns + ------- + float: The x-arm azimuth in degrees. + + """ + return self._xarm_azimuth * 180 / np.pi + + @xarm_azimuth.setter + def xarm_azimuth(self, xarm_azimuth): + self._xarm_azimuth = xarm_azimuth * np.pi / 180 + self._x_updated = False + + @property + def yarm_azimuth(self): + """ Saves the y-arm azimuth in rad internally. Updates related quantities if set to a different values. + + Returns + ------- + float: The y-arm azimuth in degrees. + + """ + return self._yarm_azimuth * 180 / np.pi + + @yarm_azimuth.setter + def yarm_azimuth(self, yarm_azimuth): + self._yarm_azimuth = yarm_azimuth * np.pi / 180 + self._y_updated = False + + @property + def xarm_tilt(self): + """ Updates related quantities if set to a different values. + + Returns + ------- + float: The x-arm tilt in radians. + + """ + return self._xarm_tilt + + @xarm_tilt.setter + def xarm_tilt(self, xarm_tilt): + self._xarm_tilt = xarm_tilt + self._x_updated = False + + @property + def yarm_tilt(self): + """ Updates related quantities if set to a different values. + + Returns + ------- + float: The y-arm tilt in radians. + + """ + return self._yarm_tilt + + @yarm_tilt.setter + def yarm_tilt(self, yarm_tilt): + self._yarm_tilt = yarm_tilt + self._y_updated = False + + @property + def vertex(self): + """ Position of the IFO vertex in geocentric coordinates in meters. + + Is automatically updated if related quantities are modified. + + Returns + ------- + array_like: A 3D array representation of the vertex + """ + if not self._vertex_updated: + self._vertex = gwutils.get_vertex_position_geocentric(self._latitude, self._longitude, + self.elevation) + self._vertex_updated = True + return self._vertex + + @property + def x(self): + """ A unit vector along the x-arm + + Is automatically updated if related quantities are modified. + + Returns + ------- + array_like: A 3D array representation of a unit vector along the x-arm + + """ + if not self._x_updated: + self._x = self.unit_vector_along_arm('x') + self._x_updated = True + self._detector_tensor_updated = False + return self._x + + @property + def y(self): + """ A unit vector along the y-arm + + Is automatically updated if related quantities are modified. + + Returns + ------- + array_like: A 3D array representation of a unit vector along the y-arm + + """ + if not self._y_updated: + self._y = self.unit_vector_along_arm('y') + self._y_updated = True + self._detector_tensor_updated = False + return self._y + + @property + def detector_tensor(self): + """ + Calculate the detector tensor from the unit vectors along each arm of the detector. + + See Eq. B6 of arXiv:gr-qc/0008066 + + Is automatically updated if related quantities are modified. + + Returns + ------- + array_like: A 3x3 array representation of the detector tensor + + """ + if not self._x_updated or not self._y_updated: + _, _ = self.x, self.y # noqa + if not self._detector_tensor_updated: + self._detector_tensor = 0.5 * (np.einsum('i,j->ij', self.x, self.x) - np.einsum('i,j->ij', self.y, self.y)) + self._detector_tensor_updated = True + return self._detector_tensor + + def unit_vector_along_arm(self, arm): + """ + Calculate the unit vector pointing along the specified arm in cartesian Earth-based coordinates. + + See Eqs. B14-B17 in arXiv:gr-qc/0008066 + + Parameters + ------- + arm: str + 'x' or 'y' (arm of the detector) + + Returns + ------- + array_like: 3D unit vector along arm in cartesian Earth-based coordinates + + Raises + ------- + ValueError: If arm is neither 'x' nor 'y' + + """ + if arm == 'x': + return self._calculate_arm(self._xarm_tilt, self._xarm_azimuth) + elif arm == 'y': + return self._calculate_arm(self._yarm_tilt, self._yarm_azimuth) + else: + raise ValueError("Arm must either be 'x' or 'y'.") + + def _calculate_arm(self, arm_tilt, arm_azimuth): + e_long = np.array([-np.sin(self._longitude), np.cos(self._longitude), 0]) + e_lat = np.array([-np.sin(self._latitude) * np.cos(self._longitude), + -np.sin(self._latitude) * np.sin(self._longitude), np.cos(self._latitude)]) + e_h = np.array([np.cos(self._latitude) * np.cos(self._longitude), + np.cos(self._latitude) * np.sin(self._longitude), np.sin(self._latitude)]) + + return (np.cos(arm_tilt) * np.cos(arm_azimuth) * e_long + + np.cos(arm_tilt) * np.sin(arm_azimuth) * e_lat + + np.sin(arm_tilt) * e_h) diff --git a/bilby/gw/detector/interferometer.py b/bilby/gw/detector/interferometer.py index f8e524ece71e933d2ec5777509dba0d6dc3e0f9b..c88b12db7d0f52d79f0b33a9c02f97152c609106 100644 --- a/bilby/gw/detector/interferometer.py +++ b/bilby/gw/detector/interferometer.py @@ -8,6 +8,7 @@ from bilby.core import utils from bilby.core.utils import logger from bilby.gw import utils as gwutils from bilby.gw.calibration import Recalibrate +from bilby.gw.detector.geometry import InterferometerGeometry from .strain_data import InterferometerStrainData try: @@ -49,270 +50,6 @@ class _GenericInterferometerProperty(object): setattr(getattr(instance, self.container_instance_name), self.property_name, value) -class InterferometerGeometry(object): - def __init__(self, length, latitude, longitude, elevation, xarm_azimuth, yarm_azimuth, - xarm_tilt=0., yarm_tilt=0.): - self._x_updated = False - self._y_updated = False - self._vertex_updated = False - self._detector_tensor_updated = False - - self.length = length - self.latitude = latitude - self.longitude = longitude - self.elevation = elevation - self.xarm_azimuth = xarm_azimuth - self.yarm_azimuth = yarm_azimuth - self.xarm_tilt = xarm_tilt - self.yarm_tilt = yarm_tilt - self._vertex = None - self._x = None - self._y = None - self._detector_tensor = None - - @property - def latitude(self): - """ Saves latitude in rad internally. Updates related quantities if set to a different value. - - Returns - ------- - float: The latitude position of the detector in degree - """ - return self._latitude * 180 / np.pi - - @latitude.setter - def latitude(self, latitude): - self._latitude = latitude * np.pi / 180 - self._x_updated = False - self._y_updated = False - self._vertex_updated = False - - @property - def latitude_radians(self): - """ - Returns - ------- - float: The latitude position of the detector in radians - """ - - return self._latitude - - @property - def longitude(self): - """ Saves longitude in rad internally. Updates related quantities if set to a different value. - - Returns - ------- - float: The longitude position of the detector in degree - """ - return self._longitude * 180 / np.pi - - @longitude.setter - def longitude(self, longitude): - self._longitude = longitude * np.pi / 180 - self._x_updated = False - self._y_updated = False - self._vertex_updated = False - - @property - def longitude_radians(self): - """ - Returns - ------- - float: The latitude position of the detector in radians - """ - - return self._longitude - - @property - def elevation(self): - """ Updates related quantities if set to a different values. - - Returns - ------- - float: The height about the surface in meters - """ - return self._elevation - - @elevation.setter - def elevation(self, elevation): - self._elevation = elevation - self._vertex_updated = False - - @property - def xarm_azimuth(self): - """ Saves the x-arm azimuth in rad internally. Updates related quantities if set to a different values. - - Returns - ------- - float: The x-arm azimuth in degrees. - - """ - return self._xarm_azimuth * 180 / np.pi - - @xarm_azimuth.setter - def xarm_azimuth(self, xarm_azimuth): - self._xarm_azimuth = xarm_azimuth * np.pi / 180 - self._x_updated = False - - @property - def yarm_azimuth(self): - """ Saves the y-arm azimuth in rad internally. Updates related quantities if set to a different values. - - Returns - ------- - float: The y-arm azimuth in degrees. - - """ - return self._yarm_azimuth * 180 / np.pi - - @yarm_azimuth.setter - def yarm_azimuth(self, yarm_azimuth): - self._yarm_azimuth = yarm_azimuth * np.pi / 180 - self._y_updated = False - - @property - def xarm_tilt(self): - """ Updates related quantities if set to a different values. - - Returns - ------- - float: The x-arm tilt in radians. - - """ - return self._xarm_tilt - - @xarm_tilt.setter - def xarm_tilt(self, xarm_tilt): - self._xarm_tilt = xarm_tilt - self._x_updated = False - - @property - def yarm_tilt(self): - """ Updates related quantities if set to a different values. - - Returns - ------- - float: The y-arm tilt in radians. - - """ - return self._yarm_tilt - - @yarm_tilt.setter - def yarm_tilt(self, yarm_tilt): - self._yarm_tilt = yarm_tilt - self._y_updated = False - - @property - def vertex(self): - """ Position of the IFO vertex in geocentric coordinates in meters. - - Is automatically updated if related quantities are modified. - - Returns - ------- - array_like: A 3D array representation of the vertex - """ - if not self._vertex_updated: - self._vertex = gwutils.get_vertex_position_geocentric(self._latitude, self._longitude, - self.elevation) - self._vertex_updated = True - return self._vertex - - @property - def x(self): - """ A unit vector along the x-arm - - Is automatically updated if related quantities are modified. - - Returns - ------- - array_like: A 3D array representation of a unit vector along the x-arm - - """ - if not self._x_updated: - self._x = self.unit_vector_along_arm('x') - self._x_updated = True - self._detector_tensor_updated = False - return self._x - - @property - def y(self): - """ A unit vector along the y-arm - - Is automatically updated if related quantities are modified. - - Returns - ------- - array_like: A 3D array representation of a unit vector along the y-arm - - """ - if not self._y_updated: - self._y = self.unit_vector_along_arm('y') - self._y_updated = True - self._detector_tensor_updated = False - return self._y - - @property - def detector_tensor(self): - """ - Calculate the detector tensor from the unit vectors along each arm of the detector. - - See Eq. B6 of arXiv:gr-qc/0008066 - - Is automatically updated if related quantities are modified. - - Returns - ------- - array_like: A 3x3 array representation of the detector tensor - - """ - if not self._x_updated or not self._y_updated: - _, _ = self.x, self.y # noqa - if not self._detector_tensor_updated: - self._detector_tensor = 0.5 * (np.einsum('i,j->ij', self.x, self.x) - np.einsum('i,j->ij', self.y, self.y)) - self._detector_tensor_updated = True - return self._detector_tensor - - def unit_vector_along_arm(self, arm): - """ - Calculate the unit vector pointing along the specified arm in cartesian Earth-based coordinates. - - See Eqs. B14-B17 in arXiv:gr-qc/0008066 - - Parameters - ------- - arm: str - 'x' or 'y' (arm of the detector) - - Returns - ------- - array_like: 3D unit vector along arm in cartesian Earth-based coordinates - - Raises - ------- - ValueError: If arm is neither 'x' nor 'y' - - """ - if arm == 'x': - return self._calculate_arm(self._xarm_tilt, self._xarm_azimuth) - elif arm == 'y': - return self._calculate_arm(self._yarm_tilt, self._yarm_azimuth) - else: - raise ValueError("Arm must either be 'x' or 'y'.") - - def _calculate_arm(self, arm_tilt, arm_azimuth): - e_long = np.array([-np.sin(self._longitude), np.cos(self._longitude), 0]) - e_lat = np.array([-np.sin(self._latitude) * np.cos(self._longitude), - -np.sin(self._latitude) * np.sin(self._longitude), np.cos(self._latitude)]) - e_h = np.array([np.cos(self._latitude) * np.cos(self._longitude), - np.cos(self._latitude) * np.sin(self._longitude), np.sin(self._latitude)]) - - return (np.cos(arm_tilt) * np.cos(arm_azimuth) * e_long + - np.cos(arm_tilt) * np.sin(arm_azimuth) * e_lat + - np.sin(arm_tilt) * e_h) - - class Interferometer(object): """Class for the Interferometer """