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