From 8afbf72c176ce6abe13c2b44d7b78ab4fa3e6668 Mon Sep 17 00:00:00 2001
From: Aaron Viets <aaron.viets@ligo.org>
Date: Sun, 1 Jul 2018 16:49:40 -0700
Subject: [PATCH] lal_transferfunction:  Perform checks on FFTs before
 computing transfer functions, to minimize loss of calculated stuff.

---
 gstlal-calibration/bin/gstlal_compute_strain  |   2 +-
 .../gst/lal/gstlal_transferfunction.c         | 135 ++++++++----------
 2 files changed, 62 insertions(+), 75 deletions(-)

diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain
index 6c10636766..27f53b31b2 100755
--- a/gstlal-calibration/bin/gstlal_compute_strain
+++ b/gstlal-calibration/bin/gstlal_compute_strain
@@ -791,7 +791,7 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 	# pcal excitation channel, which will be demodulated
 	pcal = calibration_parts.caps_and_progress(pipeline, head_dict["pcal"], hoft_caps, "pcal")
 	pcaltee = pipeparts.mktee(pipeline, pcal)
-	
+
 	# DARM_ERR channel, which will have followed different paths if we're doing full vs. partial calibration
 	if options.full_calibration:
 		darm_err = calibration_parts.caps_and_progress(pipeline, head_dict["res"], hoft_caps, "darm_err")
diff --git a/gstlal-calibration/gst/lal/gstlal_transferfunction.c b/gstlal-calibration/gst/lal/gstlal_transferfunction.c
index 8fe231242d..dca136e608 100644
--- a/gstlal-calibration/gst/lal/gstlal_transferfunction.c
+++ b/gstlal-calibration/gst/lal/gstlal_transferfunction.c
@@ -122,7 +122,6 @@ enum property {
 	ARG_LOW_PASS,
 	ARG_TRANSFER_FUNCTIONS,
 	ARG_FIR_FILTERS,
-	ARG_INPUT_MAY_BE_ZERO,
 	ARG_FAKE
 };
 
@@ -426,10 +425,6 @@ static gboolean find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *elemen
 	stride = element->fft_length - element->fft_overlap; \
 	num_tfs = element->channels - 1; \
 	DTYPE *real_fft = (DTYPE *) element->workspace.w ## S_OR_D ## pf.fft; \
- \
-	/* Check a few inputs to see if we have valid data yet */ \
-	for(i = 0; i < 2 * element->channels; i++) \
-		success &= isnormal(src[i]) || (element->input_may_be_zero && src[i] == 0.0); \
  \
 	/* Determine how many fft's we will calculate from combined leftover and new input data */ \
 	num_ffts = minimum64((element->workspace.w ## S_OR_D ## pf.num_leftover + stride - 1) / stride, element->num_ffts - element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg); \
@@ -461,32 +456,38 @@ static gboolean find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *elemen
 				element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = element->workspace.w ## S_OR_D ## pf.fft[k]; \
 		} \
  \
-		/* 
-		 * Add into the autocorrelation matrix to be averaged. The autocorrelation
-		 * matrix includes all transfer functions. Note that the data is stored in
-		 * "frequency-major" order: transfer functions at a particular frequency are
-		 * stored contiguously in memory before incrementing to the next frequency.
-		 */ \
-		for(j = 0; j < fd_fft_length; j++) { \
-			first_index = j * element->channels * num_tfs - 1; \
-			for(k = 1; k <= num_tfs; k++) { \
-				/* First, divide FFT's of first channel by others to get those transfer functions */ \
-				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k] += element->workspace.w ## S_OR_D ## pf.ffts[j] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
+		/* Check the FFTs to see if their values will produce usable transfer functions */ \
+		for(j = 0; j < fd_fft_length * element->channels; j++) \
+			success &= isnormal(creal ## F_OR_BLANK(element->workspace.w ## S_OR_D ## pf.ffts[j])); \
+ \
+		if(success) { \
+			/* 
+			 * Add into the autocorrelation matrix to be averaged. The autocorrelation
+			 * matrix includes all transfer functions. Note that the data is stored in
+			 * "frequency-major" order: transfer functions at a particular frequency are
+			 * stored contiguously in memory before incrementing to the next frequency.
+			 */ \
+			for(j = 0; j < fd_fft_length; j++) { \
+				first_index = j * element->channels * num_tfs - 1; \
+				for(k = 1; k <= num_tfs; k++) { \
+					/* First, divide FFT's of first channel by others to get those transfer functions */ \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k] += element->workspace.w ## S_OR_D ## pf.ffts[j] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
  \
-				/* Now set elements of the autocorrelation matrix along the diagonal equal to one */ \
-				first_index2 = first_index + k * element->channels; \
-				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2] += 1.0; \
+					/* Now set elements of the autocorrelation matrix along the diagonal equal to one */ \
+					first_index2 = first_index + k * element->channels; \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2] += 1.0; \
  \
-				/* Now find all other elements of the autocorrelation matrix */ \
-				for(m = 1; m <= num_tfs - k; m++) { \
-					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m] += element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
-					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m * num_tfs] += element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length]; \
+					/* Now find all other elements of the autocorrelation matrix */ \
+					for(m = 1; m <= num_tfs - k; m++) { \
+						element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m] += element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
+						element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m * num_tfs] += element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length]; \
+					} \
 				} \
 			} \
+		} else { \
+			GST_WARNING_OBJECT(element, "Computed FFT is not usable. Dropping..."); \
+			success = TRUE; \
 		} \
-		/* Check a few values in the autocorrelation matrix to see if it was computed successfully */ \
-		for(j = 0; j < 2 * num_tfs * element->channels; j++) \
-			success &= isnormal(creal ## F_OR_BLANK(element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[j])) || creal ## F_OR_BLANK(element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[j]) == 0.0; \
 	} \
  \
 	element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg += num_ffts; \
@@ -522,32 +523,38 @@ static gboolean find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *elemen
 				element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = element->workspace.w ## S_OR_D ## pf.fft[k]; \
 		} \
  \
-		/* 
-		 * Add into the autocorrelation matrix to be averaged. The autocorrelation
-		 * matrix includes all transfer functions. Note that the data is stored in
-		 * "frequency-major" order: transfer functions at a particular frequency are
-		 * stored contiguously in memory before incrementing to the next frequency.
-		 */ \
-		for(j = 0; j < fd_fft_length; j++) { \
-			first_index = j * element->channels * num_tfs - 1; \
-			for(k = 1; k <= num_tfs; k++) { \
-				/* First, divide FFT's of first channel by others to get those transfer functions */ \
-				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k] += element->workspace.w ## S_OR_D ## pf.ffts[j] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
+		/* Check the FFTs to see if their values will produce usable transfer functions */ \
+		for(j = 0; j < fd_fft_length * element->channels; j++) \
+			success &= isnormal(creal ## F_OR_BLANK(element->workspace.w ## S_OR_D ## pf.ffts[j])); \
  \
-				/* Now set elements of the autocorrelation matrix along the diagonal equal to one */ \
-				first_index2 = first_index + k * element->channels; \
-				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2] += 1.0; \
+		if(success) { \
+			/* 
+			 * Add into the autocorrelation matrix to be averaged. The autocorrelation
+			 * matrix includes all transfer functions. Note that the data is stored in
+			 * "frequency-major" order: transfer functions at a particular frequency are
+			 * stored contiguously in memory before incrementing to the next frequency.
+			 */ \
+			for(j = 0; j < fd_fft_length; j++) { \
+				first_index = j * element->channels * num_tfs - 1; \
+				for(k = 1; k <= num_tfs; k++) { \
+					/* First, divide FFT's of first channel by others to get those transfer functions */ \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k] += element->workspace.w ## S_OR_D ## pf.ffts[j] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
+ \
+					/* Now set elements of the autocorrelation matrix along the diagonal equal to one */ \
+					first_index2 = first_index + k * element->channels; \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2] += 1.0; \
  \
-				/* Now find all other elements of the autocorrelation matrix */ \
-				for(m = 1; m <= num_tfs - k; m++) { \
-					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m] += element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
-					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m * num_tfs] += element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length]; \
+					/* Now find all other elements of the autocorrelation matrix */ \
+					for(m = 1; m <= num_tfs - k; m++) { \
+						element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m] += element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length]; \
+						element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m * num_tfs] += element->workspace.w ## S_OR_D ## pf.ffts[j + k * fd_fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * fd_fft_length]; \
+					} \
 				} \
 			} \
+		} else { \
+			GST_WARNING_OBJECT(element, "Computed FFT is not usable. Dropping..."); \
+			success = TRUE; \
 		} \
-		/* Check a few values in the autocorrelation matrix to see if it was computed successfully */ \
-		for(j = 0; j < 2 * num_tfs * element->channels; j++) \
-			success &= isnormal(creal ## F_OR_BLANK(element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[j])) || creal ## F_OR_BLANK(element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[j]) == 0.0; \
 	} \
  \
 	element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg += num_ffts; \
@@ -578,30 +585,32 @@ static gboolean find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *elemen
 	element->workspace.w ## S_OR_D ## pf.num_leftover = maximum64(0, element->sample_count + 1 - sample_count_next_fft); \
  \
 	/* Finally, update transfer functions if ready */ \
-	if(success && element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg == element->num_ffts) { \
+	if(element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg == element->num_ffts) { \
 		success &= update_transfer_functions_ ## DTYPE(element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix, num_tfs, fd_fft_length, element->num_ffts, element->workspace.w ## S_OR_D ## pf.transfer_functions_at_f, element->workspace.w ## S_OR_D ## pf.transfer_functions_solved_at_f, element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix_at_f, element->workspace.w ## S_OR_D ## pf.permutation, element->transfer_functions); \
-		GST_INFO_OBJECT(element, "Just computed new transfer functions"); \
 		if(success) { \
+			GST_INFO_OBJECT(element, "Just computed new transfer functions"); \
 			/* Let other elements know about the update */ \
 			g_object_notify_by_pspec(G_OBJECT(element), properties[ARG_TRANSFER_FUNCTIONS]); \
 			/* Write transfer functions to the screen or a file if we want */ \
 			if(element->write_to_screen || element->filename) \
 				write_transfer_functions(element->transfer_functions, gst_element_get_name(element), element->rate / 2.0 / (fd_fft_length - 1.0), fd_fft_length, num_tfs, element->write_to_screen, element->filename, TRUE); \
-		} \
+		} else \
+			GST_WARNING_OBJECT(element, "Transfer function(s) computation failed. Trying again."); \
 		element->sample_count -= element->update_samples + element->num_ffts * stride + element->fft_overlap; \
 		element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg = 0; \
  \
 		/* Update FIR filters if we want */ \
 		if(element->make_fir_filters) { \
 			success &= update_fir_filters_ ## DTYPE(element->transfer_functions, num_tfs, element->fft_length, element->fir_length, element->rate, element->workspace.w ## S_OR_D ## pf.sinc_table, element->workspace.w ## S_OR_D ## pf.sinc_length, element->workspace.w ## S_OR_D ## pf.sinc_taps_per_df, element->workspace.w ## S_OR_D ## pf.fir_filter, element->workspace.w ## S_OR_D ## pf.fir_plan, element->workspace.w ## S_OR_D ## pf.fir_window, element->workspace.w ## S_OR_D ## pf.tukey, element->fir_filters); \
-			GST_INFO_OBJECT(element, "Just computed new FIR filters"); \
 			if(success) { \
+				GST_INFO_OBJECT(element, "Just computed new FIR filters"); \
 				/* Let other elements know about the update */ \
 				g_object_notify_by_pspec(G_OBJECT(element), properties[ARG_FIR_FILTERS]); \
 				/* Write FIR filters to the screen or a file if we want */ \
 				if(element->write_to_screen || element->filename) \
 					write_fir_filters(element->fir_filters, gst_element_get_name(element), element->fir_length, num_tfs, element->write_to_screen, element->filename, TRUE); \
-			} \
+			} else \
+				GST_WARNING_OBJECT(element, "FIR filter(s) computation failed. Trying again."); \
 		} \
 	} \
  \
@@ -1452,10 +1461,6 @@ static void set_property(GObject *object, enum property id, const GValue *value,
 		element->fir_filters = gstlal_doubles_from_g_value_array(g_value_get_boxed(value), NULL, &m);
 		break;
 
-	case ARG_INPUT_MAY_BE_ZERO:
-		element->input_may_be_zero = g_value_get_boolean(value);
-		break;
-
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, id, pspec);
 		break;
@@ -1550,10 +1555,6 @@ static void get_property(GObject *object, enum property id, GValue *value, GPara
 		}
 		break;
 
-	case ARG_INPUT_MAY_BE_ZERO:
-		g_value_set_boolean(value, element->input_may_be_zero);
-		break;
-
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, id, pspec);
 		break;
@@ -1771,15 +1772,6 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas
 		),
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE
 	);
-	properties[ARG_INPUT_MAY_BE_ZERO] = g_param_spec_boolean(
-		"input-may-be-zero",
-		"Input may be zero",
-		"Set to True if transfer functions should be computed even if an input\n\t\t\t"
-		"channel contains zeros. Default is False, to prevent errors when an\n\t\t\t"
-		"input channel has no data yet at start of stream, etc.",
-		FALSE,
-		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
-	);
 
 
 	g_object_class_install_property(
@@ -1852,11 +1844,6 @@ static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klas
 		ARG_FIR_FILTERS,
 		properties[ARG_FIR_FILTERS]
 	);
-	g_object_class_install_property(
-		gobject_class,
-		ARG_INPUT_MAY_BE_ZERO,
-		properties[ARG_INPUT_MAY_BE_ZERO]
-	);
 }
 
 
-- 
GitLab