Skip to content

Commit

Permalink
add high frequency sky location dependent response for long detectors (
Browse files Browse the repository at this point in the history
…gwastro#4377)

* reset commit, add in high freqeuncy response to detector

* update

* fixes
  • Loading branch information
ahnitz authored and Bhooshan Gadre committed Jul 11, 2023
1 parent db0c986 commit 73f37bb
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 44 deletions.
2 changes: 1 addition & 1 deletion bin/hwinj/pycbc_generate_hwinj_from_xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ parser.add_argument('--sample-rate', type=int, required=True,
parser.add_argument("--tag", type=str, default='hwinjcbcsimid',
help="Prefix added to output filenames.")
parser.add_argument('--ifos', nargs='+', default=['H1', 'L1'], required=True,
choices=list(zip(*get_available_detectors()))[0],
choices=get_available_detectors(),
help='List of IFOs to generate injections for.')
# parse command line
opts = parser.parse_args()
Expand Down
4 changes: 2 additions & 2 deletions examples/detector/loc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# along with a longer name. Note that some of these are not physical detectors
# but may be useful for testing or study purposes

for abv, long_name in get_available_detectors():
for abv in get_available_detectors():
d = Detector(abv)

# Note that units are all in radians
print("{} {} Latitude {} Longitude {}".format(long_name, abv,
print("{} Latitude {} Longitude {}".format(abv,
d.latitude,
d.longitude))
133 changes: 94 additions & 39 deletions pycbc/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,14 @@ def gmst_accurate(gps_time):
location=(0, 0)).sidereal_time('mean').rad
return gmst


def get_available_detectors():
""" List the available detectors """
dets = list(_ground_detectors.keys())
for pfx, name in get_available_lal_detectors():
dets += [pfx]
return dets

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',
Expand All @@ -66,10 +72,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
Expand All @@ -94,36 +101,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
Expand Down Expand Up @@ -175,16 +207,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 _ground_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))

Expand Down Expand Up @@ -257,7 +290,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
Expand Down Expand Up @@ -289,48 +324,68 @@ 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()
return fplus, fcross

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()
Expand Down
2 changes: 1 addition & 1 deletion test/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
class TestDetector(unittest.TestCase):
def setUp(self):
self.d = [det.Detector(ifo)
for ifo, name in det.get_available_detectors()]
for ifo in det.get_available_detectors()]

# not distributed sanely, but should provide some good coverage
N = 1000
Expand Down
1 change: 0 additions & 1 deletion test/test_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ def fill_sim_inspiral_row(self, row):
class TestInjection(unittest.TestCase):
def setUp(self):
available_detectors = get_available_detectors()
available_detectors = [a[0] for a in available_detectors]
self.assertTrue('H1' in available_detectors)
self.assertTrue('L1' in available_detectors)
self.assertTrue('V1' in available_detectors)
Expand Down

0 comments on commit 73f37bb

Please sign in to comment.