From c9e35610243e508ed90cf4abfa80d5cae9e48561 Mon Sep 17 00:00:00 2001 From: Gregory Ashton <gregory.ashton@ligo.org> Date: Fri, 3 Sep 2021 14:43:05 +0000 Subject: [PATCH] Resolve "Check the template fits in the segment" --- bilby/gw/detector/interferometer.py | 51 ++++++++++++++++++- bilby/gw/prior.py | 38 ++++++++++++++ bilby/gw/utils.py | 35 +++++++++++++ .../injection_examples/fast_tutorial.py | 11 ++-- test/gw/detector/interferometer_test.py | 5 +- 5 files changed, 135 insertions(+), 5 deletions(-) diff --git a/bilby/gw/detector/interferometer.py b/bilby/gw/detector/interferometer.py index b017f36d8..874d213df 100644 --- a/bilby/gw/detector/interferometer.py +++ b/bilby/gw/detector/interferometer.py @@ -9,6 +9,7 @@ from .. import utils as gwutils from .calibration import Recalibrate from .geometry import InterferometerGeometry from .strain_data import InterferometerStrainData +from ..conversion import generate_all_bbh_parameters class Interferometer(object): @@ -318,8 +319,50 @@ class Interferometer(object): return signal_ifo + def check_signal_duration(self, parameters, raise_error=True): + """ Check that the signal with the given parameters fits in the data + + Parameters + ========== + parameters: dict + A dictionary of the injection parameters + raise_error: bool + If True, raise an error in the signal does not fit. Otherwise, print + a warning message. + """ + try: + parameters = generate_all_bbh_parameters(parameters) + except AttributeError: + logger.debug( + "generate_all_bbh_parameters parameters failed during check_signal_duration" + ) + return + + if ("mass_1" not in parameters) and ("mass_2" not in parameters): + if raise_error: + raise AttributeError("Unable to check signal duration as mass not given") + else: + return + + # Calculate the time to merger + deltaT = gwutils.calculate_time_to_merger( + frequency=self.minimum_frequency, + mass_1=parameters["mass_1"], + mass_2=parameters["mass_2"], + ) + deltaT = np.round(deltaT, 1) + if deltaT > self.duration: + msg = ( + f"The injected signal has a duration in-band of {deltaT}s, but " + f"the data for detector {self.name} has a duration of {self.duration}s" + ) + if raise_error: + raise ValueError(msg) + else: + logger.warning(msg) + def inject_signal(self, parameters, injection_polarizations=None, - waveform_generator=None): + waveform_generator=None, raise_error=True): """ General signal injection method. Provide the injection parameters and either the injection polarizations or the waveform generator to inject a signal into the detector. @@ -337,6 +380,10 @@ class Interferometer(object): waveform_generator: bilby.gw.waveform_generator.WaveformGenerator, optional A WaveformGenerator instance using the source model to inject. If `injection_polarizations` is given, this will be ignored. + raise_error: bool + If true, raise an error if the injected signal has a duration + longer than the data duration. If False, a warning will be printed + instead. Notes ===== @@ -351,6 +398,8 @@ class Interferometer(object): if it was passed in. Otherwise it is the return value of waveform_generator.frequency_domain_strain(). """ + self.check_signal_duration(parameters, raise_error) + if injection_polarizations is None and waveform_generator is None: raise ValueError( "inject_signal needs one of waveform_generator or " diff --git a/bilby/gw/prior.py b/bilby/gw/prior.py index d88bc23d0..7f63d91ee 100644 --- a/bilby/gw/prior.py +++ b/bilby/gw/prior.py @@ -17,10 +17,12 @@ from .conversion import ( convert_to_lal_binary_black_hole_parameters, convert_to_lal_binary_neutron_star_parameters, generate_mass_parameters, generate_tidal_parameters, fill_from_fixed_priors, + generate_all_bbh_parameters, chirp_mass_and_mass_ratio_to_total_mass, total_mass_and_mass_ratio_to_component_masses) from .cosmology import get_cosmology from .source import PARAMETER_SETS +from .utils import calculate_time_to_merger DEFAULT_PRIOR_DIR = os.path.join(os.path.dirname(__file__), 'prior_files') @@ -683,6 +685,42 @@ class CBCPriorDict(ConditionalPriorDict): """ Return true if priors include phase parameters """ return self.is_nonempty_intersection("phase") + def validate_prior(self, duration, minimum_frequency, N=1000, error=True, warning=False): + """ Validate the prior is suitable for use + + Parameters + ========== + duration: float + The data duration in seconds + minimum_frequency: float + The minimum frequency in Hz of the analysis + N: int + The number of samples to draw when checking + """ + samples = self.sample(N) + samples = generate_all_bbh_parameters(samples) + deltaT = np.array([ + calculate_time_to_merger( + frequency=minimum_frequency, + mass_1=mass_1, + mass_2=mass_2, + ) + for (mass_1, mass_2) in zip(samples["mass_1"], samples["mass_2"]) + ]) + if np.all(deltaT < duration): + return True + if warning: + logger.warning( + "Prior contains regions of parameter space in which the signal" + f" is longer than the data duration {duration}s" + ) + return False + if error: + raise ValueError( + "Prior contains regions of parameter space in which the signal" + f" is longer than the data duration {duration}s" + ) + class BBHPriorDict(CBCPriorDict): def __init__(self, dictionary=None, filename=None, aligned_spin=False, diff --git a/bilby/gw/utils.py b/bilby/gw/utils.py index 8f8aeb035..a52bfd663 100644 --- a/bilby/gw/utils.py +++ b/bilby/gw/utils.py @@ -10,6 +10,7 @@ from ..core.utils import (ra_dec_to_theta_phi, speed_of_light, logger, run_commandline, check_directory_exists_and_if_not_mkdir, SamplesSummary, theta_phi_to_ra_dec) +from ..core.utils.constants import solar_mass def asd_from_freq_series(freq_data, df): @@ -1006,3 +1007,37 @@ def ln_i0(value): The natural logarithm of the bessel function """ return np.log(i0e(value)) + value + + +def calculate_time_to_merger(frequency, mass_1, mass_2, chi=0, safety=1.1): + """ Leading-order calculation of the time to merger from frequency + + This uses the XLALSimInspiralTaylorF2ReducedSpinChirpTime routine to + estimate the time to merger. Based on the implementation in + `pycbc.pnutils._get_imr_duration`. + + Parameters + ========== + frequency: float + The frequency (Hz) of the signal from which to calculate the time to merger + mass_1, mass_2: float + The detector frame component masses + chi: float + Dimensionless aligned-spin param + safety: + Multiplicitive safety factor + + Returns + ======= + time_to_merger: float + The time to merger from frequency in seconds + """ + + import lalsimulation + return safety * lalsimulation.SimInspiralTaylorF2ReducedSpinChirpTime( + frequency, + mass_1 * solar_mass, + mass_2 * solar_mass, + chi, + -1 + ) diff --git a/examples/gw_examples/injection_examples/fast_tutorial.py b/examples/gw_examples/injection_examples/fast_tutorial.py index 0f40818cc..3a2e18fc1 100644 --- a/examples/gw_examples/injection_examples/fast_tutorial.py +++ b/examples/gw_examples/injection_examples/fast_tutorial.py @@ -15,6 +15,7 @@ import bilby # going to inject the signal into duration = 4. sampling_frequency = 2048. +minimum_frequency = 20 # Specify the output directory and the name of the simulation. outdir = 'outdir' @@ -35,7 +36,8 @@ injection_parameters = dict( # Fixed arguments passed into the source model waveform_arguments = dict(waveform_approximant='IMRPhenomPv2', - reference_frequency=50., minimum_frequency=20.) + reference_frequency=50., + minimum_frequency=minimum_frequency) # Create the waveform_generator using a LAL BinaryBlackHole source function waveform_generator = bilby.gw.WaveformGenerator( @@ -66,13 +68,16 @@ ifos.inject_signal(waveform_generator=waveform_generator, # sampler. If we do nothing, then the default priors get used. priors = bilby.gw.prior.BBHPriorDict() priors['geocent_time'] = bilby.core.prior.Uniform( - minimum=injection_parameters['geocent_time'] - 1, - maximum=injection_parameters['geocent_time'] + 1, + minimum=injection_parameters['geocent_time'] - 0.1, + maximum=injection_parameters['geocent_time'] + 0.1, name='geocent_time', latex_label='$t_c$', unit='$s$') for key in ['a_1', 'a_2', 'tilt_1', 'tilt_2', 'phi_12', 'phi_jl', 'psi', 'ra', 'dec', 'geocent_time', 'phase']: priors[key] = injection_parameters[key] +# Perform a check that the prior does not extend to a parameter space longer than the data +priors.validate_prior(duration, minimum_frequency) + # Initialise the likelihood by passing in the interferometer data (ifos) and # the waveform generator likelihood = bilby.gw.GravitationalWaveTransient( diff --git a/test/gw/detector/interferometer_test.py b/test/gw/detector/interferometer_test.py index d204d6cbc..b01b57e17 100644 --- a/test/gw/detector/interferometer_test.py +++ b/test/gw/detector/interferometer_test.py @@ -61,7 +61,10 @@ class TestInterferometer(unittest.TestCase): self.waveform_generator.frequency_domain_strain = ( lambda _: self.wg_polarizations ) - self.parameters = dict(ra=0.0, dec=0.0, geocent_time=0.0, psi=0.0) + self.parameters = dict( + ra=0.0, dec=0.0, geocent_time=0.0, psi=0.0, + mass_1=100, mass_2=100 + ) bilby.core.utils.check_directory_exists_and_if_not_mkdir(self.outdir) -- GitLab