diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c91df81b115d2d65941fce1d6ca732001779ca..169ab1ac37537eb82430a965d5b658cb33442a5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Changes currently on master, but not under a tag. ### Changes +- Make BBH/BNS parameter conversion more logical +- Source frame masses/spins included in posterior - Make filling in posterior with fixed parameters work ## [0.3] 2018-01-02 diff --git a/bilby/core/result.py b/bilby/core/result.py index 3ca06858412997c83f0f78fde58e79b0598c2839..86faa4aa732370ad89a82f8f3d14e4351c7eef52 100644 --- a/bilby/core/result.py +++ b/bilby/core/result.py @@ -234,8 +234,9 @@ class Result(dict): elif k in self.parameter_labels: latex_labels.append(k) else: - raise ValueError('key {} not a parameter label or latex label' - .format(k)) + logger.info( + 'key {} not a parameter label or latex label'.format(k)) + latex_labels.append(' '.join(k.split('_'))) return latex_labels @property @@ -588,39 +589,6 @@ class Result(dict): self.prior_values[key]\ = priors[key].prob(self.posterior[key].values) - def construct_cbc_derived_parameters(self): - """ Construct widely used derived parameters of CBCs """ - self.posterior['mass_chirp'] = ( - (self.posterior.mass_1 * self.posterior.mass_2) ** 0.6 / ( - self.posterior.mass_1 + self.posterior.mass_2) ** 0.2) - self.search_parameter_keys.append('mass_chirp') - self.parameter_labels.append('$\mathcal{M}$') - - self.posterior['q'] = self.posterior.mass_2 / self.posterior.mass_1 - self.search_parameter_keys.append('q') - self.parameter_labels.append('$q$') - - self.posterior['eta'] = ( - (self.posterior.mass_1 * self.posterior.mass_2) / ( - self.posterior.mass_1 + self.posterior.mass_2) ** 2) - self.search_parameter_keys.append('eta') - self.parameter_labels.append('$\eta$') - - self.posterior['chi_eff'] = ( - (self.posterior.a_1 * np.cos(self.posterior.tilt_1) + - self.posterior.q * self.posterior.a_2 * - np.cos(self.posterior.tilt_2)) / (1 + self.posterior.q)) - self.search_parameter_keys.append('chi_eff') - self.parameter_labels.append('$\chi_{\mathrm eff}$') - - self.posterior['chi_p'] = ( - np.maximum(self.posterior.a_1 * np.sin(self.posterior.tilt_1), - (4 * self.posterior.q + 3) / (3 * self.posterior.q + 4) * - self.posterior.q * self.posterior.a_2 * - np.sin(self.posterior.tilt_2))) - self.search_parameter_keys.append('chi_p') - self.parameter_labels.append('$\chi_{\mathrm p}$') - def check_attribute_match_to_other_object(self, name, other_object): """ Check attribute name exists in other_object and is the same diff --git a/bilby/gw/conversion.py b/bilby/gw/conversion.py index 793fecb0b8862fd074aa469444e54929c3099634..ac40efe0cf323ae2c290d1a881da2251d6d3a8fa 100644 --- a/bilby/gw/conversion.py +++ b/bilby/gw/conversion.py @@ -1,6 +1,5 @@ from __future__ import division import numpy as np -import pandas as pd from ..core.utils import logger, solar_mass from ..core.prior import DeltaFunction @@ -47,6 +46,53 @@ def luminosity_distance_to_comoving_distance(distance): return redshift_to_comoving_distance(redshift) +@np.vectorize +def transform_precessing_spins(theta_jn, phi_jl, tilt_1, tilt_2, phi_12, a_1, + a_2, mass_1, mass_2, reference_frequency, phase): + """ + Vectorized version of + lalsimulation.SimInspiralTransformPrecessingNewInitialConditions + + All parameters are defined at the reference frequency + + Parameters + ---------- + theta_jn: float + Inclination angle + phi_jl: float + Spin phase angle + tilt_1: float + Primary object tilt + tilt_2: float + Secondary object tilt + phi_12: float + Relative spin azimuthal angle + a_1: float + Primary dimensionless spin magnitude + a_2: float + Secondary dimensionless spin magnitude + mass_1: float + Primary mass _in SI units_ + mass_2: float + Secondary mass _in SI units_ + reference_frequency: float + phase: float + Orbital phase + + Returns + ------- + iota: float + Transformed inclination + spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z: float + Cartesian spin components + """ + iota, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z = \ + lalsim.SimInspiralTransformPrecessingNewInitialConditions( + theta_jn, phi_jl, tilt_1, tilt_2, phi_12, a_1, a_2, mass_1, + mass_2, reference_frequency, phase) + return iota, spin_1x, spin_1y, spin_1z, spin_2x, spin_2y, spin_2z + + def convert_to_lal_binary_black_hole_parameters(parameters): """ Convert parameters we have into parameters we need. @@ -164,7 +210,7 @@ def convert_to_lal_binary_neutron_star_parameters(parameters): Mass: mass_1, mass_2 - Spin: a_1, a_2, tilt_1, tilt_2, phi_12, phi_jl + Spin: chi_1, chi_2, tilt_1, tilt_2, phi_12, phi_jl Extrinsic: luminosity_distance, theta_jn, phase, ra, dec, geocent_time, psi This involves popping a lot of things from parameters. @@ -187,6 +233,14 @@ def convert_to_lal_binary_neutron_star_parameters(parameters): converted_parameters, added_keys =\ convert_to_lal_binary_black_hole_parameters(converted_parameters) + # catch if tidal parameters aren't present + if not any([key in converted_parameters for key in + ['lambda_1', 'lambda_2', 'lambda_tilde', 'delta_lambda']]): + converted_parameters['lambda_1'] = 0 + converted_parameters['lambda_2'] = 0 + added_keys = added_keys + ['lambda_1', 'lambda_2'] + return converted_parameters, added_keys + if 'delta_lambda' in converted_parameters.keys(): converted_parameters['lambda_1'], converted_parameters['lambda_2'] =\ lambda_tilde_delta_lambda_to_lambda_1_lambda_2( @@ -406,6 +460,71 @@ def mass_1_and_chirp_mass_to_mass_ratio(mass_1, chirp_mass): return mass_ratio +def lambda_1_lambda_2_to_lambda_tilde(lambda_1, lambda_2, mass_1, mass_2): + """ + Convert from individual tidal parameters to domainant tidal term. + + See, e.g., Wade et al., https://arxiv.org/pdf/1402.5156.pdf. + + Parameters + ---------- + lambda_1: float + Tidal parameter of more massive neutron star. + lambda_2: float + Tidal parameter of less massive neutron star. + mass_1: float + Mass of more massive neutron star. + mass_2: float + Mass of less massive neutron star. + + Return + ------ + lambda_tilde: float + Dominant tidal term. + """ + eta = component_masses_to_symmetric_mass_ratio(mass_1, mass_2) + lambda_plus = lambda_1 + lambda_2 + lambda_minus = lambda_1 - lambda_2 + lambda_tilde = 8 / 13 * ( + (1 + 7 * eta - 31 * eta**2) * lambda_plus + + (1 - 4 * eta)**0.5 * (1 + 9 * eta - 11 * eta**2) * lambda_minus) + + return lambda_tilde + + +def lambda_1_lambda_2_to_delta_lambda(lambda_1, lambda_2, mass_1, mass_2): + """ + Convert from individual tidal parameters to second domainant tidal term. + + See, e.g., Wade et al., https://arxiv.org/pdf/1402.5156.pdf. + + Parameters + ---------- + lambda_1: float + Tidal parameter of more massive neutron star. + lambda_2: float + Tidal parameter of less massive neutron star. + mass_1: float + Mass of more massive neutron star. + mass_2: float + Mass of less massive neutron star. + + Return + ------ + delta_lambda: float + Second dominant tidal term. + """ + eta = component_masses_to_symmetric_mass_ratio(mass_1, mass_2) + lambda_plus = lambda_1 + lambda_2 + lambda_minus = lambda_1 - lambda_2 + delta_lambda = 1 / 2 * ( + (1 - 4 * eta) ** 0.5 * (1 - 13272 / 1319 * eta + 8944 / 1319 * eta**2) * + lambda_plus + (1 - 15910 / 1319 * eta + 32850 / 1319 * eta**2 + + 3380 / 1319 * eta**3) * lambda_minus) + + return delta_lambda + + def lambda_tilde_delta_lambda_to_lambda_1_lambda_2( lambda_tilde, delta_lambda, mass_1, mass_2): """ @@ -484,6 +603,29 @@ def lambda_tilde_to_lambda_1_lambda_2( return lambda_1, lambda_2 +def _generate_all_cbc_parameters(sample, defaults, base_conversion, + likelihood=None, priors=None): + """Generate all cbc parameters, helper function for BBH/BNS""" + output_sample = sample.copy() + waveform_defaults = defaults + for key in waveform_defaults: + try: + output_sample[key] = \ + likelihood.waveform_generator.waveform_arguments[key] + except (KeyError, AttributeError): + default = waveform_defaults[key] + output_sample[key] = default + logger.warning('Assuming {} = {}'.format(key, default)) + + output_sample = fill_from_fixed_priors(output_sample, priors) + output_sample, _ = base_conversion(output_sample) + output_sample = generate_mass_parameters(output_sample) + output_sample = generate_spin_parameters(output_sample) + output_sample = generate_source_frame_parameters(output_sample) + compute_snrs(output_sample, likelihood) + return output_sample + + def generate_all_bbh_parameters(sample, likelihood=None, priors=None): """ From either a single sample or a set of samples fill in all missing @@ -500,21 +642,43 @@ def generate_all_bbh_parameters(sample, likelihood=None, priors=None): priors: dict, optional Dictionary of prior objects, used to fill in non-sampled parameters. """ - output_sample = sample.copy() - if likelihood is not None: - output_sample['reference_frequency'] =\ - likelihood.waveform_generator.waveform_arguments[ - 'reference_frequency'] - output_sample['waveform_approximant'] =\ - likelihood.waveform_generator.waveform_arguments[ - 'waveform_approximant'] + waveform_defaults = { + 'reference_frequency': 50.0, 'waveform_approximant': 'IMRPhenomPv2', + 'minimum_frequency': 20.0} + output_sample = _generate_all_cbc_parameters( + sample, defaults=waveform_defaults, + base_conversion=convert_to_lal_binary_black_hole_parameters, + likelihood=likelihood, priors=priors) + return output_sample - output_sample = fill_from_fixed_priors(output_sample, priors) - output_sample, _ =\ - convert_to_lal_binary_black_hole_parameters(output_sample) - output_sample = generate_non_standard_parameters(output_sample) - output_sample = generate_component_spins(output_sample) - compute_snrs(output_sample, likelihood) + +def generate_all_bns_parameters(sample, likelihood=None, priors=None): + """ + From either a single sample or a set of samples fill in all missing + BNS parameters, in place. + + Since we assume BNS waveforms are aligned, component spins won't be + calculated. + + Parameters + ---------- + sample: dict or pandas.DataFrame + Samples to fill in with extra parameters, this may be either an + injection or posterior samples. + likelihood: bilby.gw.likelihood.GravitationalWaveTransient, optional + GravitationalWaveTransient used for sampling, used for waveform and + likelihood.interferometers. + priors: dict, optional + Dictionary of prior objects, used to fill in non-sampled parameters. + """ + waveform_defaults = { + 'reference_frequency': 50.0, 'waveform_approximant': 'TaylorF2', + 'minimum_frequency': 20.0} + output_sample = _generate_all_cbc_parameters( + sample, defaults=waveform_defaults, + base_conversion=convert_to_lal_binary_neutron_star_parameters, + likelihood=likelihood, priors=priors) + output_sample = generate_tidal_parameters(output_sample) return output_sample @@ -540,13 +704,12 @@ def fill_from_fixed_priors(sample, priors): return output_sample -def generate_non_standard_parameters(sample): +def generate_mass_parameters(sample): """ - Add the known non-standard parameters to the data frame/dictionary. + Add the known mass parameters to the data frame/dictionary. We add: - chirp mass, total mass, symmetric mass ratio, mass ratio, cos tilt 1, - cos tilt 2, cos iota + chirp mass, total mass, symmetric mass ratio, mass ratio Parameters ---------- @@ -569,12 +732,47 @@ def generate_non_standard_parameters(sample): output_sample['mass_ratio'] =\ component_masses_to_mass_ratio(sample['mass_1'], sample['mass_2']) - output_sample['cos_tilt_1'] = np.cos(output_sample['tilt_1']) - output_sample['cos_tilt_2'] = np.cos(output_sample['tilt_2']) - output_sample['cos_iota'] = np.cos(output_sample['iota']) + return output_sample + + +def generate_spin_parameters(sample): + """ + Add all spin parameters to the data frame/dictionary. + + We add: + cartestian spin components, chi_eff, chi_p cos tilt 1, cos tilt 2 + + Parameters + ---------- + sample : dict, pandas.DataFrame + The input dictionary with some spin parameters + + Returns + ------- + dict: The updated dictionary + + """ + output_sample = sample.copy() + + output_sample = generate_component_spins(output_sample) + + output_sample['chi_eff'] = (output_sample['spin_1z'] + + output_sample['spin_2z'] * + output_sample['mass_ratio']) /\ + (1 + output_sample['mass_ratio']) + + output_sample['chi_p'] = np.maximum( + (output_sample['spin_1x'] ** 2 + output_sample['spin_1y']**2)**0.5, + (4 * output_sample['mass_ratio'] + 3) / + (3 * output_sample['mass_ratio'] + 4) * output_sample['mass_ratio'] * + (output_sample['spin_2x'] ** 2 + output_sample['spin_2y']**2)**0.5) + + try: + output_sample['cos_tilt_1'] = np.cos(output_sample['tilt_1']) + output_sample['cos_tilt_2'] = np.cos(output_sample['tilt_2']) + except KeyError: + pass - output_sample['redshift'] =\ - luminosity_distance_to_redshift(sample['luminosity_distance']) return output_sample @@ -599,13 +797,12 @@ def generate_component_spins(sample): spin_conversion_parameters =\ ['iota', 'phi_jl', 'tilt_1', 'tilt_2', 'phi_12', 'a_1', 'a_2', 'mass_1', 'mass_2', 'reference_frequency', 'phase'] - if all(key in output_sample.keys() for key in spin_conversion_parameters)\ - and isinstance(output_sample, dict): + if all(key in output_sample.keys() for key in spin_conversion_parameters): output_sample['iota'], output_sample['spin_1x'],\ output_sample['spin_1y'], output_sample['spin_1z'], \ output_sample['spin_2x'], output_sample['spin_2y'],\ output_sample['spin_2z'] =\ - lalsim.SimInspiralTransformPrecessingNewInitialConditions( + transform_precessing_spins( output_sample['iota'], output_sample['phi_jl'], output_sample['tilt_1'], output_sample['tilt_2'], output_sample['phi_12'], output_sample['a_1'], @@ -618,40 +815,73 @@ def generate_component_spins(sample): np.arctan(output_sample['spin_1y'] / output_sample['spin_1x']) output_sample['phi_2'] =\ np.arctan(output_sample['spin_2y'] / output_sample['spin_2x']) + elif 'chi_1' in output_sample and 'chi_2' in output_sample: + output_sample['spin_1x'] = 0 + output_sample['spin_1y'] = 0 + output_sample['spin_1z'] = output_sample['chi_1'] + output_sample['spin_2x'] = 0 + output_sample['spin_2y'] = 0 + output_sample['spin_2z'] = output_sample['chi_2'] + else: + logger.warning("Component spin extraction failed.") + logger.warning(output_sample.keys()) - elif all(key in output_sample.keys() for key in spin_conversion_parameters)\ - and isinstance(output_sample, pd.DataFrame): - logger.debug('Extracting component spins.') - new_spin_parameters =\ - ['spin_1x', 'spin_1y', 'spin_1z', 'spin_2x', 'spin_2y', 'spin_2z'] - new_spins =\ - {name: np.zeros(len(output_sample)) for name in new_spin_parameters} - - for ii in range(len(output_sample)): - new_spins['iota'], new_spins['spin_1x'][ii],\ - new_spins['spin_1y'][ii], new_spins['spin_1z'][ii], \ - new_spins['spin_2x'][ii], new_spins['spin_2y'][ii],\ - new_spins['spin_2z'][ii] =\ - lalsim.SimInspiralTransformPrecessingNewInitialConditions( - output_sample['iota'][ii], output_sample['phi_jl'][ii], - output_sample['tilt_1'][ii], output_sample['tilt_2'][ii], - output_sample['phi_12'][ii], output_sample['a_1'][ii], - output_sample['a_2'][ii], - output_sample['mass_1'][ii] * solar_mass, - output_sample['mass_2'][ii] * solar_mass, - output_sample['reference_frequency'][ii], - output_sample['phase'][ii]) - - for name in new_spin_parameters: - output_sample[name] = new_spins[name] + return output_sample - output_sample['phi_1'] =\ - np.arctan(output_sample['spin_1y'] / output_sample['spin_1x']) - output_sample['phi_2'] =\ - np.arctan(output_sample['spin_2y'] / output_sample['spin_2x']) - else: - logger.warning("Component spin extraction failed.") +def generate_tidal_parameters(sample): + """ + Generate all tidal parameters + + lambda_tilde, delta_lambda + + Parameters + ---------- + sample: dict, pandas.DataFrame + Should contain lambda_1, lambda_2 + + Returns + ------- + output_sample: dict, pandas.DataFrame + Updated sample + """ + output_sample = sample.copy() + + output_sample['lambda_tilde'] =\ + lambda_1_lambda_2_to_lambda_tilde( + output_sample['lambda_1'], output_sample['lambda_2'], + output_sample['mass_1'], output_sample['mass_2']) + output_sample['delta_lambda'] = \ + lambda_1_lambda_2_to_delta_lambda( + output_sample['lambda_1'], output_sample['lambda_2'], + output_sample['mass_1'], output_sample['mass_2']) + + return output_sample + + +def generate_source_frame_parameters(sample): + """ + Generate source frame masses along with redshifts and comoving distance. + + Parameters + ---------- + sample: dict, pandas.DataFrame + + Returns + ------- + output_sample: dict, pandas.DataFrame + """ + output_sample = sample.copy() + + output_sample['redshift'] =\ + luminosity_distance_to_redshift(output_sample['luminosity_distance']) + output_sample['comoving_distance'] =\ + redshift_to_comoving_distance(output_sample['redshift']) + + for key in ['mass_1', 'mass_2', 'chirp_mass', 'total_mass']: + if key in output_sample: + output_sample['{}_source'.format(key)] =\ + output_sample[key] / (1 + output_sample['redshift']) return output_sample @@ -674,7 +904,6 @@ def compute_snrs(sample, likelihood): if isinstance(temp_sample, dict): temp = dict() for key in likelihood.waveform_generator.parameters.keys(): - # likelihood.waveform_generator.parameters[key] = temp_sample[key] temp[key] = temp_sample[key] signal_polarizations =\ likelihood.waveform_generator.frequency_domain_strain(temp) @@ -696,8 +925,6 @@ def compute_snrs(sample, likelihood): temp = dict() for key in set(temp_sample.keys()).intersection( likelihood.waveform_generator.parameters.keys()): - # likelihood.waveform_generator.parameters[key] =\ - # temp_sample[key][ii] temp[key] = temp_sample[key][ii] signal_polarizations =\ likelihood.waveform_generator.frequency_domain_strain(temp) diff --git a/bilby/gw/prior_files/binary_neutron_stars.prior b/bilby/gw/prior_files/binary_neutron_stars.prior index c30fa00bca63d5d64675c361f5e64b42fe45409d..1bc4d485c1cfdf89ddee3ab4b7b33d6cabd80654 100644 --- a/bilby/gw/prior_files/binary_neutron_stars.prior +++ b/bilby/gw/prior_files/binary_neutron_stars.prior @@ -8,8 +8,8 @@ mass_2 = Uniform(name='mass_2', minimum=1, maximum=2, unit='$M_{\\odot}$') # total_mass = Uniform(name='total_mass', minimum=2, maximum=4, unit='$M_{\\odot}$') # mass_ratio = Uniform(name='mass_ratio', minimum=0.5, maximum=1) # symmetric_mass_ratio = Uniform(name='symmetric_mass_ratio', minimum=0.22, maximum=0.25) -a_1 = Uniform(name='a_1', minimum= -0.05, maximum= 0.05) -a_2 = Uniform(name='a_2', minimum= -0.05, maximum= 0.05) +chi_1 = bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_1', latex_label='$\\chi_1$') +chi_2 = bilby.gw.prior.AlignedSpin(a_prior=Uniform(0, 0.05), z_prior=Uniform(-1, 1), name='chi_2', latex_label='$\\chi_2$') luminosity_distance = bilby.gw.prior.UniformComovingVolume(name='luminosity_distance', minimum=10, maximum=500, unit='Mpc') dec = Cosine(name='dec') ra = Uniform(name='ra', minimum=0, maximum=2 * np.pi) diff --git a/bilby/gw/source.py b/bilby/gw/source.py index 6ea32a002b47d9db62b0e1faf2ed77ca0af6662b..75bf22febf755b10732b21d057d7a738826f44bc 100644 --- a/bilby/gw/source.py +++ b/bilby/gw/source.py @@ -255,7 +255,7 @@ def supernova_pca_model( def lal_binary_neutron_star( - frequency_array, mass_1, mass_2, luminosity_distance, a_1, a_2, + frequency_array, mass_1, mass_2, luminosity_distance, chi_1, chi_2, iota, phase, lambda_1, lambda_2, ra, dec, geocent_time, psi, **kwargs): """ A Binary Neutron Star waveform model using lalsimulation @@ -269,10 +269,10 @@ def lal_binary_neutron_star( The mass of the lighter object in solar masses luminosity_distance: float The luminosity distance in megaparsec - a_1: float - Dimensionless spin magnitude - a_2: float - Dimensionless secondary spin magnitude + chi_1: float + Dimensionless aligned spin + chi_2: float + Dimensionless aligned spin iota: float Orbital inclination phase: float @@ -314,10 +314,10 @@ def lal_binary_neutron_star( spin_1x = 0 spin_1y = 0 - spin_1z = a_1 + spin_1z = chi_1 spin_2x = 0 spin_2y = 0 - spin_2z = a_2 + spin_2z = chi_2 longitude_ascending_nodes = 0.0 eccentricity = 0.0 diff --git a/examples/injection_examples/basic_tutorial.py b/examples/injection_examples/basic_tutorial.py index 1e90bcddcda848fb378792bb6ff7f788ba10a41f..2f3766f8adfd656b63e22b706fe80b5fc54d3bf9 100644 --- a/examples/injection_examples/basic_tutorial.py +++ b/examples/injection_examples/basic_tutorial.py @@ -1,9 +1,12 @@ #!/usr/bin/env python """ -Tutorial to demonstrate running parameter estimation on a reduced parameter space for an injected signal. +Tutorial to demonstrate running parameter estimation on a reduced parameter +space for an injected signal. -This example estimates the masses using a uniform prior in both component masses and distance using a uniform in -comoving volume prior on luminosity distance between luminosity distances of 100Mpc and 5Gpc, the cosmology is WMAP7. +This example estimates the masses using a uniform prior in both component masses +and distance using a uniform in +comoving volume prior on luminosity distance between luminosity distances of +100Mpc and 5Gpc, the cosmology is WMAP7. """ from __future__ import division, print_function @@ -11,7 +14,8 @@ import numpy as np import bilby -# Set the duration and sampling frequency of the data segment that we're going to inject the signal into +# Set the duration and sampling frequency of the data segment that we're going +# to inject the signal into duration = 4. sampling_frequency = 2048. @@ -24,12 +28,14 @@ bilby.core.utils.setup_logger(outdir=outdir, label=label) # Set up a random seed for result reproducibility. This is optional! np.random.seed(88170235) -# We are going to inject a binary black hole waveform. We first establish a dictionary of parameters that -# includes all of the different waveform parameters, including masses of the two black holes (mass_1, mass_2), +# We are going to inject a binary black hole waveform. We first establish a +# dictionary of parameters that includes all of the different waveform +# parameters, including masses of the two black holes (mass_1, mass_2), # spins of both black holes (a, tilt, phi), etc. -injection_parameters = dict(mass_1=36., mass_2=29., a_1=0.4, a_2=0.3, tilt_1=0.5, tilt_2=1.0, phi_12=1.7, phi_jl=0.3, - luminosity_distance=2000., iota=0.4, psi=2.659, phase=1.3, geocent_time=1126259642.413, - ra=1.375, dec=-1.2108) +injection_parameters = dict( + mass_1=36., mass_2=29., a_1=0.4, a_2=0.3, tilt_1=0.5, tilt_2=1.0, + phi_12=1.7, phi_jl=0.3, luminosity_distance=4000., iota=0.4, psi=2.659, + phase=1.3, geocent_time=1126259642.413, ra=1.375, dec=-1.2108) # Fixed arguments passed into the source model waveform_arguments = dict(waveform_approximant='IMRPhenomPv2', @@ -51,29 +57,37 @@ ifos.set_strain_data_from_power_spectral_densities( ifos.inject_signal(waveform_generator=waveform_generator, parameters=injection_parameters) -# Set up prior, which is a dictionary -# By default we will sample all terms in the signal models. However, this will take a long time for the calculation, -# so for this example we will set almost all of the priors to be equall to their injected values. This implies the -# prior is a delta function at the true, injected value. In reality, the sampler implementation is smart enough to -# not sample any parameter that has a delta-function prior. -# The above list does *not* include mass_1, mass_2, iota and luminosity_distance, which means those are the parameters -# that will be included in the sampler. If we do nothing, then the default priors get used. +# Set up PriorSet, which inherits from dict +# By default we will sample all terms in the signal models. However, this will +# take a long time for the calculation, so for this example we will set almost +# all of the priors to be equal to their injected values. This implies the +# prior is a delta function at the true, injected value. In reality, the +# sampler implementation is smart enough to not sample any parameter that has a +# delta-function prior. The above list does *not* include mass_1, mass_2, iota +# and luminosity_distance, which means those are the parameters that will be +# included in the sampler. If we do nothing, then the default priors get used. priors = bilby.gw.prior.BBHPriorSet() -priors['geocent_time'] = bilby.core.prior.Uniform( - minimum=injection_parameters['geocent_time'] - 1, - maximum=injection_parameters['geocent_time'] + 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['geocent_time'] = bilby.core.prior.Uniform( +# minimum=injection_parameters['geocent_time'] - 1, +# maximum=injection_parameters['geocent_time'] + 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] -# Initialise the likelihood by passing in the interferometer data (IFOs) and the waveoform generator -likelihood = bilby.gw.GravitationalWaveTransient(interferometers=ifos, waveform_generator=waveform_generator, - time_marginalization=False, phase_marginalization=False, - distance_marginalization=False, prior=priors) +# Initialise the likelihood by passing in the interferometer data (IFOs) +# and the waveoform generator +likelihood = bilby.gw.GravitationalWaveTransient( + interferometers=ifos, waveform_generator=waveform_generator, + time_marginalization=False, phase_marginalization=False, + distance_marginalization=False, prior=priors) # Run sampler. In this case we're going to use the `dynesty` sampler -result = bilby.run_sampler(likelihood=likelihood, priors=priors, sampler='dynesty', npoints=1000, - injection_parameters=injection_parameters, outdir=outdir, label=label) +result = bilby.run_sampler( + likelihood=likelihood, priors=priors, sampler='nestle', npoints=1000, + injection_parameters=injection_parameters, outdir=outdir, label=label, + conversion_function=bilby.gw.conversion.generate_all_bbh_parameters) + # make some plots of the outputs result.plot_corner() diff --git a/examples/injection_examples/binary_neutron_star_example.py b/examples/injection_examples/binary_neutron_star_example.py index 4f826a060037129c891286c28e2a022d626a6edd..76cf0c1ec9d27f79099f2798df6847a963070ee1 100644 --- a/examples/injection_examples/binary_neutron_star_example.py +++ b/examples/injection_examples/binary_neutron_star_example.py @@ -25,9 +25,9 @@ np.random.seed(88170235) # We are going to inject a binary neutron star waveform. We first establish a # dictionary of parameters that includes all of the different waveform # parameters, including masses of the two black holes (mass_1, mass_2), -# spins of both black holes (a_1,a_2) , etc. +# aligned spins of both black holes (chi_1, chi_2), etc. injection_parameters = dict( - mass_1=1.5, mass_2=1.3, a_1=0.0, a_2=0.0, luminosity_distance=50., + mass_1=1.5, mass_2=1.3, chi_1=0.02, chi_2=0.02, luminosity_distance=50., iota=0.4, psi=2.659, phase=1.3, geocent_time=1126259642.413, ra=1.375, dec=-1.2108, lambda_1=400, lambda_2=450) @@ -46,6 +46,7 @@ waveform_arguments = dict(waveform_approximant='TaylorF2', waveform_generator = bilby.gw.WaveformGenerator( duration=duration, sampling_frequency=sampling_frequency, frequency_domain_source_model=bilby.gw.source.lal_binary_neutron_star, + parameter_conversion=bilby.gw.conversion.convert_to_lal_binary_neutron_star_parameters, waveform_arguments=waveform_arguments) # Set up interferometers. In this case we'll use three interferometers @@ -60,11 +61,25 @@ interferometers.set_strain_data_from_power_spectral_densities( interferometers.inject_signal(parameters=injection_parameters, waveform_generator=waveform_generator) +# Load the default prior for binary neutron stars. +# We're going to sample in chirp_mass, symmetric_mass_ratio, lambda_tilde, and +# delta_lambda rather than mass_1, mass_2, lambda_1, and lambda_2. priors = bilby.gw.prior.BNSPriorSet() -for key in ['a_1', 'a_2', 'psi', 'geocent_time', 'ra', 'dec', +for key in ['psi', 'geocent_time', 'ra', 'dec', 'chi_1', 'chi_2', 'iota', 'luminosity_distance', 'phase']: priors[key] = injection_parameters[key] - +priors.pop('mass_1') +priors.pop('mass_2') +priors.pop('lambda_1') +priors.pop('lambda_2') +priors['chirp_mass'] = bilby.core.prior.Gaussian( + 1.215, 0.1, name='chirp_mass', unit='$M_{\\odot}$') +priors['symmetric_mass_ratio'] = bilby.core.prior.Uniform( + 0.1, 0.25, name='symmetric_mass_ratio') +priors['lambda_tilde'] = bilby.core.prior.Uniform(0, 5000, name='lambda_tilde') +priors['delta_lambda'] = bilby.core.prior.Uniform( + -5000, 5000, name='delta_lambda') + # Initialise the likelihood by passing in the interferometer data (IFOs) # and the waveoform generator likelihood = bilby.gw.GravitationalWaveTransient( @@ -75,7 +90,8 @@ likelihood = bilby.gw.GravitationalWaveTransient( # Run sampler. In this case we're going to use the `nestle` sampler result = bilby.run_sampler( likelihood=likelihood, priors=priors, sampler='nestle', npoints=1000, - injection_parameters=injection_parameters, outdir=outdir, label=label) + injection_parameters=injection_parameters, outdir=outdir, label=label, + conversion_function=bilby.gw.conversion.generate_all_bns_parameters) result.plot_corner() diff --git a/test/conversion_test.py b/test/conversion_test.py index b578237877fac820ba2d7952f68048a8b2b11f9b..3d2d698582db814f0b58bf34e32b6f7d75a27132 100644 --- a/test/conversion_test.py +++ b/test/conversion_test.py @@ -97,8 +97,20 @@ class TestBasicConversions(unittest.TestCase): self.assertTrue(all([abs(self.lambda_1 - lambda_1) < 1e-5, abs(self.lambda_2 - lambda_2) < 1e-5])) + def test_lambda_1_lambda_2_to_lambda_tilde(self): + lambda_tilde = \ + conversion.lambda_1_lambda_2_to_lambda_tilde( + self.lambda_1, self.lambda_2, self.mass_1, self.mass_2) + self.assertTrue((self.lambda_tilde - lambda_tilde) < 1e-5) -class TestConvertToLALBBHParams(unittest.TestCase): + def test_lambda_1_lambda_2_to_delta_lambda(self): + delta_lambda = \ + conversion.lambda_1_lambda_2_to_lambda_tilde( + self.lambda_1, self.lambda_2, self.mass_1, self.mass_2) + self.assertTrue((self.delta_lambda - delta_lambda) < 1e-5) + + +class TestConvertToLALParams(unittest.TestCase): def setUp(self): self.search_keys = [] @@ -110,7 +122,7 @@ class TestConvertToLALBBHParams(unittest.TestCase): del self.parameters del self.remove - def test_cos_angle_to_angle_conversion(self): + def test_bbh_cos_angle_to_angle_conversion(self): with mock.patch('numpy.arccos') as m: m.return_value = 42 self.parameters['cos_tilt_1'] = 1 @@ -119,6 +131,16 @@ class TestConvertToLALBBHParams(unittest.TestCase): self.parameters) self.assertDictEqual(self.parameters, dict(tilt_1=42, cos_tilt_1=1)) + def test_bns_cos_angle_to_angle_conversion(self): + with mock.patch('numpy.arccos') as m: + m.return_value = 42 + self.parameters['cos_tilt_1'] = 1 + self.parameters, _ = \ + conversion.convert_to_lal_binary_neutron_star_parameters( + self.parameters) + self.assertDictEqual(self.parameters, dict( + tilt_1=42, cos_tilt_1=1, lambda_1=0, lambda_2=0)) + if __name__ == '__main__': unittest.main()