LALSimulation 6.2.0.1-eeff03c
gw.py
Go to the documentation of this file.
1from astropy.coordinates import Angle, SkyCoord
2from astropy.time import Time
3from astropy import units as u
4from typing import Dict, NamedTuple, Union
5from gwpy.timeseries import TimeSeries
6from gwpy.frequencyseries import FrequencySeries
7import lal
8import lalsimulation as lalsim
9import numpy as np
10import warnings
11
13 hp: Union[TimeSeries, FrequencySeries]
14 hc: Union[TimeSeries, FrequencySeries]
15 """
16 GravitationalWavePolarizations class that takes hp,hc GwPy Time/FrequencySeries objects as inputs. Provides method to compute
17 zero noise strain given a detector, sky-position, polarization and time of arrival values.
18 Parameters
19 ----------
20 hp, hc : GwPy `TimeSeries` or `FrequencySeries` objects
21 """
22 def domain(self):
23 if isinstance(self.hp, TimeSeries) and isinstance(self.hc, TimeSeries):
24 return 'time'
25 elif isinstance(self.hp, FrequencySeries) and isinstance(self.hc, FrequencySeries):
26 return 'frequency'
27 else:
28 return 'mixed'
29
30 def strain(self, det, ra, dec, psi, tgps):
31 """
32 Compute the detector strain in zero-noise
33
34 Parameters
35 ----------
36 det : `str`
37 Name of the detector, for eg: 'H1' or 'L1'
38 ra : `~astropy.units.Quantity`
39 Right-ascension of the binary in units of rad
40 dec : `~astropy.units.Quantity`
41 Declination of the binary in units of rad
42 psi : `~astropy.units.Quantity`
43 Polarization in units of rad
44 tgps : `float`, `~astropy.units.Quantity`
45 GPS Time of time-of-arrival of signal. Either as float or in units of s
46
47 Returns
48 -------
49 h : `TimeSeries` or `FrequencySeries` object
50 Waveform recomposed with detector response (Fp*hp + Fc*hc)
51 """
52
53 # Might change it later; for now keeping it as used by ligo.
54 warnings.warn("This code is currently UNREVIEWED, use with caution!!")
55 pos = SkyCoord(ra = ra, dec = dec)
56 psi = Angle(psi)
57 t = Time(tgps, format='gps', scale='utc')
58
59 if isinstance(det, str):
60 det = lalsim.DetectorPrefixToLALDetector(det)
61
62 if self.domain() == 'time':
63 hp = self.hp.to_lal()
64 hc = self.hc.to_lal()
65 hp.epoch += t.gps
66 hc.epoch += t.gps
67 h = lalsim.SimDetectorStrainREAL8TimeSeries(hp, hc, pos.ra.rad, pos.dec.rad, psi.rad, det)
68 h = TimeSeries.from_lal(h)
69 return h
70
71 elif self.domain() == 'frequency':
72 # WARNING: does not account for Earth rotation or non-LWL effects
73
74 epoch = lal.LIGOTimeGPS(t.gps)
75 dt = lal.TimeDelayFromEarthCenter(det.location, pos.ra.rad, pos.dec.rad, epoch)
76 fp, fc = lal.ComputeDetAMResponse(det.response, pos.ra.rad, pos.dec.rad, psi.rad, lal.GreenwichMeanSiderealTime(epoch))
77
78 # Shouldn't this have a overall time shift due to time-delay from earth center? or does epoch take care of it?
79 # TODO: adress during phase 2 of review
80
81 h = (fp * self.hp + fc * self.hc)
82 h.epoch = Time(t.gps + dt, format='gps', scale='utc')
83 return h
84 else:
85 return ValueError('hp and hc must both be either TimeSeries or FrequencySeries')
86
87 def add_strain(self, data, det, ra, dec, psi, tgps, response=None):
88 """
89 Add strain to some LAL strain as required
90
91 Parameters:
92 data : LAL TimeSeries data
93 Data in which to add detector response strain
94 det : `str`
95 Name of the detector, for eg: 'H1' or 'L1'
96 ra : `~astropy.units.Quantity`
97 Right-ascension of the binary in units of rad
98 dec : `~astropy.units.Quantity`
99 Declination of the binary in units of rad
100 psi : `~astropy.units.Quantity`
101 Polarization in units of rad
102 tgps : `float`, `~astropy.units.Quantity`
103 GPS Time of time-of-arrival of signal. Either as float or in units of s
104 response : `COMPLEX16FrequencySeries`
105 Response function passed to lalsimulation.SimAddInjectionREAL8TimeSeries,
106 transforming strain to detector output units. Use None for unit response.
107
108 Returns
109 -------
110 data : `TimeSeries` object
111 Detector response injected in strain data
112 """
113 warnings.warn("This code is currently UNREVIEWED, use with caution!")
114 h = self.strain(det, ra, dec, psi, tgps)
115 # Adding condition to convert FrequencySeries to TimeSeries if strain is in freq domain
116 if isinstance(h, FrequencySeries):
117 h = h.ifft()
118 h = h.to_lal()
119 data = data.to_lal()
120 lalsim.SimAddInjectionREAL8TimeSeries(data, h, response)
121 data = TimeSeries.from_lal(data)
122 return data
123
124
125class ModePair(NamedTuple):
126 """
127 Returns a named tuple given the l,m values
128 """
129 l: int
130 m: int
131
132
134 """
135 Class to return spin `s` weighted spherical harmonic given ModePair
136 """
137
138 def __new__(cls, s, l, m):
139 new = super().__new__(cls, l, m)
140 if new.l < abs(s):
141 raise ValueError('Require l >= |s|')
142 if abs(new.m) > new.l:
143 raise ValueError('Require |m| <= l')
144 new.s = s
145 return new
146
147 def __call__(self, theta, phi):
148 theta = Angle(theta, u.rad)
149 phi = Angle(phi, u.rad)
150 return lal.SpinWeightedSphericalHarmonic(theta.rad, phi.rad, self.s, self.l, self.m)
151
152
154 """
155 Class to return spin `-2` weighted spherical harmonic given ModePair
156 """
157 def __new__(cls, l, m):
158 return super().__new__(cls, -2, l, m)
159
160
161class GravitationalWaveModes(Dict[SpinWeightedSphericalHarmonicMode, Union[TimeSeries, FrequencySeries]]):
162 """
163 Class for gravitational wave modes which returns the waveform recomposed with -2 spin weighted spherical harmonics
164 given a (theta, phi) value
165 """
166 def __call__(self, theta, phi):
167 """
168 Return plus and cross polarizations from the gravitational wave modes.
169
170 Parameters
171 ----------
172 theta : `~astropy.units.Quantity`
173 Theta angle (inclination, theta_jn) in units of rad.
174 phi : `~astropy.units.Quantity`
175 Phi angle (inclination, theta_jn) in units of rad.
176 """
177 if isinstance(theta, float) and isinstance(phi, float):
178 raise TypeError("Theta and phi should be in units of astropy.units.rad, not float")
179 elif theta.unit.is_equivalent(u.rad) and phi.unit.is_equivalent(u.rad):
180 pass
181 else:
182 raise TypeError("Theta and phi should be in units of astropy.units.rad")
183
184
185 hpc = 0.
186 hp = 0.
187 hc = 0.
188 for key, hlm in self.items():
189 if isinstance(key, str):
190 pass
191 elif isinstance(hlm, TimeSeries):
192 mode = TensorSphericalHarmonicMode(int(key[0]), int(key[1]))
193 hpc += mode(theta, phi) * hlm
194 hp = hpc.real
195 hc = -hpc.imag
196
197 elif isinstance(hlm, FrequencySeries):
198 # Recombination of freq domain hp/hc from here :
199 # https://git.ligo.org/lscsoft/lalsuite/-/blob/master/lalsimulation/lib/LALSimInspiral.c#L3477
200 l, m = int(key[0]), int(key[1])
201 mode = TensorSphericalHarmonicMode(l, m)
202 ylm = mode(theta, phi)
203 hp += 0.5*( ylm * hlm + np.conj(ylm) * np.conj(hlm)[::-1])
204 hc += 1j*0.5*( ylm * hlm - np.conj(ylm)* np.conj(hlm)[::-1])
205
206 return GravitationalWavePolarizations(hp, hc)
Class for gravitational wave modes which returns the waveform recomposed with -2 spin weighted spheri...
Definition: gw.py:165
def __call__(self, theta, phi)
Return plus and cross polarizations from the gravitational wave modes.
Definition: gw.py:176
def strain(self, det, ra, dec, psi, tgps)
Compute the detector strain in zero-noise.
Definition: gw.py:51
def add_strain(self, data, det, ra, dec, psi, tgps, response=None)
Add strain to some LAL strain as required.
Definition: gw.py:112
Returns a named tuple given the l,m values.
Definition: gw.py:128
Class to return spin s weighted spherical harmonic given ModePair.
Definition: gw.py:136
Class to return spin -2 weighted spherical harmonic given ModePair.
Definition: gw.py:156