From b670e4a0afba18edfd930d6f188317cad8fd55b9 Mon Sep 17 00:00:00 2001 From: Alexander Harvey Nitz Date: Fri, 30 Jun 2023 14:08:37 -0400 Subject: [PATCH] reset commit, add in high freqeuncy response to detector --- pycbc/detector.py | 130 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 39 deletions(-) diff --git a/pycbc/detector.py b/pycbc/detector.py index 35c3dd3d15c..5cead045dc3 100644 --- a/pycbc/detector.py +++ b/pycbc/detector.py @@ -48,8 +48,11 @@ def gmst_accurate(gps_time): location=(0, 0)).sidereal_time('mean').rad return gmst - def get_available_detectors(): + """ List the available detectors """ + return list(_ground_detectors.keys()) + +def get_available_lal_detectors(): """Return list of detectors known in the currently sourced lalsuite. This function will query lalsuite about which detectors are known to lalsuite. Detectors are identified by a two character string e.g. 'K1', @@ -66,10 +69,11 @@ def get_available_detectors(): known_names = [ld[k.replace('PREFIX', 'NAME')] for k in known_lal_names] return list(zip(known_prefixes, known_names)) +_ground_detectors = {} -_custom_ground_detectors = {} def add_detector_on_earth(name, longitude, latitude, - yangle=0, xangle=None, height=0): + yangle=0, xangle=None, height=0, + xlength=4000, ylength=4000): """ Add a new detector on the earth Parameters @@ -94,36 +98,61 @@ def add_detector_on_earth(name, longitude, latitude, # assume right angle detector if no separate xarm direction given xangle = yangle + np.pi / 2.0 + # Rotation matrix to move detector to correct orientation + rm1 = rotation_matrix(longitude * units.rad, 'z') + rm2 = rotation_matrix((np.pi / 2.0 - latitude) * units.rad, 'y') + rm = np.matmul(rm2, rm1) + # Calculate response in earth centered coordinates # by rotation of response in coordinates aligned # with the detector arms - a, b = cos(2*xangle), sin(2*xangle) - xresp = np.array([[-a, b, 0], [b, a, 0], [0, 0, 0]]) - a, b = cos(2*yangle), sin(2*yangle) - yresp = np.array([[-a, b, 0], [b, a, 0], [0, 0, 0]]) - resp = (yresp - xresp) / 4.0 + resps = [] + vecs = [] + for angle in [yangle, xangle]: + a, b = cos(2 * angle), sin(2 * angle) + resp = np.array([[-a, b, 0], [b, a, 0], [0, 0, 0]]) - rm1 = rotation_matrix(longitude * units.rad, 'z') - rm2 = rotation_matrix((np.pi / 2.0 - latitude) * units.rad, 'y') - rm = np.matmul(rm2, rm1) + # apply rotation + resp = np.matmul(resp, rm) + resp = np.matmul(rm.T, resp) / 4.0 + resps.append(resp) - resp = np.matmul(resp, rm) - resp = np.matmul(rm.T, resp) + vec = np.matmul(rm.T, np.array([-np.cos(angle), np.sin(angle), 0])) + vecs.append(vec) + full_resp = (resps[0] - resps[1]) loc = coordinates.EarthLocation.from_geodetic(longitude * units.rad, latitude * units.rad, height=height*units.meter) - loc = np.array([loc.x.value, - loc.y.value, - loc.z.value]) - _custom_ground_detectors[name] = {'location': loc, - 'response': resp, - 'yangle': yangle, - 'xangle': xangle, - 'height': height, - 'xaltitude': 0.0, - 'yaltitude': 0.0, - } + loc = np.array([loc.x.value, loc.y.value, loc.z.value]) + _ground_detectors[name] = {'location': loc, + 'response': full_resp, + 'xresp': resps[1], + 'yresp': resps[0], + 'xvec': vecs[1], + 'yvec': vecs[0], + 'yangle': yangle, + 'xangle': xangle, + 'height': height, + 'xaltitude': 0.0, + 'yaltitude': 0.0, + 'ylength': ylength, + 'xlength': xlength, + } + +# Notation matches +# Eq 4 of https://link.aps.org/accepted/10.1103/PhysRevD.96.084004 +def single_arm_frequency_response(f, n, arm_length): + """ The relative amplitude factor of the arm response due to + signal delay. This is relevant where the long-wavelength + approximation no longer applies) + """ + n = np.clip(n, -0.999, 0.999) + phase = arm_length / constants.c.value * 2.0j * np.pi * f + a = 1.0 / 4.0 / phase + b = (1 - np.exp(-phase * (1 - n))) / (1 - n) + c = np.exp(-2.0 * phase) * (1 - np.exp(phase * (1 + n))) / (1 + n) + return a * (b - c) * 2.0 # We'll make this relative to the static resp def load_detector_config(config_files): """ Add custom detectors from a configuration file @@ -175,16 +204,17 @@ def __init__(self, detector_name, reference_time=1126259462.0): using a slower but higher precision method. """ self.name = str(detector_name) - - if detector_name in [pfx for pfx, name in get_available_detectors()]: + + lal_detectors = [pfx for pfx, name in get_available_lal_detectors()] + if detector_name in get_available_detectors(): + self.info = _ground_detectors[detector_name] + self.response = self.info['response'] + self.location = self.info['location'] + elif detector_name in lal_detectors: lalsim = pycbc.libutils.import_optional('lalsimulation') self._lal = lalsim.DetectorPrefixToLALDetector(self.name) self.response = self._lal.response self.location = self._lal.location - elif detector_name in _custom_ground_detectors: - self.info = _custom_ground_detectors[detector_name] - self.response = self.info['response'] - self.location = self.info['location'] else: raise ValueError("Unkown detector {}".format(detector_name)) @@ -257,7 +287,9 @@ def light_travel_time_to_detector(self, det): d = self.location - det.location return float(d.dot(d)**0.5 / constants.c.value) - def antenna_pattern(self, right_ascension, declination, polarization, t_gps, polarization_type='tensor'): + def antenna_pattern(self, right_ascension, declination, polarization, t_gps, + frequency=0, + polarization_type='tensor'): """Return the detector response. Parameters @@ -289,31 +321,50 @@ def antenna_pattern(self, right_ascension, declination, polarization, t_gps, pol cospsi = cos(polarization) sinpsi = sin(polarization) + if frequency: + e0 = cosdec * cosgha + e1 = cosdec * -singha + e2 = sin(declination) + nhat = np.array([e0, e1, e2], dtype=object) + + nx = nhat.dot(self.info['xvec']) + ny = nhat.dot(self.info['yvec']) + + rx = single_arm_frequency_response(frequency, nx, + self.info['xlength']) + ry = single_arm_frequency_response(frequency, ny, + self.info['ylength']) + resp = ry * self.info['yresp'] - rx * self.info['xresp'] + ttype = np.complex128 + else: + resp = self.response + ttype = np.float64 + x0 = -cospsi * singha - sinpsi * cosgha * sindec x1 = -cospsi * cosgha + sinpsi * singha * sindec x2 = sinpsi * cosdec x = np.array([x0, x1, x2], dtype=object) - dx = self.response.dot(x) + dx = resp.dot(x) y0 = sinpsi * singha - cospsi * cosgha * sindec y1 = sinpsi * cosgha + cospsi * singha * sindec y2 = cospsi * cosdec y = np.array([y0, y1, y2], dtype=object) - dy = self.response.dot(y) + dy = resp.dot(y) if polarization_type != 'tensor': z0 = -cosdec * cosgha z1 = cosdec * singha z2 = -sindec z = np.array([z0, z1, z2], dtype=object) - dz = self.response.dot(z) + dz = resp.dot(z) if polarization_type == 'tensor': if hasattr(dx, 'shape'): - fplus = (x * dx - y * dy).sum(axis=0).astype(np.float64) - fcross = (x * dy + y * dx).sum(axis=0).astype(np.float64) + fplus = (x * dx - y * dy).sum(axis=0).astype(ttype) + fcross = (x * dy + y * dx).sum(axis=0).astype(ttype) else: fplus = (x * dx - y * dy).sum() fcross = (x * dy + y * dx).sum() @@ -321,16 +372,17 @@ def antenna_pattern(self, right_ascension, declination, polarization, t_gps, pol elif polarization_type == 'vector': if hasattr(dx, 'shape'): - fx = (z * dx + x * dz).sum(axis=0).astype(np.float64) - fy = (z * dy + y * dz).sum(axis=0).astype(np.float64) + fx = (z * dx + x * dz).sum(axis=0).astype(ttype) + fy = (z * dy + y * dz).sum(axis=0).astype(ttype) else: fx = (z * dx + x * dz).sum() fy = (z * dy + y * dz).sum() + return fx, fy elif polarization_type == 'scalar': if hasattr(dx, 'shape'): - fb = (x * dx + y * dy).sum(axis=0).astype(np.float64) + fb = (x * dx + y * dy).sum(axis=0).astype(ttype) fl = (z * dz).sum(axis=0) else: fb = (x * dx + y * dy).sum()