LALPulsar  6.1.0.1-b72065a
testWriteSFTsfromSFDBs.py
Go to the documentation of this file.
1 # Copyright (C) 2020 Pep Covas, David Keitel, Rodrigo Tenorio
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by the
5 # Free Software Foundation; either version 2 of the License, or (at your
6 # option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 # Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 
17 import os
18 import lalpulsar
19 import glob
20 import subprocess
21 import numpy as np
22 from numpy.testing import assert_allclose
23 
24 # run this test with "make check TESTS=testWriteSFTsfromSFDBs"
25 # everything will be automatically put into testWriteSFTsfromSFDBs.testdir
26 testdir = "."
27 
28 
29 def validate_and_load_SFTs(globstr, fmin, fmax):
30  sftnames = list(glob.glob(os.path.join(testdir, globstr)))
31  assert len(sftnames) == 1
32  sftname = sftnames[0]
33  app = "lalpulsar_SFTvalidate"
34  cl = app + " " + sftname
35  print("Executing: " + cl)
36  out = subprocess.check_output(
37  cl, shell=True, stderr=subprocess.STDOUT, universal_newlines=True
38  )
39  print("{:s}: {}".format(app, out))
40  catalog = lalpulsar.SFTdataFind(sftname, None)
41  sfts = lalpulsar.LoadSFTs(catalog, fmin, fmax)
42  return sfts, sftname
43 
44 
46  sfts1, sfts2, plotnamebase="plot", label1="SFTs1", label2="SFTs2", title=""
47 ):
48  minlen = min(sfts1.length, sfts2.length)
49  if sfts1.length != sfts2.length:
50  print(
51  "Inconsistent SFT vector lengths {:d}!={:d} for {:s}, {:s}. Will only plot the first {:d}.".format(
52  sfts1.length, sfts2.length, label1, label2, minlen
53  )
54  )
55  # note: zip will do this automagically
56  plotnamebase += "-" + label1 + "-" + label2
57  for k, (sft1, sft2) in enumerate(zip(sfts1.data, sfts2.data)):
58  if sft1.epoch != sft2.epoch:
59  raise ValueError(
60  "Inconsistent epochs {:d}!={:d} in SFT {:d} of {:s}, {:s}".format(
61  sft1.epoch.gpsSeconds, sft2.epoch.gpsSeconds, k, label1, label2
62  )
63  )
64  title_this_epoch = title + ", epoch={:d}".format(sft1.epoch.gpsSeconds)
65  plotnamebase_this_epoch = plotnamebase + "_{:d}".format(sft1.epoch.gpsSeconds)
66  freqs1 = sft1.f0 + sft1.deltaF * np.arange(0, sft1.data.length)
67  freqs2 = sft2.f0 + sft2.deltaF * np.arange(0, sft2.data.length)
68  sft1_abs = np.abs(sft1.data.data)
69  sft2_abs = np.abs(sft2.data.data)
70  plt.plot(freqs1, sft1_abs, label=label1, color="black")
71  plt.plot(freqs2, sft2_abs, label=label2, color="red")
72  plt.xlabel("frequency [Hz]")
73  plt.ylabel("|complex SFT bin|")
74  plt.title(title_this_epoch)
75  plt.legend()
76  plotfile = os.path.join(testdir, plotnamebase_this_epoch + "-comparison.png")
77  print("Plotting to " + plotfile)
78  plt.savefig(plotfile)
79  plt.close()
80  plt.plot(freqs1, np.abs(sft1.data.data - sft2.data.data), color="black")
81  plt.xlabel("frequency [Hz]")
82  plt.ylabel("|{:s}-{:s}|".format(label1, label2))
83  plt.title(title_this_epoch)
84  plotfile = os.path.join(testdir, plotnamebase_this_epoch + "-absdiff.png")
85  print("Plotting to " + plotfile)
86  plt.savefig(plotfile)
87  plt.close()
88  plt.plot(
89  freqs1, np.abs(sft1.data.data - sft2.data.data) / sft2_abs, color="black"
90  )
91  plt.xlabel("frequency [Hz]")
92  plt.ylabel("|{:s}-{:s}|/|{:s}|".format(label1, label2, label2))
93  plt.title(title_this_epoch)
94  plotfile = os.path.join(testdir, plotnamebase_this_epoch + "-reldiff.png")
95  print("Plotting to " + plotfile)
96  plt.savefig(plotfile)
97  plt.close()
98  plt.plot(sft2_abs, np.abs(sft1.data.data - sft2.data.data), ".", color="black")
99  plt.xlabel("|{:s}|".format(label2))
100  plt.ylabel("|{:s}-{:s}|".format(label1, label2))
101  plt.title(title_this_epoch)
102  plotfile = os.path.join(
103  testdir, plotnamebase_this_epoch + "-absdiff-vs-abs2.png"
104  )
105  print("Plotting to " + plotfile)
106  plt.savefig(plotfile)
107  plt.close()
108  plt.plot(
109  sft2_abs,
110  np.abs(sft1.data.data - sft2.data.data) / sft2_abs,
111  ".",
112  color="black",
113  )
114  plt.xlabel("|{:s}|".format(label2))
115  plt.ylabel("|{:s}-{:s}|/|{:s}|".format(label1, label2, label2))
116  plt.title(title_this_epoch)
117  plotfile = os.path.join(
118  testdir, plotnamebase_this_epoch + "-reldiff-vs-abs2.png"
119  )
120  print("Plotting to " + plotfile)
121  plt.savefig(plotfile)
122  plt.close()
123 
124 
125 # files from reference tarball
126 ref_gwf = "V-V1_mfdv5-1257741529-2048.gwf"
127 ref_sft = "V-3_V1_1024SFT_mfdv5-1257741529-2048.sft"
128 ref_sfdb = "V1-mfdv5_20191114_043831.SFDB09"
129 
130 # data parameters
131 IFOs = ["V1"]
132 fmin = 49.0
133 fmax = 51.0
134 startTime = 1257741529
135 frameDuration = 2048
136 # the following are only for the SFDBs and the SFTs we'll make to compare against,
137 # ref_sft has Tsft=frameDuration and Toverlap=0 instead!
138 Tsft = int(ref_sft.split("SFT")[0].split("_")[-1])
139 Toverlap = int(0.5 * Tsft)
140 
141 
142 print("\n=== Running WriteSFTsfromSFDBs on reference SFDB file ===\n")
143 cl_SFDB = " ".join(
144  [
145  "lalpulsar_WriteSFTsfromSFDBs",
146  "--file-pattern",
147  ref_sfdb,
148  "--fmin",
149  str(fmin),
150  "--fmax",
151  str(fmax),
152  "--outSFTdir",
153  testdir,
154  ]
155 )
156 print("Executing: " + cl_SFDB)
157 subprocess.check_call(cl_SFDB, shell=True)
158 sfts_from_sfdb, _ = validate_and_load_SFTs(IFOs[0][0] + "*_fromSFDBs-*.sft", fmin, fmax)
159 
160 # TODO: also test and validate --outSFTnames, --outSingleSFT=False modes
161 
162 print("\n=== Reading reference SFTs from MFDv5 ===\n")
163 sfts_ref, _ = validate_and_load_SFTs(ref_sft, fmin, fmax)
164 # catalog_ref = lalpulsar.SFTdataFind(ref_sft, None)
165 # sfts_ref = lalpulsar.LoadSFTs(catalog_ref, fmin, fmax)
166 
167 
168 print("=== Comparing outputs of WriteSFTsfromSFDBs and MFDv5: overall properties ===\n")
169 # compare overall properties
170 atol_freq = 1e-6
171 print(
172  "nSFTs: {} vs {}".format(sfts_from_sfdb.length, sfts_ref.length)
173 ) # Number of SFTs
174 print(
175  "Epochs: {} vs {}".format(
176  [sft.epoch for sft in sfts_from_sfdb.data], [sft.epoch for sft in sfts_ref.data]
177  )
178 )
179 # lengths will not actually be the same:
180 # SFDBs contain a 4th SFT starting at 1257743065 and hanging over the end of the .gwf by half
181 if sfts_from_sfdb.length == sfts_ref.length + 1:
182  print(
183  "Inconsistent SFT vector lengths {:d}=={:d}+1 from SFDBs vs referenc SFT. This is expected.".format(
184  sfts_from_sfdb.length, sfts_ref.length
185  )
186  )
187 assert sfts_from_sfdb.length == sfts_ref.length + 1
188 mean_sfts = np.zeros(sfts_ref.length) # will reuse this for test at the end
189 for k, (sft1, sft2) in enumerate(zip(sfts_from_sfdb.data, sfts_ref.data)):
190  print("Comparing SFT number {:d}...".format(k))
191  f0_sfdb = sft1.f0
192  deltaF_sfdb = sft1.deltaF
193  nBins_sfdb = sft1.data.length
194  abs_sfdb = np.abs(sft1.data.data)
195  max_sfdb = np.max(abs_sfdb)
196  maxbin_sfdb = np.argmax(abs_sfdb)
197  maxfreq_sfdb = f0_sfdb + deltaF_sfdb * maxbin_sfdb
198  without_maxbin = np.arange(nBins_sfdb) != maxbin_sfdb
199  abs_without = abs_sfdb[without_maxbin]
200  mean_sfdb = np.mean(abs_without)
201  stdev_sfdb = mean_sfdb * np.std(abs_without / mean_sfdb)
202  f0_sfts = sft2.f0
203  deltaF_sfts = sft2.deltaF
204  nBins_sfts = sft2.data.length
205  abs_sfts = np.abs(sft2.data.data)
206  max_sfts = np.max(abs_sfts)
207  maxbin_sfts = np.argmax(abs_sfts)
208  maxfreq_sfts = f0_sfts + deltaF_sfts * maxbin_sfts
209  abs_without = abs_sfts[np.arange(nBins_sfts) != maxbin_sfts]
210  mean_sfts[k] = np.mean(abs_without)
211  stdev_sfts = mean_sfts[k] * np.std(abs_without / mean_sfts[k])
212  print("IFO: {} vs {}".format(sft1.name, sft2.name)) # Name of IFO
213  print("f0: {} vs {}".format(f0_sfdb, f0_sfts)) # Starting frequency
214  print(
215  "df: {} vs {}".format(deltaF_sfdb, deltaF_sfts)
216  ) # Frequency spacing between bins
217  print(
218  "nBins: {} vs {}".format(nBins_sfdb, nBins_sfts)
219  ) # Number of frequency bins in one SFT
220  print(
221  "max in bin: {} vs {}".format(maxbin_sfdb, maxbin_sfts)
222  ) # bin index for max of |SFT|
223  print("freq of max: {:.4f} vs {:.4f}".format(maxfreq_sfdb, maxfreq_sfts))
224  print("max |value|: {:.4e} vs {:.4e}".format(max_sfdb, max_sfts))
225  print("background mean: {:.4e} vs {:.4e}".format(mean_sfdb, mean_sfts[k]))
226  print("background stdev: {:.4e} vs {:.4e}".format(stdev_sfdb, stdev_sfts))
227  assert sft1.name == sft2.name
228  assert abs(f0_sfdb - f0_sfts) < atol_freq
229  assert abs(deltaF_sfdb - deltaF_sfts) < atol_freq
230  assert nBins_sfdb == nBins_sfts
231  assert maxbin_sfdb == maxbin_sfts
232  assert abs(max_sfdb - max_sfts) / max_sfts < 0.01
233  assert abs(maxfreq_sfdb - maxfreq_sfts) < atol_freq
234  assert abs(mean_sfdb - mean_sfts[k]) / mean_sfts[k] < 0.001
235  assert abs(stdev_sfdb - stdev_sfts) / stdev_sfts < 0.001
236 
237 print("\n=== Optional: comparison plots ===\n")
238 try:
239  import matplotlib
240 
241  matplotlib.use("Agg")
242  from matplotlib import pyplot as plt
243 
245  sfts_ref,
246  sfts_from_sfdb,
247  plotnamebase=IFOs[0],
248  label1="SFTs",
249  label2="SFDBs",
250  title="Tsft={:d}, Toverlap={:d}".format(Tsft, Toverlap),
251  )
252 except ImportError:
253  print(
254  "Cannot import matplotlib to make comparison plots, continuing with numerical comparisons."
255  )
256 except Exception as e:
257  print(
258  "Failed to create comparison plots, continuing with numerical comparisons. Exception was:"
259  )
260  print(e)
261 
262 
263 print(
264  "\n=== Comparing outputs of WriteSFTsfromSFDBs and MFDv5: actual per-bin data ===\n"
265 )
266 # ignoring here the overhanging 4th SFT from the SFDBs
267 # we use an atol threshold at 2% of the mean noise level
268 # because rtol tends to blow up in bins with very low noise
269 for k, (sft1, sft2) in enumerate(zip(sfts_from_sfdb.data, sfts_ref.data)):
270  atol = 0.02 * mean_sfts[k]
271  maxdiff = max(np.abs(sft1.data.data - sft2.data.data))
272  print(
273  "Comparing SFT number {:d}: max absolute difference is {:g} (allowed: {:g})".format(
274  k, maxdiff, atol
275  )
276  )
277  assert_allclose(sft1.data.data, sft2.data.data, rtol=0, atol=atol, verbose=True)
278 
279 print("All SFDB checks passed (or skipped).")
#define min(a, b)
def validate_and_load_SFTs(globstr, fmin, fmax)
def plot_SFTs_comparison(sfts1, sfts2, plotnamebase="plot", label1="SFTs1", label2="SFTs2", title="")
#define max(a, b)