Note
Go to the end to download the full example code.
Binary CW example: Comparison between MCMC and grid search
Comparison of the semicoherent F-statistic MCMC search algorithm to a simple grid search across the parameter space corresponding to a CW source in a binary system.
10 import os
11
12 import matplotlib.pyplot as plt
13 import numpy as np
14
15 import pyfstat
16
17 # Set to false to include eccentricity
18 circular_orbit = False
19
20 label = "PyFstatExampleBinaryMCMCvsGridComparison" + (
21 "CircularOrbit" if circular_orbit else ""
22 )
23 outdir = os.path.join("PyFstat_example_data", label)
24 logger = pyfstat.set_up_logger(label=label, outdir=outdir)
25
26 # Parameters to generate a data set
27 data_parameters = {
28 "sqrtSX": 1e-22,
29 "tstart": 1000000000,
30 "duration": 90 * 86400,
31 "detectors": "H1,L1",
32 "Tsft": 3600,
33 "Band": 4,
34 }
35
36 # Injected signal parameters
37 tend = data_parameters["tstart"] + data_parameters["duration"]
38 mid_time = 0.5 * (data_parameters["tstart"] + tend)
39 depth = 10.0
40 signal_parameters = {
41 "tref": data_parameters["tstart"],
42 "F0": 40.0,
43 "F1": 0,
44 "F2": 0,
45 "Alpha": 0.5,
46 "Delta": 0.5,
47 "period": 85 * 24 * 3600.0,
48 "asini": 4.0,
49 "tp": mid_time * 1.05,
50 "argp": 0.0 if circular_orbit else 0.54,
51 "ecc": 0.0 if circular_orbit else 0.7,
52 "h0": data_parameters["sqrtSX"] / depth,
53 "cosi": 1.0,
54 }
55
56
57 logger.info("Generating SFTs with injected signal...")
58 writer = pyfstat.BinaryModulatedWriter(
59 label=label + "SimulatedSignal",
60 outdir=outdir,
61 **data_parameters,
62 **signal_parameters,
63 )
64 writer.make_data()
65 logger.info("")
66
67 logger.info("Performing Grid Search...")
68
69 # Create ad-hoc grid and compute Fstatistic around injection point
70 # There's no class supporting a binary search in the same way as
71 # grid_based_searches.GridSearch, so we do it by hand constructing
72 # a grid and using core.ComputeFstat.
73 half_points_per_dimension = 2
74 search_keys = ["period", "asini", "tp", "argp", "ecc"]
75 search_keys_label = [
76 r"$P$ [s]",
77 r"$a_p$ [s]",
78 r"$t_{p}$ [s]",
79 r"$\omega$ [rad]",
80 r"$e$",
81 ]
82
83 grid_arrays = np.meshgrid(
84 *[
85 signal_parameters[key]
86 * (
87 1
88 + 0.01
89 * np.arange(-half_points_per_dimension, half_points_per_dimension + 1, 1)
90 )
91 for key in search_keys
92 ]
93 )
94 grid_points = np.hstack(
95 [grid_arrays[i].reshape(-1, 1) for i in range(len(grid_arrays))]
96 )
97
98 compute_f_stat = pyfstat.ComputeFstat(
99 sftfilepattern=writer.sftfilepath,
100 tref=signal_parameters["tref"],
101 binary=True,
102 minCoverFreq=-0.5,
103 maxCoverFreq=-0.5,
104 )
105 twoF_values = np.zeros(grid_points.shape[0])
106 for ind in range(grid_points.shape[0]):
107 point = grid_points[ind]
108 twoF_values[ind] = compute_f_stat.get_fullycoherent_twoF(
109 F0=signal_parameters["F0"],
110 F1=signal_parameters["F1"],
111 F2=signal_parameters["F2"],
112 Alpha=signal_parameters["Alpha"],
113 Delta=signal_parameters["Delta"],
114 period=point[0],
115 asini=point[1],
116 tp=point[2],
117 argp=point[3],
118 ecc=point[4],
119 )
120 logger.info(f"2Fstat computed on {grid_points.shape[0]} points")
121 logger.info("")
122 logger.info("Plotting results...")
123 dim = len(search_keys)
124 fig, ax = plt.subplots(dim, 1, figsize=(10, 10))
125 for ind in range(dim):
126 a = ax.ravel()[ind]
127 a.grid()
128 a.set(xlabel=search_keys_label[ind], ylabel=r"$2 \mathcal{F}$", yscale="log")
129 a.plot(grid_points[:, ind], twoF_values, "o")
130 a.axvline(signal_parameters[search_keys[ind]], label="Injection", color="orange")
131 plt.tight_layout()
132 fig.savefig(os.path.join(outdir, "grid_twoF_per_dimension.png"))
133
134
135 logger.info("Performing MCMCSearch...")
136 # Fixed points in frequency and sky parameters
137 theta_prior = {
138 "F0": signal_parameters["F0"],
139 "F1": signal_parameters["F1"],
140 "F2": signal_parameters["F2"],
141 "Alpha": signal_parameters["Alpha"],
142 "Delta": signal_parameters["Delta"],
143 }
144
145 # Set up priors for the binary parameters
146 for key in search_keys:
147 theta_prior.update(
148 {
149 key: {
150 "type": "unif",
151 "lower": 0.999 * signal_parameters[key],
152 "upper": 1.001 * signal_parameters[key],
153 }
154 }
155 )
156 if circular_orbit:
157 for key in ["ecc", "argp"]:
158 theta_prior[key] = 0
159 search_keys.remove(key)
160
161 # ptemcee sampler settings - in a real application we might want higher values
162 ntemps = 2
163 log10beta_min = -1
164 nwalkers = 100
165 nsteps = [100, 100] # [burn-in,production]
166
167 mcmcsearch = pyfstat.MCMCSearch(
168 label=label + "MCMCSearch",
169 outdir=outdir,
170 sftfilepattern=writer.sftfilepath,
171 theta_prior=theta_prior,
172 tref=signal_parameters["tref"],
173 nsteps=nsteps,
174 nwalkers=nwalkers,
175 ntemps=ntemps,
176 log10beta_min=log10beta_min,
177 binary=True,
178 )
179 # walker plot is generated during main run of the search class
180 mcmcsearch.run(
181 plot_walkers=True,
182 walker_plot_args={"plot_det_stat": True, "injection_parameters": signal_parameters},
183 )
184 mcmcsearch.print_summary()
185
186 # call some built-in plotting methods
187 # these can all highlight the injection parameters, too
188 logger.info("Making MCMCSearch {:s} corner plot...".format("-".join(search_keys)))
189 mcmcsearch.plot_corner(truths=signal_parameters)
190 logger.info("Making MCMCSearch prior-posterior comparison plot...")
191 mcmcsearch.plot_prior_posterior(injection_parameters=signal_parameters)
192 logger.info("")
193
194 logger.info("*" * 20)
195 logger.info("Quantitative comparisons:")
196 logger.info("*" * 20)
197
198 # some informative command-line output comparing search results and injection
199 # get max twoF and binary Doppler parameters
200 max_grid_index = np.argmax(twoF_values)
201 max_grid_2F = twoF_values[max_grid_index]
202 max_grid_parameters = grid_points[max_grid_index]
203
204 # same for MCMCSearch, here twoF is separate, and non-sampled parameters are not included either
205 max_dict_mcmc, max_2F_mcmc = mcmcsearch.get_max_twoF()
206 logger.info(
207 "Grid Search:\n\tmax2F={:.4f}\n\tOffsets from injection parameters (relative error): {:s}.".format(
208 max_grid_2F,
209 ", ".join(
210 [
211 "\n\t\t{1:s}: {0:.4e} ({2:.4f}%)".format(
212 max_grid_parameters[search_keys.index(key)]
213 - signal_parameters[key],
214 key,
215 100
216 * (
217 max_grid_parameters[search_keys.index(key)]
218 - signal_parameters[key]
219 )
220 / signal_parameters[key],
221 )
222 for key in search_keys
223 ]
224 ),
225 )
226 )
227 logger.info(
228 "Max 2F candidate from MCMC Search:\n\tmax2F={:.4f}"
229 "\n\tOffsets from injection parameters (relative error): {:s}.".format(
230 max_2F_mcmc,
231 ", ".join(
232 [
233 "\n\t\t{1:s}: {0:.4e} ({2:.4f}%)".format(
234 max_dict_mcmc[key] - signal_parameters[key],
235 key,
236 100
237 * (max_dict_mcmc[key] - signal_parameters[key])
238 / signal_parameters[key],
239 )
240 for key in search_keys
241 ]
242 ),
243 )
244 )
245 # get additional point and interval estimators
246 stats_dict_mcmc = mcmcsearch.get_summary_stats()
247 logger.info(
248 "Mean from MCMCSearch:\n\tOffset from injection parameters (relative error): {:s}"
249 "\n\tExpressed as fractions of 2sigma intervals: {:s}.".format(
250 ", ".join(
251 [
252 "\n\t\t{1:s}: {0:.4e} ({2:.4f}%)".format(
253 stats_dict_mcmc[key]["mean"] - signal_parameters[key],
254 key,
255 100
256 * (stats_dict_mcmc[key]["mean"] - signal_parameters[key])
257 / signal_parameters[key],
258 )
259 for key in search_keys
260 ]
261 ),
262 ", ".join(
263 [
264 "\n\t\t{1:s}: {0:.4f}%".format(
265 100
266 * np.abs(stats_dict_mcmc[key]["mean"] - signal_parameters[key])
267 / (2 * stats_dict_mcmc[key]["std"]),
268 key,
269 )
270 for key in search_keys
271 ]
272 ),
273 )
274 )
275 logger.info(
276 "Median from MCMCSearch:\n\tOffset from injection parameters (relative error): {:s},"
277 "\n\tExpressed as fractions of 90% confidence intervals: {:s}.".format(
278 ", ".join(
279 [
280 "\n\t\t{1:s}: {0:.4e} ({2:.4f}%)".format(
281 stats_dict_mcmc[key]["median"] - signal_parameters[key],
282 key,
283 100
284 * (stats_dict_mcmc[key]["median"] - signal_parameters[key])
285 / signal_parameters[key],
286 )
287 for key in search_keys
288 ]
289 ),
290 ", ".join(
291 [
292 "\n\t\t{1:s}: {0:.4f}%".format(
293 100
294 * np.abs(stats_dict_mcmc[key]["median"] - signal_parameters[key])
295 / (
296 stats_dict_mcmc[key]["upper90"]
297 - stats_dict_mcmc[key]["lower90"]
298 ),
299 key,
300 )
301 for key in search_keys
302 ]
303 ),
304 )
305 )