From a073d60006d86be782b9eba550b62cb0dd806cab Mon Sep 17 00:00:00 2001
From: Aaron Viets <aaron.viets@ligo.org>
Date: Fri, 13 Jul 2018 14:39:12 -0700
Subject: [PATCH] gstlal_compute_strain:  Simplified removal of pcal lines.
 Also slightly simplified calculation of kappas.

---
 gstlal-calibration/bin/gstlal_compute_strain  | 479 ++++++++----------
 .../gst/lal/gstlal_adaptivefirfilt.c          |   2 +-
 2 files changed, 207 insertions(+), 274 deletions(-)

diff --git a/gstlal-calibration/bin/gstlal_compute_strain b/gstlal-calibration/bin/gstlal_compute_strain
index 4cfc5f6130..ce52aed05c 100755
--- a/gstlal-calibration/bin/gstlal_compute_strain
+++ b/gstlal-calibration/bin/gstlal_compute_strain
@@ -193,10 +193,8 @@ parser.add_option("--apply-kappap", action = "store_true", help = "Set this to h
 parser.add_option("--apply-complex-kappap", action = "store_true", help = "Set this to have the \kappa_p factors the actuation chain with an adaptive filter that corrects for both magnitude and phase errors.")
 parser.add_option("--apply-kappau", action = "store_true", help = "Set this to have the \kappa_u factors multiply the actuation chain.")
 parser.add_option("--apply-complex-kappau", action = "store_true", help = "Set this to have the \kappa_u factors the actuation chain with an adaptive filter that corrects for both magnitude and phase errors.")
-parser.add_option("--act-timing-from-kappapu", action = "store_true", help = "Set this to use the calculated value of \kappa_pu to measure any timing error in the actuation. If this is set, the phase of \kappa_tst will be adjusted accordingly.")
 parser.add_option("--apply-kappatst", action = "store_true", help = "Set this to have the \kappa_tst factors multiply the actuation chain.")
 parser.add_option("--apply-complex-kappatst", action = "store_true", help = "Set this to have the \kappa_tst factors filter the actuation chain with an adaptive filter that corrects for both magnitude and phase errors.")
-parser.add_option("--act-timing-from-kappatst", action = "store_true", help = "Set this to use the calculated value of \kappa_tst to measure any timing error in the actuation. If this is set, the phase of \kappa_pu will be adjusted accordingly.")
 parser.add_option("--apply-kappac", action = "store_true", help = "Set this to have the \kappa_c factors multiply the sensing chain.")
 parser.add_option("--compute-factors-sr", metavar = "Hz", type = int, default = 16, help = "Sample rate at which calibration factors are computed. (Default = 16 Hz)")
 parser.add_option("--demodulation-filter-time", metavar = "s", type = int, default = 20, help = "Length in seconds of low-pass FIR filter used in demodulation of the calibration lines. (Default = 20 seconds)")
@@ -241,7 +239,7 @@ parser.add_option("--sensing-filter-averaging-time", metavar = "seconds", type =
 parser.add_option("--sensing-filter-taper-length", metavar = "samples", type = int, default = 32768, help = "Number of samples to be used when tapering old inverse sensing filter and ramping in new filter. (Default = 32768)")
 parser.add_option("--actuation-filter-update-time", metavar = "seconds", type = float, default = 60, help = "Length of time (in seconds) between when the actuation FIR filters are updated. (Default = 60 seconds)")
 parser.add_option("--actuation-filter-averaging-time", metavar = "seconds", type = float, default = 1, help = "Length of time (in seconds) over which the smoothed time-dependent parameters of the actuation function are averaged before updating the filter. (Default = 1 second)")
-parser.add_option("--actuation-filter-taper-length", metavar = "samples", type = int, default = 32768, help = "Number of samples to be used when tapering old actuation filters and ramping in new filters. (Default = 32768)")
+parser.add_option("--actuation-filter-taper-length", metavar = "samples", type = int, default = 4096, help = "Number of samples to be used when tapering old actuation filters and ramping in new filters. (Default = 32768)")
 
 # These are all options related to the reference channels used in the calibration factors computation
 parser.add_option("--ref-channels-sr", metavar = "Hz", default = 16, help = "Set the sample rate for the reference model channels used in the calibration factors calculation. (Default = 16 Hz)")
@@ -498,16 +496,17 @@ opt_gain_fcc_line_freq = float(filters["kc_pcal_line_freq"])
 pcal_corr_at_opt_gain_fcc_freq_real = float(filters["kc_pcal_corr_re"])
 pcal_corr_at_opt_gain_fcc_freq_imag = float(filters["kc_pcal_corr_im"])
 esd_act_line_freq = float(filters["ktst_esd_line_freq"])
+pcal_line_removal_dict = {}
+if options.remove_callines:
+	pcal_line_removal_dict["pcal1"] = [darm_act_line_freq, pcal_corr_at_darm_act_freq_real, pcal_corr_at_darm_act_freq_imag, None]
+	pcal_line_removal_dict["pcal2"] = [opt_gain_fcc_line_freq, pcal_corr_at_opt_gain_fcc_freq_real, pcal_corr_at_opt_gain_fcc_freq_imag, None]
 try:
 	src_pcal_line_freq = float(filters["src_pcal_line_freq"])
 	pcal_corr_at_src_freq_real = float(filters["src_pcal_corr_re"])
 	pcal_corr_at_src_freq_imag = float(filters["src_pcal_corr_im"])
-	if src_pcal_line_freq > 10.0 and options.remove_callines:
-		remove_src_pcal_line = True
-	else:
-		remove_src_pcal_line = False
+	if src_pcal_line_freq > 10.0 and options.remove_callines and src_pcal_line_freq != darm_act_line_freq:
+		pcal_line_removal_dict["pcal4"] = [src_pcal_line_freq, pcal_corr_at_src_freq_real, pcal_corr_at_src_freq_imag, None]
 except:
-	remove_src_pcal_line = False
 	if not options.no_srcQ or not options.no_fs:
 		raise ValueError("Cannot compute SRC spring frequency or Q, as the calibration line frequency is not contained in the specified filters file.")
 try:
@@ -515,21 +514,17 @@ try:
 	pcal_corr_at_high_line_freq_real = float(filters["high_pcal_corr_re"])
 	pcal_corr_at_high_line_freq_imag = float(filters["high_pcal_corr_im"])
 	if high_pcal_line_freq > 0 and options.remove_callines:
-		remove_high_pcal_line = True
-	else:
-		remove_high_pcal_line = False
+		pcal_line_removal_dict["pcal3"] = [high_pcal_line_freq, pcal_corr_at_high_line_freq_real, pcal_corr_at_high_line_freq_imag, None]
 except:
-	remove_high_pcal_line = False
+	high_pcal_line_freq = 0.0
 try:
 	roaming_pcal_line_freq = float(filters["roaming_pcal_line_freq"])
 	pcal_corr_at_roaming_line_real = float(filters["roaming_pcal_corr_re"])
 	pcal_corr_at_roaming_line_imag = float(filters["roaming_pcal_corr_im"])
 	if roaming_pcal_line_freq > 0.0 and options.remove_callines:
-		remove_roaming_pcal_line = True
-	else:
-		remove_roaming_pcal_line = False
+		pcal_line_removal_dict["pcal5"] = [roaming_pcal_line_freq, pcal_corr_at_roaming_line_real, pcal_corr_at_roaming_line_imag, None]
 except:
-	remove_roaming_pcal_line = False
+	roaming_pcal_line_freq = 0.0
 try:
 	fcc_default = float(filters["fcc"])
 except:
@@ -801,8 +796,10 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 
 	# demodulate the PCAL channel and apply the PCAL correction factor at the DARM actuation line frequency
 	pcal_at_darm_act_freq = calibration_parts.demodulate(pipeline, pcaltee, darm_act_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_darm_act_freq_real, prefactor_imag = pcal_corr_at_darm_act_freq_imag)
-	if not options.no_kappapu or not options.no_kappac or not options.no_fcc or not options.no_srcQ or not options.no_fs or options.remove_callines:
-		pcal_at_darm_act_freq = pipeparts.mktee(pipeline, pcal_at_darm_act_freq)
+	pcal_at_darm_act_freq = pipeparts.mktee(pipeline, pcal_at_darm_act_freq)
+	if options.remove_callines:
+		# This will save having to demodulate it again
+		pcal_line_removal_dict["pcal1"][3] = pcal_at_darm_act_freq
 
 	# demodulate DARM_ERR at the DARM actuation line frequency
 	derr_at_darm_act_freq = calibration_parts.demodulate(pipeline, derrtee, darm_act_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
@@ -832,67 +829,7 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappatst or not
 
 	ktst = pipeparts.mktee(pipeline, ktst)
 
-	# Put off smoothing \kappa_tst until after \kappa_pu is computed in case we are correcting the phase of \kappa_tst using \kappa_pu
-
-# If we're also computing \kappa_c, f_cc, or \kappa_pu, keep going
-if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not options.no_srcQ or not options.no_fs or (not options.no_kappatst and options.act_timing_from_kappapu):
-	# demodulate excitation channel at PU actuation line frequency
-	exc_at_pu_act_freq = calibration_parts.demodulate(pipeline, exc, pu_act_esd_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
-
-	# demodulate DARM_ERR at PU actuation line frequency
-	derr_at_pu_act_freq = calibration_parts.demodulate(pipeline, derrtee, pu_act_esd_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
-	if options.dewhitening:
-		# dewhiten DARM_ERR at the PU actuation line frequency
-		derr_at_pu_act_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_pu_act_freq, derr_dewhiten_at_pu_act_freq_real, derr_dewhiten_at_pu_act_freq_imag)
-
-	# compute the factor Afctrl that will be used in the computation of kappa_pu and kappa_a, either using reference factors from the filters file or reading them from EPICS channels
-	if not options.factors_from_filters_file:
-		EP2 = calibration_parts.merge_into_complex(pipeline, head_dict["EP2_real"], head_dict["EP2_imag"])
-		EP3 = calibration_parts.merge_into_complex(pipeline, head_dict["EP3_real"], head_dict["EP3_imag"])
-		EP4 = calibration_parts.merge_into_complex(pipeline, head_dict["EP4_real"], head_dict["EP4_imag"])
-		afctrl = calibration_parts.compute_afctrl(pipeline, derr_at_pu_act_freq, exc_at_pu_act_freq, pcal_at_darm_act_freq, derr_at_darm_act_freq, EP2)
-	elif options.factors_from_filters_file:
-		afctrl = calibration_parts.compute_afctrl_from_filters_file(pipeline, derr_at_pu_act_freq, exc_at_pu_act_freq, pcal_at_darm_act_freq, derr_at_darm_act_freq, EP2_real, EP2_imag)
-
-	# \kappa_pu calcuation, which needs to happen for any of the other kappas to be computed
-	if not options.factors_from_filters_file:
-		kpu = calibration_parts.compute_kappapu(pipeline, EP3, afctrl, ktst, EP4)
-	elif options.factors_from_filters_file:
-		kpu = calibration_parts.compute_kappapu_from_filters_file(pipeline, EP3_real, EP3_imag, afctrl, ktst, EP4_real, EP4_imag)
-
-	kpu = pipeparts.mktee(pipeline, kpu)
-
-	# Put off smoothing \kappa_pu in case we are correcting the phase of \kappa_pu using \kappa_tst
-
-	# If desired, correct the phase of \kappa_pu using \kappa_tst (This assumes that all stages of actuation have the same variable time delay, and that \kappa_tst is doing a better job of measuring it)
-	if options.act_timing_from_kappatst:
-		# Find the magnitude of \kappa_pu
-		kpu = pipeparts.mkgeneric(pipeline, kpu, "cabs")
-		# Find the phase of \kappa_tst
-		phi_ktst = pipeparts.mkgeneric(pipeline, ktst, "carg")
-		# Multiply by the line-frequency ratio to get the phase of \kappa_pu
-		phi_kpu = pipeparts.mkaudioamplify(pipeline, phi_ktst, pu_act_esd_line_freq / esd_act_line_freq)
-		# Find the phase factor
-		kpu_phase_factor = pipeparts.mkgeneric(pipeline, pipeparts.mktogglecomplex(pipeline, pipeparts.mkmatrixmixer(pipeline, phi_kpu, matrix = [[0.0, 1.0]])), "cexp")
-		# Multiply by the magnitude of \kappa_pu
-		kpu = calibration_parts.mkmultiplier(pipeline, calibration_parts.list_srcs(pipeline, pipeparts.mktogglecomplex(pipeline, pipeparts.mkmatrixmixer(pipeline, kpu, matrix = [[1.0, 0.0]])), kpu_phase_factor))
-		kpu = pipeparts.mktee(pipeline, kpu)
-
-	# If desired, correct the phase of \kappa_tst using \kappa_pu (This assumes that all stages of actuation have the same variable time delay, and that \kappa_pu is doing a better job of measuring it)
-	if options.act_timing_from_kappapu:
-		# Find the magnitude of \kappa_tst
-		ktst = pipeparts.mkgeneric(pipeline, ktst, "cabs")
-		# Find the phase of \kappa_tst
-		phi_kpu = pipeparts.mkgeneric(pipeline, kpu, "carg")
-		# Multiply by the line-frequency ratio to get the phase of \kappa_tst
-		phi_ktst = pipeparts.mkaudioamplify(pipeline, phi_kpu, esd_act_line_freq / pu_act_esd_line_freq)
-		# Find the phase factor
-		ktst_phase_factor = pipeparts.mkgeneric(pipeline, pipeparts.mktogglecomplex(pipeline, pipeparts.mkmatrixmixer(pipeline, phi_ktst, matrix = [[0.0, 1.0]])), "cexp")
-		# Multiply by the magnitude of \kappa_tst
-		ktst = calibration_parts.mkmultiplier(pipeline, calibration_parts.list_srcs(pipeline, pipeparts.mktogglecomplex(pipeline, pipeparts.mkmatrixmixer(pipeline, ktst, matrix = [[1.0, 0.0]])), ktst_phase_factor))
-		ktst = pipeparts.mktee(pipeline, ktst)
-
-	# Now apply the gating and smoothing to \kappa_tst and \kappa_pu
+	# Now apply the gating and smoothing to \kappa_tst
 	if not options.no_kappatst:
 		smooth_ktst_nogate = pipeparts.mkgeneric(pipeline, ktst, "lal_smoothkappas", default_kappa_re = options.expected_kappatst_real, default_kappa_im = options.expected_kappatst_imag, array_size = median_smoothing_samples, avg_array_size = factors_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
 		smooth_ktstR_nogate, smooth_ktstI_nogate = calibration_parts.split_into_real(pipeline, smooth_ktst_nogate)
@@ -918,8 +855,35 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 		smooth_ktstRtee = pipeparts.mktee(pipeline, smooth_ktstR)
 		smooth_ktstItee = pipeparts.mktee(pipeline, smooth_ktstI)
 
+# If we're also computing \kappa_pu, \kappa_c, f_cc, f_s, or Q, keep going
+if not options.no_kappapu or not options.no_kappac or not options.no_fcc or not options.no_fs or not options.no_srcQ:
+	# demodulate excitation channel at PU actuation line frequency
+	exc_at_pu_act_freq = calibration_parts.demodulate(pipeline, exc, pu_act_esd_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
 
+	# demodulate DARM_ERR at PU actuation line frequency
+	derr_at_pu_act_freq = calibration_parts.demodulate(pipeline, derrtee, pu_act_esd_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
+	if options.dewhitening:
+		# dewhiten DARM_ERR at the PU actuation line frequency
+		derr_at_pu_act_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_pu_act_freq, derr_dewhiten_at_pu_act_freq_real, derr_dewhiten_at_pu_act_freq_imag)
 
+	# compute the factor Afctrl that will be used in the computation of kappa_pu and kappa_a, either using reference factors from the filters file or reading them from EPICS channels
+	if not options.factors_from_filters_file:
+		EP2 = calibration_parts.merge_into_complex(pipeline, head_dict["EP2_real"], head_dict["EP2_imag"])
+		EP3 = calibration_parts.merge_into_complex(pipeline, head_dict["EP3_real"], head_dict["EP3_imag"])
+		EP4 = calibration_parts.merge_into_complex(pipeline, head_dict["EP4_real"], head_dict["EP4_imag"])
+		afctrl = calibration_parts.compute_afctrl(pipeline, derr_at_pu_act_freq, exc_at_pu_act_freq, pcal_at_darm_act_freq, derr_at_darm_act_freq, EP2)
+	elif options.factors_from_filters_file:
+		afctrl = calibration_parts.compute_afctrl_from_filters_file(pipeline, derr_at_pu_act_freq, exc_at_pu_act_freq, pcal_at_darm_act_freq, derr_at_darm_act_freq, EP2_real, EP2_imag)
+
+	# \kappa_pu calcuation, which needs to happen for any of the other kappas to be computed
+	if not options.factors_from_filters_file:
+		kpu = calibration_parts.compute_kappapu(pipeline, EP3, afctrl, ktst, EP4)
+	elif options.factors_from_filters_file:
+		kpu = calibration_parts.compute_kappapu_from_filters_file(pipeline, EP3_real, EP3_imag, afctrl, ktst, EP4_real, EP4_imag)
+
+	kpu = pipeparts.mktee(pipeline, kpu)
+
+	# Now apply the gating and smoothing to \kappa_pu
 	if not options.no_kappapu:
 		smooth_kpu_nogate = pipeparts.mkgeneric(pipeline, kpu, "lal_smoothkappas", default_kappa_re = options.expected_kappapu_real, default_kappa_im = options.expected_kappapu_imag, array_size = median_smoothing_samples, avg_array_size = factors_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
 		smooth_kpuR_nogate, smooth_kpuI_nogate = calibration_parts.split_into_real(pipeline, smooth_kpu_nogate)
@@ -945,167 +909,171 @@ if not options.no_kappac or not options.no_fcc or not options.no_kappapu or not
 		smooth_kpuRtee = pipeparts.mktee(pipeline, smooth_kpuR)
 		smooth_kpuItee = pipeparts.mktee(pipeline, smooth_kpuI)	
 
-	# Finally, compute \kappa_c and f_cc
-	if not options.no_kappac or not options.no_fcc or not options.no_srcQ or not options.no_fs:
-		# demodulate the PCAL channel and apply the PCAL correction factor at optical gain and f_cc line frequency
-		pcal_at_opt_gain_freq = calibration_parts.demodulate(pipeline, pcaltee, opt_gain_fcc_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_opt_gain_fcc_freq_real, prefactor_imag = pcal_corr_at_opt_gain_fcc_freq_imag)
-		if options.remove_callines:
-			pcal_at_opt_gain_freq = pipeparts.mktee(pipeline, pcal_at_opt_gain_freq)
-
-		# demodulate DARM_ERR at optical gain and f_cc line frequency
-		derr_at_opt_gain_freq = calibration_parts.demodulate(pipeline, derrtee, opt_gain_fcc_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
-		if options.dewhitening:
-			# dewhiten DARM_ERR at optical gain and f_cc line frequency
-			derr_at_opt_gain_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_opt_gain_freq, derr_dewhiten_at_opt_gain_fcc_freq_real, derr_dewhiten_at_opt_gain_fcc_freq_imag)
-
-		# Compute the factor S which will be used for the kappa_c and f_cc calculations
-		# \kappa_tst and \kappa_pu need to be evaluated at the higher pcal line frequency
-		ktst_at_opt_gain_freq = pipeparts.mkgeneric(pipeline, ktst, "lpshiftfreq", frequency_ratio = opt_gain_fcc_line_freq / esd_act_line_freq)
-		kpu_at_opt_gain_freq = pipeparts.mkgeneric(pipeline, kpu, "lpshiftfreq", frequency_ratio = opt_gain_fcc_line_freq / pu_act_esd_line_freq)
-		if not options.factors_from_filters_file:
-			EP6 = calibration_parts.merge_into_complex(pipeline, head_dict["EP6_real"], head_dict["EP6_imag"])
-			EP7 = calibration_parts.merge_into_complex(pipeline, head_dict["EP7_real"], head_dict["EP7_imag"])
-			EP8 = calibration_parts.merge_into_complex(pipeline, head_dict["EP8_real"], head_dict["EP8_imag"])
-			EP9 = calibration_parts.merge_into_complex(pipeline, head_dict["EP9_real"], head_dict["EP9_imag"])
-			S = calibration_parts.compute_S(pipeline, EP6, pcal_at_opt_gain_freq, derr_at_opt_gain_freq, EP7, ktst_at_opt_gain_freq, EP8, kpu_at_opt_gain_freq, EP9)
-		elif options.factors_from_filters_file:
-			S = calibration_parts.compute_S_from_filters_file(pipeline, EP6_real, EP6_imag, pcal_at_opt_gain_freq, derr_at_opt_gain_freq, EP7_real, EP7_imag, ktst_at_opt_gain_freq, EP8_real, EP8_imag, kpu_at_opt_gain_freq, EP9_real, EP9_imag)
-
-		S = pipeparts.mktee(pipeline, S)
-
-		SR, SI = calibration_parts.split_into_real(pipeline, S)
-
-		if not options.no_kappac and not options.no_fcc:
-			SR = pipeparts.mktee(pipeline, SR)
-			SI = pipeparts.mktee(pipeline, SI)
-
-		# compute kappa_c
-		if not options.no_kappac or not options.no_srcQ or not options.no_fs:
-			kc = calibration_parts.compute_kappac(pipeline, SR, SI)
-		if not options.no_kappac:
-			kc = pipeparts.mktee(pipeline, kc)
-			smooth_kc_nogate = pipeparts.mkgeneric(pipeline, kc, "lal_smoothkappas", default_kappa_re = options.expected_kappac, array_size = median_smoothing_samples, avg_array_size = factors_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
-
-			if not options.no_coherence:
-				# Gate kappa_c with the coherence of all four of the calibration lines
-				kc_gated = calibration_parts.mkgate(pipeline, kc, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, darm_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-				kc_gated = calibration_parts.mkgate(pipeline, kc_gated, sus_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-
-				# Smooth kappa_c
-				smooth_kc = calibration_parts.smooth_kappas(pipeline, kc_gated, options.expected_kappac, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
-
-			else:
-				# Smooth kappa_c
-				smooth_kc = calibration_parts.smooth_kappas_no_coherence(pipeline, kc, options.kappac_ok_var, options.expected_kappac, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
+# Compute \kappa_c and f_cc
+if not options.no_kappac or not options.no_fcc or not options.no_fs or not options.no_srcQ:
+	# demodulate the PCAL channel and apply the PCAL correction factor at optical gain and f_cc line frequency
+	pcal_at_opt_gain_freq = calibration_parts.demodulate(pipeline, pcaltee, opt_gain_fcc_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_opt_gain_fcc_freq_real, prefactor_imag = pcal_corr_at_opt_gain_fcc_freq_imag)
+	if options.remove_callines:
+		# This will save having to demodulate it again
+		pcal_at_opt_gain_freq = pipeparts.mktee(pipeline, pcal_at_opt_gain_freq)
+		pcal_line_removal_dict["pcal2"][3] = pcal_at_opt_gain_freq
+
+	# demodulate DARM_ERR at optical gain and f_cc line frequency
+	derr_at_opt_gain_freq = calibration_parts.demodulate(pipeline, derrtee, opt_gain_fcc_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
+	if options.dewhitening:
+		# dewhiten DARM_ERR at optical gain and f_cc line frequency
+		derr_at_opt_gain_freq = calibration_parts.complex_audioamplify(pipeline, derr_at_opt_gain_freq, derr_dewhiten_at_opt_gain_fcc_freq_real, derr_dewhiten_at_opt_gain_fcc_freq_imag)
 
-		smooth_kctee = pipeparts.mktee(pipeline, smooth_kc)
+	# Compute the factor S which will be used for the kappa_c and f_cc calculations
+	# \kappa_tst and \kappa_pu need to be evaluated at the higher pcal line frequency
+	ktst_at_opt_gain_freq = pipeparts.mkgeneric(pipeline, ktst, "lpshiftfreq", frequency_ratio = opt_gain_fcc_line_freq / esd_act_line_freq)
+	kpu_at_opt_gain_freq = pipeparts.mkgeneric(pipeline, kpu, "lpshiftfreq", frequency_ratio = opt_gain_fcc_line_freq / pu_act_esd_line_freq)
+	if not options.factors_from_filters_file:
+		EP6 = calibration_parts.merge_into_complex(pipeline, head_dict["EP6_real"], head_dict["EP6_imag"])
+		EP7 = calibration_parts.merge_into_complex(pipeline, head_dict["EP7_real"], head_dict["EP7_imag"])
+		EP8 = calibration_parts.merge_into_complex(pipeline, head_dict["EP8_real"], head_dict["EP8_imag"])
+		EP9 = calibration_parts.merge_into_complex(pipeline, head_dict["EP9_real"], head_dict["EP9_imag"])
+		S = calibration_parts.compute_S(pipeline, EP6, pcal_at_opt_gain_freq, derr_at_opt_gain_freq, EP7, ktst_at_opt_gain_freq, EP8, kpu_at_opt_gain_freq, EP9)
+	elif options.factors_from_filters_file:
+		S = calibration_parts.compute_S_from_filters_file(pipeline, EP6_real, EP6_imag, pcal_at_opt_gain_freq, derr_at_opt_gain_freq, EP7_real, EP7_imag, ktst_at_opt_gain_freq, EP8_real, EP8_imag, kpu_at_opt_gain_freq, EP9_real, EP9_imag)
+
+	S = pipeparts.mktee(pipeline, S)
+
+	SR, SI = calibration_parts.split_into_real(pipeline, S)
 
-		# compute f_cc
-		if not options.no_fcc or not options.no_srcQ or not options.no_fs:
-			fcc = calibration_parts.compute_fcc(pipeline, SR, SI, opt_gain_fcc_line_freq)
-		if not options.no_fcc:
-			fcc = pipeparts.mktee(pipeline, fcc)
-			smooth_fcc_nogate = pipeparts.mkgeneric(pipeline, fcc, "lal_smoothkappas", default_kappa_re = fcc_default, array_size = median_smoothing_samples, avg_array_size = factors_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
-
-			if not options.no_coherence:
-				# Gate f_cc with all four of the calibration lines
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, darm_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-				fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, sus_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-
-				# Smooth f_cc
-				smooth_fcc = calibration_parts.smooth_kappas(pipeline, fcc_gated, fcc_default, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
-			else:
-				# Smooth f_cc
-				smooth_fcc = calibration_parts.smooth_kappas_no_coherence(pipeline, fcc, options.fcc_ok_var, fcc_default, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
-				
-			smooth_fcctee = pipeparts.mktee(pipeline, smooth_fcc)
-
-#			if options.update_fcc:
-#				update_fcc = pipeparts.mkgeneric(pipeline, smooth_fcctee, "lal_fcc_update", data_rate = hoftsr, fcc_rate = options.compute_factors_sr, fcc_model = fcc_default, averaging_time = options.fcc_averaging_time, filter_duration = options.fcc_filter_duration)
-#				pipeparts.mkfakesink(pipeline, update_fcc)
-
-	# compute f_s and Q
-	if not options.no_fs or not options.no_srcQ:
-		expected_Xi = complex((fs_default * fs_default - 1j * src_pcal_line_freq * fs_default / srcQ_default) / (src_pcal_line_freq * src_pcal_line_freq))
-		Xi_real_ok_var = float((pow(fs_default + options.fs_ok_var, 2) - pow(fs_default, 2.0)) / pow(src_pcal_line_freq, 2))
-		Xi_imag_ok_var = float(options.fs_ok_var / (srcQ_default * src_pcal_line_freq))
-
-		# demodulate PCAL channel and apply the PCAL correction factor at SRC detuning line frequency
-		pcal_at_src_freq = calibration_parts.demodulate(pipeline, pcaltee, src_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_src_freq_real, prefactor_imag = pcal_corr_at_src_freq_imag)
-		if remove_src_pcal_line:
-			pcal_at_src_freq = pipeparts.mktee(pipeline, pcal_at_src_freq)
-
-		# demodulate DARM_ERR at SRC detuning line frequency
-		derr_at_src_freq = calibration_parts.demodulate(pipeline, derrtee, src_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
-
-		# Compute the factor Xi which will be used for the f_s and src_Q calculations
-		# \kappa_tst and \kappa_pu need to be evaluated at the SRC pcal line frequency
-		ktst_at_src_freq = pipeparts.mkgeneric(pipeline, ktst, "lpshiftfreq", frequency_ratio = src_pcal_line_freq / esd_act_line_freq)
-		kpu_at_src_freq = pipeparts.mkgeneric(pipeline, kpu, "lpshiftfreq", frequency_ratio = src_pcal_line_freq / pu_act_esd_line_freq)
-		if not options.factors_from_filters_file:
-			EP11 = calibration_parts.merge_into_complex(pipeline, head_dict["EP11_real"], head_dict["EP11_imag"])
-			EP12 = calibration_parts.merge_into_complex(pipeline, head_dict["EP12_real"], head_dict["EP12_imag"])
-			EP13 = calibration_parts.merge_into_complex(pipeline, head_dict["EP13_real"], head_dict["EP13_imag"])
-			EP14 = calibration_parts.merge_into_complex(pipeline, head_dict["EP14_real"], head_dict["EP14_imag"])
-			Xi = calibration_parts.compute_Xi(pipeline, pcal_at_src_freq, derr_at_src_freq, src_pcal_line_freq, EP11, EP12, EP13, EP14, ktst_at_src_freq, kpu_at_src_freq, kc, fcc)
-		elif options.factors_from_filters_file:
-			Xi = calibration_parts.compute_Xi_from_filters_file(pipeline, pcal_at_src_freq, derr_at_src_freq, src_pcal_line_freq, EP11_real, EP11_imag, EP12_real, EP12_imag, EP13_real, EP13_imag, EP14_real, EP14_imag, ktst_at_src_freq, kpu_at_src_freq, kc, fcc)
-
-		Xi = pipeparts.mktee(pipeline, Xi)
-		smooth_Xi_nogate = pipeparts.mkgeneric(pipeline, Xi, "lal_smoothkappas", default_kappa_re = float(numpy.real(expected_Xi)), default_kappa_im = float(numpy.imag(expected_Xi)), array_size = median_smoothing_samples, avg_array_size = src_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
+	if not options.no_kappac and not options.no_fcc:
+		SR = pipeparts.mktee(pipeline, SR)
+		SI = pipeparts.mktee(pipeline, SI)
+
+	# compute kappa_c
+	if not options.no_kappac or not options.no_srcQ or not options.no_fs:
+		kc = calibration_parts.compute_kappac(pipeline, SR, SI)
+	if not options.no_kappac:
+		kc = pipeparts.mktee(pipeline, kc)
+		smooth_kc_nogate = pipeparts.mkgeneric(pipeline, kc, "lal_smoothkappas", default_kappa_re = options.expected_kappac, array_size = median_smoothing_samples, avg_array_size = factors_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
 
 		if not options.no_coherence:
-			# Gate Xi with all coherences. We apply the gating and smoothing here since Q depends on the inverse of Im(Xi), which fluctuates about zero.
-			Xi_gated = calibration_parts.mkgate(pipeline, Xi, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-			Xi_gated = calibration_parts.mkgate(pipeline, Xi_gated, darm_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-			Xi_gated = calibration_parts.mkgate(pipeline, Xi_gated, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
-			Xi_gated = calibration_parts.mkgate(pipeline, Xi_gated, sus_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			# Gate kappa_c with the coherence of all four of the calibration lines
+			kc_gated = calibration_parts.mkgate(pipeline, kc, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			kc_gated = calibration_parts.mkgate(pipeline, kc_gated, darm_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			kc_gated = calibration_parts.mkgate(pipeline, kc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			kc_gated = calibration_parts.mkgate(pipeline, kc_gated, sus_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
 
-			smooth_Xi = calibration_parts.smooth_complex_kappas(pipeline, Xi_gated, float(numpy.real(expected_Xi)), float(numpy.imag(expected_Xi)), median_smoothing_samples, src_average_samples, options.kappas_default_to_median, options.filter_latency)
+			# Smooth kappa_c
+			smooth_kc = calibration_parts.smooth_kappas(pipeline, kc_gated, options.expected_kappac, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
 
 		else:
-			smooth_Xi = calibration_parts.smooth_complex_kappas_no_coherence(pipeline, Xi, Xi_real_ok_var, Xi_real_ok_var, float(numpy.real(expected_Xi)), float(numpy.imag(expected_Xi)), median_smoothing_samples, src_average_samples, options.kappas_default_to_median, options.filter_latency)
+			# Smooth kappa_c
+			smooth_kc = calibration_parts.smooth_kappas_no_coherence(pipeline, kc, options.kappac_ok_var, options.expected_kappac, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
 
-		if options.no_srcQ:
-			# the imaginary part is only used to compute Q
-			smooth_XiR = pipeparts.mkgeneric(pipeline, smooth_Xi, "creal")
-			smooth_XiR_nogate = pipeparts.mkgeneric(pipeline, smooth_Xi_nogate, "creal")
+		smooth_kctee = pipeparts.mktee(pipeline, smooth_kc)
+
+	# compute f_cc
+	if not options.no_fcc or not options.no_srcQ or not options.no_fs:
+		fcc = calibration_parts.compute_fcc(pipeline, SR, SI, opt_gain_fcc_line_freq)
+	if not options.no_fcc:
+		fcc = pipeparts.mktee(pipeline, fcc)
+		smooth_fcc_nogate = pipeparts.mkgeneric(pipeline, fcc, "lal_smoothkappas", default_kappa_re = fcc_default, array_size = median_smoothing_samples, avg_array_size = factors_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
+
+		if not options.no_coherence:
+			# Gate f_cc with all four of the calibration lines
+			fcc_gated = calibration_parts.mkgate(pipeline, fcc, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, darm_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, pcaly_line1_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+			fcc_gated = calibration_parts.mkgate(pipeline, fcc_gated, sus_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+
+			# Smooth f_cc
+			smooth_fcc = calibration_parts.smooth_kappas(pipeline, fcc_gated, fcc_default, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
 		else:
-			smooth_XiR, smooth_XiI = calibration_parts.split_into_real(pipeline, smooth_Xi)
-			smooth_XiR_nogate, smooth_XiI_nogate = calibration_parts.split_into_real(pipeline, smooth_Xi_nogate)
+			# Smooth f_cc
+			smooth_fcc = calibration_parts.smooth_kappas_no_coherence(pipeline, fcc, options.fcc_ok_var, fcc_default, median_smoothing_samples, factors_average_samples, options.kappas_default_to_median, options.filter_latency)
+
+		smooth_fcctee = pipeparts.mktee(pipeline, smooth_fcc)
+
+#		if options.update_fcc:
+#			update_fcc = pipeparts.mkgeneric(pipeline, smooth_fcctee, "lal_fcc_update", data_rate = hoftsr, fcc_rate = options.compute_factors_sr, fcc_model = fcc_default, averaging_time = options.fcc_averaging_time, filter_duration = options.fcc_filter_duration)
+#			pipeparts.mkfakesink(pipeline, update_fcc)
+
+# compute f_s and Q
+if not options.no_fs or not options.no_srcQ:
+	expected_Xi = complex((fs_default * fs_default - 1j * src_pcal_line_freq * fs_default / srcQ_default) / (src_pcal_line_freq * src_pcal_line_freq))
+	Xi_real_ok_var = float((pow(fs_default + options.fs_ok_var, 2) - pow(fs_default, 2.0)) / pow(src_pcal_line_freq, 2))
+	Xi_imag_ok_var = float(options.fs_ok_var / (srcQ_default * src_pcal_line_freq))
+
+	# demodulate PCAL channel and apply the PCAL correction factor at SRC detuning line frequency
+	pcal_at_src_freq = calibration_parts.demodulate(pipeline, pcaltee, src_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_src_freq_real, prefactor_imag = pcal_corr_at_src_freq_imag)
+	if "pcal4" in pcal_line_removal_dict:
+		# This will save having to demodulate it again
+		pcal_at_src_freq = pipeparts.mktee(pipeline, pcal_at_src_freq)
+		pcal_line_removal_dict["pcal4"][3] = pcal_at_src_freq
+
+	# demodulate DARM_ERR at SRC detuning line frequency
+	derr_at_src_freq = calibration_parts.demodulate(pipeline, derrtee, src_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency)
+
+	# Compute the factor Xi which will be used for the f_s and src_Q calculations
+	# \kappa_tst and \kappa_pu need to be evaluated at the SRC pcal line frequency
+	ktst_at_src_freq = pipeparts.mkgeneric(pipeline, ktst, "lpshiftfreq", frequency_ratio = src_pcal_line_freq / esd_act_line_freq)
+	kpu_at_src_freq = pipeparts.mkgeneric(pipeline, kpu, "lpshiftfreq", frequency_ratio = src_pcal_line_freq / pu_act_esd_line_freq)
+	if not options.factors_from_filters_file:
+		EP11 = calibration_parts.merge_into_complex(pipeline, head_dict["EP11_real"], head_dict["EP11_imag"])
+		EP12 = calibration_parts.merge_into_complex(pipeline, head_dict["EP12_real"], head_dict["EP12_imag"])
+		EP13 = calibration_parts.merge_into_complex(pipeline, head_dict["EP13_real"], head_dict["EP13_imag"])
+		EP14 = calibration_parts.merge_into_complex(pipeline, head_dict["EP14_real"], head_dict["EP14_imag"])
+		Xi = calibration_parts.compute_Xi(pipeline, pcal_at_src_freq, derr_at_src_freq, src_pcal_line_freq, EP11, EP12, EP13, EP14, ktst_at_src_freq, kpu_at_src_freq, kc, fcc)
+	elif options.factors_from_filters_file:
+		Xi = calibration_parts.compute_Xi_from_filters_file(pipeline, pcal_at_src_freq, derr_at_src_freq, src_pcal_line_freq, EP11_real, EP11_imag, EP12_real, EP12_imag, EP13_real, EP13_imag, EP14_real, EP14_imag, ktst_at_src_freq, kpu_at_src_freq, kc, fcc)
 
-		smooth_sqrtXiR = calibration_parts.mkpow(pipeline, smooth_XiR, exponent = 0.5)
-		smooth_sqrtXiR_nogate = calibration_parts.mkpow(pipeline, smooth_XiR_nogate, exponent = 0.5)
+	Xi = pipeparts.mktee(pipeline, Xi)
+	smooth_Xi_nogate = pipeparts.mkgeneric(pipeline, Xi, "lal_smoothkappas", default_kappa_re = float(numpy.real(expected_Xi)), default_kappa_im = float(numpy.imag(expected_Xi)), array_size = median_smoothing_samples, avg_array_size = src_average_samples, default_to_median = options.kappas_default_to_median, filter_latency = options.filter_latency)
 
-		if not options.no_fs and not options.no_srcQ:
-			smooth_sqrtXiR = pipeparts.mktee(pipeline, smooth_sqrtXiR)
-			smooth_sqrtXiR_nogate = pipeparts.mktee(pipeline, smooth_sqrtXiR_nogate)
+	if not options.no_coherence:
+		# Gate Xi with all coherences. We apply the gating and smoothing here since Q depends on the inverse of Im(Xi), which fluctuates about zero.
+		Xi_gated = calibration_parts.mkgate(pipeline, Xi, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+		Xi_gated = calibration_parts.mkgate(pipeline, Xi_gated, darm_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+		Xi_gated = calibration_parts.mkgate(pipeline, Xi_gated, pcaly_line2_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
+		Xi_gated = calibration_parts.mkgate(pipeline, Xi_gated, sus_coh, options.coherence_uncertainty_threshold, attack_length = kappa_gate_attack_length, hold_length = kappa_gate_hold_length, invert_control = True)
 
-		# compute f_s
-		if not options.no_fs:
-			smooth_fs = pipeparts.mkaudioamplify(pipeline, smooth_sqrtXiR, src_pcal_line_freq)
-			smooth_fs_nogate = pipeparts.mkaudioamplify(pipeline, smooth_sqrtXiR_nogate, src_pcal_line_freq)
+		smooth_Xi = calibration_parts.smooth_complex_kappas(pipeline, Xi_gated, float(numpy.real(expected_Xi)), float(numpy.imag(expected_Xi)), median_smoothing_samples, src_average_samples, options.kappas_default_to_median, options.filter_latency)
 
-			if not options.no_dq_vector or options.update_fs:
-				smooth_fs = pipeparts.mktee(pipeline, smooth_fs)
+	else:
+		smooth_Xi = calibration_parts.smooth_complex_kappas_no_coherence(pipeline, Xi, Xi_real_ok_var, Xi_real_ok_var, float(numpy.real(expected_Xi)), float(numpy.imag(expected_Xi)), median_smoothing_samples, src_average_samples, options.kappas_default_to_median, options.filter_latency)
 
-		# compute SRC Q_inv
-		if not options.no_srcQ:
-			smooth_sqrtXiR_inv = calibration_parts.mkpow(pipeline, smooth_sqrtXiR, exponent = -1.0)
-			smooth_sqrtXiR_inv_nogate = calibration_parts.mkpow(pipeline, smooth_sqrtXiR_nogate, exponent = -1.0)
-			smooth_srcQ_inv = calibration_parts.mkmultiplier(pipeline, calibration_parts.list_srcs(pipeline, smooth_sqrtXiR_inv, pipeparts.mkaudioamplify(pipeline, smooth_XiI, -1.0)))
-			smooth_srcQ_inv_nogate = calibration_parts.mkmultiplier(pipeline, calibration_parts.list_srcs(pipeline, smooth_sqrtXiR_inv_nogate, pipeparts.mkaudioamplify(pipeline, smooth_XiI_nogate, -1.0)))
+	if options.no_srcQ:
+		# the imaginary part is only used to compute Q
+		smooth_XiR = pipeparts.mkgeneric(pipeline, smooth_Xi, "creal")
+		smooth_XiR_nogate = pipeparts.mkgeneric(pipeline, smooth_Xi_nogate, "creal")
+	else:
+		smooth_XiR, smooth_XiI = calibration_parts.split_into_real(pipeline, smooth_Xi)
+		smooth_XiR_nogate, smooth_XiI_nogate = calibration_parts.split_into_real(pipeline, smooth_Xi_nogate)
+
+	smooth_sqrtXiR = calibration_parts.mkpow(pipeline, smooth_XiR, exponent = 0.5)
+	smooth_sqrtXiR_nogate = calibration_parts.mkpow(pipeline, smooth_XiR_nogate, exponent = 0.5)
 
-			# We don't want to correct for Q < 0, since this is nonphysical and such a correction would most likely have the wrong frequency-dependence.
-			if options.update_srcQ:
-				smooth_srcQ_inv = pipeparts.mkgeneric(pipeline, smooth_srcQ_inv, "lal_insertgap", bad_data_intervals = [1e-35, 1e35], replace_value = 1e-35, insert_gap = False)
+	if not options.no_fs and not options.no_srcQ:
+		smooth_sqrtXiR = pipeparts.mktee(pipeline, smooth_sqrtXiR)
+		smooth_sqrtXiR_nogate = pipeparts.mktee(pipeline, smooth_sqrtXiR_nogate)
 
-			if not options.no_dq_vector or options.update_srcQ:
-				smooth_srcQ_inv = pipeparts.mktee(pipeline, smooth_srcQ_inv)
+	# compute f_s
+	if not options.no_fs:
+		smooth_fs = pipeparts.mkaudioamplify(pipeline, smooth_sqrtXiR, src_pcal_line_freq)
+		smooth_fs_nogate = pipeparts.mkaudioamplify(pipeline, smooth_sqrtXiR_nogate, src_pcal_line_freq)
+
+		if not options.no_dq_vector or options.update_fs:
+			smooth_fs = pipeparts.mktee(pipeline, smooth_fs)
+
+	# compute SRC Q_inv
+	if not options.no_srcQ:
+		smooth_sqrtXiR_inv = calibration_parts.mkpow(pipeline, smooth_sqrtXiR, exponent = -1.0)
+		smooth_sqrtXiR_inv_nogate = calibration_parts.mkpow(pipeline, smooth_sqrtXiR_nogate, exponent = -1.0)
+		smooth_srcQ_inv = calibration_parts.mkmultiplier(pipeline, calibration_parts.list_srcs(pipeline, smooth_sqrtXiR_inv, pipeparts.mkaudioamplify(pipeline, smooth_XiI, -1.0)))
+		smooth_srcQ_inv_nogate = calibration_parts.mkmultiplier(pipeline, calibration_parts.list_srcs(pipeline, smooth_sqrtXiR_inv_nogate, pipeparts.mkaudioamplify(pipeline, smooth_XiI_nogate, -1.0)))
+
+		# We don't want to correct for Q < 0, since this is nonphysical and such a correction would most likely have the wrong frequency-dependence.
+		if options.update_srcQ:
+			smooth_srcQ_inv = pipeparts.mkgeneric(pipeline, smooth_srcQ_inv, "lal_insertgap", bad_data_intervals = [1e-35, 1e35], replace_value = 1e-35, insert_gap = False)
+
+		if not options.no_dq_vector or options.update_srcQ:
+			smooth_srcQ_inv = pipeparts.mktee(pipeline, smooth_srcQ_inv)
 
 #
 # TIME-VARYING FACTORS COMPENSATIONS
@@ -1850,25 +1818,21 @@ if options.remove_callines:
 	if options.no_kappatst and options.no_kappapu and options.no_kappac and options.no_fcc and options.no_srcQ and options.no_fs:
 		pcal = calibration_parts.caps_and_progress(pipeline, head_dict["pcal"], hoft_caps, "pcal")
 		pcaltee = pipeparts.mktee(pipeline, pcal)
-		# Demodulate pcal at the ~30 Hz pcal line
-		pcal_at_darm_act_freq = calibration_parts.demodulate(pipeline, pcal_tee, darm_act_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_darm_act_freq_real, prefactor_imag =  pcal_corr_at_darm_act_freq_imag)
-
-	# Reconstruct a calibrated pcal at only the ~30 Hz pcal line
-	pcaly_line1 = calibration_parts.mkresample(pipeline, pcal_at_darm_act_freq, 3, False, "audio/x-raw, format=Z128LE, rate=%d, channel-mask=(bitmask)0x0" % hoftsr)
-	pcaly_line1 = pipeparts.mkgeneric(pipeline, pcaly_line1, "lal_demodulate", line_frequency = -1.0 * darm_act_line_freq, prefactor_real = 2.0)
-	remove_pcaly_line1 = pipeparts.mkgeneric(pipeline, pcaly_line1, "creal")
-
-	# Make sure we have demodulated pcal at the ~300 Hz pcal line
-	if options.no_kappac and options.no_fcc and options.no_srcQ and options.no_fs:
-		pcal_at_opt_gain_freq = calibration_parts.demodulate(pipeline, pcaltee, opt_gain_fcc_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_opt_gain_fcc_freq_real, prefactor_imag = pcal_corr_at_opt_gain_fcc_freq_imag)
-	# Reconstruct a calibrated pcal at only the ~330 Hz pcal line
-	pcaly_line2 = calibration_parts.mkresample(pipeline, pcal_at_opt_gain_freq, 3, False, "audio/x-raw, format=Z128LE, rate=%d, channel-mask=(bitmask)0x0" % hoftsr)
-	pcaly_line2 = pipeparts.mkgeneric(pipeline, pcaly_line2, "lal_demodulate", line_frequency = -1.0 * opt_gain_fcc_line_freq, prefactor_real = 2.0)
-	remove_pcaly_line2 = pipeparts.mkgeneric(pipeline, pcaly_line2, "creal")
-
-	# Add the first two components to the list. We will add everything in this list to h(t) to remove these lines
-	callines_list = [remove_pcaly_line1, remove_pcaly_line2]
 
+	callines_list = []
+	# Start with the pcal lines. Loop through the dictionary, reconstruct each line, and add to the list.
+	for pcal_line_name in pcal_line_removal_dict:
+		if pcal_line_removal_dict[pcal_line_name][3] is None:
+			# This line still needs to be demodulated
+			pcal_line_removal_dict[pcal_line_name][3] = calibration_parts.demodulate(pipeline, pcaltee, pcal_line_removal_dict[pcal_line_name][0], td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_line_removal_dict[pcal_line_name][1], prefactor_imag = pcal_line_removal_dict[pcal_line_name][2])
+		# Reconstruct a pcal signal at only this pcal line
+		pcal_line_removal_dict[pcal_line_name][3] = calibration_parts.mkresample(pipeline, pcal_line_removal_dict[pcal_line_name][3], 3, False, "audio/x-raw, format=Z128LE, rate=%d, channel-mask=(bitmask)0x0" % hoftsr)
+		pcal_line_removal_dict[pcal_line_name][3] = pipeparts.mkgeneric(pipeline, pcal_line_removal_dict[pcal_line_name][3], "lal_demodulate", line_frequency = -1.0 * pcal_line_removal_dict[pcal_line_name][0], prefactor_real = 2.0)
+		pcal_line_removal_dict[pcal_line_name][3] = pipeparts.mkgeneric(pipeline, pcal_line_removal_dict[pcal_line_name][3], "creal")
+		# Add this line to the list
+		callines_list.append(pcal_line_removal_dict[pcal_line_name][3])
+
+	# Now deal with the ESD line
 	if remove_esd_act_line:
 		# Make sure we have demodulated the ESD excitation channel at the ~30 Hz ESD line
 		if options.no_kappac and options.no_fcc and options.no_kappatst and options.no_kappapu and options.no_srcQ and options.no_fs:
@@ -1893,37 +1857,6 @@ if options.remove_callines:
 		# Add into the total line removal stream
 		callines_list.append(esd_act_line_remove)
 
-	if remove_high_pcal_line:
-		# Demodulate pcal at the ~1kHz pcal line
-		pcaly_line3 = calibration_parts.demodulate(pipeline, pcaltee, high_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_high_line_freq_real, prefactor_imag = pcal_corr_at_high_line_freq_imag)
-		# Reconstruct a calibrated pcal at only the ~1kHz pcal line
-		pcaly_line3 = calibration_parts.mkresample(pipeline, pcaly_line3, 3, False, "audio/x-raw, format=Z128LE, rate=%d, channel-mask=(bitmask)0x0" % hoftsr)
-		pcaly_line3 = pipeparts.mkgeneric(pipeline, pcaly_line3, "lal_demodulate", line_frequency = -1.0 * high_pcal_line_freq, prefactor_real = 2.0)
-		remove_pcaly_line3 = pipeparts.mkgeneric(pipeline, pcaly_line3, "creal")
-		# Add into the total line removal stream
-		callines_list.append(remove_pcaly_line3)
-
-	if remove_roaming_pcal_line:
-		# Demodulate pcal at the ~3kHz pcal line
-		pcaly_line4 = calibration_parts.demodulate(pipeline, pcaltee, roaming_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_roaming_line_real, prefactor_imag = pcal_corr_at_roaming_line_imag)
-		# Reconstruct a calibrated pcal at only the ~3kHz pcal line
-		pcaly_line4 = calibration_parts.mkresample(pipeline, pcaly_line4, 3, False, "audio/x-raw, format=Z128LE, rate=%d, channel-mask=(bitmask)0x0" % hoftsr)
-		pcaly_line4 = pipeparts.mkgeneric(pipeline, pcaly_line4, "lal_demodulate", line_frequency = -1.0 * roaming_pcal_line_freq, prefactor_real = 2.0)
-		remove_pcaly_line4 = pipeparts.mkgeneric(pipeline, pcaly_line4, "creal")
-		# Add into the total line removal stream
-		callines_list.append(remove_pcaly_line4)
-
-	if remove_src_pcal_line:
-		# Make sure we have demodulated pcal at the ~8 Hz pcal line
-		if options.no_fs and options.no_srcQ:
-			pcal_at_src_freq = calibration_parts.demodulate(pipeline, pcaltee, src_pcal_line_freq, td, options.compute_factors_sr, options.demodulation_filter_time, options.filter_latency, prefactor_real = pcal_corr_at_src_freq_real, prefactor_imag = pcal_corr_at_src_freq_imag)
-		# Reconstruct a calibrated pcal at only the ~3kHz pcal line
-		pcaly_line0 = calibration_parts.mkresample(pipeline, pcal_at_src_freq, 3, False, "audio/x-raw, format=Z128LE, rate=%d, channel-mask=(bitmask)0x0" % hoftsr)
-		pcaly_line0 = pipeparts.mkgeneric(pipeline, pcaly_line0, "lal_demodulate", line_frequency = -1.0 * src_pcal_line_freq, prefactor_real = 2.0)
-		remove_pcaly_line0 = pipeparts.mkgeneric(pipeline, pcaly_line0, "creal")
-		# Add into the total line removal stream
-		callines_list.append(remove_pcaly_line0)
-
 	# Add all the lines together
 	calibration_lines = calibration_parts.mkadder(pipeline, tuple(callines_list))
 
diff --git a/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c b/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c
index 39d5ec71eb..587cdc3f28 100644
--- a/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c
+++ b/gstlal-calibration/gst/lal/gstlal_adaptivefirfilt.c
@@ -316,7 +316,7 @@ static gboolean update_variable_filter(complex double *variable_filter, gint64 v
 	/* Check if the filter has sain values in it */
 	gboolean success = TRUE;
 	for(n = 0; n < variable_filter_length; n++)
-		success &= isnormal(((double *) variable_filter)[n]);
+		success &= isnormal(((double *) variable_filter)[n]) || ((double *) variable_filter)[n] == 0.0;
 
 	return success;
 }
-- 
GitLab