Skip to content
Snippets Groups Projects
Select Git revision
  • 6a2d7a88e36e40a273dce316c5942cb9d6f47900
  • master default protected
  • fix_Makefile.mingw#2
  • update_Makefile.mingw
  • fix_Makefile.mingw
  • fix_API_for_C_apps
  • fix_procinfo_mac
  • boinccmd_gpu_mode_always_until_sigterm
  • fgrp_osx_hotfix
  • fix_boinc_master@f8250782
  • eah_wrapper_improvements
  • diagnostics_win-hotfix
  • diagnostics_win-hotfix-old
  • current_fgrp_apps
  • testing_gw_apps
  • gw_app_darwin_15
  • current_brp_apps
  • current_brp_apps_android10
  • current_gfx_apps
  • current_server
  • current_gw_apps
  • previous_fgrp_apps
  • previous_gw_apps
  • testing_brp_apps
  • apps_FGRP3_1.07
  • apps_FGRP3_1.08
26 results

boinc_api.cpp

Blame
  • make_sfts.py 24.97 KiB
    """ pyfstat tools to generate sfts """
    
    
    import numpy as np
    import logging
    import os
    import glob
    import pkgutil
    
    import lal
    import lalpulsar
    
    from pyfstat.core import BaseSearchClass, tqdm, args, predict_fstat
    import pyfstat.helper_functions as helper_functions
    
    
    class KeyboardInterruptError(Exception):
        pass
    
    
    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,
            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="none",
        ):
            """
            Parameters
            ----------
            label: string
                a human-readable label to be used in naming the output files
            tstart, duration : int
                start and duration (in gps seconds) of the total observation span
            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
                frequency, sky-position, and amplitude parameters
            Tsft: float
                the sft duration
            minStartTime, maxStartTime: float
                if not None, the total span of data, this can be used to generate
                transient signals
    
            see `lalapps_Makefakedata_v5 --help` for help with the other paramaters
            """
    
            self.set_ephemeris_files()
            self.basic_setup()
            self.calculate_fmin_Band()
    
            self.tbounds = [self.tstart, self.tend]
            logging.info("Using segment boundaries {}".format(self.tbounds))
    
        def basic_setup(self):
            self.tstart = int(self.tstart)
            self.duration = int(self.duration)
            self.tend = self.tstart + self.duration
            if self.minStartTime is None:
                self.minStartTime = self.tstart
            if self.maxStartTime is None:
                self.maxStartTime = self.tend
            self.minStartTime = int(self.minStartTime)
            self.maxStartTime = int(self.maxStartTime)
            self.duration_days = (self.tend - self.tstart) / 86400
    
            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])
    
            if os.path.isdir(self.outdir) is False:
                os.makedirs(self.outdir)
            if self.tref is None:
                self.tref = self.tstart
            self.config_file_name = "{}/{}.cff".format(self.outdir, self.label)
            self.sftfilenames = [
                lalpulsar.OfficialSFTFilename(
                    dets[0],
                    dets[1],
                    numSFTs,
                    self.Tsft,
                    self.minStartTime,
                    self.data_duration,
                    self.label,
                )
                for dets in self.detectors.split(",")
            ]
            self.sftfilepath = ";".join(
                ["{}/{}".format(self.outdir, fn) for fn in self.sftfilenames]
            )
            self.IFOs = ",".join(['"{}"'.format(d) for d in self.detectors.split(",")])
    
        def make_data(self):
            """ A convienience wrapper to generate a cff file then sfts """
            self.make_cff()
            self.run_makefakedata()
    
        def get_base_template(self, i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref):
            return """[TS{}]
    Alpha = {:1.18e}
    Delta = {:1.18e}
    h0 = {:1.18e}
    cosi = {:1.18e}
    psi = {:1.18e}
    phi0 = {:1.18e}
    Freq = {:1.18e}
    f1dot = {:1.18e}
    f2dot = {: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"""
            )
            return template.format(i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref)
    
        def get_single_config_line_tcw(
            self,
            i,
            Alpha,
            Delta,
            h0,
            cosi,
            psi,
            phi,
            F0,
            F1,
            F2,
            tref,
            window,
            tstart,
            duration_days,
        ):
            template = (
                self.get_base_template(
                    i, Alpha, Delta, h0, cosi, psi, phi, F0, F1, F2, tref
                )
                + """
    transientWindowType = {:s}
    transientStartTime = {:10.0f}
    transientTau = {:10.0f}\n"""
            )
            return template.format(
                i,
                Alpha,
                Delta,
                h0,
                cosi,
                psi,
                phi,
                F0,
                F1,
                F2,
                tref,
                window,
                tstart,
                duration_days * 86400,
            )
    
        def get_single_config_line(
            self,
            i,
            Alpha,
            Delta,
            h0,
            cosi,
            psi,
            phi,
            F0,
            F1,
            F2,
            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
                )
            else:
                return self.get_single_config_line_tcw(
                    i,
                    Alpha,
                    Delta,
                    h0,
                    cosi,
                    psi,
                    phi,
                    F0,
                    F1,
                    F2,
                    tref,
                    window,
                    tstart,
                    duration_days,
                )
    
        def make_cff(self):
            """
            Generates a .cff file
    
            """
    
            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.transientWindowType,
                self.tstart,
                self.duration_days,
            )
    
            if self.check_if_cff_file_needs_rewritting(content):
                config_file = open(self.config_file_name, "w+")
                config_file.write(content)
                config_file.close()
    
        def calculate_fmin_Band(self):
            self.fmin = self.F0 - 0.5 * self.Band
    
        def check_cached_data_okay_to_use(self, cl_mfd):
            """ Check if cached data exists and, if it does, if it can be used """
    
            need_new = "Will create new SFT file."
    
            logging.info("Checking if cached data good to reuse...")
            if os.path.isfile(self.sftfilepath) is False:
                logging.info("No SFT file matching {} found. {}".format(self.sftfilepath, need_new))
                return False
            else:
                logging.info("OK: Matching SFT file found.")
    
            if "injectionSources" in cl_mfd:
                if os.path.isfile(self.config_file_name):
                    if os.path.getmtime(self.sftfilepath) < os.path.getmtime(self.config_file_name):
                        logging.info(
                            (
                                "The config file {} has been modified since the SFT file {} "
                                + "was created. {}"
                            ).format(self.config_file_name, self.sftfilepath, need_new)
                        )
                        return False
                    else:
                        logging.info(
                            "OK: The config file {} is older than the SFT file {}".format(
                                self.config_file_name, self.sftfilepath)
                        # NOTE: at this point we assume it's safe to re-use, since
                        # check_if_cff_file_needs_rewritting()
                        # should have already been called before
                        )
                else:
                    raise RuntimeError("Commandline requires file '{}' but it is missing.".format(self.config_file_name))
    
            logging.info("Checking new commandline against existing SFT header...")
            cl_dump = "lalapps_SFTdumpheader {} | head -n 20".format(self.sftfilepath)
            output = helper_functions.run_commandline(cl_dump)
            header_lines_lalapps = [line for line in output.split("\n") if "lalapps" in line]
            if len(header_lines_lalapps)==0:
                logging.info("Could not obtain comparison commandline from old SFT header. "+need_new)
                return False
            cl_old = header_lines_lalapps[0]
            if not helper_functions.match_commandlines(cl_old,cl_mfd):
                logging.info("Commandlines unmatched. "+need_new)
                return False
            else:
                logging.info("OK: Commandline matched with old SFT header.")
            logging.info("Looks like cached data matches current options, will re-use it!")
            return True
    
        def check_if_cff_file_needs_rewritting(self, content):
            """ Check if the .cff file has changed
    
            Returns True if the file should be overwritten - where possible avoid
            overwriting to allow cached data to be used
            """
            logging.info("Checking if we can re-use injection config file...")
            if os.path.isfile(self.config_file_name) is False:
                logging.info("No config file {} found.".format(self.config_file_name))
                return True
            else:
                logging.info("Config file {} already exists.".format(self.config_file_name))
    
            with open(self.config_file_name, "r") as f:
                file_content = f.read()
                if file_content == content:
                    logging.info(
                        "File contents match, no update of {} required.".format(
                            self.config_file_name
                        )
                    )
                    return False
                else:
                    logging.info(
                        "File contents unmatched, updating {}.".format(self.config_file_name)
                    )
                    return True
    
        def run_makefakedata(self):
            """ Generate the sft data from the configuration file """
    
            # Remove old data:
            try:
                os.unlink("{}/*{}*.sft".format(self.outdir, self.label))
            except OSError:
                pass
    
            cl_mfd = []
            cl_mfd.append("lalapps_Makefakedata_v5")
            cl_mfd.append("--outSingleSFT=TRUE")
            cl_mfd.append('--outSFTdir="{}"'.format(self.outdir))
            cl_mfd.append('--outLabel="{}"'.format(self.label))
            cl_mfd.append("--IFOs={}".format(self.IFOs))
            if self.add_noise:
                cl_mfd.append('--sqrtSX="{}"'.format(self.sqrtSX))
            if self.minStartTime is None:
                cl_mfd.append("--startTime={:0.0f}".format(float(self.tstart)))
            else:
                cl_mfd.append("--startTime={:0.0f}".format(float(self.minStartTime)))
            if self.maxStartTime is None:
                cl_mfd.append("--duration={}".format(int(self.duration)))
            else:
                data_duration = self.maxStartTime - self.minStartTime
                cl_mfd.append("--duration={}".format(int(data_duration)))
            cl_mfd.append("--fmin={:.16g}".format(self.fmin))
            cl_mfd.append("--Band={:.16g}".format(self.Band))
            cl_mfd.append("--Tsft={}".format(self.Tsft))
            if self.h0 != 0:
                cl_mfd.append('--injectionSources="{}"'.format(self.config_file_name))
            earth_ephem = getattr(self, "earth_ephem", None)
            sun_ephem = getattr(self, "sun_ephem", None)
            if earth_ephem is not None:
                cl_mfd.append('--ephemEarth="{}"'.format(earth_ephem))
            if sun_ephem is not None:
                cl_mfd.append('--ephemSun="{}"'.format(sun_ephem))
    
            cl_mfd = " ".join(cl_mfd)
            check_ok = self.check_cached_data_okay_to_use(cl_mfd)
            if check_ok is False:
                helper_functions.run_commandline(cl_mfd)
    
        def predict_fstat(self):
            """ Wrapper to lalapps_PredictFstat """
            twoF_expected, twoF_sigma = predict_fstat(
                self.h0,
                self.cosi,
                self.psi,
                self.Alpha,
                self.Delta,
                self.F0,
                self.sftfilepath,
                self.minStartTime,
                self.maxStartTime,
                self.detectors,
                self.sqrtSX,
                tempory_filename="{}.tmp".format(self.label),
                earth_ephem=self.earth_ephem,
                sun_ephem=self.sun_ephem
            )  # detectors OR IFO?
            return twoF_expected
    
    
    class GlitchWriter(Writer):
        """ Instance object for generating SFTs containing glitch signals """
    
        @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,
            outdir=".",
            sqrtSX=1,
            Band=4,
            detectors="H1",
            minStartTime=None,
            maxStartTime=None,
            add_noise=True,
            transientWindowType="rect",
        ):
            """
            Parameters
            ----------
            label: string
                a human-readable label to be used in naming the output files
            tstart, duration : float
                start and duration (in gps seconds) of the total observation span
            dtglitch: float
                time (in gps seconds) of the glitch after tstart. To create data
                without a glitch, set dtglitch=None
            delta_phi, delta_F0, delta_F1: float
                instanteneous glitch magnitudes in rad, Hz, and Hz/s respectively
            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
                frequency, sky-position, and amplitude parameters
            Tsft: float
                the sft duration
            minStartTime, maxStartTime: float
                if not None, the total span of data, this can be used to generate
                transient signals
    
            see `lalapps_Makefakedata_v5 --help` for help with the other paramaters
            """
    
            self.set_ephemeris_files()
            self.basic_setup()
            self.calculate_fmin_Band()
    
            shapes = np.array(
                [
                    np.shape(x)
                    for x in [self.delta_phi, self.delta_F0, self.delta_F1, self.delta_F2]
                ]
            )
            if not np.all(shapes == shapes[0]):
                raise ValueError("all delta_* must be the same shape: {}".format(shapes))
    
            for d in self.delta_phi, self.delta_F0, self.delta_F1, self.delta_F2:
                if np.size(d) == 1:
                    d = np.atleast_1d(d)
    
            if self.dtglitch is None:
                self.tbounds = [self.tstart, self.tend]
            else:
                self.dtglitch = np.atleast_1d(self.dtglitch)
                self.tglitch = self.tstart + self.dtglitch
                self.tbounds = np.concatenate(([self.tstart], self.tglitch, [self.tend]))
            logging.info("Using segment boundaries {}".format(self.tbounds))
    
            tbs = np.array(self.tbounds)
            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
            )
    
        def make_cff(self):
            """
            Generates an .cff file for a 'glitching' signal
    
            """
    
            thetas = self._calculate_thetas(self.theta, self.delta_thetas, self.tbounds)
    
            content = ""
            for i, (t, d, ts) in enumerate(
                zip(thetas, self.durations_days, 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,
                )
    
                content += line
    
            if self.check_if_cff_file_needs_rewritting(content):
                config_file = open(self.config_file_name, "w+")
                config_file.write(content)
                config_file.close()
    
    
    class FrequencyModulatedArtifactWriter(Writer):
        """ Instance object for generating SFTs containing artifacts """
    
        @helper_functions.initializer
        def __init__(
            self,
            label,
            outdir=".",
            tstart=700000000,
            duration=86400,
            F0=30,
            F1=0,
            tref=None,
            h0=10,
            Tsft=1800,
            sqrtSX=1,
            Band=4,
            Pmod=lal.DAYSID_SI,
            Pmod_phi=0,
            Pmod_amp=1,
            Alpha=None,
            Delta=None,
            IFO="H1",
            minStartTime=None,
            maxStartTime=None,
            detectors="H1",
        ):
            """
            Parameters
            ----------
            tstart, duration : int
                start and duration times (in gps seconds) of the total observation
            Pmod, F0, F1 h0: float
                Modulation period, freq, freq-drift, and h0 of the artifact
            Alpha, Delta: float
                Sky position, in radians, of a signal of which to add the orbital
                modulation to the artifact, if not `None`.
            Tsft: float
                the sft duration
            sqrtSX: float
                Background IFO noise
    
            see `lalapps_Makefakedata_v4 --help` for help with the other paramaters
            """
            self.phi = 0
            self.F2 = 0
    
            self.basic_setup()
            self.set_ephemeris_files()
            self.tstart = int(tstart)
            self.duration = int(duration)
    
            if os.path.isdir(self.outdir) is False:
                os.makedirs(self.outdir)
            if tref is None:
                raise ValueError("Input `tref` not specified")
    
            self.nsfts = int(np.ceil(self.duration / self.Tsft))
            self.duration = self.duration / 86400.0
            self.calculate_fmin_Band()
    
            self.cosi = 0
            self.Fmax = F0
    
            if Alpha is not None and Delta is not None:
                self.n = np.array(
                    [
                        np.cos(Alpha) * np.cos(Delta),
                        np.sin(Alpha) * np.cos(Delta),
                        np.sin(Delta),
                    ]
                )
    
        def get_frequency(self, t):
            DeltaFDrift = self.F1 * (t - self.tref)
    
            phir = 2 * np.pi * t / self.Pmod + self.Pmod_phi
    
            if self.Alpha is not None and self.Delta is not None:
                spin_posvel = lalpulsar.PosVel3D_t()
                orbit_posvel = lalpulsar.PosVel3D_t()
                det = lal.CachedDetectors[4]
                ephems = lalpulsar.InitBarycenter(self.earth_ephem, self.sun_ephem)
                lalpulsar.DetectorPosVel(
                    spin_posvel,
                    orbit_posvel,
                    lal.LIGOTimeGPS(t),
                    det,
                    ephems,
                    lalpulsar.DETMOTION_ORBIT,
                )
                # Pos and vel returned in units of c
                DeltaFOrbital = np.dot(self.n, orbit_posvel.vel) * self.Fmax
    
                if self.IFO == "H1":
                    Lambda = lal.LHO_4K_DETECTOR_LATITUDE_RAD
                elif self.IFO == "L1":
                    Lambda = lal.LLO_4K_DETECTOR_LATITUDE_RAD
    
                DeltaFSpin = (
                    self.Pmod_amp
                    * lal.REARTH_SI
                    / lal.C_SI
                    * 2
                    * np.pi
                    / self.Pmod
                    * (np.cos(self.Delta) * np.cos(Lambda) * np.sin(self.Alpha - phir))
                    * self.Fmax
                )
            else:
                DeltaFOrbital = 0
                DeltaFSpin = 2 * np.pi * self.Pmod_amp / self.Pmod * np.cos(phir)
    
            f = self.F0 + DeltaFDrift + DeltaFOrbital + DeltaFSpin
            return f
    
        def get_h0(self, t):
            return self.h0
    
        def concatenate_sft_files(self):
            SFTFilename = lalpulsar.OfficialSFTFilename(
                self.IFO[0],
                self.IFO[1],
                self.nsfts,
                self.Tsft,
                int(self.tstart),
                int(self.duration),
                self.label,
            )
    
            # If the file already exists, simply remove it for now (no caching
            # implemented)
            helper_functions.run_commandline(
                "rm {}/{}".format(self.outdir, SFTFilename), raise_error=False, log_level=10
            )
    
            cl_splitSFTS = "lalapps_splitSFTs -fs {} -fb {} -fe {} -o {}/{} -i {}/*sft".format(
                self.fmin,
                self.Band,
                self.fmin + self.Band,
                self.outdir,
                SFTFilename,
                self.tmp_outdir,
            )
            helper_functions.run_commandline(cl_splitSFTS)
            helper_functions.run_commandline("rm {} -r".format(self.tmp_outdir))
            files = glob.glob("{}/{}*".format(self.outdir, SFTFilename))
            if len(files) == 1:
                fn = files[0]
                fn_new = fn.split(".")[0] + ".sft"
                helper_functions.run_commandline("mv {} {}".format(fn, fn_new))
            else:
                raise IOError(
                    "Attempted to rename file, but multiple files found: {}".format(files)
                )
    
        def pre_compute_evolution(self):
            logging.info("Precomputing evolution parameters")
            self.lineFreqs = []
            self.linePhis = []
            self.lineh0s = []
            self.mid_times = []
    
            linePhi = 0
            lineFreq_old = 0
            for i in tqdm(list(range(self.nsfts))):
                mid_time = self.tstart + (i + 0.5) * self.Tsft
                lineFreq = self.get_frequency(mid_time)
    
                self.mid_times.append(mid_time)
                self.lineFreqs.append(lineFreq)
                self.linePhis.append(
                    linePhi + np.pi * self.Tsft * (lineFreq_old + lineFreq)
                )
                self.lineh0s.append(self.get_h0(mid_time))
    
                lineFreq_old = lineFreq
    
        def make_ith_sft(self, i):
            try:
                self.run_makefakedata_v4(
                    self.mid_times[i],
                    self.lineFreqs[i],
                    self.linePhis[i],
                    self.lineh0s[i],
                    self.tmp_outdir,
                )
            except KeyboardInterrupt:
                raise KeyboardInterruptError()
    
        def make_data(self):
            self.maxStartTime = None
            self.duration = self.Tsft
    
            self.tmp_outdir = "{}/{}_tmp".format(self.outdir, self.label)
            if os.path.isdir(self.tmp_outdir) is True:
                raise ValueError(
                    "Temporary directory {} already exists, please rename".format(
                        self.tmp_outdir
                    )
                )
            else:
                os.makedirs(self.tmp_outdir)
    
            self.pre_compute_evolution()
    
            logging.info("Generating SFTs")
    
            if args.N > 1 and pkgutil.find_loader("pathos") is not None:
                import pathos.pools
    
                logging.info("Using {} threads".format(args.N))
                try:
                    with pathos.pools.ProcessPool(args.N) as p:
                        list(
                            tqdm(
                                p.imap(self.make_ith_sft, list(range(self.nsfts))),
                                total=self.nsfts,
                            )
                        )
                except KeyboardInterrupt:
                    p.terminate()
            else:
                logging.info(
                    "No multiprocessing requested or `pathos` not install, cont."
                    " without multiprocessing"
                )
                for i in tqdm(list(range(self.nsfts))):
                    self.make_ith_sft(i)
    
            self.concatenate_sft_files()
    
        def run_makefakedata_v4(self, mid_time, lineFreq, linePhi, h0, tmp_outdir):
            """ Generate the sft data using the --lineFeature option """
            cl_mfd = []
            cl_mfd.append("lalapps_Makefakedata_v4")
            cl_mfd.append("--outSingleSFT=FALSE")
            cl_mfd.append('--outSFTbname="{}"'.format(tmp_outdir))
            cl_mfd.append("--IFO={}".format(self.IFO))
            cl_mfd.append('--noiseSqrtSh="{}"'.format(self.sqrtSX))
            cl_mfd.append("--startTime={:0.0f}".format(mid_time - self.Tsft / 2.0))
            cl_mfd.append("--refTime={:0.0f}".format(mid_time))
            cl_mfd.append("--duration={}".format(int(self.duration)))
            cl_mfd.append("--fmin={:.16g}".format(self.fmin))
            cl_mfd.append("--Band={:.16g}".format(self.Band))
            cl_mfd.append("--Tsft={}".format(self.Tsft))
            cl_mfd.append("--Freq={}".format(lineFreq))
            cl_mfd.append("--phi0={}".format(linePhi))
            cl_mfd.append("--h0={}".format(h0))
            cl_mfd.append("--cosi={}".format(self.cosi))
            cl_mfd.append("--lineFeature=TRUE")
            earth_ephem = getattr(self, "earth_ephem", None)
            sun_ephem = getattr(self, "sun_ephem", None)
            if earth_ephem is not None:
                cl_mfd.append('--ephemEarth="{}"'.format(earth_ephem))
            if sun_ephem is not None:
                cl_mfd.append('--ephemSun="{}"'.format(sun_ephem))
            cl_mfd = " ".join(cl_mfd)
            helper_functions.run_commandline(cl_mfd, log_level=10)
    
    
    class FrequencyAmplitudeModulatedArtifactWriter(FrequencyModulatedArtifactWriter):
        """ Instance object for generating SFTs containing artifacts """
    
        def get_h0(self, t):
            return self.h0 * np.sin(2 * np.pi * t / self.Pmod + self.Pmod_phi)