gstlal-inspiral  0.4.2
 All Classes Namespaces Files Functions Variables Pages
plotfar.py
1 # Copyright (C) 2011--2014 Kipp Cannon, Chad Hanna, Drew Keppel
2 # Copyright (C) 2013 Jacob Peoples
3 #
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License as published by the
6 # Free Software Foundation; either version 2 of the License, or (at your
7 # option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 
18 
19 #
20 # =============================================================================
21 #
22 # Preamble
23 #
24 # =============================================================================
25 #
26 
27 import warnings
28 import math
29 import matplotlib
30 matplotlib.rcParams.update({
31  "font.size": 10.0,
32  "axes.titlesize": 10.0,
33  "axes.labelsize": 10.0,
34  "xtick.labelsize": 8.0,
35  "ytick.labelsize": 8.0,
36  "legend.fontsize": 8.0,
37  "figure.dpi": 600,
38  "savefig.dpi": 600,
39  "text.usetex": True
40 })
41 from matplotlib import figure
42 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
43 import numpy
44 from gstlal import plotutil
45 from gstlal import far
46 
47 
48 def plot_snr_chi_pdf(coinc_param_distributions, instrument, binnedarray_string, snr_max, dynamic_range_factor = 1e-10):
49  key = "%s_snr_chi" % instrument
50  if binnedarray_string == "LR":
51  binnedarray = getattr(coinc_param_distributions, "injection_pdf")[key]
52  else:
53  binnedarray = getattr(coinc_param_distributions, binnedarray_string)[key]
54  tag = {"background_pdf":"Noise", "injection_pdf":"Signal", "zero_lag_pdf":"Candidates", "LR":"LR"}[binnedarray_string]
55 
56  fig = figure.Figure()
57  FigureCanvas(fig)
58  fig.set_size_inches((4.5, 3.5))
59  axes = fig.gca()
60  # the last bin can have a centre at infinity, and its value is
61  # always 0 anyway so there's no point in trying to include it
62  x = binnedarray.bins[0].centres()[:-1]
63  y = binnedarray.bins[1].centres()[:-1]
64  z = binnedarray.array[:-1,:-1]
65  # FIXME make the LR pdf returned by a method of this class instead?
66  if binnedarray_string == "LR":
67  denom_binnedarray = coinc_param_distributions.background_pdf[key]
68  assert (denom_binnedarray.bins[0].centres()[:-1] == x).all()
69  assert (denom_binnedarray.bins[1].centres()[:-1] == y).all()
70  z /= denom_binnedarray.array[:-1,:-1]
71  if numpy.isnan(z).any():
72  warnings.warn("%s PDF contains NaNs" % instrument)
73  z = numpy.ma.masked_where(numpy.isnan(z), z)
74  if not z.any():
75  warnings.warn("%s PDF is 0, skipping" % instrument)
76  return None
77 
78  # the range of the plots
79  xlo, xhi = far.ThincaCoincParamsDistributions.snr_min, snr_max
80  ylo, yhi = .0001, 1.
81 
82  x = x[binnedarray.bins[xlo:xhi, ylo:yhi][0]]
83  y = y[binnedarray.bins[xlo:xhi, ylo:yhi][1]]
84  z = z[binnedarray.bins[xlo:xhi, ylo:yhi]]
85 
86  # matplotlib's colour bar seems to rely on being able to store the
87  # ratio of the lowest and highest value in a double so the range
88  # cannot be more than about 300 orders of magnitude. experiments
89  # show it starts to go wrong before that but 250 orders of
90  # magnitude seems to be OK
91  numpy.clip(z, z.max() * dynamic_range_factor, float("+inf"), out = z)
92 
93  mesh = axes.pcolormesh(x, y, z.T, norm = matplotlib.colors.LogNorm(), cmap = "afmhot", shading = "gouraud")
94  axes.contour(x, y, z.T, norm = matplotlib.colors.LogNorm(), colors = "k", linewidths = .5)
95  axes.loglog()
96  axes.grid(which = "both")
97  #axes.set_xlim((xlo, xhi))
98  #axes.set_ylim((ylo, yhi))
99  fig.colorbar(mesh, ax = axes)
100  axes.set_xlabel(r"$\mathrm{SNR}$")
101  axes.set_ylabel(r"$\chi^{2} / \mathrm{SNR}^{2}$")
102  if tag.lower() in ("signal",):
103  axes.set_title(r"%s %s $P(\chi^{2} / \mathrm{SNR}^{2} | \mathrm{SNR})$" % (instrument, tag))
104  elif tag.lower() in ("noise", "candidates"):
105  axes.set_title(r"%s %s $P(\mathrm{SNR}, \chi^{2} / \mathrm{SNR}^{2})$" % (instrument, tag))
106  elif tag.lower() in ("lr",):
107  axes.set_title(r"%s $P(\chi^{2} / \mathrm{SNR}^{2} | \mathrm{SNR}) / P(\mathrm{SNR}, \chi^{2} / \mathrm{SNR}^{2})$" % instrument)
108  else:
109  raise ValueError(tag)
110  return fig
111 
112 
113 def plot_rates(coinc_param_distributions, ranking_data = None):
114  fig = figure.Figure()
115  FigureCanvas(fig)
116  fig.set_size_inches((6., 6.))
117  axes0 = fig.add_subplot(2, 2, 1)
118  axes1 = fig.add_subplot(2, 2, 2)
119  axes2 = fig.add_subplot(2, 2, 3)
120  axes3 = fig.add_subplot(2, 2, 4)
121 
122  # singles counts
123  labels = []
124  sizes = []
125  colours = []
126  for instrument, category in sorted(coinc_param_distributions.instrument_categories.items()):
127  count = coinc_param_distributions.background_rates["instruments"][category,]
128  if not count:
129  continue
130  labels.append("%s\n(%d)" % (instrument, count))
131  sizes.append(count)
132  colours.append(plotutil.colour_from_instruments((instrument,)))
133  axes0.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
134  axes0.set_title("Observed Background Event Counts")
135 
136  # projected background counts
137  labels = []
138  sizes = []
139  colours = []
140  for instruments in sorted(sorted(instruments) for instruments in coinc_param_distributions.count_above_threshold if instruments is not None):
141  count = coinc_param_distributions.background_rates["instruments"][coinc_param_distributions.instrument_categories.category(instruments),]
142  if len(instruments) < 2 or not count:
143  continue
144  labels.append("%s\n(%d)" % (", ".join(instruments), count))
145  sizes.append(count)
146  colours.append(plotutil.colour_from_instruments(instruments))
147  axes1.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
148  axes1.set_title("Projected Background Coincidence Counts")
149 
150  # recovered signal distribution
151  # FIXME ranking data is not even used, why is this check here?
152  if ranking_data is not None:
153  labels = []
154  sizes = []
155  colours = []
156  for instruments, fraction in sorted(coinc_param_distributions.Pinstrument_signal.items(), key = lambda (instruments, fraction): sorted(instruments)):
157  if len(instruments) < 2 or not fraction:
158  continue
159  labels.append(", ".join(sorted(instruments)))
160  sizes.append(fraction)
161  colours.append(plotutil.colour_from_instruments(instruments))
162  axes2.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
163  axes2.set_title(r"Projected Recovered Signal Distribution")
164 
165  # observed counts
166  labels = []
167  sizes = []
168  colours = []
169  for instruments, count in sorted((sorted(instruments), count) for instruments, count in coinc_param_distributions.count_above_threshold.items() if instruments is not None):
170  if len(instruments) < 2 or not count:
171  continue
172  labels.append("%s\n(%d)" % (", ".join(instruments), count))
173  sizes.append(count)
174  colours.append(plotutil.colour_from_instruments(instruments))
175  axes3.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
176  axes3.set_title("Observed Coincidence Counts")
177 #FIXME: remove when we have a new enough matplotlib on all the reference platforms
178  try:
179  fig.tight_layout(pad = .8)
180  return fig
181  except AttributeError:
182  return fig
183 
184 def plot_snr_joint_pdf(coinc_param_distributions, instruments, horizon_distances, max_snr):
185  if len(instruments) > 2:
186  # FIXME: figure out how to plot 3D PDFs
187  return None
188  ignored, binnedarray, ignored = coinc_param_distributions.snr_joint_pdf_cache[(instruments, horizon_distances)]
189  instruments = sorted(instruments)
190  horizon_distances = dict(horizon_distances)
191  fig = figure.Figure()
192  FigureCanvas(fig)
193  fig.set_size_inches((5, 4))
194  axes = fig.gca()
195  x = binnedarray.bins[0].centres()
196  y = binnedarray.bins[1].centres()
197  z = binnedarray.array
198  if numpy.isnan(z).any():
199  warnings.warn("%s SNR PDF for %s contains NaNs" % (", ".join(instruments), ", ".join("%s=%g" % instdist for instdist in sorted(horizon_distances.items()))))
200  z = numpy.ma.masked_where(numpy.isnan(z), z)
201 
202  # the range of the plots
203  xlo, xhi = far.ThincaCoincParamsDistributions.snr_min, max_snr
204 
205  x = x[binnedarray.bins[xlo:xhi, xlo:xhi][0]]
206  y = y[binnedarray.bins[xlo:xhi, xlo:xhi][1]]
207  z = z[binnedarray.bins[xlo:xhi, xlo:xhi]]
208 
209  # don't try to plot blank PDFs (it upsets older matplotlibs)
210  if z.max() == 0.:
211  return None
212 
213  # these plots only require about 20 orders of magnitude of dynamic
214  # range
215  numpy.clip(z, z.max() * 1e-20, float("+inf"), out = z)
216 
217  # one last check for craziness to make error messages more
218  # meaningful
219  assert not numpy.isnan(z).any()
220  assert not (z <= 0.).any()
221 
222  mesh = axes.pcolormesh(x, y, z.T, norm = matplotlib.colors.LogNorm(), cmap = "afmhot", shading = "gouraud")
223  axes.contour(x, y, z.T, norm = matplotlib.colors.LogNorm(), colors = "k", linewidths = .5)
224  axes.loglog()
225  axes.grid(which = "both", linestyle = "-", linewidth = 0.2)
226  #axes.set_xlim((xlo, xhi))
227  #axes.set_ylim((xlo, xhi))
228  fig.colorbar(mesh, ax = axes)
229  # co-ordinates are in alphabetical order
230  axes.set_xlabel(r"$\mathrm{SNR}_{\mathrm{%s}}$" % instruments[0])
231  axes.set_ylabel(r"$\mathrm{SNR}_{\mathrm{%s}}$" % instruments[1])
232  axes.set_title(r"$P(%s)$" % ", ".join("\mathrm{SNR}_{\mathrm{%s}}" % instrument for instrument in instruments))
233 
234  return fig
235 
236 
237 def plot_likelihood_ratio_pdf(ranking_data, instruments, (xlo, xhi), tag, binnedarray_string = "background_likelihood_pdfs"):
238  pdf = getattr(ranking_data, binnedarray_string)[instruments]
239  if binnedarray_string == "background_likelihood_pdfs":
240  zerolag_pdf = ranking_data.zero_lag_likelihood_pdfs[instruments]
241  else:
242  zerolag_pdf = None
243 
244  fig = figure.Figure()
245  FigureCanvas(fig)
246  fig.set_size_inches((4., 4. / plotutil.golden_ratio))
247  axes = fig.gca()
248  axes.semilogy(pdf.bins[0].centres(), pdf.array, color = "k")
249  if zerolag_pdf is not None:
250  axes.semilogy(zerolag_pdf.bins[0].centres(), zerolag_pdf.array, color = "k", linestyle = "--")
251  axes.grid(which = "major", linestyle = "-", linewidth = 0.2)
252  if instruments is None:
253  axes.set_title(r"%s Log Likelihood Ratio PDF" % tag)
254  else:
255  axes.set_title(r"%s %s Log Likelihood Ratio PDF" % (", ".join(sorted(instruments)), tag))
256  axes.set_xlabel(r"$\ln \mathcal{L}$")
257  axes.set_ylabel(r"$P(\ln \mathcal{L} | \mathrm{%s})$" % tag.lower())
258  yhi = pdf[xlo:xhi,].max()
259  ylo = pdf[xlo:xhi,].min()
260  if zerolag_pdf is not None:
261  yhi = max(yhi, zerolag_pdf[xlo:xhi,].max())
262  ylo = min(ylo, zerolag_pdf[xlo:xhi,].min())
263  ylo = max(yhi * 1e-40, ylo)
264  axes.set_ylim((10**math.floor(math.log10(ylo) - .5), 10**math.ceil(math.log10(yhi) + .5)))
265  axes.set_xlim((xlo, xhi))
266 #FIXME: remove when we have a new enough matplotlib on all the reference platforms
267  try:
268  fig.tight_layout(pad = .8)
269  return fig
270  except AttributeError:
271  return fig
272 
273 def plot_likelihood_ratio_ccdf(fapfar, (xlo, xhi), tag, zerolag_ln_likelihood_ratios = None):
274  ccdf = fapfar.ccdf_interpolator
275 
276  fig = figure.Figure()
277  FigureCanvas(fig)
278  fig.set_size_inches((4., 4. / plotutil.golden_ratio))
279  axes = fig.gca()
280  x = numpy.linspace(xlo, xhi, 10000)
281  y = ccdf(x)
282  axes.semilogy(x, y, color = "k")
283  yhi = y.max()
284  ylo = max(yhi * 1e-40, y.min())
285  if zerolag_ln_likelihood_ratios is not None:
286  x = numpy.array(sorted(zerolag_ln_likelihood_ratios), dtype = "double")
287  y = numpy.arange(len(zerolag_ln_likelihood_ratios), 0, -1, dtype = "double")
288  y *= (1. - 1. / math.e) / y[0]
289  axes.semilogy(x, y, color = "k", linestyle = "--")
290  yhi = max(yhi, y.max())
291  ylo = min(ylo, y.min())
292  ylo = max(ylo, yhi * (y.min() / yhi)**1.5)
293  axes.set_ylim((10**math.floor(math.log10(ylo) - .5), 10**math.ceil(math.log10(yhi) + .5)))
294  axes.set_xlim((xlo, xhi))
295  axes.grid(which = "major", linestyle = "-", linewidth = 0.2)
296  axes.set_title(r"%s Log Likelihood Ratio CCDF" % tag)
297  axes.set_xlabel(r"$\ln \mathcal{L}$")
298  axes.set_ylabel(r"$P(\geq \ln \mathcal{L} | \mathrm{%s})$" % tag.lower())
299 #FIXME: remove when we have a new enough matplotlib on all the reference platforms
300  try:
301  fig.tight_layout(pad = .8)
302  return fig
303  except AttributeError:
304  return fig