From ab93bc33f8c7d405c6ad436101cca6081df57cce Mon Sep 17 00:00:00 2001
From: Gregory Ashton <gregory.ashton@ligo.org>
Date: Mon, 19 Mar 2018 17:25:53 +1100
Subject: [PATCH] Add add higher order spin-down components F2 and above

---
 pyfstat/core.py                | 23 ++++++++-------
 pyfstat/make_sfts.py           | 54 +++++++++++++++++++---------------
 pyfstat/mcmc_based_searches.py | 19 +++++++-----
 3 files changed, 54 insertions(+), 42 deletions(-)

diff --git a/pyfstat/core.py b/pyfstat/core.py
index e781c88..3adcf58 100755
--- a/pyfstat/core.py
+++ b/pyfstat/core.py
@@ -665,11 +665,11 @@ class ComputeFstat(BaseSearchClass):
                 tcw.init_transient_fstat_map_features(
                     self.tCWFstatMapVersion == 'pycuda', self.cudaDeviceName))
 
-    def get_fullycoherent_twoF(self, tstart, tend, F0, F1, F2, Alpha, Delta,
-                               asini=None, period=None, ecc=None, tp=None,
-                               argp=None):
+    def get_fullycoherent_twoF(self, tstart, tend, F0, F1, F2=0, F3=0,
+                               Alpha=0, Delta=0, asini=None, period=None,
+                               ecc=None, tp=None, argp=None):
         """ Returns twoF or ln(BSGL) fully-coherently at a single point """
-        self.PulsarDopplerParams.fkdot = np.array([F0, F1, F2, 0, 0, 0, 0])
+        self.PulsarDopplerParams.fkdot = np.array([F0, F1, F2, F3, 0, 0, 0])
         self.PulsarDopplerParams.Alpha = Alpha
         self.PulsarDopplerParams.Delta = Delta
         if self.binary:
@@ -1155,8 +1155,9 @@ class SemiCoherentGlitchSearch(ComputeFstat):
             ts, te = tboundaries[i], tboundaries[i+1]
             if te - ts > 1800:
                 twoFVal = self.get_fullycoherent_twoF(
-                    ts, te, theta_i_at_tref[1], theta_i_at_tref[2],
-                    theta_i_at_tref[3], Alpha, Delta)
+                    tstart=ts, tend=te, F0=theta_i_at_tref[1],
+                    F2=theta_i_at_tref[2], F3=theta_i_at_tref[3],
+                    Alpha=Alpha, Delta=Delta)
                 twoFSum += twoFVal
 
         if np.isfinite(twoFSum):
@@ -1181,15 +1182,15 @@ class SemiCoherentGlitchSearch(ComputeFstat):
             theta_post_glitch_at_glitch, tref - tglitch)
 
         twoFsegA = self.get_fullycoherent_twoF(
-            self.minStartTime, tglitch, theta[0], theta[1], theta[2], Alpha,
-            Delta)
+            self.minStartTime, tglitch, F0=theta[0], F1=theta[1], F2=theta[2],
+            Alpha=Alpha, Delta=Delta)
 
         if tglitch == self.maxStartTime:
             return twoFsegA
 
         twoFsegB = self.get_fullycoherent_twoF(
-            tglitch, self.maxStartTime, theta_post_glitch[0],
-            theta_post_glitch[1], theta_post_glitch[2], Alpha,
-            Delta)
+            tglitch, self.maxStartTime, F0=theta_post_glitch[0],
+            F1=theta_post_glitch[1], F2=theta_post_glitch[2], Alpha=Alpha,
+            Delta=Delta)
 
         return twoFsegA + twoFsegB
diff --git a/pyfstat/make_sfts.py b/pyfstat/make_sfts.py
index c3a1812..3e50403 100644
--- a/pyfstat/make_sfts.py
+++ b/pyfstat/make_sfts.py
@@ -22,7 +22,7 @@ class Writer(BaseSearchClass):
     """ Instance object for generating SFTs """
     @helper_functions.initializer
     def __init__(self, label='Test', tstart=700000000, duration=100*86400,
-                 tref=None, F0=30, F1=1e-10, F2=0, Alpha=5e-3,
+                 tref=None, F0=30, F1=1e-10, F2=0, F3=0, F4=0, Alpha=5e-3,
                  Delta=6e-2, h0=0.1, cosi=0.0, psi=0.0, phi=0, Tsft=1800,
                  outdir=".", sqrtSX=1, Band=4, detectors='H1',
                  minStartTime=None, maxStartTime=None, add_noise=True,
@@ -37,7 +37,7 @@ class Writer(BaseSearchClass):
         tref: float or None
             reference time (default is None, which sets the reference time to
             tstart)
-        F0, F1, F2, Alpha, Delta, h0, cosi, psi, phi: float
+        F0, F1, F2, F3, F4, Alpha, Delta, h0, cosi, psi, phi: float
             frequency, sky-position, and amplitude parameters
         Tsft: float
             the sft duration
@@ -70,7 +70,8 @@ class Writer(BaseSearchClass):
         self.data_duration = self.maxStartTime - self.minStartTime
         numSFTs = int(float(self.data_duration) / self.Tsft)
 
-        self.theta = np.array([self.phi, self.F0, self.F1, self.F2])
+        self.theta = np.array([self.phi, self.F0, self.F1, self.F2, self.F3,
+                               self.F4])
 
         if os.path.isdir(self.outdir) is False:
             os.makedirs(self.outdir)
@@ -93,7 +94,7 @@ class Writer(BaseSearchClass):
         self.run_makefakedata()
 
     def get_base_template(self, i, Alpha, Delta, h0, cosi, psi, phi, F0,
-                          F1, F2, tref):
+                          F1, F2, F3, F4, tref):
         return (
 """[TS{}]
 Alpha = {:1.18e}
@@ -105,35 +106,42 @@ phi0 = {:1.18e}
 Freq = {:1.18e}
 f1dot = {:1.18e}
 f2dot = {:1.18e}
+f3dot = {:1.18e}
+f4dot = {:1.18e}
 refTime = {:10.6f}""")
 
     def get_single_config_line_cw(
-            self, i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref):
-        template = (self.get_base_template(
-            i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref) + """\n""")
+            self, i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4,
+            tref):
+        template = (
+            self.get_base_template(
+                i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4, tref)
+            + """\n""")
         return template.format(
-            i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref)
+            i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4, tref)
 
     def get_single_config_line_tcw(
-            self, i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref,
-            window, tstart, duration_days):
+            self, i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4,
+            tref, window, tstart, duration_days):
         template = (self.get_base_template(
-            i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref) + """
+            i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4, tref)
+            + """
 transientWindowType = {:s}
 transientStartTime = {:10.3f}
 transientTauDays = {:1.3f}\n""")
         return template.format(i, Alpha, Delta, h0, cosi, psi, phi, F0, F1,
-                               F2, tref, window, tstart, duration_days)
+                               F2, F3, F4, tref, window, tstart, duration_days)
 
     def get_single_config_line(self, i, Alpha, Delta, h0, cosi, psi, phi, F0,
-                               F1, F2, tref, window, tstart, duration_days):
+                               F1, F2, F3, F4, tref, window, tstart,
+                               duration_days):
         if window == 'none':
             return self.get_single_config_line_cw(
-                i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref)
+                i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4, tref)
         else:
             return self.get_single_config_line_tcw(
-                i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref, window,
-                tstart, duration_days)
+                i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, F3, F4, tref,
+                window, tstart, duration_days)
 
     def make_cff(self):
         """
@@ -143,7 +151,7 @@ transientTauDays = {:1.3f}\n""")
 
         content = self.get_single_config_line(
             0, self.Alpha, self.Delta, self.h0, self.cosi, self.psi,
-            self.phi, self.F0, self.F1, self.F2, self.tref,
+            self.phi, self.F0, self.F1, self.F2, self.F3, self.F4, self.tref,
             self.transientWindowType, self.tstart, self.duration_days)
 
         if self.check_if_cff_file_needs_rewritting(content):
@@ -152,7 +160,7 @@ transientTauDays = {:1.3f}\n""")
             config_file.close()
 
     def calculate_fmin_Band(self):
-        self.fmin = self.F0 - .5 * self.Band
+        self.fmin = np.max([self.F0 - .5 * self.Band, 0.1])
 
     def check_cached_data_okay_to_use(self, cl_mfd):
         """ Check if cached data exists and, if it does, if it can be used """
@@ -269,8 +277,8 @@ class GlitchWriter(Writer):
     @helper_functions.initializer
     def __init__(self, label='Test', tstart=700000000, duration=100*86400,
                  dtglitch=None, delta_phi=0, delta_F0=0, delta_F1=0,
-                 delta_F2=0, tref=None, F0=30, F1=1e-10, F2=0, Alpha=5e-3,
-                 Delta=6e-2, h0=0.1, cosi=0.0, psi=0.0, phi=0, Tsft=1800,
+                 delta_F2=0, tref=None, F0=30, F1=1e-10, F2=0, F3=0, F4=0,
+                 Alpha=5e-3, Delta=6e-2, h0=0.1, cosi=0.0, psi=0.0, phi=0, Tsft=1800,
                  outdir=".", sqrtSX=1, Band=4, detectors='H1',
                  minStartTime=None, maxStartTime=None, add_noise=True,
                  transientWindowType='rect'):
@@ -327,7 +335,7 @@ class GlitchWriter(Writer):
         self.durations_days = (tbs[1:] - tbs[:-1]) / 86400
 
         self.delta_thetas = np.atleast_2d(
-                np.array([delta_phi, delta_F0, delta_F1, delta_F2]).T)
+                np.array([delta_phi, delta_F0, delta_F1, delta_F2, 0, 0]).T)
 
     def make_cff(self):
         """
@@ -343,8 +351,8 @@ class GlitchWriter(Writer):
                                            self.tbounds[:-1])):
             line = self.get_single_config_line(
                 i, self.Alpha, self.Delta, self.h0, self.cosi, self.psi,
-                t[0], t[1], t[2], t[3], self.tref, self.transientWindowType,
-                ts, d)
+                t[0], t[1], t[2], t[3], t[4], t[5], self.tref,
+                self.transientWindowType, ts, d)
 
             content += line
 
diff --git a/pyfstat/mcmc_based_searches.py b/pyfstat/mcmc_based_searches.py
index 59d8b83..7f4355a 100644
--- a/pyfstat/mcmc_based_searches.py
+++ b/pyfstat/mcmc_based_searches.py
@@ -102,12 +102,12 @@ class MCMCSearch(core.BaseSearchClass):
     """
 
     symbol_dictionary = dict(
-        F0='$f$', F1='$\dot{f}$', F2='$\ddot{f}$', Alpha=r'$\alpha$',
-        Delta='$\delta$', asini='asini', period='P', ecc='ecc', tp='tp',
-        argp='argp')
+        F0='$f$', F1='$\dot{f}$', F2='$\ddot{f}$', F3='$\dddot{f}$',
+        Alpha=r'$\alpha$', Delta='$\delta$', asini='asini', period='P',
+        ecc='ecc', tp='tp', argp='argp')
     unit_dictionary = dict(
-        F0='Hz', F1='Hz/s', F2='Hz/s$^2$', Alpha=r'rad', Delta='rad',
-        asini='', period='s', ecc='', tp='', argp='')
+        F0='Hz', F1='Hz/s', F2='Hz/s$^2$', F3='Hz/s$^3$', Alpha=r'rad',
+        Delta='rad', asini='', period='s', ecc='', tp='', argp='')
     transform_dictionary = {}
 
     @helper_functions.initializer
@@ -184,18 +184,21 @@ class MCMCSearch(core.BaseSearchClass):
         return twoF/2.0 + self.likelihoodcoef
 
     def _unpack_input_theta(self):
-        full_theta_keys = ['F0', 'F1', 'F2', 'Alpha', 'Delta']
+        full_theta_keys = ['F0', 'F1', 'F2', 'F3', 'Alpha', 'Delta']
         if self.binary:
             full_theta_keys += [
                 'asini', 'period', 'ecc', 'tp', 'argp']
         full_theta_keys_copy = copy.copy(full_theta_keys)
 
-        full_theta_symbols = ['$f$', '$\dot{f}$', '$\ddot{f}$', r'$\alpha$',
-                              r'$\delta$']
+        full_theta_symbols = ['$f$', '$\dot{f}$', '$\ddot{f}$', r'$\ddot{f}$',
+                              r'$\alpha$', r'$\delta$']
         if self.binary:
             full_theta_symbols += [
                 'asini', 'period', 'ecc', 'tp', 'argp']
 
+        for default in ['F1', 'F2', 'F3']:
+            if default not in self.theta_prior:
+                self.theta_prior[default] = 0
         self.theta_keys = []
         fixed_theta_dict = {}
         for key, val in self.theta_prior.iteritems():
-- 
GitLab