Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALInference 4.1.9.1-8a6b96f
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
lalinference_evolve_spins_and_append_samples.py
Go to the documentation of this file.
1##python
2'''
3Code to read in LALInference HDF5 posterior samples, evolve the spins up to a specified value (default: Schwarzschild ISCO), and write the resulting evolved spin samples to the HDF5 file.
4
5Usage:
6python lalinference_evolve_spins_and_append_samples.py --sample_file <directory path to the posterior samples> [--vfinal <final orbital velocity>]
7'''
8# NKJ-M and Anuradha Gupta, 05.2019
9
10import numpy as np
11import argparse
12import lalsimulation as lalsim
13import h5py
14
15from numpy.linalg import norm
16from lal import MSUN_SI, MTSUN_SI
17
18import matplotlib
19matplotlib.use('Agg')
20
21from lalinference.io.hdf5 import read_samples, write_samples, extract_metadata
22from astropy.table import Column, Table, hstack
23
24from lalinference.bayespputils import mc2ms
25
26# Define a convenience function
28
29 # norms and normalizing
30 chi1_v_norm = norm(chi1_v)
31 chi2_v_norm = norm(chi2_v)
32
33 Ln_v /= norm(Ln_v)
34
35 # dot products
36 chi1dL_v = np.dot(chi1_v, Ln_v)
37 chi2dL_v = np.dot(chi2_v, Ln_v)
38
39 # in-plane spins
40
41 chi1inplane = chi1_v - chi1dL_v*Ln_v
42 chi2inplane = chi2_v - chi2dL_v*Ln_v
43
44 # computing cosine of tilts and phi12
45 cos_tilt1 = chi1dL_v/chi1_v_norm
46 cos_tilt2 = chi2dL_v/chi2_v_norm
47 cos_phi12 = np.dot(chi1inplane,chi2inplane)/(norm(chi1inplane)*norm(chi2inplane))
48
49 # set quadrant of phi12
50 phi12_evol_i = np.arccos(cos_phi12)
51 if np.sign(np.dot(Ln_v,np.cross(chi1_v, chi2_v))) < 0:
52 phi12_evol_i = 2.*np.pi - phi12_evol_i
53
54 return np.arccos(cos_tilt1), np.arccos(cos_tilt2), phi12_evol_i
55
56# Settings
57dt = 0.1 # steps in time for the integration, in terms of the total mass of the binary
58
59# Choose which approximant to use for spin evolution
60approx = lalsim.GetApproximantFromString("SpinTaylorT5")
61
62# Set up the parsing
63parser = argparse.ArgumentParser(description = 'Evolve the LALInference posterior samples of spins and append to HDF5 file of samples')
64parser.add_argument("--sample_file", help = "path to the HDF5 posterior samples file", required=True)
65parser.add_argument("--vfinal", help = "final orbital velocity for the evolution (default is the Schwarzschild ISCO velocity 6**-0.5 ~= 0.408)", type=str, default="ISCO")
66args = parser.parse_args()
67
68hdf_pos_file = args.sample_file
69
70if args.vfinal=='ISCO':
71 v_final = 6.**-0.5
72 label = '_isco'
73else:
74 try:
75 v_final = float(args.vfinal)
76 except ValueError:
77 raise ValueError("vfinal has to either be ISCO or a fraction of the speed of light")
78 label = '_evol_vfinal'
79
80# Functions for table formatting
81
82def _identity(x):
83 return x
84
85_colname_map = (('tilt_spin1' + label, 'tilt1' + label, _identity),
86 ('tilt_spin2' + label, 'tilt2' + label, _identity))
87
88def _remap_colnames(table):
89 for old_name, new_name, func in _colname_map:
90 if old_name in table.colnames:
91 table[new_name] = func(table.columns.pop(old_name))
92
93# read the posterior samples and metadata
94data = read_samples(hdf_pos_file)
95metadata = {}
96run_identifier = extract_metadata(hdf_pos_file, metadata)
97
98# extract mass and spin samples
99if ('mc' in data.dtype.names) and ('q' in data.dtype.names):
100 q = np.atleast_1d(data['q'])
101 eta = q/(1. + q)/(1. + q)
102 m1, m2 = mc2ms(np.atleast_1d(data['mc']), eta) # We use the redshifted masses here, since the starting frequency is in the detector frame
103else:
104 raise ValueError("Chirp mass and mass ratio are not found in %s. There is likely something wrong with this posterior file."%hdf_pos_file)
105if ('a1' in data.dtype.names) and ('a2' in data.dtype.names):
106 chi1, chi2 = np.atleast_1d(data['a1']), np.atleast_1d(data['a2'])
107else:
108 raise ValueError("No spin magnitude samples were found in %s. There are no spins to evolve."%hdf_pos_file)
109if ('tilt1' in data.dtype.names) and ('tilt2' in data.dtype.names):
110 tilt1, tilt2 = np.atleast_1d(data['tilt1']), np.atleast_1d(data['tilt2'])
111else:
112 raise ValueError("No tilt angle samples were found in %s; cannot evolve spins."%hdf_pos_file)
113if 'phi12' in data.dtype.names:
114 phi12 = np.atleast_1d(data['phi12'])
115else:
116 raise ValueError("No phi12 samples were found in %s; cannot evolve spins."%hdf_pos_file)
117
118# Extract start frequency appropriate for the waveform used in the analysis
119
120if 'LAL_APPROXIMANT' not in data.dtype.names:
121 raise ValueError('LAL_APPROXIMANT is missing--unable to determine which waveform was used to obtain the samples.')
122
123spinfreq_enum = np.array([lalsim.SimInspiralGetSpinFreqFromApproximant(int(lal_approx)) for lal_approx in data['LAL_APPROXIMANT']])
124
125if len(np.where(spinfreq_enum == lalsim.SIM_INSPIRAL_SPINS_CASEBYCASE)[0]) > 0:
126 raise ValueError('Unable to evolve spins since approximant does not have a set frequency at which the spins are defined.')
127
128f_start = np.where(np.array(spinfreq_enum == lalsim.SIM_INSPIRAL_SPINS_FLOW), data['flow'], data['f_ref'])
129
130# Evolve spins
131tilt1_evol = np.zeros_like(m1)
132tilt2_evol = np.zeros_like(m1)
133phi12_evol = np.zeros_like(m1)
134
135for i, _ in enumerate(m1):
136 # Only evolve spins if at least one spin magnitude is above 1e-3
137 if np.logical_or(chi1[i] > 1e-3, chi2[i] > 1e-3):
138 mtot_s = (m1[i] + m2[i])*MTSUN_SI # total mass in seconds
139 f_final = v_final*v_final*v_final/(mtot_s*np.pi)
140
141 _, _, chi1x_v_data, chi1y_v_data, chi1z_v_data, chi2x_v_data, chi2y_v_data, chi2z_v_data, Lnx_v_data, Lny_v_data, Lnz_v_data, _, _, _ = lalsim.SimInspiralSpinTaylorPNEvolveOrbit(deltaT=dt*mtot_s, m1=m1[i]*MSUN_SI, m2=m2[i]*MSUN_SI, fStart=f_start[i], fEnd=f_final, s1x=chi1[i]*np.sin(tilt1[i]), s1y=0., s1z=chi1[i]*np.cos(tilt1[i]), s2x=chi2[i]*np.sin(tilt2[i])*np.cos(phi12[i]), s2y=chi2[i]*np.sin(tilt2[i])*np.sin(phi12[i]), s2z=chi2[i]*np.cos(tilt2[i]), lnhatx=0., lnhaty=0., lnhatz=1., e1x=1., e1y=0., e1z=0., lambda1=0., lambda2=0., quadparam1=1., quadparam2=1., spinO=6, tideO=0, phaseO=7, lscorr=0, approx=approx)
142
143 # Set index to take from array output by lalsim.SimInspiralSpinTaylorPNEvolveOrbit: -1 for evolving forward in time and 0 for evolving backward in time
144 if f_start[i] <= f_final:
145 idx_use = -1
146 else:
147 idx_use = 0
148
149 chi1_v = np.array([chi1x_v_data.data.data[idx_use], chi1y_v_data.data.data[idx_use], chi1z_v_data.data.data[idx_use]])
150 chi2_v = np.array([chi2x_v_data.data.data[idx_use], chi2y_v_data.data.data[idx_use], chi2z_v_data.data.data[idx_use]])
151
152 Ln_v = np.array([Lnx_v_data.data.data[idx_use], Lny_v_data.data.data[idx_use], Lnz_v_data.data.data[idx_use]])
153
154 tilt1_evol[i], tilt2_evol[i], phi12_evol[i] = tilts_and_phi12_from_Cartesian_spins_and_L(chi1_v, chi2_v, Ln_v)
155 else:
156 tilt1_evol[i], tilt2_evol[i], phi12_evol[i] = tilt1[i], tilt2[i], phi12[i]
157
158# Setup for output
159
160# Get the names of the columns
161colnames = np.array(data.colnames)
162
163tilt1_id = np.where(colnames=='tilt1')[0][0]
164tilt2_id = np.where(colnames=='tilt2')[0][0]
165phi12_id = np.where(colnames=='phi12')[0][0]
166f_ref_id = np.where(colnames=='f_ref')[0][0]
167
168# Get the meta for tilt1, tilt2, phi12
169meta_tilt1 = tuple(data.columns.items())[tilt1_id][1].meta
170meta_tilt2 = tuple(data.columns.items())[tilt2_id][1].meta
171meta_phi12 = tuple(data.columns.items())[phi12_id][1].meta
172meta_f_ref = tuple(data.columns.items())[f_ref_id][1].meta
173
174# Create an astropy table with the evolved spin samples
175tilts_evol = Table([Column(tilt1_evol, name='tilt1' + label, meta=meta_tilt1), Column(tilt2_evol, name='tilt2' + label, meta=meta_tilt2), Column(phi12_evol, name='phi12' + label, meta=meta_phi12)])
176
177# Append the columns to the existing astropy table of samples from the HDF5 file
178data_joined = hstack([data, tilts_evol])
179
180if v_final != "ISCO":
181 vfinal_col = Table([Column(v_final*np.ones_like(tilt1_evol), name='vfinal', meta=meta_f_ref)])
182 data_joined = hstack([data_joined, vfinal_col])
183
184_remap_colnames(data_joined)
185
186f = h5py.File(hdf_pos_file, 'r')
187path = '/lalinference/'+run_identifier+'/posterior_samples'
188level = f[path]
189arrt = level.attrs
190names = np.array([list(arrt.items())[i][0] for i in range(len(list(arrt.items())))])
191num_names = 0
192for name in names:
193 if 'NAME' in name:
194 num_names += 1
195
196# Updating the metadata
197metadata[path]['FIELD_{0}_NAME'.format(num_names+1)]='tilt_spin1' + label
198metadata[path]['FIELD_{0}_NAME'.format(num_names+2)]='tilt_spin2' + label
199metadata[path]['FIELD_{0}_NAME'.format(num_names+3)]='phi12' + label
200
201# Write the joined table
202write_samples(data_joined, hdf_pos_file, path=path, metadata=metadata, overwrite=True)
static REAL8 norm(const REAL8 x[3])
def mc2ms(mc, eta)
Utility function for converting mchirp,eta to component masses.
def extract_metadata(filename, metadata, log_noise_evidences=[], log_max_likelihoods=[], nlive=[], dset_name=None, nest=False, strict_versions=True)
Extract metadata from HDF5 sample chain file.
Definition: hdf5.py:332
def write_samples(table, filename, metadata=None, **kwargs)
Write an HDF5 sample chain file.
Definition: hdf5.py:243
def read_samples(filename, path=None, tablename=POSTERIOR_SAMPLES)
Read an HDF5 sample chain file.
Definition: hdf5.py:168