2This script computes the tilt angles at infinity from a given reference frequency, by combining orbit-averaged evolution at higher frequencies
3until a transition frequency, with precession-averaged evolution until infinite separation.
4There is also an option to compute the bounds on the tilts and an average value at a finite separation, though this has not been tested extensively.
5This implementation is described in the paper, <https://dcc.ligo.org/P2100029>, arXiv:2107.11902
12from .calc_tilts_prec_avg_regularized
import prec_avg_tilt_comp
13from .tilts_at_infinity_utils
import *
14import lalsimulation
as lalsim
15from warnings
import warn
18def get_tilts(chi1x, chi1y, chi1z, chi2x, chi2y, chi2z, Lnx, Lny, Lnz):
21 Given the spins and ang momentum at a given frequency, find the tilt
and in-plane spin angles at that frequency.
24 chi1x, chi1y, chi1z: Cartesian spin-magnitude components
for the primary object (m1) of the binary
25 chi2x, chi2y, chi2z: Cartesian spin-magnitude components
for the secondary object (m2) of the binary
26 Lnx, Lny, Lnz: Cartesian components of the direction of the Newtonian orbital angular momentum (will be normalized) of the binary
29 tilt1, tilt2: tilt angles of the binary spin-vectors w.r.t. the z-axis: the direction of the Newtonian orbital angular momentum
30 phi12: angle between projection of the two spin-vectors onto the xy plane
33 chi1 = np.array([chi1x, chi1y, chi1z])
34 chi2 = np.array([chi2x, chi2y, chi2z])
36 Ln = np.array([Lnx, Lny, Lnz])
39 chi1_norm = np.linalg.norm(chi1)
40 chi2_norm = np.linalg.norm(chi2)
42 Ln /= np.linalg.norm(Ln)
45 chi1dL = np.dot(chi1, Ln)
46 chi2dL = np.dot(chi2, Ln)
49 chi1inplane = chi1 - chi1dL*Ln
50 chi2inplane = chi2 - chi2dL*Ln
53 cos_tilt1 = np.clip(chi1dL/chi1_norm, -1., 1.)
54 cos_tilt2 = np.clip(chi2dL/chi2_norm, -1., 1.)
55 cos_phi12 = np.clip(np.dot(chi1inplane,chi2inplane)/(np.linalg.norm(chi1inplane) * np.linalg.norm(chi2inplane)),-1.,1.)
58 phi12_evol_i = np.arccos(cos_phi12)
59 if np.sign(np.dot(Ln,np.cross(chi1, chi2))) < 0:
60 phi12_evol_i = 2.*np.pi - phi12_evol_i
62 return np.arccos(cos_tilt1), np.arccos(cos_tilt2), phi12_evol_i
68 Calculates the transition orbital speed (v_trans) to shift from orbit-averaged to precession-averaged evolution
in this
69 hybrid spin evolution code. v_trans depends on the mass ratio, (q),
and is determined using the fitting curve
from the
70 paper [Eq. (31)
in the paper, <https://dcc.ligo.org/P2100029>, arXiv:2107.11902]
74 q, the binary mass ratio defined
as m2/m1,
with m1 being the primary (heavier) object; range (0,1].
80 raise ValueError(
"The mass ratio must be a float between 0 and 1, defined as m2/m1 where m1 is the heavier component")
90 Determine the number of frequency steps to use in each segment of the integration
while performing orbit-averaged evolution
94 v_trans (float): the transition orbital speed determined by the fit
in calc_v_trans()
95 The value
for v_trans must be >= 0.01, which
is the least transition orbital speed given
in our fit
in the paper [Eq. (31)
in
96 <https://dcc.ligo.org/P2100029>, arXiv:2107.11902]
103 elif 0.03 < v_trans <= 0.05:
105 elif 0.02 < v_trans <= 0.03:
107 elif 0.01 <= v_trans <= 0.02:
110 raise ValueError(
"The number of steps has not been calibrated for v_trans < 0.01 and might lead to memory errors")
117 Determine the step-size (dt) to use in each frequency interval used
for orbit-averaged evolution
from fref to v_trans.
118 This function returns the denominator X
in the constant c = 1/X, such that f_high*dt = c
in determining the timestep
119 of orbital evolution. Here, f_high
is the higher frequency
in the frequency interval.
122 v_trans (float): the transition orbital speed determined by the fit
in calc_v_trans()
129 elif 0.03 < v_trans <= 0.05:
131 elif 0.01 <= v_trans <= 0.03:
134 raise ValueError(
"Evolution to v_trans lower than 0.01 not possible with current configuration")
138def calc_tilts_at_infty_hybrid_evolve(m1, m2, chi1, chi2, tilt1, tilt2, phi12, fref, approx="SpinTaylorT5", spinO=6, lscorr=1, verbose=False, prec_only=False, version='v1', failure_mode='None', **kwargs):
140 Calculate tilts at infinity with hybrid orbit-averaged
and precession-averaged evolution
141 Evolves tilt1
and tilt2
for a given binary
from a reference frequency, fref, to infinite separation by first evolving using orbit-averaged evolution
142 until a transition orbital speed (v_trans) given by
calc_v_trans(),
and from that point until infinity using precession-averaged evolution.
144 There
is also an option to compute the bounds on the tilts
and average values at a finite separation (given by the value of the orbital angular momentum, Lf)
145 but the application of the hybrid evolution to this case
is not so well tested. In particular,
for small enough values of Lf, the hybrid evolution will end
146 at a larger value of the orbital angular momentum than Lf
and the precession-averaged evolution will fail because of this. This function does
not check
for
152 m1, m2: Detector frame masses of the binary,
in kg
153 chi1, chi2: The dimensionless spin-magnitudes of the binary
154 tilt1, tilt2: tilt angles of the binary spin-vectors w.r.t. the z-axis: the direction of the Newtonian orbital angular momentum
155 phi12: angle between projection of the two spin-vectors onto the xy plane
156 fref: Reference frequency,
in Hz; must be <= 20 Hz, since the stepwise evolution
is currently only calibrated
for such values
159 approx: The approximant to use (options:
"SpinTaylorT1",
"SpinTaylorT4",
"SpinTaylorT5"), default:
"SpinTaylorT5"
160 spinO: The order of the spin contributions included
in the post-Newtonian expansion (possible values:[4,5,6]), default: 6
161 lscorr: activates spin contributions to the orbital angular momentum
in the precession equations, default: 1 (active)
162 verbose (bool): display details of integration steps
and print output of orbit-averaged evolution, default:
False
163 prec_only: Use only the precession-averaged evolution
for a quick but less accurate result, default:
False
164 version: Version of the calculation to use--currently, two versions are available: v1 divides the orbit-averaged part of the hybrid evolution into a series of multiple integration steps,
while in v2 it proceeds
in a single step using a modified integrator that only outputs the final spin values, default:
"v1", though this will be changed to
"v2" in a near future release, since the
"v2" evolution
is much faster
and somewhat more accurate
165 failure_mode: How the code behaves when the evolution fails.
'Error' means that the code raises a RuntimeError,
while 'NAN' and 'None' mean that the code raises a RuntimeWarning
and returns np.nan
or None for the output, respectively, default:
'None'
167 **kwargs: dict, optional: precession-averaged evolution settings, passed through **kwargs to
prec_avg_tilt_comp()
168 Please refer to the
prec_avg_tilt_comp() documentation
in calc_tilts_prec_avg_regularized.py
for the list of settings
170 NOTE: The keyword argument Lf: Final magnitude of orbital angular momentum,
if set to
None, gives the output at infinity; this
is the default.
171 To obtain tilts at a finite separation, provide the corresponding Lf
in total mass = 1 units
173 NOTE:
prec_avg_tilt_comp() options LPNorder
and LPNspins are
not available through here, but are set automatically by the choice of spinO
and lscorr.
177 Dictionary
with entries
'tilt1_inf',
'tilt2_inf' for evolution to infinity,
and entries
'tilt1_transition',
'tilt2_transition',
178 'phi12_transition' for the tilts at the transition orbital speed (v_trans).
180 NOTE: If kwarg Lf
is not set to
None in prec_avg_tilt_comp(), the output gives bounds
and average values
for the tilts at a final separation determined by Lf.
181 In this case, the entries
'tilt1_inf' and 'tilt2_inf' will be replaced by
'tilt1_sep_min',
'tilt1_sep_max',
'tilt1_sep_avg',
'tilt2_sep_min',
'tilt2_sep_max',
186 if version
in [
'v1',
'v2']:
187 version_prec_avg =
'v1'
190 warn(
"v1 of the hybrid tilts at infinity calculation is deprecated, since v2 is much faster and somewhat more accurate. In a near future release, the default version will be changed to v2.", FutureWarning)
192 raise ValueError(
"Only versions ['v1', 'v2'] are available at the moment, while version = %s"%version)
197 if failure_mode ==
'NAN':
198 failure_output = np.nan
199 failure_output_string =
'np.nan'
201 failure_output =
None
202 failure_output_string =
'None'
224 if (tilt1 == np.pi
and tilt2
in [0., np.pi])
or (tilt1 == 0.
and tilt2 == 0.):
228 if chi1 == 0.
or chi2 == 0.:
233 spin_angles_output =
prec_avg_tilt_comp(m1, m2, chi1, chi2, tilt1, tilt2, phi12, fref, LPNorder = 2, LPNspins =
False, **kwargs)
234 spin_angles_output[
"tilt1_transition"] =
None
235 spin_angles_output[
"tilt2_transition"] =
None
236 spin_angles_output[
"phi12_transition"] =
None
237 spin_angles_output[
"f_transition"] =
None
238 return spin_angles_output
255 MT_s = (m1 + m2) * kg_to_s
260 print(
"v_trans = {}".format(v_trans))
261 f_trans = v_trans**3/np.pi/MT_s
264 v_ref = np.power((fref*(np.pi*MT_s)),1./3)
275 raise ValueError(
"The value of lscorr should either be 0 or 1")
277 approx = lalsim.GetApproximantFromString(approx)
281 elif spinO == 5
and lscorr == 0:
283 elif spinO == 5
and lscorr == 1:
285 elif spinO == 6
and lscorr == 0:
287 elif spinO == 6
and lscorr == 1:
295 raise ValueError(
"Check your SpinO input, which must be one from [4,5,6]; given input = {}".format(spinO))
298 chi1x_high, chi1y_high, chi1z_high = chi1*np.sin(tilt1), 0.0, chi1*np.cos(tilt1)
299 chi2x_high, chi2y_high, chi2z_high = chi2*np.sin(tilt2)*np.cos(phi12), chi2*np.sin(tilt2)*np.sin(phi12), chi2*np.cos(tilt2)
309 lalpars=lal.CreateDict()
310 lalsim.SimInspiralWaveformParamsInsertFinalFreq(lalpars, f_trans)
311 lalsim.SimInspiralWaveformParamsInsertPNSpinOrder(lalpars, spinO)
312 lalsim.SimInspiralWaveformParamsInsertPNPhaseOrder(lalpars, phaseO)
313 lalsim.SimInspiralWaveformParamsInsertLscorr(lalpars, lscorr)
324 lalsim.SimInspiralWaveformParamsInsertOnlyFinal(lalpars, 1)
326 dictparams = {
'phiRef':0.,
'deltaT': dt,
'm1_SI': m1,
'm2_SI': m2,
'fStart': fref,
'fRef': fref,
's1x': chi1x_high,
's1y': chi1y_high,
's1z': chi1z_high,
327 's2x': chi2x_high,
's2y': chi2y_high,
's2z': chi2z_high,
'lnhatx': Lnx_high,
'lnhaty': Lny_high,
'lnhatz': Lnz_high,
328 'e1x': E1x_high,
'e1y': E1y_high,
'e1z': E1z_high,
'LALparams': lalpars,
'approx': approx}
331 _,_,c1x, c1y, c1z, c2x, c2y, c2z, Lnx, Lny, Lnz, E1x, E1y, E1z = lalsim.SimInspiralSpinTaylorOrbitalDriver(**dictparams)
333 failure_message =
"The orbit-averaged evolution failed."
334 return evolution_error_handling(failure_mode, failure_message, failure_output, failure_output_string, Lf, swap=
False, hybrid_evol=
True)
336 c1x = c1x.data.data[-1]
337 c2x = c2x.data.data[-1]
338 c1y = c1y.data.data[-1]
339 c2y = c2y.data.data[-1]
340 c2z = c2z.data.data[-1]
341 c1z = c1z.data.data[-1]
342 Lnx = Lnx.data.data[-1]
343 Lny = Lny.data.data[-1]
344 Lnz = Lnz.data.data[-1]
346 tilt1_transition, tilt2_transition, phi12_transition =
get_tilts(c1x, c1y, c1z, c2x, c2y, c2z, Lnx, Lny, Lnz)
349 print(
"The tilts at transition are: tilt1 = {0}, tilt2 = {1}, phi12 = {2}".format(tilt1_transition, tilt2_transition, phi12_transition))
351 spin_angles_output =
prec_avg_tilt_comp(m1, m2, chi1, chi2, tilt1_transition, tilt2_transition, phi12_transition, f_trans, LPNorder = LPNorder, LPNspins = LPNspins,
352 version=version_prec_avg, **kwargs)
353 spin_angles_output[
"tilt1_transition"] = tilt1_transition
354 spin_angles_output[
"tilt2_transition"] = tilt2_transition
355 spin_angles_output[
"phi12_transition"] = phi12_transition
356 spin_angles_output[
"f_transition"] = v_trans**3/np.pi/(M*kg_to_s)
361 elif version ==
'v1':
365 dt = 1./(dt_constant * fref)
367 dictparams = {
'phiRef':0.,
'deltaT': dt,
'm1_SI': m1,
'm2_SI': m2,
'fStart': fref,
'fRef': fref,
's1x': chi1x_high,
's1y': chi1y_high,
's1z': chi1z_high,
368 's2x': chi2x_high,
's2y': chi2y_high,
's2z': chi2z_high,
'lnhatx': Lnx_high,
'lnhaty': Lny_high,
'lnhatz': Lnz_high,
369 'e1x': E1x_high,
'e1y': E1y_high,
'e1z': E1z_high,
'LALparams': lalpars,
'approx': approx}
377 print(
"Starting orbital evolution in nsteps = ",n_steps)
379 v_start = 0.5*(v_ref + v_trans)
380 v_arr = np.geomspace(v_start,v_trans,n_steps+1)
381 v_arr = np.insert(v_arr, 0, v_ref)
383 for i
in range(len(v_arr)-1):
385 print(
"starting step {}".format(i+1))
387 f_high = v_high**3/np.pi/MT_s
388 dictparams[
'fStart'] = f_high
389 dictparams[
'fRef'] = f_high
392 f_low = v_low**3/np.pi/MT_s
393 lalsim.SimInspiralWaveformParamsInsertFinalFreq(lalpars, f_low)
394 dictparams[
'LALparams'] = lalpars
395 dictparams[
'deltaT'] = 1./(dt_constant * f_high)
397 print(
"Evolving to v_low = {0} corresponding to f_low = {1} with dt = {2}".format(v_low, f_low, dictparams[
'deltaT']))
400 v_out,_,chi1x_low, chi1y_low, chi1z_low, chi2x_low, chi2y_low, chi2z_low, Lnx_low, Lny_low, Lnz_low, E1x, E1y, E1z = lalsim.SimInspiralSpinTaylorOrbitalDriver(**dictparams)
402 failure_message =
"The orbit-averaged evolution failed."
403 return evolution_error_handling(failure_mode, failure_message, failure_output, failure_output_string, Lf, swap=
False, hybrid_evol=
True)
405 if abs(v_out.data.data[0] - v_low) > 1.e-4:
406 failure_message = f
"The orbit-averaged evolution failed to reached the specified final velocity, v={v_low} at step {i+1}. The input binary parameters cannot be evolved from the given frequency {fref*fac} Hz."
407 return evolution_error_handling(failure_mode, failure_message, failure_output, failure_output_string, Lf, swap=
False, hybrid_evol=
True)
410 dictparams[
's1x'] = chi1x_low.data.data[0]
411 dictparams[
's1y'] = chi1y_low.data.data[0]
412 dictparams[
's1z'] = chi1z_low.data.data[0]
414 dictparams[
's2x'] = chi2x_low.data.data[0]
415 dictparams[
's2y'] = chi2y_low.data.data[0]
416 dictparams[
's2z'] = chi2z_low.data.data[0]
418 dictparams[
'lnhatx'] = Lnx_low.data.data[0]
419 dictparams[
'lnhaty'] = Lny_low.data.data[0]
420 dictparams[
'lnhatz'] = Lnz_low.data.data[0]
422 dictparams[
'e1x'] = E1x.data.data[0]
423 dictparams[
'e1y'] = E1y.data.data[0]
424 dictparams[
'e1z'] = E1z.data.data[0]
426 c1x = dictparams[
's1x']
427 c1y = dictparams[
's1y']
428 c1z = dictparams[
's1z']
430 c2x = dictparams[
's2x']
431 c2y = dictparams[
's2y']
432 c2z = dictparams[
's2z']
434 Lnx = dictparams[
'lnhatx']
435 Lny = dictparams[
'lnhaty']
436 Lnz = dictparams[
'lnhatz']
438 tilt1_transition, tilt2_transition, phi12_transition =
get_tilts(c1x, c1y, c1z, c2x, c2y, c2z, Lnx, Lny, Lnz)
441 print(
"The tilts at transition are: tilt1 = {0}, tilt2 = {1}, phi12 = {2}".format(tilt1_transition, tilt2_transition, phi12_transition))
444 spin_angles_output =
prec_avg_tilt_comp(m1, m2, chi1, chi2, tilt1_transition, tilt2_transition, phi12_transition, f_trans, LPNorder = LPNorder, LPNspins = LPNspins,
445 version=version_prec_avg, **kwargs)
446 spin_angles_output[
"tilt1_transition"] = tilt1_transition
447 spin_angles_output[
"tilt2_transition"] = tilt2_transition
448 spin_angles_output[
"phi12_transition"] = phi12_transition
449 spin_angles_output[
"f_transition"] = v_trans**3/np.pi/(M*kg_to_s)
453 print(
"The fitted transition frequency is higher than fref; Computing tilts at infinity from {} Hz. instead using precession-averaged evolution only".format(fref))
454 spin_angles_output =
prec_avg_tilt_comp(m1, m2, chi1, chi2, tilt1, tilt2, phi12, fref, LPNorder = LPNorder, LPNspins = LPNspins, version=version_prec_avg,
456 spin_angles_output[
"tilt1_transition"] =
None
457 spin_angles_output[
"tilt2_transition"] =
None
458 spin_angles_output[
"phi12_transition"] =
None
459 spin_angles_output[
"f_transition"] =
None
461 return spin_angles_output
def prec_avg_tilt_comp(m1, m2, chi1, chi2, tilt1, tilt2, phi12, fref, Lf=None, **kwargs)
Compute tilt angles at infinite separation or bounds and average value of tilt angles at finite separ...
def calc_v_trans(q)
Calculates the transition orbital speed (v_trans) to shift from orbit-averaged to precession-averaged...
def get_dt_constant(v_trans)
Determine the step-size (dt) to use in each frequency interval used for orbit-averaged evolution from...
def get_nsteps(v_trans)
Determine the number of frequency steps to use in each segment of the integration while performing or...
def get_tilts(chi1x, chi1y, chi1z, chi2x, chi2y, chi2z, Lnx, Lny, Lnz)
Given the spins and ang momentum at a given frequency, find the tilt and in-plane spin angles at that...
def calc_tilts_at_infty_hybrid_evolve(m1, m2, chi1, chi2, tilt1, tilt2, phi12, fref, approx="SpinTaylorT5", spinO=6, lscorr=1, verbose=False, prec_only=False, version='v1', failure_mode='None', **kwargs)
Calculate tilts at infinity with hybrid orbit-averaged and precession-averaged evolution Evolves tilt...
def evolution_error_handling(failure_mode, failure_message, failure_output, failure_output_string, Lf, swap, hybrid_evol=False)
Take care of the error message or warning and returning something for the tilts when the precession-a...
def package_tilts(tilt1, tilt2, Lf, swap)
Package tilts to be returned by prec_avg_tilt_comp_vec_inputs() or prec_avg_tilt_comp() depending on ...
def check_tilts(tilt1, tilt2)
def check_fref(fref, m1, m2, evol_type)
def eq_mass_check(m1, m2, Lf)
def check_spin_mags(chi1, chi2)