From ffdeeb226c7b4686b3bf99b501c0476b91390ae5 Mon Sep 17 00:00:00 2001
From: Sylvia Biscoveanu <sylvia.biscoveanu@ligo.org>
Date: Wed, 24 Apr 2019 01:02:51 -0500
Subject: [PATCH] Calibration plotting

---
 bilby/core/utils.py | 25 ++++++++++++++
 bilby/gw/result.py  | 81 +++++++++++++++++++++++++++++++++++++++++++++
 bilby/gw/utils.py   | 71 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 176 insertions(+), 1 deletion(-)

diff --git a/bilby/core/utils.py b/bilby/core/utils.py
index 5e97a641b..5636a594a 100644
--- a/bilby/core/utils.py
+++ b/bilby/core/utils.py
@@ -703,6 +703,31 @@ def logtrapzexp(lnf, dx):
     return np.log(dx / 2.) + logsumexp([logsumexp(lnf[:-1]), logsumexp(lnf[1:])])
 
 
+def credible_interval(samples, confidence_level=.9, lower=True):
+    """
+    Return location of lower or upper confidence levels
+    Based on lalinference.bayespputils.cred_interval
+
+    Parameters
+    ----------
+    x: List of samples.
+    cl: Confidence level to return the bound of.
+    lower: If ``True``, return the lower bound, otherwise return the upper bound.
+
+    Returns
+    -------
+    float: the upper or lower confidence level
+
+    """
+    def cred_level(cl, x):
+        return np.sort(x, axis=0)[int(cl * len(x))]
+
+    if lower:
+        return cred_level((1. - confidence_level) / 2, samples)
+    else:
+        return cred_level((1. + confidence_level) / 2, samples)
+
+
 def run_commandline(cl, log_level=20, raise_error=True, return_output=True):
     """Run a string cmd as a subprocess, check for errors and return output.
 
diff --git a/bilby/gw/result.py b/bilby/gw/result.py
index 8f38de813..ed9e0ada1 100644
--- a/bilby/gw/result.py
+++ b/bilby/gw/result.py
@@ -1,7 +1,13 @@
 from __future__ import division
 
+import matplotlib.pyplot as plt
+import numpy as np
+import os
+
 from ..core.result import Result as CoreResult
 from ..core.utils import logger
+from .utils import (plot_spline_pos,
+                    spline_angle_xform)
 
 
 class CompactBinaryCoalesenceResult(CoreResult):
@@ -80,5 +86,80 @@ class CompactBinaryCoalesenceResult(CoreResult):
             logger.info("No injection for detector {}".format(detector))
             return None
 
+    def plot_calibration_posterior(self, level=.9):
+        """ Plots the calibration amplitude and phase uncertainty.
+        Adapted from the LALInference version in bayespputils
+
+        Parameters
+        ----------
+        level: float,  percentage for confidence levels
+
+        Returns
+        -------
+        saves a plot to outdir+label+calibration.png
+
+        """
+        fig, [ax1, ax2] = plt.subplots(2, 1, figsize=(15, 15), dpi=500)
+        posterior = self.posterior
+
+        font_size = 32
+        outdir = self.outdir
+
+        parameters = posterior.keys()
+        ifos = np.unique([param.split('_')[1] for param in parameters if 'recalib_' in param])
+        if ifos.size == 0:
+            logger.info("No calibration parameters found. Aborting calibration plot.")
+            return
+
+        for ifo in ifos:
+            if ifo == 'H1':
+                color = 'r'
+            elif ifo == 'L1':
+                color = 'g'
+            elif ifo == 'V1':
+                color = 'm'
+            else:
+                color = 'c'
+
+            # Assume spline control frequencies are constant
+            freq_params = np.sort([param for param in parameters if
+                                   'recalib_{0}_frequency_'.format(ifo) in param])
+
+            logfreqs = np.log([posterior[param].iloc[0] for param in freq_params])
+
+            # Amplitude calibration model
+            plt.sca(ax1)
+            amp_params = np.sort([param for param in parameters if
+                                  'recalib_{0}_amplitude_'.format(ifo) in param])
+            if len(amp_params) > 0:
+                amplitude = 100 * np.column_stack([posterior[param] for param in amp_params])
+                plot_spline_pos(logfreqs, amplitude, color=color, level=level,
+                                label="{0} (mean, {1}%)".format(ifo.upper(), int(level * 100)))
+
+            # Phase calibration model
+            plt.sca(ax2)
+            phase_params = np.sort([param for param in parameters if
+                                    'recalib_{0}_phase_'.format(ifo) in param])
+            if len(phase_params) > 0:
+                phase = np.column_stack([posterior[param] for param in phase_params])
+                plot_spline_pos(logfreqs, phase, color=color, level=level,
+                                label="{0} (mean, {1}%)".format(ifo.upper(), int(level * 100)),
+                                xform=spline_angle_xform)
+
+        ax1.tick_params(labelsize=.75 * font_size)
+        ax2.tick_params(labelsize=.75 * font_size)
+        plt.legend(loc='upper right', prop={'size': .75 * font_size}, framealpha=0.1)
+        ax1.set_xscale('log')
+        ax2.set_xscale('log')
+
+        ax2.set_xlabel('Frequency (Hz)', fontsize=font_size)
+        ax1.set_ylabel('Amplitude (%)', fontsize=font_size)
+        ax2.set_ylabel('Phase (deg)', fontsize=font_size)
+
+        filename = os.path.join(outdir, self.label + '_calibration.png')
+        fig.tight_layout()
+        fig.savefig(filename, bbox_inches='tight')
+        plt.close(fig)
+
 
 CBCResult = CompactBinaryCoalesenceResult
diff --git a/bilby/gw/utils.py b/bilby/gw/utils.py
index 3bb38eb7a..934bd3914 100644
--- a/bilby/gw/utils.py
+++ b/bilby/gw/utils.py
@@ -3,10 +3,13 @@ import os
 import json
 
 import numpy as np
+from scipy import interpolate
+import matplotlib.pyplot as plt
 
 from ..core.utils import (gps_time_to_gmst, ra_dec_to_theta_phi,
                           speed_of_light, logger, run_commandline,
-                          check_directory_exists_and_if_not_mkdir)
+                          check_directory_exists_and_if_not_mkdir,
+                          credible_interval)
 
 try:
     from gwpy.timeseries import TimeSeries
@@ -856,3 +859,69 @@ def lalsim_SimInspiralWaveformParamsInsertTidalLambda2(
 
     return lalsim.SimInspiralWaveformParamsInsertTidalLambda2(
         waveform_dictionary, lambda_2)
+
+
+def spline_angle_xform(delta_psi):
+    """
+    Returns the angle in degrees corresponding to the spline
+    calibration parameters delta_psi.
+    Based on the same function in lalinference.bayespputils
+
+    Parameters
+    ----------
+    delta_psi: calibration phase uncertainity
+
+    Returns
+    -------
+    float: delta_psi in degrees
+
+    """
+    rotation = (2.0 + 1.0j * delta_psi) / (2.0 - 1.0j * delta_psi)
+
+    return 180.0 / np.pi * np.arctan2(np.imag(rotation), np.real(rotation))
+
+
+def plot_spline_pos(log_freqs, samples, nfreqs=100, level=0.9, color='k', label=None, xform=None):
+    """
+    Plot calibration posterior estimates for a spline model in log space.
+    Adapted from the same function in lalinference.bayespputils
+
+    Parameters
+    ----------
+        log_freqs: The (log) location of spline control points.
+        samples: List of posterior draws of function at control points ``log_freqs``
+        nfreqs: Number of points to evaluate spline at for plotting.
+        level: Credible level to fill in.
+        color: Color to plot with.
+        label: Label for plot.
+        xform: Function to transform the spline into plotted values.
+
+    """
+    freq_points = np.exp(log_freqs)
+    freqs = np.logspace(min(log_freqs), max(log_freqs), nfreqs, base=np.exp(1))
+
+    data = np.zeros((samples.shape[0], nfreqs))
+
+    if xform is None:
+        scaled_samples = samples
+    else:
+        scaled_samples = xform(samples)
+
+    mu = np.mean(scaled_samples, axis=0)
+    lower_confidence_level = mu - credible_interval(scaled_samples, level, lower=True)
+    upper_confidence_level = credible_interval(scaled_samples, level, lower=False) - mu
+    plt.errorbar(freq_points, mu, yerr=[lower_confidence_level, upper_confidence_level],
+                 fmt='.', color=color, lw=4, alpha=0.5, capsize=0)
+
+    for i, sample in enumerate(samples):
+        temp = interpolate.spline(log_freqs, sample, np.log(freqs))
+        if xform is None:
+            data[i] = temp
+        else:
+            data[i] = xform(temp)
+
+    line, = plt.plot(freqs, np.mean(data, axis=0), color=color, label=label)
+    color = line.get_color()
+    plt.fill_between(freqs, credible_interval(data, level), credible_interval(data, level, lower=False),
+                     color=color, alpha=.1, linewidth=0.1)
+    plt.xlim(freq_points.min() - .5, freq_points.max() + 50)
-- 
GitLab