gstlal-inspiral  0.4.2
 All Classes Namespaces Files Functions Variables Pages
lloidplots.py
1 # Copyright (C) 2010 Leo Singer
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 
18 Plotting tools, mostly for inspiral searches.
19 
20 """
21 __author__ = "Leo Singer <leo.singer@ligo.org>"
22 __organization__ = ["LIGO", "California Institute of Technology"]
23 __copyright__ = "Copyright 2010, Leo Singer"
24 
25 
26 import gstlal.pipeutil # FIXME: needed because we have GStreamer stuff mixed in where it shouldn't be
27 import pylab
28 
29 """Dictionary of strings for useful units."""
30 units = {
31  'msun': r"$M_\odot$",
32 }
33 
34 
35 """Dictionary of strings for useful plot labels. Keys are generallly named the
36 same as a matching ligolw column."""
37 labels = {
38  'mtotal': r"total mass, $M$ (%s)" % units['msun'],
39  'mchirp': r"chirp mass, $\mathcal{M}$ (%s)" % units['msun'],
40  'mass1': r"component mass 1, $M_1$ (%s)" % units['msun'],
41  'mass2': r"component mass 2, $M_2$ (%s)" % units['msun'],
42  'snr': r"SNR $\rho$",
43  'eff_snr': r"effective SNR $\rho_\mathrm{eff}$",
44  'combined_snr': r"combined SNR, $\sqrt{\sum\rho^2}$",
45  'combined_eff_snr': r"combined effective SNR, $\sqrt{\sum\rho_\mathrm{eff}^2}$",
46  'chisq': r"$\chi^2$",
47  'tau0': r"chirp time $\tau0$",
48  'tau3': r"chirp time $\tau3$",
49 }
50 
51 
52 def plotbank(in_filename, out_filename=None, column1='mchirp', column2='mtotal'):
53  """Plot template bank parameters from a file generated by lalapps_tmpltbank."""
54  from glue.ligolw import ligolw, utils, lsctables
55  class LIGOLWContentHandler(ligolw.LIGOLWContentHandler):
56  pass
57  lsctables.use_in(LIGOLWContentHandler)
58  table = lsctables.SnglInspiralTable.get_table(
59  utils.load_filename(in_filename, contenthandler = LIGOLWContentHandler)
60  )
61  pylab.figure()
62  pylab.title('%s: placement of %d templates' % (in_filename, len(table)))
63  pylab.plot(table.get_column(column1), table.get_column(column2), ',')
64  pylab.xlabel(labels[column1])
65  pylab.ylabel(labels[column2])
66  pylab.grid()
67  if out_filename is None:
68  pylab.show()
69  else:
70  pylab.savefig(out_filename)
71  pylab.close()
72 
73 
74 def plotsvd(in_filename, out_filename=None):
75  """Plot heatmap of orthogonal template components."""
76  from gstlal.gstlal_svd_bank import read_bank
77  bank = read_bank(in_filename)
78  ntemplates = 0
79  for bf in bank.bank_fragments:
80  next_ntemplates = ntemplates + bf.orthogonal_template_bank.shape[0]
81  pylab.imshow(
82  pylab.log10(abs(bf.orthogonal_template_bank[::-1,:])),
83  extent = (bf.end, bf.start, ntemplates, next_ntemplates),
84  hold=True, aspect='auto'
85  )
86  pylab.text(bf.end + bank.filter_length / 30, ntemplates + 0.5 * bf.orthogonal_template_bank.shape[0], '%d Hz' % bf.rate, size='x-small')
87  ntemplates = next_ntemplates
88 
89  pylab.xlim(0, 1.15*bank.filter_length)
90  pylab.ylim(0, 1.05*ntemplates)
91  pylab.colorbar().set_label('$\mathrm{log}_{10} |u_{i}(t)|$')
92  pylab.xlabel(r"Time $t$ until coalescence (seconds)")
93  pylab.ylabel(r"Basis index $i$")
94  pylab.title(r"Orthonormal basis templates $u_{i}(t)$")
95  if out_filename is None:
96  pylab.show()
97  else:
98  pylab.savefig(out_filename)
99  pylab.close()
100 
101 
102 def plotskymap(fig, theta, phi, logp, gpstime, arrival_times=None, inj_lon_lat=None):
103  """Draw a skymap as produced by the lal_skymap element.
104  arrival_times should be a dictionary with keys being IFO names
105  (e.g. 'H1', 'L1', 'V1', ...) and values being double precision GPS arrival
106  at each IFO.
107 
108  If inj_lon_at is set to a celestial Dec/RA tuple in radians, then the
109  injection point of origin will be marked with a cross.
110 
111  Currently, lal_skymap generates a grid of 450x900 points, and this code
112  relies on that. It could be generalized to handle any rectangular grid,
113  but the pcolormesh method that is used here is really only finally tuned for
114  quadrilateral meshes.
115  """
116 
117 
118  # Some imports that are only useful for this function
119 
120  from math import atan2, acos, asin, degrees, sqrt, pi
121  from mpl_toolkits.basemap import Basemap, shiftgrid
122  import numpy as np
123  import lal
124  import lalsimulation
125  from glue.iterutils import choices
126 
127 
128  # Some useful functions
129 
130  def location_for_site(prefix):
131  """Get the Cartesian (WGS84) coordinates of a site, given its prefix
132  (H1 for Hanford, L1 for Livingston...)."""
133  return lalsimulation.DetectorPrefixToLALDetector(prefix).location
134 
135  def cart2spherical(cart):
136  """Convert a Cartesian vector to spherical polar azimuth (phi) and elevation (theta) in radians."""
137  return atan2(cart[1], cart[0]), acos(cart[2] / sqrt(np.dot(cart, cart)))
138 
139 
140  def spherical2latlon(spherical):
141  """Converts spherical polar coordinates in radians to latitude, longitude in degrees."""
142  return degrees(spherical[0]), 90 - degrees(spherical[1])
143 
144 
145  # Get figure axes.
146  ax = fig.gca()
147 
148  # Initialize map; draw gridlines and map boundary.
149  m = Basemap(projection='moll', lon_0=0, lat_0=0, ax=ax)
150  m.drawparallels(np.arange(-45,46,45), linewidth=0.5, labels=[1, 0, 0, 0], labelstyle="+/-")
151  m.drawmeridians(np.arange(-180,180,90), linewidth=0.5)
152  m.drawmapboundary()
153 
154  # lal_skymap outputs geographic coordinates; convert to celestial here.
155  sidereal_time = np.mod(lal.GreenwichMeanSiderealTime(lal.LIGOTimeGPS(gpstime)) / lal.PI_180, 360)
156  lons_grid = sidereal_time + phi.reshape(450, 900) / lal.PI_180
157  lats_grid = 90 - theta.reshape(450, 900) / lal.PI_180
158  logp_grid = logp.reshape(450, 900)
159 
160  # Rotate the coordinate grid; Basemap is too stupid to correctly handle a
161  # scalar field that must wrap around the edge of the map.
162  # FIXME: Find a mapping library that isn't a toy.
163  gridshift = round(sidereal_time / 360) * 360 + 180
164  lats_grid, dummy = shiftgrid(gridshift, lats_grid, lons_grid[0,:], start=False)
165  logp_grid, dummy = shiftgrid(gridshift, logp_grid, lons_grid[0,:], start=False)
166  lons_grid, dummy = shiftgrid(gridshift, lons_grid, lons_grid[0,:], start=False)
167 
168  # Transform from longitude/latitude to selected projection.
169  x, y = m(lons_grid, lats_grid)
170 
171  # Draw log probability distribution
172  pc = m.pcolormesh(x, y, logp_grid, vmin=logp[np.isfinite(logp)].min())
173  cb = fig.colorbar(pc, shrink=0.5)
174  cb.set_label('log relative probability')
175  cb.cmap.set_under('1.0', alpha=1.0)
176  cb.cmap.set_bad('1.0', alpha=1.0)
177 
178  # Draw mode of probability distribution
179  maxidx = logp_grid.flatten().argmax()
180  m.plot(x.flatten()[maxidx], y.flatten()[maxidx], '*', markerfacecolor='white', markersize=10)
181 
182  # Draw time delay loci, if arrival times were provided.
183  if arrival_times is not None:
184  for sites in choices(arrival_times.keys(), 2):
185  site0_location = location_for_site(sites[0])
186  site1_location = location_for_site(sites[1])
187  site_separation = site0_location - site1_location
188  site_distance_seconds = sqrt(np.dot(site_separation, site_separation)) / lal.C_SI
189  lon, lat = spherical2latlon(cart2spherical(site_separation))
190  site0_toa = arrival_times[sites[0]]
191  site1_toa = arrival_times[sites[1]]
192  radius = acos((site1_toa - site0_toa) / site_distance_seconds) * 180 / pi
193  # Sigh. Basemap is too stupid to be able to draw circles that wrap around
194  # the dateline. We'll just grab the points it generated, and plot it as
195  # a dense scatter point series.
196  poly = m.tissot(lon + sidereal_time, lat, radius, 1000, facecolor='none')
197  poly.remove()
198  x, y = zip(*poly.xy)
199  m.plot(x, y, ',k')
200 
201  # Draw injection point, if provided
202  if inj_lon_lat is not None:
203  inj_x, inj_y = m(degrees(inj_lon_lat[0]), degrees(inj_lon_lat[1]))
204  m.plot(inj_x, inj_y, '+k', markersize=20, markeredgewidth=1)
205 
206  # Add labels
207  ax.set_title('Candidate log probability distribution')
208  ax.set_xlabel('RA/dec, J2000. White star marks the mode of the PDF.\nBlack lines represent time delay solution loci for each pair of detectors.', fontsize=10)