From 48649ffc0e0371fd590010478196250aa2ef5636 Mon Sep 17 00:00:00 2001
From: Yun-Jing Huang <yun-jing.huang@ligo.org>
Date: Tue, 26 Jul 2022 17:21:28 +0000
Subject: [PATCH] gstlal_ll_inspiral_event_uploader: make composite events

---
 .../bin/gstlal_ll_inspiral_event_uploader     | 103 +++++++++++++-----
 1 file changed, 73 insertions(+), 30 deletions(-)

diff --git a/gstlal-inspiral/bin/gstlal_ll_inspiral_event_uploader b/gstlal-inspiral/bin/gstlal_ll_inspiral_event_uploader
index 4c4d125872..e9cf33e173 100755
--- a/gstlal-inspiral/bin/gstlal_ll_inspiral_event_uploader
+++ b/gstlal-inspiral/bin/gstlal_ll_inspiral_event_uploader
@@ -311,56 +311,99 @@ class EventUploader(events.EventProcessor):
 
 	def process_candidates(self, event):
 		"""
-		process candidates and update the favored event
+		process candidates and update the favored (maxsnr) event
 		if needed
 
-		returns event and whether the favored event was updated
+		returns event and whether the favored (maxsnr) event was updated
 		"""
-		favored = self.select_favored_event(event['candidates'])
+		if event['favored']:
+			event['candidates'].append(event['favored'])
+
+		### get overall max SNR
+		maxsnr_candidate = self.select_maxsnr_candidate(event['candidates'])
+		maxsnr = maxsnr_candidate['snr']
+
+		### get overall min FAR
+		minfar_candidate = self.select_minfar_candidate(event['candidates'])
+		minfar = minfar_candidate['far']
+
 		event['candidates'].clear()
 
-		### no favored event yet
-		if not event['favored']:
+		### not a better candidate
+		if event['favored'] and maxsnr <= event['favored']['snr'] and minfar >= event['favored']['far']:
+
+			return False, event
+
+		### found a new or better candidate 
+		else:
 			logging.info(
 				'found candidate with FAR: {:.3E}, '
-				'SNR: {:2.1f}'.format(favored['far'], favored['snr'])
+				'SNR: {:2.3f}'.format(maxsnr_candidate['far'], maxsnr_candidate['snr'])
 			)
-			event['favored'] = favored
-			return True, event
 
-		### favored event is more significant
-		elif self.rank_candidate(favored) > self.rank_candidate(event['favored']):
-			logging.info(
-				'found new favored candidate with FAR: {:.3E}, '
-				'SNR: {:2.1f}'.format(favored['far'], favored['snr'])
-			)
-			event['favored'] = favored
-			return True, event
+			### composite event
+			if maxsnr_candidate['far'] != minfar:
+				logging.info(
+					'construct new composite event with FAR: {:.3E}, '
+					'SNR: {:2.3f}'.format(minfar, maxsnr_candidate['snr'])
+				)
+				maxsnr_candidate = self.composite_event(maxsnr_candidate, minfar_candidate, minfar)
 
-		### previous favored is better
-		else:
-			return False, event
+			if event['favored']:
+				assert maxsnr_candidate['far'] <= event['favored']['far'], "far should be smaller"
+				assert maxsnr_candidate['snr'] >= event['favored']['snr'], "snr should be larger"
+
+			event['favored'] = maxsnr_candidate 
+				
+			return True, event
 
 
-	def select_favored_event(self, candidates):
+	def composite_event(self, maxsnr_candidate, minfar_candidate, minfar):
 		"""
-		select the best event out of the candidates
+		Consturct composite event. 
+
+		Replace far, likelihood in maxsnr candidate with those in the minfar candidate.
 		"""
-		return max(candidates, key=self.rank_candidate)
 
+		### replace far
+		maxsnr_candidate['far'] = minfar
+		
+		### load coinc file
+		maxsnr_candidate_coinc = self.load_xmlobj(maxsnr_candidate["coinc"])
+		minfar_candidate_coinc = self.load_xmlobj(minfar_candidate["coinc"])
+
+		### update likelihood in coinc file
+		lsctables.CoincTable.get_table(maxsnr_candidate_coinc).getColumnByName("likelihood")[0] = lsctables.CoincTable.get_table(minfar_candidate_coinc).getColumnByName("likelihood")[0]
+
+		### update combined_far in coinc file
+		lsctables.CoincInspiralTable.get_table(maxsnr_candidate_coinc).getColumnByName("combined_far")[0] = minfar
+
+		### save coinc file
+		coinc_obj = BytesIO()
+		ligolw_utils.write_fileobj(maxsnr_candidate_coinc, coinc_obj)
+		maxsnr_candidate["coinc"] = coinc_obj.getvalue().decode("utf-8")
+		
+		return maxsnr_candidate
 
-	def rank_candidate(self, candidate):
+
+	def select_maxsnr_candidate(self, candidates):
+		"""
+		select the max snr candidate out of the candidates
 		"""
-		rank a candidate based on the following criterion:
+		return max(candidates, key=self.rank_snr)
 
-		    * FAR >  public threshold, choose lowest FAR
-		    * FAR <= public threshold, choose highest SNR
+	def select_minfar_candidate(self, candidates):
 		"""
-		if candidate['far'] <= self.public_far_threshold:
-			return True, candidate['snr'], 1. / candidate['far']
-		else:
-			return False, 1. / candidate['far'], candidate['snr']
+		select the min far candidate out of the candidates
+		"""
+		return min(candidates, key=self.rank_far)
+
+
+	def rank_snr(self, candidate): 
+		return candidate['snr']
 
+	def rank_far(self, candidate): 
+		return candidate['far']
 
 	def send_favored_event(self, event, event_window):
 		"""
-- 
GitLab