From bbdacd0c3d5a5e86817ac73582c1610e103dc985 Mon Sep 17 00:00:00 2001
From: alejandraglz <alejandra.gonzalez@uni-jena.de>
Date: Tue, 1 Nov 2022 12:24:24 +0100
Subject: [PATCH] Added functions to compute strain from modes

---
 watpy/utils/units.py  |  4 ++++
 watpy/wave/gwutils.py | 41 ++++++++++++++++++++++++++++++++++-------
 watpy/wave/wave.py    | 37 ++++++++++++++++++++++++++++++++++---
 3 files changed, 72 insertions(+), 10 deletions(-)

diff --git a/watpy/utils/units.py b/watpy/utils/units.py
index 48d2244..d6af39c 100644
--- a/watpy/utils/units.py
+++ b/watpy/utils/units.py
@@ -5,11 +5,15 @@
 
 uts= {
   'Msun_sec': 4.925794970773135e-06,
+  'Msun_meter': 1.476625061404649406193430731479084713e3
 }
 
 def MSun_sec():
   return uts['Msun_sec']
 
+def MSun_meter():
+    return uts['Msun_meter']
+
 
 # For plots:
 TEX_KM    = r'\mathrm{km}'
diff --git a/watpy/wave/gwutils.py b/watpy/wave/gwutils.py
index bcd3f4b..42e5ee1 100644
--- a/watpy/wave/gwutils.py
+++ b/watpy/wave/gwutils.py
@@ -3,6 +3,7 @@ import scipy as sp
 import math
 from ..utils import num as num 
 import warnings as wrn
+from scipy.special import factorial as fact
 try:
     from scipy import factorial2
 except:  
@@ -13,6 +14,32 @@ except:
 # Waveform analysis utilities
 # ------------------------------------------------------------------
 
+def wigner_d_function(l, m, s, incl):
+    """
+    Wigner-d functions
+    """
+    costheta = np.cos(incl*0.5)
+    sintheta = np.sin(incl*0.5)
+    norm = np.sqrt( (fact(l+m) * fact(l-m) * fact(l+s) * fact(l-s)) )
+    ki = np.amax([0,m-s])
+    kf = np.amin([l+m,l-s])
+    k = np.arange(ki,kf+1)
+    div = 1.0/( fact(k) * fact(l+m-k) * fact(l-s-k) * fact(s-m+k) );
+    dWig = div*( np.power(-1.,k) * np.power(costheta,2*l+m-s-2*k) * np.power(sintheta,2*k+s-m) )
+    return norm * np.sum(dWig)
+
+def spinw_spherical_harm(s, l, m, incl,phi):
+    """ 
+    Spin-weighted spherical harmonics
+    E.g. https://arxiv.org/abs/0709.0093
+    """
+    if ((l<0) or (m<-l) or (m>l)):
+        raise ValueError("wrong (l,m)")
+    c = np.power(-1.,-s) * np.sqrt( (2.*l+1.)/(4.*np.pi) )
+    dWigner = c * wigner_d_function(l,m,-s,incl)
+    exp_mphi = ( np.cos(m*phi) + 1j * np.sin(m*phi) )
+    return dWigner * exp_mphi
+
 def interp_fd_wave(fnew, f, h, kind = 'quadratic'):
     """
         Interpolate frequency-domain waveform
@@ -295,7 +322,7 @@ def richardson_extrap_series(p, y, t, h):
     ye = np.zeros(n); err = np.zeros(n)
 
     # interpolate all data sets on time grid of extrapolated result
-    intrp_y = [ num.linterp(te, t[k], y[k]) for k in range(N-1) ]
+    intrp_y = [ np.interp(te, t[k], y[k]) for k in range(N-1) ]
     intrp_y.append(y[-1])
 
     # compute Richardson extrapolation pointwise
@@ -445,20 +472,20 @@ def waveform2energetics(h, h_dot, t, modes, mmodes):
     * modes        : (l,m) indexes
     * mmodes       :   m   indexes
     """
-    print('got in')
+    
     dtime = t[1]-t[0]
-    print('step 1')
+    
     E_GW_dot_ff = {}
     E_GW_ff = {}
     J_GW_dot_ff = {}
     J_GW_ff = {}    
     for l, m in modes:
-        print('step 2')
+        
         E_GW_dot_ff[(l,m)] = 1.0/(16.*np.pi) * np.abs(h_dot[l,m])**2 
         E_GW_ff[(l,m)] = num.integrate(E_GW_dot_ff[l,m]) * dtime
         J_GW_dot_ff[(l,m)] = 1.0/(16.*np.pi) * m * np.imag(h[l,m] * np.conj(h_dot[l,m])) 
         J_GW_ff[(l,m)] = num.integrate(J_GW_dot_ff[l,m]) * dtime
-    print('step 3')
+    
     E_GW_dot = {}
     E_GW = {}
     J_GW_dot = {}
@@ -468,13 +495,13 @@ def waveform2energetics(h, h_dot, t, modes, mmodes):
         E_GW[m] = np.zeros_like(t)
         J_GW_dot[m] = np.zeros_like(t)
         J_GW[m] = np.zeros_like(t)
-    print('step 4')
+    
     for l, m in modes:
         E_GW_dot[m] += mnfactor(m) * E_GW_dot_ff[l,m]
         E_GW[m] += mnfactor(m) * E_GW_ff[l,m]
         J_GW_dot[m] += mnfactor(m) * J_GW_dot_ff[l,m]
         J_GW[m] += mnfactor(m) * J_GW_ff[l,m]
-    print('step 5')
+    
     E_GW_dot_all = np.zeros_like(t)
     E_GW_all = np.zeros_like(t)
     J_GW_dot_all = np.zeros_like(t)
diff --git a/watpy/wave/wave.py b/watpy/wave/wave.py
index 9ad911d..041d8c9 100644
--- a/watpy/wave/wave.py
+++ b/watpy/wave/wave.py
@@ -1,7 +1,8 @@
 from ..utils.ioutils import *
 from ..utils.num import diff1, diffo
 from ..utils.viz import wplot
-from .gwutils import fixed_freq_int_2, waveform2energetics, ret_time
+from ..utils.units import *
+from .gwutils import fixed_freq_int_2, waveform2energetics, ret_time, spinw_spherical_harm
 import numpy as np
 
 
@@ -353,6 +354,37 @@ class mwaves(object):
                 fname = "EJ_r"+rad_str+".txt"
                 np.savetxt('{}/{}'.format(path_out,fname), data, header=headstr)
 
+    def hlm_to_strain(self,phi=0,inclination=0,add_negative_modes=False):
+        """
+        Build strain from time-domain modes in mass rescaled, geom. units
+        Return result in SI units
+
+        Negative m-modes can be added using positive m-modes, 
+        h_{l-m} = (-)^l h^{*}_{lm}
+        """
+        PC_SI  = 3.085677581491367e+16 # m
+        MPC_SI = 1e6 * PC_SI
+        wave22 = self.get(l=2, m=2)
+        time = wave22.time_ret() * MSun_sec()
+        distance = wave22.prop['detector.radius']*MPC_SI
+        amplitude_prefactor = wave22.prop['mass'] * MSun_meter() / distance
+        h = np.zeros_like( 1j* time  )
+        for (l,m) in self.modes:
+            wavelm = self.get(l=l,m=m)
+            amplitude = wavelm.amplitude(var='h')
+            philm = wavelm.phase(var='h')
+            sYlm = spinw_spherical_harm(-2, l, m, phi, inclination)
+            Alm = amplitude_prefactor * amplitude
+            hlm = Alm * np.exp( - 1j * philm )
+            h += hlm * sYlm
+            if (add_negative_modes):            
+                sYlm_neg = spinw_spherical_harm(-2, l, -m, phi, inclination)            
+                hlm_neg = (-1)**l * np.conj(hlm) 
+                h += hlm_neg * sYlm_neg            
+        hplus = np.real(h)
+        hcross = - np.imag(h)
+        return time, hplus, hcross
+
 
 
 class wave(object):
@@ -622,5 +654,4 @@ class wave(object):
             dt = self.time[1] - self.time[0]
             return win * fixed_freq_int_2( win * self.p4, fcut, dt = dt)
         else:
-            return self.h
-
+            return self.h
\ No newline at end of file
-- 
GitLab