<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">"""Convert a posterior distribution of dchi in the parameterization used with SEOBNRT to a posterior distribution of dchi in the parameterization used with PhenomPNRT"""

import numpy as np
import argparse
import re
from scipy import random
from scipy.special import lambertw
from scipy import interpolate

#Component mass range for runs in BNS TGR paper
compmin = 0.5
compmax = 7.73105475907

#Dimensionless spin range for runs in BNS TGR paper
spinmin = -0.99
spinmax = 0.99

#Euler-Mascheroni Constant
euler_gamma = 0.57721566

#The functions phi${N} return the coefficient of the N/2-PN term in the inspiral (as in Eq. A4 of https://arxiv.org/abs/1005.3306)

def phi0(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return 1.

def phi1(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return 1.

def phi2(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  eta = (m1*m2)/(m1+m2)**2.
  return 5.*(743./84. + 11.*eta)/9.

def phi3(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  m1M = m1/(m1+m2)
  m2M = m2/(m1+m2)
  d = (m1-m2)/(m1+m2)
  SL = m1M * m1M * a1L + m2M * m2M * a2L
  dSigmaL = d * (m2M * a2L - m1M * a1L)
  return -16.* np.pi + 188.*SL/3. + 25.*dSigmaL

def phi4(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  #qm_def are the spin susceptibailities of the objects, which we take as the black hole value of 1. These enter in the "quadrupole-monopole" terms.
  qm_def1 = 1
  qm_def2 = 1
  m1M = m1/(m1+m2)
  m2M = m2/(m1+m2)
  eta = (m1*m2)/(m1+m2)**2.
  pnsigma = eta * (721./48. * a1L * a2L - 247./48. * a1dota2) + (720.*(qm_def1) - 1.)/96.0* m1M* m1M * a1L * a1L + (720. *(qm_def2) - 1.)/96.0 * m2M * m2M * a2L * a2L - (240.*(qm_def1) - 7.)/96.0 * m1M * m1M * a1sq - (240.*(qm_def2) - 7.)/96.0 * m2M * m2M * a2sq
  
  return 5.*(3058.673/7.056 + 5429./7.*eta + 617.*eta*eta)/72. - 10.*pnsigma

def phi5l(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  m1M = m1/(m1+m2)
  m2M = m2/(m1+m2)
  d = (m1-m2)/(m1+m2)
  eta = (m1*m2)/(m1+m2)**2.
  SL = m1M * m1M * a1L + m2M * m2M * a2L
  dSigmaL = d * (m2M * a2L - m1M * a1L)
  pngamma = (554345./1134. + 110.*eta/9.)*SL + (13915./84. - 10.*eta/3.)*dSigmaL
  return 5./3. * (7729./84. - 13. * eta) * np.pi - 3. * pngamma

def phi6(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  #qm_def are the spin susceptibailities of the objects, which we take as the black hole value of 1. These enter in the "quadrupole-monopole" terms.
  qm_def1 = 1
  qm_def2 = 1
  m1M = m1/(m1+m2)
  m2M = m2/(m1+m2)
  d = (m1-m2)/(m1+m2)
  eta = (m1*m2)/(m1+m2)**2.
  SL = m1M * m1M * a1L + m2M * m2M * a2L
  dSigmaL = d * (m2M * a2L - m1M * a1L)
  pnss3 = (326.75/1.12 + 557.5/1.8*eta) * eta * a1L * a2L + ((4703.5/8.4 + 2935./6. * m1M - 120. * m1M * m1M)*(qm_def1) + (-4108.25/6.72 - 108.5/1.2*m1M + 125.5/3.6*m1M*m1M))*m1M*m1M* a1sq + ((4703.5/8.4 + 2935./6. * m2M - 120. * m2M * m2M)*(qm_def2) + (-4108.25/6.72 - 108.5/1.2*m2M + 125.5/3.6*m2M*m2M))*m2M*m2M* a2sq
  return (11583.231236531/4.694215680 - 640./3. * np.pi * np.pi - 6848./21.*euler_gamma) + eta*(-15737.765635/3.048192 + 2255./12.*np.pi*np.pi) + eta*eta*76055./1728. - eta*eta*eta*127825./1296. + (-6848./21.)*np.log(4.) + np.pi*(3760.*SL + 1490*dSigmaL)/3. + pnss3

def phi6l(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return -6848./21.

def phi7(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  m1M = m1/(m1+m2)
  m2M = m2/(m1+m2)
  d = (m1-m2)/(m1+m2)
  eta = (m1*m2)/(m1+m2)**2.
  SL = m1M * m1M * a1L + m2M * m2M * a2L
  dSigmaL = d * (m2M * a2L - m1M * a1L)
  return np.pi*(77096675./254016. + 378515./1512.*eta - 74045./756.*eta*eta) + (-8980424995./762048. + 6586595.*eta/756. - 305.*eta*eta/36.)* SL - (170978035./48384. - 2876425.*eta/672. - 4735.*eta*eta/144.)* dSigmaL

def phiMinus2(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return 1.

#The functions phi${N}NS return the spin-independent component of the coefficient of the N/2-PN term in the inspiral

def phi0NS(m1,m2):
  return phi0(m1,m2,0.,0.,0.,0.,0.)

def phi1NS(m1,m2):
  return phi1(m1,m2,0.,0.,0.,0.,0.)

def phi2NS(m1,m2):
  return phi2(m1,m2,0.,0.,0.,0.,0.)

def phi3NS(m1,m2):
  return phi3(m1,m2,0.,0.,0.,0.,0.)

def phi4NS(m1,m2):
  return phi4(m1,m2,0.,0.,0.,0.,0.)

def phi5lNS(m1,m2):
  return phi5l(m1,m2,0.,0.,0.,0.,0.)

def phi6NS(m1,m2):
  return phi6(m1,m2,0.,0.,0.,0.,0.)

def phi6lNS(m1,m2):
  return phi6l(m1,m2,0.,0.,0.,0.,0.)

def phi7NS(m1,m2):
  return phi7(m1,m2,0.,0.,0.,0.,0.)

def phiMinus2NS(m1,m2):
  return phiMinus2(m1,m2,0.,0.,0.,0.,0.)

#The functions phi${N}S return the spin-dependent component of the coefficient of the N/2-PN term in the inspiral

def phi0S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi0(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi0NS(m1, m2)

def phi1S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi1(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi1NS(m1, m2)

def phi2S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi2(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi2NS(m1, m2)

def phi3S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi3(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi3NS(m1, m2)

def phi4S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi4(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi4NS(m1, m2)

def phi5lS(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi5l(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi5lNS(m1, m2)

def phi6S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi6(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi6NS(m1, m2)

def phi6lS(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi6l(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi6lNS(m1, m2)

def phi7S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phi7(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phi7NS(m1, m2)

def phiMinus2S(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2):
  return phiMinus2(m1, m2, a1L, a2L, a1sq, a2sq, a1dota2) - phiMinus2NS(m1, m2)


#Dictionaries that map the testing-GR parameter of each run to the corresponding function above
phiDict = {'dchi0':phi0, 'dchi1':phi1, 'dchi2':phi2, 'dchi3':phi3, 'dchi4':phi4, 'dchi5l':phi5l, 'dchi6':phi6, 'dchi6l':phi6l, 'dchi7':phi7, 'dchiminus2':phiMinus2, 'dipolecoeff':phiMinus2}
phiNSDict = {'dchi0':phi0NS, 'dchi1':phi1NS, 'dchi2':phi2NS, 'dchi3':phi3NS, 'dchi4':phi4NS, 'dchi5l':phi5lNS, 'dchi6':phi6NS, 'dchi6l':phi6lNS, 'dchi7':phi7NS, 'dchiminus2':phiMinus2NS, 'dipolecoeff':phiMinus2NS}
phiSDict = {'dchi0':phi0S, 'dchi1':phi1S, 'dchi2':phi2S, 'dchi3':phi3S, 'dchi4':phi4S, 'dchi5l':phi5lS, 'dchi6':phi6S, 'dchi6l':phi6lS, 'dchi7':phi7S, 'dchiminus2':phiMinus2S, 'dipolecoeff':phiMinus2S}

#Testing-GR parameter ranges used in BNS TGR paper
#
dchiminus2min = -1.0
dchiminus2max = 1.0
dchi0min = -5.0
dchi0max = 5.0
dchi1min = -5.0
dchi1max = 5.0
dchi2min = -5.0
dchi2max = 5.0
dchi3min = -5.0
dchi3max = 5.0
dchi4min = -10.0
dchi4max = 10.0
dchi5lmin = -5.0
dchi5lmax = 5.0
dchi6min = -5.0
dchi6max = 5.0
dchi6lmin = -20.0
dchi6lmax = 20.0
dchi7min = -30.0
dchi7max = 30.0

#Dictionaries that map the testing-GR parameter of each run to ranges function above
dchiMinDict = {'dchi0':dchi0min, 'dchi1':dchi1min, 'dchi2':dchi2min, 'dchi3':dchi3min, 'dchi4':dchi4min, 'dchi5l':dchi5lmin, 'dchi6':dchi6min, 'dchi6l':dchi6lmin, 'dchi7':dchi7min, 'dchiminus2':dchiminus2min, 'dipolecoeff':dchiminus2min}
dchiMaxDict = {'dchi0':dchi0max, 'dchi1':dchi1max, 'dchi2':dchi2max, 'dchi3':dchi3max, 'dchi4':dchi4max, 'dchi5l':dchi5lmax, 'dchi6':dchi6max, 'dchi6l':dchi6lmax, 'dchi7':dchi7max, 'dchiminus2':dchiminus2max, 'dipolecoeff':dchiminus2max}

def convert_SEOBNRT_to_PhenomPNRT_parameterization(data, param, bins_arg=25, nsamples=1000000):
  """Given a full set of posterior samples from a SEOBNRT run, return the PDF for dchi for an equivalent PhenomPNRT run"""

  if param in ['dchi0', 'dchi1', 'dchi2', 'dchi6l', 'dchiminus2']:
    return data[param]

  #First draw nsamples number of samples from the prior used in the runs for the BNS TGR paper.

  m1prior=random.uniform(compmin,compmax,nsamples)
  m2prior=random.uniform(compmin,compmax,nsamples)

  #The priors on z-component of spins are compatible with those used for runs with precession
  x1prior=random.uniform(-0.5,0.5,nsamples)
  x2prior=random.uniform(-0.5,0.5,nsamples)
  a1zprior=spinmax * np.real(-2.*x1prior/lambertw(-2.*np.abs(x1prior)/np.e,-1))
  a2zprior=spinmax * np.real(-2.*x2prior/lambertw(-2.*np.abs(x2prior)/np.e,-1))

  dchiprior_SEOBNRT = random.uniform(dchiMinDict[param],dchiMaxDict[param],nsamples)
  
  #Compute the prior distribution on dchi_i (parameterized as with PhenomPNRT) corresponding to a uniform distribution in dchi_i (parameterized as with SEOBNRT)
  dchiprior_PhenomPNRT = []
  for i in range(len(dchiprior_SEOBNRT)):
    m1 = m1prior[i]
    m2 = m2prior[i]
    a1z = a1zprior[i]
    a2z = a2zprior[i]
    a1sq = a1zprior[i]*a1zprior[i]
    a2sq = a2zprior[i]*a2zprior[i]
    a1dota2 = a1zprior[i]*a2zprior[i]
    dchiprior_PhenomPNRT.append(dchiprior_SEOBNRT[i]*(1. + phiSDict[param](m1,m2,a1z,a2z,a1sq,a2sq,a1dota2)/phiNSDict[param](m1,m2)))
  
  #Convert the posetrior distribution of dchi_i (as parameterized with SEOBNRT) into a distribution pf dchi_i (parameterized with PhenomPNRT)
  dchidata_PhenomPNRT = []
  for j in range(data.size):
    m1 = data['m1'][j]
    m2 = data['m2'][j]
    a1z = data['a1'][j] * data['costilt1'][j]
    a2z = data['a2'][j] * data['costilt2'][j]
    a1sq = data['a1'][j] * data['a1'][j]
    a2sq = data['a2'][j] * data['a2'][j]
    a1dota2 = data['a1'][j] * data['a2'][j] * data['costilt1'][j] * data['costilt2'][j]
    dchidata_PhenomPNRT.append(data[param][j]*(1. + phiSDict[param](m1,m2,a1z,a2z,a1sq,a2sq,a1dota2)/phiNSDict[param](m1,m2)))
  
  dchi_min=min(dchidata_PhenomPNRT)
  dchi_max=max(dchidata_PhenomPNRT)
  
  P_dchi_pr, dchi_bins = np.histogram(dchiprior_PhenomPNRT, bins=np.linspace(dchi_min,dchi_max,num=bins_arg+1), normed=True)
  P_dchi, dchi_bins = np.histogram(dchidata_PhenomPNRT, bins=dchi_bins, normed=True)
  P_dchi_SEOBNRT, dchi_bins = np.histogram(data[param], bins=dchi_bins, normed=True)
  
  #Compute the posterior distribution on dchi_i (as parameterized with PhenomPNRT) corresponding to a flat prior in dchi_i (as parameterized with PhenomPNRT) by reweighting by the prior on dchi_i (as parameterized with PhenomPNRT) given a flat prior on dchi_i (as parameterized by SEOBNRT)
  bin_width=(dchi_bins[1]-dchi_bins[0])
  P_dchi_corrected = P_dchi/P_dchi_pr
  P_dchi_corrected[np.isnan(P_dchi_corrected)] = 0.
  P_dchi_corrected = P_dchi_corrected/(np.sum(P_dchi_corrected)*bin_width)
    
  dchi_bins_center = (dchi_bins[:-1]+dchi_bins[1:])/2.

  #Tabulate the discrete CDF, invert it, and interpolate the inverse CDF
  cumulative_values=np.zeros(dchi_bins.shape)
  cumulative_values[1:]=np.cumsum(P_dchi_corrected*np.diff(dchi_bins))
  inv_cdf = interpolate.interp1d(cumulative_values, dchi_bins)
  
  #Return the bins and values of the reweighted posterior distribution
  r = random.uniform(0.,1.,nsamples/10)
  
  return inv_cdf(r)

</pre></body></html>