From e70070a7e17af4d6f5cf1dc4bd2553299d508e37 Mon Sep 17 00:00:00 2001
From: Kipp Cannon <kipp.cannon@ligo.org>
Date: Tue, 19 Jun 2018 13:16:21 +0900
Subject: [PATCH] PSDFirKernel: add phase correction option

- add option to reduce the phase difference between a the FIR filter for the current PSD and the FIR filter for some other reference PSD.  the use case is in reducing the phase discrepancy between a signal and a previously whitened template.
---
 gstlal/python/reference_psd.py | 57 ++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/gstlal/python/reference_psd.py b/gstlal/python/reference_psd.py
index acf0c65f21..f26da856ac 100644
--- a/gstlal/python/reference_psd.py
+++ b/gstlal/python/reference_psd.py
@@ -444,6 +444,42 @@ class PSDFirKernel(object):
 	def __init__(self):
 		self.revplan = None
 		self.fwdplan = None
+		self.target_phase = None
+		self.target_phase_mask = None
+
+	def set_phase(self, psd, f_low = 10.0, m1 = 1.4, m2 = 1.4):
+		# compute phase response of zero-latency whitening filter
+		# corresponding to psd
+		kernel, latency, sample_rate = self.psd_to_linear_phase_whitening_fir_kernel(psd)
+		kernel, phase = self.linear_phase_fir_kernel_to_minimum_phase_whitening_fir_kernel(kernel, sample_rate)
+
+		# get merger model for SNR = 1.
+		f_psd = psd.f0 + numpy.arange(len(psd.data.data)) * psd.deltaF
+		horizon_distance = HorizonDistance(f_low, f_psd[-1], psd.deltaF, m1, m2)
+		f_model, model= horizon_distance(psd, 1.)[1]
+
+		# find the range of frequency bins covered by the merger
+		# model
+		kmin, kmax = f_psd.searchsorted(f_model[0]), f_psd.searchsorted(f_model[-1]) + 1
+
+		# compute SNR=1 model's (d SNR^2 / df) spectral density
+		unit_snr2_density = numpy.zeros_like(phase)
+		unit_snr2_density[kmin:kmax] = model / psd.data.data[kmin:kmax]
+
+		# integrate across each frequency bin, converting to
+		# snr^2/bin.  NOTE:  this step is here for the record, but
+		# is commented out because it has no effect on the result
+		# given the renormalization that occurs next.
+		#unit_snr2_density *= psd.deltaF
+
+		# take 16th root, then normalize so max=1.  why?  I don't
+		# know, just feels good, on the whole.
+		unit_snr2_density = unit_snr2_density**(1./16)
+		unit_snr2_density /= unit_snr2_density.max()
+
+		# record phase vector and SNR^2 density vector
+		self.target_phase = phase
+		self.target_phase_mask = unit_snr2_density
 
 	def psd_to_linear_phase_whitening_fir_kernel(self, psd, invert = True, nyquist = None):
 		"""
@@ -678,6 +714,27 @@ class PSDFirKernel(object):
 		#gain = numpy.exp(theta_data.real)
 		phase = -theta_data.imag
 
+		#
+		# apply optional masked phase adjustment
+		#
+
+		if self.target_phase is not None:
+			# compute phase adjustment for +ve frequencies
+			phase_adjustment = (self.target_phase - phase) * self.target_phase_mask
+
+			# combine with phase adjustment for -ve frequencies
+			phase_adjustment = numpy.concatenate((phase_adjustment[1:][-1::-1].conj(), phase_adjustment))
+
+			# apply adjustment.  phase adjustment is what we
+			# wish to add to the phase.  theta's imaginary
+			# component contains the negative of the phase, so
+			# we need to add -phase to theta's imaginary
+			# component
+			theta.data.data += -1.j * phase_adjustment
+
+			# report adjusted phase
+			#phase = -theta.data.data[working_length // 2:].imag
+
 		#
 		# compute minimum phase kernel
 		#
-- 
GitLab