Coverage for bilby/gw/utils.py: 48%
378 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-06 04:57 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-06 04:57 +0000
1import json
2import os
3from functools import lru_cache
5import numpy as np
6from scipy.interpolate import interp1d
7from scipy.special import i0e
8from bilby_cython.geometry import (
9 zenith_azimuth_to_theta_phi as _zenith_azimuth_to_theta_phi,
10)
11from bilby_cython.time import greenwich_mean_sidereal_time
13from ..core.utils import (logger, run_commandline,
14 check_directory_exists_and_if_not_mkdir,
15 SamplesSummary, theta_phi_to_ra_dec)
16from ..core.utils.constants import solar_mass
19def asd_from_freq_series(freq_data, df):
20 """
21 Calculate the ASD from the frequency domain output of gaussian_noise()
23 Parameters
24 ==========
25 freq_data: array_like
26 Array of complex frequency domain data
27 df: float
28 Spacing of freq_data, 1/(segment length) used to generate the gaussian noise
30 Returns
31 =======
32 array_like: array of real-valued normalized frequency domain ASD data
34 """
35 return np.absolute(freq_data) * 2 * df**0.5
38def psd_from_freq_series(freq_data, df):
39 """
40 Calculate the PSD from the frequency domain output of gaussian_noise()
41 Calls asd_from_freq_series() and squares the output
43 Parameters
44 ==========
45 freq_data: array_like
46 Array of complex frequency domain data
47 df: float
48 Spacing of freq_data, 1/(segment length) used to generate the gaussian noise
50 Returns
51 =======
52 array_like: Real-valued normalized frequency domain PSD data
54 """
55 return np.power(asd_from_freq_series(freq_data, df), 2)
58def get_vertex_position_geocentric(latitude, longitude, elevation):
59 """
60 Calculate the position of the IFO vertex in geocentric coordinates in meters.
62 Based on arXiv:gr-qc/0008066 Eqs. B11-B13 except for the typo in the definition of the local radius.
63 See Section 2.1 of LIGO-T980044-10 for the correct expression
65 Parameters
66 ==========
67 latitude: float
68 Latitude in radians
69 longitude:
70 Longitude in radians
71 elevation:
72 Elevation in meters
74 Returns
75 =======
76 array_like: A 3D representation of the geocentric vertex position
78 """
79 semi_major_axis = 6378137 # for ellipsoid model of Earth, in m
80 semi_minor_axis = 6356752.314 # in m
81 radius = semi_major_axis**2 * (semi_major_axis**2 * np.cos(latitude)**2 +
82 semi_minor_axis**2 * np.sin(latitude)**2)**(-0.5)
83 x_comp = (radius + elevation) * np.cos(latitude) * np.cos(longitude)
84 y_comp = (radius + elevation) * np.cos(latitude) * np.sin(longitude)
85 z_comp = ((semi_minor_axis / semi_major_axis)**2 * radius + elevation) * np.sin(latitude)
86 return np.array([x_comp, y_comp, z_comp])
89def inner_product(aa, bb, frequency, PSD):
90 """
91 Calculate the inner product defined in the matched filter statistic
93 Parameters
94 ==========
95 aa, bb: array_like
96 Single-sided Fourier transform, created, e.g., by the nfft function above
97 frequency: array_like
98 An array of frequencies associated with aa, bb, also returned by nfft
99 PSD: bilby.gw.detector.PowerSpectralDensity
101 Returns
102 =======
103 The matched filter inner product for aa and bb
105 """
106 psd_interp = PSD.power_spectral_density_interpolated(frequency)
108 # calculate the inner product
109 integrand = np.conj(aa) * bb / psd_interp
111 df = frequency[1] - frequency[0]
112 integral = np.sum(integrand) * df
113 return 4. * np.real(integral)
116def noise_weighted_inner_product(aa, bb, power_spectral_density, duration):
117 """
118 Calculate the noise weighted inner product between two arrays.
120 Parameters
121 ==========
122 aa: array_like
123 Array to be complex conjugated
124 bb: array_like
125 Array not to be complex conjugated
126 power_spectral_density: array_like
127 Power spectral density of the noise
128 duration: float
129 duration of the data
131 Returns
132 =======
133 Noise-weighted inner product.
134 """
136 integrand = np.conj(aa) * bb / power_spectral_density
137 return 4 / duration * np.sum(integrand)
140def matched_filter_snr(signal, frequency_domain_strain, power_spectral_density, duration):
141 """
142 Calculate the _complex_ matched filter snr of a signal.
143 This is <signal|frequency_domain_strain> / optimal_snr
145 Parameters
146 ==========
147 signal: array_like
148 Array containing the signal
149 frequency_domain_strain: array_like
151 power_spectral_density: array_like
153 duration: float
154 Time duration of the signal
156 Returns
157 =======
158 float: The matched filter signal to noise ratio squared
160 """
161 rho_mf = noise_weighted_inner_product(
162 aa=signal, bb=frequency_domain_strain,
163 power_spectral_density=power_spectral_density, duration=duration)
164 rho_mf /= optimal_snr_squared(
165 signal=signal, power_spectral_density=power_spectral_density,
166 duration=duration)**0.5
167 return rho_mf
170def optimal_snr_squared(signal, power_spectral_density, duration):
171 """
172 Compute the square of the optimal matched filter SNR for the provided
173 signal.
176 Parameters
177 ==========
178 signal: array_like
179 Array containing the signal
180 power_spectral_density: array_like
182 duration: float
183 Time duration of the signal
185 Returns
186 =======
187 float: The matched filter signal to noise ratio squared
189 """
190 return noise_weighted_inner_product(signal, signal, power_spectral_density, duration)
193def overlap(signal_a, signal_b, power_spectral_density=None, delta_frequency=None,
194 lower_cut_off=None, upper_cut_off=None, norm_a=None, norm_b=None):
195 r"""
196 Compute the overlap between two signals
198 .. math::
200 {\cal O} = \frac{4 \Delta f}{N_{a} N_{b}} \sum_{i} \frac{h^{*}_{a,i} h_{b,i}}{S_{i}}
202 Parameters
203 ----------
204 signal_a: array-like
205 signal_b: array-like
206 power_spectral_density : array-like
207 delta_frequency: float
208 Frequency spacing of the signals
209 lower_cut_off: float
210 Minimum frequency for the integral
211 upper_cut_off: float
212 Maximum frequency for the integral
213 norm_a: float
214 Normalizing factor for signal_a
215 norm_b: float
216 Normalizing factor for signal_b
218 Returns
219 -------
220 float
221 The overlap
222 """
223 low_index = int(lower_cut_off / delta_frequency)
224 up_index = int(upper_cut_off / delta_frequency)
225 integrand = np.conj(signal_a) * signal_b
226 integrand = integrand[low_index:up_index] / power_spectral_density[low_index:up_index]
227 integral = (4 * delta_frequency * integrand) / norm_a / norm_b
228 return sum(integral).real
231def zenith_azimuth_to_theta_phi(zenith, azimuth, ifos):
232 """
233 Convert from the 'detector frame' to the Earth frame.
235 Parameters
236 ==========
237 kappa: float
238 The zenith angle in the detector frame
239 eta: float
240 The azimuthal angle in the detector frame
241 ifos: list
242 List of Interferometer objects defining the detector frame
244 Returns
245 =======
246 theta, phi: float
247 The zenith and azimuthal angles in the earth frame.
248 """
249 delta_x = ifos[0].geometry.vertex - ifos[1].geometry.vertex
250 return _zenith_azimuth_to_theta_phi(zenith, azimuth, delta_x)
253def zenith_azimuth_to_ra_dec(zenith, azimuth, geocent_time, ifos):
254 """
255 Convert from the 'detector frame' to the Earth frame.
257 Parameters
258 ==========
259 kappa: float
260 The zenith angle in the detector frame
261 eta: float
262 The azimuthal angle in the detector frame
263 geocent_time: float
264 GPS time at geocenter
265 ifos: list
266 List of Interferometer objects defining the detector frame
268 Returns
269 =======
270 ra, dec: float
271 The zenith and azimuthal angles in the sky frame.
272 """
273 theta, phi = zenith_azimuth_to_theta_phi(zenith, azimuth, ifos)
274 gmst = greenwich_mean_sidereal_time(geocent_time)
275 ra, dec = theta_phi_to_ra_dec(theta, phi, gmst)
276 ra = ra % (2 * np.pi)
277 return ra, dec
280def get_event_time(event):
281 """
282 Get the merger time for known GW events using the gwosc package
284 Parameters
285 ----------
286 event: str
287 Event name, e.g. GW150914
289 Returns
290 -------
291 event_time: float
292 Merger time
294 Raises
295 ------
296 ImportError
297 If the gwosc package is not installed
298 ValueError
299 If the event is not in the gwosc dataset
300 """
301 try:
302 from gwosc import datasets
303 except ImportError:
304 raise ImportError("You do not have the gwosc package installed")
306 return datasets.event_gps(event)
309def get_open_strain_data(
310 name, start_time, end_time, outdir, cache=False, buffer_time=0, **kwargs):
311 """ A function which accesses the open strain data
313 This uses `gwpy` to download the open data and then saves a cached copy for
314 later use
316 Parameters
317 ==========
318 name: str
319 The name of the detector to get data for
320 start_time, end_time: float
321 The GPS time of the start and end of the data
322 outdir: str
323 The output directory to place data in
324 cache: bool
325 If true, cache the data
326 buffer_time: float
327 Time to add to the beginning and end of the segment.
328 **kwargs:
329 Passed to `gwpy.timeseries.TimeSeries.fetch_open_data`
331 Returns
332 =======
333 strain: gwpy.timeseries.TimeSeries
334 The object containing the strain data. If the connection to the open-data server
335 fails, this function returns `None`.
337 """
338 from gwpy.timeseries import TimeSeries
339 filename = '{}/{}_{}_{}.txt'.format(outdir, name, start_time, end_time)
341 if buffer_time < 0:
342 raise ValueError("buffer_time < 0")
343 start_time = start_time - buffer_time
344 end_time = end_time + buffer_time
346 if os.path.isfile(filename) and cache:
347 logger.info('Using cached data from {}'.format(filename))
348 strain = TimeSeries.read(filename)
349 else:
350 logger.info('Fetching open data from {} to {} with buffer time {}'
351 .format(start_time, end_time, buffer_time))
352 try:
353 strain = TimeSeries.fetch_open_data(name, start_time, end_time, **kwargs)
354 logger.info('Saving cache of data to {}'.format(filename))
355 strain.write(filename)
356 except Exception as e:
357 logger.info("Unable to fetch open data, see debug for detailed info")
358 logger.info("Call to gwpy.timeseries.TimeSeries.fetch_open_data returned {}"
359 .format(e))
360 strain = None
362 return strain
365def read_frame_file(file_name, start_time, end_time, resample=None, channel=None, buffer_time=0, **kwargs):
366 """ A function which accesses the open strain data
368 This uses `gwpy` to download the open data and then saves a cached copy for
369 later use
371 Parameters
372 ==========
373 file_name: str
374 The name of the frame to read
375 start_time, end_time: float
376 The GPS time of the start and end of the data
377 buffer_time: float
378 Read in data with `t1-buffer_time` and `end_time+buffer_time`
379 channel: str
380 The name of the channel being searched for, some standard channel names are attempted
381 if channel is not specified or if specified channel is not found.
382 resample: float
383 The sampling frequency to use for the TimeSeries in Hz
384 **kwargs:
385 Passed to `gwpy.timeseries.TimeSeries.fetch_open_data`
387 Returns
388 =======
389 strain: gwpy.timeseries.TimeSeries
391 """
392 from gwpy.timeseries import TimeSeries
393 loaded = False
394 strain = None
396 if channel is not None:
397 try:
398 strain = TimeSeries.read(source=file_name, channel=channel, start=start_time, end=end_time, **kwargs)
399 loaded = True
400 logger.info('Successfully loaded {}.'.format(channel))
401 except (RuntimeError, ValueError):
402 logger.warning('Channel {} not found. Trying preset channel names'.format(channel))
404 if loaded is False:
405 ligo_channel_types = ['GDS-CALIB_STRAIN', 'DCS-CALIB_STRAIN_C01', 'DCS-CALIB_STRAIN_C02',
406 'DCH-CLEAN_STRAIN_C02', 'GWOSC-16KHZ_R1_STRAIN',
407 'GWOSC-4KHZ_R1_STRAIN']
408 virgo_channel_types = ['Hrec_hoft_V1O2Repro2A_16384Hz', 'FAKE_h_16384Hz_4R',
409 'GWOSC-16KHZ_R1_STRAIN', 'GWOSC-4KHZ_R1_STRAIN']
410 channel_types = dict(H1=ligo_channel_types, L1=ligo_channel_types, V1=virgo_channel_types)
411 for detector in channel_types.keys():
412 for channel_type in channel_types[detector]:
413 if loaded:
414 break
415 channel = '{}:{}'.format(detector, channel_type)
416 try:
417 strain = TimeSeries.read(source=file_name, channel=channel, start=start_time, end=end_time,
418 **kwargs)
419 loaded = True
420 logger.info('Successfully read strain data for channel {}.'.format(channel))
421 except (RuntimeError, ValueError):
422 pass
424 if loaded:
425 if resample and (strain.sample_rate.value != resample):
426 strain = strain.resample(resample)
427 return strain
428 else:
429 logger.warning('No data loaded.')
430 return None
433def get_gracedb(gracedb, outdir, duration, calibration, detectors, query_types=None, server=None):
434 """
435 Download information about a trigger from GraceDb and create cache files
436 for finding gravitational-wave strain data.
438 Parameters
439 ----------
440 gracedb: str
441 The GraceDb ID of the trigger.
442 outdir: str
443 Directory to write output.
444 duration: int
445 Duration of data to find about the trigger time (units: :code:`s`).
446 calibration: int
447 Calibration label of the data, should be one of :code:`1, 2`.
448 detectors: list
449 The detectors to look for data for.
450 query_types: str
451 The LDR query type
452 server: str
453 The LIGO datafind server to look for data on.
455 Returns
456 -------
457 candidate: dict
458 Information downloaded from GraceDb about the trigger.
459 cache_files: list
460 List of cache filenames, one per interferometer.
461 """
462 candidate = gracedb_to_json(gracedb, outdir=outdir)
463 trigger_time = candidate['gpstime']
464 gps_start_time = trigger_time - duration
465 cache_files = []
466 if query_types is None:
467 query_types = [None] * len(detectors)
468 for i, det in enumerate(detectors):
469 output_cache_file = gw_data_find(
470 det, gps_start_time, duration, calibration,
471 outdir=outdir, query_type=query_types[i], server=server)
472 cache_files.append(output_cache_file)
473 return candidate, cache_files
476def gracedb_to_json(gracedb, cred=None, service_url='https://gracedb.ligo.org/api/', outdir=None):
477 """ Script to download a GraceDB candidate
479 Parameters
480 ==========
481 gracedb: str
482 The UID of the GraceDB candidate
483 cred:
484 Credentials for authentications, see ligo.gracedb.rest.GraceDb
485 service_url:
486 The url of the GraceDB candidate
487 GraceDB 'https://gracedb.ligo.org/api/' (default)
488 GraceDB-playground 'https://gracedb-playground.ligo.org/api/'
489 outdir: str, optional
490 If given, a string identfying the location in which to store the json
491 """
492 logger.info(
493 'Starting routine to download GraceDb candidate {}'.format(gracedb))
494 from ligo.gracedb.rest import GraceDb
496 logger.info('Initialise client and attempt to download')
497 logger.info('Fetching from {}'.format(service_url))
498 try:
499 client = GraceDb(cred=cred, service_url=service_url)
500 except IOError:
501 raise ValueError(
502 'Failed to authenticate with gracedb: check your X509 '
503 'certificate is accessible and valid')
504 try:
505 candidate = client.event(gracedb)
506 logger.info('Successfully downloaded candidate')
507 except Exception as e:
508 raise ValueError("Unable to obtain GraceDB candidate, exception: {}".format(e))
510 json_output = candidate.json()
512 if outdir is not None:
513 check_directory_exists_and_if_not_mkdir(outdir)
514 outfilepath = os.path.join(outdir, '{}.json'.format(gracedb))
515 logger.info('Writing candidate to {}'.format(outfilepath))
516 with open(outfilepath, 'w') as outfile:
517 json.dump(json_output, outfile, indent=2)
519 return json_output
522def gw_data_find(observatory, gps_start_time, duration, calibration,
523 outdir='.', query_type=None, server=None):
524 """ Builds a gw_data_find call and process output
526 Parameters
527 ==========
528 observatory: str, {H1, L1, V1}
529 Observatory description
530 gps_start_time: float
531 The start time in gps to look for data
532 duration: int
533 The duration (integer) in s
534 calibration: int {1, 2}
535 Use C01 or C02 calibration
536 outdir: string
537 A path to the directory where output is stored
538 query_type: string
539 The LDRDataFind query type
541 Returns
542 =======
543 output_cache_file: str
544 Path to the output cache file
546 """
547 logger.info('Building gw_data_find command line')
549 observatory_lookup = dict(H1='H', L1='L', V1='V')
550 observatory_code = observatory_lookup[observatory]
552 if query_type is None:
553 logger.warning('No query type provided. This may prevent data from being read.')
554 if observatory_code == 'V':
555 query_type = 'V1Online'
556 else:
557 query_type = '{}_HOFT_C0{}'.format(observatory, calibration)
559 logger.info('Using LDRDataFind query type {}'.format(query_type))
561 cache_file = '{}-{}_CACHE-{}-{}.lcf'.format(
562 observatory, query_type, gps_start_time, duration)
563 output_cache_file = os.path.join(outdir, cache_file)
565 gps_end_time = gps_start_time + duration
566 if server is None:
567 server = os.environ.get("LIGO_DATAFIND_SERVER")
568 if server is None:
569 logger.warning('No data_find server found, defaulting to CIT server. '
570 'To run on other clusters, pass the output of '
571 '`echo $LIGO_DATAFIND_SERVER`')
572 server = 'ldr.ldas.cit:80'
574 cl_list = ['gw_data_find']
575 cl_list.append('--observatory {}'.format(observatory_code))
576 cl_list.append('--gps-start-time {}'.format(int(np.floor(gps_start_time))))
577 cl_list.append('--gps-end-time {}'.format(int(np.ceil(gps_end_time))))
578 cl_list.append('--type {}'.format(query_type))
579 cl_list.append('--output {}'.format(output_cache_file))
580 cl_list.append('--server {}'.format(server))
581 cl_list.append('--url-type file')
582 cl_list.append('--lal-cache')
583 cl = ' '.join(cl_list)
584 run_commandline(cl)
585 return output_cache_file
588def convert_args_list_to_float(*args_list):
589 """ Converts inputs to floats, returns a list in the same order as the input"""
590 try:
591 args_list = [float(arg) for arg in args_list]
592 except ValueError:
593 raise ValueError("Unable to convert inputs to floats")
594 return args_list
597def lalsim_SimInspiralTransformPrecessingNewInitialConditions(
598 theta_jn, phi_jl, tilt_1, tilt_2, phi_12, a_1, a_2, mass_1, mass_2,
599 reference_frequency, phase):
600 from lalsimulation import SimInspiralTransformPrecessingNewInitialConditions
602 args_list = convert_args_list_to_float(
603 theta_jn, phi_jl, tilt_1, tilt_2, phi_12, a_1, a_2, mass_1, mass_2,
604 reference_frequency, phase)
606 return SimInspiralTransformPrecessingNewInitialConditions(*args_list)
609@lru_cache(maxsize=10)
610def lalsim_GetApproximantFromString(waveform_approximant):
611 from lalsimulation import GetApproximantFromString
612 if isinstance(waveform_approximant, str):
613 return GetApproximantFromString(waveform_approximant)
614 else:
615 raise ValueError("waveform_approximant must be of type str")
618def lalsim_SimInspiralFD(
619 mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y,
620 spin_2z, luminosity_distance, iota, phase,
621 longitude_ascending_nodes, eccentricity, mean_per_ano, delta_frequency,
622 minimum_frequency, maximum_frequency, reference_frequency,
623 waveform_dictionary, approximant):
624 """
625 Safely call lalsimulation.SimInspiralFD
627 Parameters
628 ==========
629 phase: float, int
630 mass_1: float, int
631 mass_2: float, int
632 spin_1x: float, int
633 spin_1y: float, int
634 spin_1z: float, int
635 spin_2x: float, int
636 spin_2y: float, int
637 spin_2z: float, int
638 reference_frequency: float, int
639 luminosity_distance: float, int
640 iota: float, int
641 longitude_ascending_nodes: float, int
642 eccentricity: float, int
643 mean_per_ano: float, int
644 delta_frequency: float, int
645 minimum_frequency: float, int
646 maximum_frequency: float, int
647 waveform_dictionary: None, lal.Dict
648 approximant: int, str
649 """
650 from lalsimulation import SimInspiralFD
652 args = convert_args_list_to_float(
653 mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z,
654 luminosity_distance, iota, phase, longitude_ascending_nodes,
655 eccentricity, mean_per_ano, delta_frequency, minimum_frequency,
656 maximum_frequency, reference_frequency)
658 approximant = _get_lalsim_approximant(approximant)
660 return SimInspiralFD(*args, waveform_dictionary, approximant)
663def lalsim_SimInspiralChooseFDWaveform(
664 mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y,
665 spin_2z, luminosity_distance, iota, phase,
666 longitude_ascending_nodes, eccentricity, mean_per_ano, delta_frequency,
667 minimum_frequency, maximum_frequency, reference_frequency,
668 waveform_dictionary, approximant):
669 """
670 Safely call lalsimulation.SimInspiralChooseFDWaveform
672 Parameters
673 ==========
674 phase: float, int
675 mass_1: float, int
676 mass_2: float, int
677 spin_1x: float, int
678 spin_1y: float, int
679 spin_1z: float, int
680 spin_2x: float, int
681 spin_2y: float, int
682 spin_2z: float, int
683 reference_frequency: float, int
684 luminosity_distance: float, int
685 iota: float, int
686 longitude_ascending_nodes: float, int
687 eccentricity: float, int
688 mean_per_ano: float, int
689 delta_frequency: float, int
690 minimum_frequency: float, int
691 maximum_frequency: float, int
692 waveform_dictionary: None, lal.Dict
693 approximant: int, str
694 """
695 from lalsimulation import SimInspiralChooseFDWaveform
697 args = convert_args_list_to_float(
698 mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z,
699 luminosity_distance, iota, phase, longitude_ascending_nodes,
700 eccentricity, mean_per_ano, delta_frequency, minimum_frequency,
701 maximum_frequency, reference_frequency)
703 approximant = _get_lalsim_approximant(approximant)
705 return SimInspiralChooseFDWaveform(*args, waveform_dictionary, approximant)
708@lru_cache(maxsize=10)
709def _get_lalsim_approximant(approximant):
710 if isinstance(approximant, int):
711 pass
712 elif isinstance(approximant, str):
713 approximant = lalsim_GetApproximantFromString(approximant)
714 else:
715 raise ValueError("approximant not an int")
716 return approximant
719def lalsim_SimInspiralChooseFDWaveformSequence(
720 phase, mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y,
721 spin_2z, reference_frequency, luminosity_distance, iota,
722 waveform_dictionary, approximant, frequency_array):
723 """
724 Safely call lalsimulation.SimInspiralChooseFDWaveformSequence
726 Parameters
727 ==========
728 phase: float, int
729 mass_1: float, int
730 mass_2: float, int
731 spin_1x: float, int
732 spin_1y: float, int
733 spin_1z: float, int
734 spin_2x: float, int
735 spin_2y: float, int
736 spin_2z: float, int
737 reference_frequency: float, int
738 luminosity_distance: float, int
739 iota: float, int
740 waveform_dictionary: None, lal.Dict
741 approximant: int, str
742 frequency_array: np.ndarray, lal.REAL8Vector
743 """
744 from lal import REAL8Vector, CreateREAL8Vector
745 from lalsimulation import SimInspiralChooseFDWaveformSequence
747 [mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z,
748 luminosity_distance, iota, phase, reference_frequency] = convert_args_list_to_float(
749 mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z,
750 luminosity_distance, iota, phase, reference_frequency)
752 if isinstance(approximant, int):
753 pass
754 elif isinstance(approximant, str):
755 approximant = lalsim_GetApproximantFromString(approximant)
756 else:
757 raise ValueError("approximant not an int")
759 if not isinstance(frequency_array, REAL8Vector):
760 old_frequency_array = frequency_array.copy()
761 frequency_array = CreateREAL8Vector(len(old_frequency_array))
762 frequency_array.data = old_frequency_array
764 return SimInspiralChooseFDWaveformSequence(
765 phase, mass_1, mass_2, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y,
766 spin_2z, reference_frequency, luminosity_distance, iota,
767 waveform_dictionary, approximant, frequency_array)
770def lalsim_SimInspiralWaveformParamsInsertTidalLambda1(
771 waveform_dictionary, lambda_1):
772 from lalsimulation import SimInspiralWaveformParamsInsertTidalLambda1
773 try:
774 lambda_1 = float(lambda_1)
775 except ValueError:
776 raise ValueError("Unable to convert lambda_1 to float")
778 return SimInspiralWaveformParamsInsertTidalLambda1(
779 waveform_dictionary, lambda_1)
782def lalsim_SimInspiralWaveformParamsInsertTidalLambda2(
783 waveform_dictionary, lambda_2):
784 from lalsimulation import SimInspiralWaveformParamsInsertTidalLambda2
785 try:
786 lambda_2 = float(lambda_2)
787 except ValueError:
788 raise ValueError("Unable to convert lambda_2 to float")
790 return SimInspiralWaveformParamsInsertTidalLambda2(
791 waveform_dictionary, lambda_2)
794def lalsim_SimNeutronStarEOS4ParamSDGammaCheck(g0, g1, g2, g3):
795 from lalsimulation import SimNeutronStarEOS4ParamSDGammaCheck
796 try:
797 g0 = float(g0)
798 g1 = float(g1)
799 g2 = float(g2)
800 g3 = float(g3)
801 except ValueError:
802 raise ValueError("Unable to convert EOS spectral parameters to floats")
803 except TypeError:
804 raise TypeError("Unable to convert EOS spectral parameters to floats")
806 return SimNeutronStarEOS4ParamSDGammaCheck(g0, g1, g2, g3)
809def lalsim_SimNeutronStarEOS4ParameterSpectralDecomposition(g0, g1, g2, g3):
810 from lalsimulation import SimNeutronStarEOS4ParameterSpectralDecomposition
811 try:
812 g0 = float(g0)
813 g1 = float(g1)
814 g2 = float(g2)
815 g3 = float(g3)
816 except ValueError:
817 raise ValueError("Unable to convert EOS spectral parameters to floats")
818 except TypeError:
819 raise TypeError("Unable to convert EOS spectral parameters to floats")
821 return SimNeutronStarEOS4ParameterSpectralDecomposition(g0, g1, g2, g3)
824def lalsim_SimNeutronStarEOS4ParamSDViableFamilyCheck(g0, g1, g2, g3):
825 from lalsimulation import SimNeutronStarEOS4ParamSDViableFamilyCheck
826 try:
827 g0 = float(g0)
828 g1 = float(g1)
829 g2 = float(g2)
830 g3 = float(g3)
831 except ValueError:
832 raise ValueError("Unable to convert EOS spectral parameters to floats")
833 except TypeError:
834 raise TypeError("Unable to convert EOS spectral parameters to floats")
836 return SimNeutronStarEOS4ParamSDViableFamilyCheck(g0, g1, g2, g3)
839def lalsim_SimNeutronStarEOS3PieceDynamicPolytrope(g0, log10p1_si, g1, log10p2_si, g2):
840 from lalsimulation import SimNeutronStarEOS3PieceDynamicPolytrope
841 try:
842 g0 = float(g0)
843 g1 = float(g1)
844 g2 = float(g2)
845 log10p1_si = float(log10p1_si)
846 log10p2_si = float(log10p2_si)
847 except ValueError:
848 raise ValueError("Unable to convert EOS polytrope parameters to floats")
849 except TypeError:
850 raise TypeError("Unable to convert EOS polytrope parameters to floats")
852 return SimNeutronStarEOS3PieceDynamicPolytrope(g0, log10p1_si, g1, log10p2_si, g2)
855def lalsim_SimNeutronStarEOS3PieceCausalAnalytic(v1, log10p1_si, v2, log10p2_si, v3):
856 from lalsimulation import SimNeutronStarEOS3PieceCausalAnalytic
857 try:
858 v1 = float(v1)
859 v2 = float(v2)
860 v3 = float(v3)
861 log10p1_si = float(log10p1_si)
862 log10p2_si = float(log10p2_si)
863 except ValueError:
864 raise ValueError("Unable to convert EOS causal parameters to floats")
865 except TypeError:
866 raise TypeError("Unable to convert EOS causal parameters to floats")
868 return SimNeutronStarEOS3PieceCausalAnalytic(v1, log10p1_si, v2, log10p2_si, v3)
871def lalsim_SimNeutronStarEOS3PDViableFamilyCheck(p0, log10p1_si, p1, log10p2_si, p2, causal):
872 from lalsimulation import SimNeutronStarEOS3PDViableFamilyCheck
873 try:
874 p0 = float(p0)
875 p1 = float(p1)
876 p2 = float(p2)
877 log10p1_si = float(log10p1_si)
878 log10p2_si = float(log10p2_si)
879 causal = int(causal)
880 except ValueError:
881 raise ValueError("Unable to convert EOS parameters to floats or int")
882 except TypeError:
883 raise TypeError("Unable to convert EOS parameters to floats or int")
885 return SimNeutronStarEOS3PDViableFamilyCheck(p0, log10p1_si, p1, log10p2_si, p2, causal)
888def lalsim_CreateSimNeutronStarFamily(eos):
889 from lalsimulation import CreateSimNeutronStarFamily
891 return CreateSimNeutronStarFamily(eos)
894def lalsim_SimNeutronStarEOSMaxPseudoEnthalpy(eos):
895 from lalsimulation import SimNeutronStarEOSMaxPseudoEnthalpy
897 return SimNeutronStarEOSMaxPseudoEnthalpy(eos)
900def lalsim_SimNeutronStarEOSSpeedOfSoundGeometerized(max_pseudo_enthalpy, eos):
901 from lalsimulation import SimNeutronStarEOSSpeedOfSoundGeometerized
902 try:
903 max_pseudo_enthalpy = float(max_pseudo_enthalpy)
904 except ValueError:
905 raise ValueError("Unable to convert max_pseudo_enthalpy to float.")
906 except TypeError:
907 raise TypeError("Unable to convert max_pseudo_enthalpy to float.")
909 return SimNeutronStarEOSSpeedOfSoundGeometerized(max_pseudo_enthalpy, eos)
912def lalsim_SimNeutronStarFamMinimumMass(fam):
913 from lalsimulation import SimNeutronStarFamMinimumMass
915 return SimNeutronStarFamMinimumMass(fam)
918def lalsim_SimNeutronStarMaximumMass(fam):
919 from lalsimulation import SimNeutronStarMaximumMass
921 return SimNeutronStarMaximumMass(fam)
924def lalsim_SimNeutronStarRadius(mass_in_SI, fam):
925 from lalsimulation import SimNeutronStarRadius
926 try:
927 mass_in_SI = float(mass_in_SI)
928 except ValueError:
929 raise ValueError("Unable to convert mass_in_SI to float.")
930 except TypeError:
931 raise TypeError("Unable to convert mass_in_SI to float.")
933 return SimNeutronStarRadius(mass_in_SI, fam)
936def lalsim_SimNeutronStarLoveNumberK2(mass_in_SI, fam):
937 from lalsimulation import SimNeutronStarLoveNumberK2
938 try:
939 mass_in_SI = float(mass_in_SI)
940 except ValueError:
941 raise ValueError("Unable to convert mass_in_SI to float.")
942 except TypeError:
943 raise TypeError("Unable to convert mass_in_SI to float.")
945 return SimNeutronStarLoveNumberK2(mass_in_SI, fam)
948def spline_angle_xform(delta_psi):
949 """
950 Returns the angle in degrees corresponding to the spline
951 calibration parameters delta_psi.
952 Based on the same function in lalinference.bayespputils
954 Parameters
955 ==========
956 delta_psi: calibration phase uncertainty
958 Returns
959 =======
960 float: delta_psi in degrees
962 """
963 rotation = (2.0 + 1.0j * delta_psi) / (2.0 - 1.0j * delta_psi)
965 return 180.0 / np.pi * np.arctan2(np.imag(rotation), np.real(rotation))
968def plot_spline_pos(log_freqs, samples, nfreqs=100, level=0.9, color='k', label=None, xform=None):
969 """
970 Plot calibration posterior estimates for a spline model in log space.
971 Adapted from the same function in lalinference.bayespputils
973 Parameters
974 ==========
975 log_freqs: array-like
976 The (log) location of spline control points.
977 samples: array-like
978 List of posterior draws of function at control points ``log_freqs``
979 nfreqs: int
980 Number of points to evaluate spline at for plotting.
981 level: float
982 Credible level to fill in.
983 color: str
984 Color to plot with.
985 label: str
986 Label for plot.
987 xform: callable
988 Function to transform the spline into plotted values.
990 """
991 import matplotlib.pyplot as plt
992 freq_points = np.exp(log_freqs)
993 freqs = np.logspace(min(log_freqs), max(log_freqs), nfreqs, base=np.exp(1))
995 data = np.zeros((samples.shape[0], nfreqs))
997 if xform is None:
998 scaled_samples = samples
999 else:
1000 scaled_samples = xform(samples)
1002 scaled_samples_summary = SamplesSummary(scaled_samples, average='mean')
1003 data_summary = SamplesSummary(data, average='mean')
1005 plt.errorbar(freq_points, scaled_samples_summary.average,
1006 yerr=[-scaled_samples_summary.lower_relative_credible_interval,
1007 scaled_samples_summary.upper_relative_credible_interval],
1008 fmt='.', color=color, lw=4, alpha=0.5, capsize=0)
1010 for i, sample in enumerate(samples):
1011 temp = interp1d(
1012 log_freqs, sample, kind="cubic", fill_value=0,
1013 bounds_error=False)(np.log(freqs))
1014 if xform is None:
1015 data[i] = temp
1016 else:
1017 data[i] = xform(temp)
1019 plt.plot(freqs, np.mean(data, axis=0), color=color, label=label)
1020 plt.fill_between(freqs, data_summary.lower_absolute_credible_interval,
1021 data_summary.upper_absolute_credible_interval,
1022 color=color, alpha=.1, linewidth=0.1)
1023 plt.xlim(freq_points.min() - .5, freq_points.max() + 50)
1026def ln_i0(value):
1027 """
1028 A numerically stable method to evaluate ln(I_0) a modified Bessel function
1029 of order 0 used in the phase-marginalized likelihood.
1031 Parameters
1032 ==========
1033 value: array-like
1034 Value(s) at which to evaluate the function
1036 Returns
1037 =======
1038 array-like:
1039 The natural logarithm of the bessel function
1040 """
1041 return np.log(i0e(value)) + value
1044def calculate_time_to_merger(frequency, mass_1, mass_2, chi=0, safety=1.1):
1045 """ Leading-order calculation of the time to merger from frequency
1047 This uses the XLALSimInspiralTaylorF2ReducedSpinChirpTime routine to
1048 estimate the time to merger. Based on the implementation in
1049 `pycbc.pnutils._get_imr_duration`.
1051 Parameters
1052 ==========
1053 frequency: float
1054 The frequency (Hz) of the signal from which to calculate the time to merger
1055 mass_1, mass_2: float
1056 The detector frame component masses
1057 chi: float
1058 Dimensionless aligned-spin param
1059 safety:
1060 Multiplicitive safety factor
1062 Returns
1063 =======
1064 time_to_merger: float
1065 The time to merger from frequency in seconds
1066 """
1068 import lalsimulation
1069 return safety * lalsimulation.SimInspiralTaylorF2ReducedSpinChirpTime(
1070 frequency,
1071 mass_1 * solar_mass,
1072 mass_2 * solar_mass,
1073 chi,
1074 -1
1075 )