Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALPulsar 7.1.1.1-5e288d3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
simulateHeterodynedCW.py
Go to the documentation of this file.
1# Copyright (C) 2019 Matthew Pitkin
2#
3# This program is free software; you can redistribute it and/or modify it
4# under the terms of the GNU General Public License as published by the
5# Free Software Foundation; either version 2 of the License, or (at your
6# option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11# Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17## \defgroup lalpulsar_py_simulateHeterodynedCW SimulateHeterodynedCW
18## \ingroup lalpulsar_python
19"""
20The module provides the HeterodynedCWSimulator() class for simulating a
21signal from a continuous wave source after application of a heterodyned as
22described in Equations 7 and 8 of @cite Pitkin2017 . An example usage to
23generate the complex heterodyned signal time series is:
24
25~~~
26from lalpulsar.simulateHeterodynedCW import HeterodynedCWSimulator
27from lalpulsar.PulsarParametersWrapper import PulsarParametersPy
28import lal
29import numpy as np
30
31# set the pulsar parameters
32par = PulsarParametersPy()
33par['RAJ'] = lal.TranslateHMStoRAD('01:23:34.5')
34par['DECJ'] = lal.TranslateDMStoRAD('-45:01:23.4')
35par['F'] = [123.456789, -9.87654321e-12] # frequency and first derivative
36pepoch = lal.TranslateStringMJDTTtoGPS('58000') # frequency epoch
37par['PEPOCH'] = pepoch.gpsSeconds + 1e-9*pepoch.gpsNanoSeconds
38par['H0'] = 5.6e-26 # GW amplitude
39par['COSIOTA'] = -0.2 # cosine of inclination angle
40par['PSI'] = 0.4 # polarization angle (rads)
41par['PHI0'] = 2.3 # initial phase (rads)
42
43# set the GPS times of the data
44times = np.arange(1000000000.0, 1000086400., 3600)
45
46# set the detector
47det = 'H1' # the LIGO Hanford Observatory
48
49# create the HeterodynedCWSimulator object
50het = HeterodynedCWSimulator(par, det, times=times)
51
52# get the model complex strain time series
53model = het.model(usephase=True)
54~~~
55
56An example of getting the time series for a signal that has phase parameters
57that are not identical to the heterodyned parameters would be:
58
59~~~
60from lalpulsar.simulateHeterodynedCW import HeterodynedCWSimulator
61from lalpulsar.PulsarParametersWrapper import PulsarParametersPy
62import lal
63import numpy as np
64
65# set the "heterodyne" pulsar parameters
67par['RAJ'] = lal.TranslateHMStoRAD('01:23:34.6')
68par['DECJ'] = lal.TranslateDMStoRAD('-45:01:23.5')
69par['F'] = [123.4567, -9.876e-12] # frequency and first derivative
70pepoch = lal.TranslateStringMJDTTtoGPS('58000') # frequency epoch
71par['PEPOCH'] = pepoch.gpsSeconds + 1e-9*pepoch.gpsNanoSeconds
72
73# set the times
74times = np.arange(1000000000., 1000000600., 60)
75
76# set the detector
77det = 'H1' # the LIGO Hanford Observatory
78
79# create the HeterodynedCWSimulator object
80het = HeterodynedCWSimulator(par, det, times=times)
81
82# set the updated parameters
83parupdate = PulsarParametersPy()
84parupdate['RAJ'] = lal.TranslateHMStoRAD('01:23:34.5')
85parupdate['DECJ'] = lal.TranslateDMStoRAD('-45:01:23.4')
86parupdate['F'] = [123.456789, -9.87654321e-12] # frequency and first derivative
87pepoch = lal.TranslateStringMJDTTtoGPS('58000') # frequency epoch
88parupdate['PEPOCH'] = pepoch.gpsSeconds + 1e-9*pepoch.gpsNanoSeconds
89parupdate['H0'] = 5.6e-26 # GW amplitude
90parupdate['COSIOTA'] = -0.2 # cosine of inclination angle
91parupdate['PSI'] = 0.4 # polarization angle (rads)
92parupdate['PHI0'] = 2.3 # initial phase (rads)
93
94# get the model complex strain time series
95model = het.model(parupdate, usephase=True, updateSSB=True)
96~~~
97
98"""
99## @{
100
101from __future__ import division, print_function
102
103import numpy as np
104
105try:
106 import lal
107except ImportError:
108 raise ImportError("SWIG wrappings of LAL cannot be imported")
109
110try:
111 import lalpulsar
112except ImportError:
113 raise ImportError("SWIG wrappings of LALPulsar cannot be imported")
114
115try:
116 from .PulsarParametersWrapper import PulsarParametersPy
117except ImportError:
118 raise ImportError("Cannot import PulsarParametersPy class")
119
120from . import git_version
121
122
123__author__ = "Matthew Pitkin <matthew.pitkin@ligo.org>"
124__version__ = git_version.id
125__date__ = git_version.date
126
127
128DOWNLOAD_URL = "https://git.ligo.org/lscsoft/lalsuite/raw/master/lalpulsar/lib/{}"
129
130
132 def __init__(
133 self,
134 par,
135 det,
136 times=None,
137 earth_ephem=None,
138 sun_ephem=None,
139 time_corr=None,
140 ephem="DE405",
141 units="TCB",
142 t0=None,
143 dt=None,
144 ):
145 """
146 A class to simulate strain data for a continuous gravitational-wave
147 signal after the data has been heterodyned, i.e., after multiplying
148 the data by a complex phase vector. This uses the Equations 7 and 8
149 from @cite Pitkin2017 accessed via the XLALHeterodynedPulsarGetModel()
150 function.
151
152 @param par: a TEMPO-style text file, or a PulsarParametersPy()
153 structure, containing the parameters for the source, in particular
154 the phase parameters at which the data is "heterodyned".
155 @param det: the name of a detector.
156 @param times: an array of GPS times at which to calculate the
157 heterodyned strain.
158 @param t0: a time epoch in GPS seconds at which to calculate the
159 detector response function. If not given and @b times is set,
160 then the first value of @b times will be used.
161 @param dt: the time steps (in seconds) in the data over which to
162 average the detector response. If not given and @b times is set,
163 the the time difference between the first two values in @b times
164 will be used.
165 @param earth_ephem: a file containing the Earth ephemeris information.
166 If not set then a default file will be used.
167 @param sun_ephem: a file containing the Earth ephemeris information.
168 If not set then a default file will be used.
169 @param time_corr: a file containing information on the time system
170 corrections for, e.g., the TCB or TDB system. If not set then
171 a default file will be used.
172 @param ephem: The solar system ephemeris system to use for the Earth
173 and Sun ephemeris, i.e., @c 'DE200', @c 'DE405', @c 'DE421', or
174 @c 'DE430'. By default the @c 'EPHEM' value from the supplied
175 @b par will be used, but if not found, and if this value is not
176 set, it will default to @c 'DE405'.
177 @param units: The time system used, i.e., @c 'TDB' or @c 'TCB'. By default
178 the @c 'UNITS' value from the @b par will be used, but if not
179 found, and if this value is not set, it will (like TEMPO2) default
180 to @c 'TCB'.
181 """
182
183 self.__hetpar = self._read_par(par)
185 self.timestimestimes = times
186
187 # set default ephemeris strings
188 self.__earthstr = "earth00-40-{}.dat.gz"
189 self.__sunstr = "sun00-40-{}.dat.gz"
190 self.__timecorrstr = "{}_2000-2040.dat.gz"
191
192 # mapping between time units and time correction file prefix
193 self.__units_map = {"TCB": "te405", "TDB": "tdb"}
194
195 self.ephemephemephem = ephem
196 self.unitsunitsunits = units
197
198 # initialise the solar system ephemeris files
199 self.__edat, self.__tdat = self._initialise_ephemeris(
200 earth_ephem, sun_ephem, time_corr
201 )
202
203 # set the "heterodyne" SSB time delay
204 if self.timestimestimes is not None:
205 self.__hetSSBdelay = lalpulsar.HeterodynedPulsarGetSSBDelay(
207 self.gpstimes,
209 self.__edat,
210 self.__tdat,
211 self.__units_type,
212 )
213 else:
214 self.__hetSSBdelay = None
215
216 # set the "heterodyne" BSB time delay
217 if self.timestimestimes is not None and self.hetpar["BINARY"] is not None:
218 self.__hetBSBdelay = lalpulsar.HeterodynedPulsarGetBSBDelay(
220 self.gpstimes,
221 self.__hetSSBdelay,
222 self.__edat,
223 )
224 else:
225 self.__hetBSBdelay = None
226
227 # set the "heterodyne" glitch phase
228 if self.timestimestimes is not None and self.hetpar["GLEP"] is not None:
229 self.__hetglitchphase = lalpulsar.HeterodynedPulsarGetGlitchPhase(
231 self.gpstimes,
232 self.__hetSSBdelay,
233 self.__hetBSBdelay,
234 )
235 else:
236 self.__hetglitchphase = None
237
238 # set the "heterodyne" FITWAVES phase
239 if (
240 self.timestimestimes is not None
241 and self.hetpar["WAVESIN"] is not None
242 and self.hetpar["WAVECOS"] is not None
243 ):
244 self.__hetfitwavesphase = lalpulsar.HeterodynedPulsarGetFITWAVESPhase(
246 self.gpstimes,
247 self.__hetSSBdelay,
248 self.hetpar["F0"],
249 )
250 else:
251 self.__hetfitwavesphase = None
252
253 # set the response function
254 if self.timestimestimes is None and t0 is None:
255 raise ValueError(
256 "Must supply either 'times' or 't0' to calculate "
257 "the response function"
258 )
259 else:
260 self.__t0 = t0 if t0 is not None else self.timestimestimes[0]
261
262 if dt is None and self.timestimestimes is None:
263 raise ValueError(
264 "Must supply either 'times' or 'dt' to calculate "
265 "the response function"
266 )
267 else:
268 if self.timestimestimes is not None and dt is None:
269 if len(self.timestimestimes) == 1:
270 raise ValueError("Must supply a 'dt' value")
271 else:
272 self.__dt = self.timestimestimes[1] - self.timestimestimes[0]
273 else:
274 self.__dt = dt
275
276 ra = self.hetpar["RA"] if self.hetpar["RAJ"] is None else self.hetpar["RAJ"]
277 dec = self.hetpar["DEC"] if self.hetpar["DECJ"] is None else self.hetpar["DECJ"]
278 if ra is None or dec is None:
279 raise ValueError("Right ascension and/or declination have not " "been set!")
280
281 self.__resp = lalpulsar.DetResponseLookupTable(
282 self.__t0, self.detectordetectordetector, ra, dec, 2880, self.__dt
283 )
284
285 @property
286 def hetpar(self):
287 return self.__hetpar
288
289 @property
290 def detector(self):
291 return self.__detector
292
293 @detector.setter
294 def detector(self, det):
295 if isinstance(det, lal.Detector):
296 # value is already a lal.Detector
297 self.__detector = det
298 else:
299 if not isinstance(det, str):
300 raise TypeError("Detector name must be a string")
301 else:
302 try:
303 self.__detector = lalpulsar.GetSiteInfo(det)
304 except RuntimeError:
305 raise ValueError(
306 "Detector '{}' was not a valid detector " "name.".format(det)
307 )
308
309 self.__detector_name = self.__detector.frDetector.name
310
311 @property
312 def resp(self):
313 """
314 Return the response function look-up table.
315 """
316
317 return self.__resp
318
319 @property
320 def times(self):
321 return self.__times
322
323 @property
324 def gpstimes(self):
325 return self.__gpstimes
326
327 @times.setter
328 def times(self, times):
329 """
330 Set an array of times, and also a @b LIGOTimeGPSVector() containing the
331 times.
332 """
333
334 if times is None:
335 self.__times = None
336 self.__gpstimes = None
337 return
338 elif isinstance(times, lal.LIGOTimeGPS):
339 self.__times = np.array(
340 [times.gpsSeconds + 1e-9 * times.gpsNanoSeconds], dtype="float64"
341 )
342 self.__gpstimes = lalpulsar.CreateTimestampVector(1)
343 self.__gpstimes.data[0] = times
344 return
345 elif isinstance(times, lalpulsar.LIGOTimeGPSVector):
346 self.__gpstimes = times
347 self.__times = np.zeros(len(times.data), dtype="float64")
348 for i, gpstime in enumerate(times.data):
349 self.__times[i] = (
350 times.data[i].gpsSeconds + 1e-9 * times.data[i].gpsNanoSeconds
351 )
352 return
353 elif isinstance(times, (int, float)):
354 self.__times = np.array([times], dtype="float64")
355 elif isinstance(times, (list, np.ndarray)):
356 self.__times = np.array(times, dtype="float64")
357 else:
358 raise TypeError("Unknown data type for times")
359
360 self.__gpstimes = lalpulsar.CreateTimestampVector(len(self.__times))
361 for i, time in enumerate(self.__times):
362 self.__gpstimes.data[i] = lal.LIGOTimeGPS(time)
363
364 @property
365 def ephem(self):
366 return self.__ephem
367
368 @ephem.setter
369 def ephem(self, ephem):
370 """
371 Set the heterodyne solar system ephemeris version. This will attempt to
372 use the value set in the heterodyne source parameters, but otherwise
373 defaults to DE405.
374 """
375
376 if self.hetpar["EPHEM"] is not None:
377 self.__ephem = self.hetpar["EPHEM"]
378 else:
379 self.__ephem = "DE405" if ephem is None else ephem
380
381 @property
382 def units(self):
383 return self.__units
384
385 @units.setter
386 def units(self, units):
387 """
388 Set the time system units, i.e., either 'TDB' or 'TCB'. This will
389 attempt to use the value set in the heterodyne source parameters, but
390 otherwise defaults to 'TCB'.
391 """
392
393 if self.hetpar["UNITS"] is not None:
394 self.__units = self.hetpar["UNITS"]
395 else:
396 self.__units = "TCB" if units is None else units
397
398 if self.__units not in ["TCB", "TDB"]:
399 raise ValueError(
400 "Unknown time system '{}' has been " "given.".format(self.__units)
401 )
402
403 if self.__units == "TCB":
404 self.__units_type = lalpulsar.TIMECORRECTION_TCB
405 else:
406 self.__units_type = lalpulsar.TIMECORRECTION_TDB
407
408 def model(
409 self,
410 newpar=None,
411 updateSSB=False,
412 updateBSB=False,
413 updateglphase=False,
414 updatefitwaves=False,
415 freqfactor=2.0,
416 usephase=False,
417 roq=False,
418 ):
419 """
420 Compute the heterodyned strain model using
422
423 @param newpar: A text parameter file, or PulsarParameterPy() object,
424 containing a set of parameter at which to calculate the strain
425 model. If this is @c None then the "heterodyne" parameters are used.
426 @param updateSSB: set to @c True to update the solar system barycentring
427 time delays compared to those used in heterodyning, i.e., if the
428 @b newpar contains updated positional parameters.
429 @param updateBSB: set to @c True to update the binary system barycentring
430 time delays compared to those used in heterodying, i.e., if the
431 @b newpar contains updated binary system parameters
432 @param updateglphase: set to @c True to update the pulsar glitch
433 evolution compared to that used in heterodyning, i.e., if the @b newpar
434 contains updated glitch parameters.
435 @param updatefitwaves: set to @c True to update the pulsar FITWAVES phase
436 evolution (used to model strong red timing noise) compared to that
437 used in heterodyning.
438 @param freqfactor: the factor by which the frequency evolution is
439 multiplied for the source model. This defaults to 2 for emission
440 from the \f$l=m=2\f$ quadrupole mode.
441 @param usephase: set to @c True if the model is to include the phase
442 evolution, i.e., if phase parameters are being updated, otherwise
443 only two (six for non-GR sources) values giving the amplitides
444 will be output.
445 @param roq: a boolean value to set to @c True requiring the output for
446 a ROQ model.
447
448 @return a complex array called @b compstrain
449 """
450
451 if newpar is not None:
452 parupdate = self._read_par(newpar)
453 else:
454 parupdate = self.hetpar
455
456 origpar = self.hetpar
457
458 self.__nonGR = self._check_nonGR(parupdate)
459 compstrain = lalpulsar.HeterodynedPulsarGetModel(
460 parupdate.PulsarParameters(),
461 origpar.PulsarParameters(),
462 freqfactor,
463 int(usephase), # phase is varying between par files
464 int(roq), # using ROQ?
465 self.__nonGR, # using non-tensorial modes?
466 self.gpstimes,
467 self.ssbdelay,
468 int(updateSSB), # the SSB delay should be updated compared to hetSSBdelay
469 self.bsbdelay,
470 int(updateBSB), # the BSB delay should be updated compared to hetBSBdelay
471 self.glitchphase,
472 int(updateglphase),
473 self.fitwavesphase,
474 int(updatefitwaves),
475 self.resp,
476 self.__edat,
477 self.__tdat,
478 self.__units_type,
479 )
480
481 return compstrain.data.data
482
483 def _read_par(self, par):
484 """
485 Read a TEMPO-style parameter file into a PulsarParameterPy object.
486 """
487
488 if isinstance(par, PulsarParametersPy):
489 return par
490
491 if isinstance(par, str):
492 try:
493 return PulsarParametersPy(par)
494 except IOError:
495 raise IOError("Could not read in parameter file: '{}'".format(par))
496 else:
497 raise TypeError("The parameter file must be a string")
498
499 @property
500 def ssbdelay(self):
501 return self.__hetSSBdelay
502
503 @property
504 def bsbdelay(self):
505 return self.__hetBSBdelay
506
507 @property
508 def glitchphase(self):
509 return self.__hetglitchphase
510
511 @property
512 def fitwavesphase(self):
513 return self.__hetfitwavesphase
514
515 def _check_nonGR(self, par):
516 """
517 Check if the source parameters are for a non-GR model, i.e., are any of
518 the amplitude/phase parameters for a non-GR model set
519 """
520
521 # non-GR amplitude parameters
522 nonGRparams = [
523 "HPLUS",
524 "HCROSS",
525 "HVECTORX",
526 "HVECTORY",
527 "HSCALARB",
528 "HSCALARL",
529 "HPLUS_F",
530 "HCROSS_F",
531 "HVECTORX_F",
532 "HVECTORY_F",
533 "HSCALARB_F",
534 "HSCALARL_F",
535 ]
536
537 for param in nonGRparams:
538 if param in par.keys():
539 return 1
540
541 return 0
542
543 def _initialise_ephemeris(self, earth_ephem, sun_ephem, time_corr):
544 """
545 Initialise the solar system ephemeris.
546 """
547
548 if earth_ephem is not None:
549 earthfile = earth_ephem
550 else:
551 earthfile = self.__earthstr.format(self.ephemephemephem)
552
553 if sun_ephem is not None:
554 sunfile = sun_ephem
555 else:
556 sunfile = self.__sunstr.format(self.ephemephemephem)
557
558 if time_corr is not None:
559 timefile = time_corr
560 else:
561 timefile = self.__timecorrstr.format(self.__units_map[self.unitsunitsunits])
562
563 try:
564 edat = lalpulsar.InitBarycenter(earthfile, sunfile)
565 except RuntimeError:
566 try:
567 # try downloading the ephemeris files
568 from astropy.utils.data import download_file
569
570 efile = download_file(DOWNLOAD_URL.format(earthfile), cache=True)
571 sfile = download_file(DOWNLOAD_URL.format(sunfile), cache=True)
572 edat = lalpulsar.InitBarycenter(efile, sfile)
573 except Exception as e:
574 raise IOError("Could not read in ephemeris files: {}".format(e))
575
576 try:
577 tdat = lalpulsar.InitTimeCorrections(timefile)
578 except RuntimeError:
579 try:
580 # try downloading the ephemeris files
581 from astropy.utils.data import download_file
582
583 tfile = download_file(DOWNLOAD_URL.format(timefile), cache=True)
584 tdat = lalpulsar.InitTimeCorrections(tfile)
585 except Exception as e:
586 raise IOError("Could not read in time correction file: {}".format(e))
587
588 return edat, tdat
589
590
591## @}
COMPLEX16TimeSeries * XLALHeterodynedPulsarGetModel(PulsarParameters *pars, PulsarParameters *origpars, REAL8 freqfactor, UINT4 usephase, UINT4 useroq, UINT4 nonGR, const LIGOTimeGPSVector *timestamps, REAL8Vector *hetssbdelays, UINT4 calcSSBDelay, REAL8Vector *hetbsbdelays, UINT4 calcBSBDelay, REAL8Vector *glphase, UINT4 calcglphase, REAL8Vector *fitwavesphase, UINT4 calcfitwaves, const DetResponseTimeLookupTable *resp, const EphemerisData *ephem, const TimeCorrectionData *tdat, TimeCorrectionType ttype)
Generate the model of the neutron star signal.
A class to wrap the SWIG-wrapped lalpulsar.PulsarParameters structure.
def resp(self)
Return the response function look-up table.
def model(self, newpar=None, updateSSB=False, updateBSB=False, updateglphase=False, updatefitwaves=False, freqfactor=2.0, usephase=False, roq=False)
Compute the heterodyned strain model using XLALHeterodynedPulsarGetModel().
def units(self, units)
Set the time system units, i.e., either 'TDB' or 'TCB'.
def _initialise_ephemeris(self, earth_ephem, sun_ephem, time_corr)
def ephem(self, ephem)
Set the heterodyne solar system ephemeris version.
def times(self, times)
Set an array of times, and also a LIGOTimeGPSVector() containing the times.
def __init__(self, par, det, times=None, earth_ephem=None, sun_ephem=None, time_corr=None, ephem="DE405", units="TCB", t0=None, dt=None)
A class to simulate strain data for a continuous gravitational-wave signal after the data has been he...
static const INT4 a
float data[BLOCKSIZE]
Definition: hwinject.c:360
A vector of 'timestamps' of type LIGOTimeGPS.
Definition: SFTfileIO.h:188
The PulsarParameters structure to contain a set of pulsar parameters.