LISTSERV mailing list manager LISTSERV 16.5

Help for HPS-SVN Archives


HPS-SVN Archives

HPS-SVN Archives


HPS-SVN@LISTSERV.SLAC.STANFORD.EDU


View:

Message:

[

First

|

Previous

|

Next

|

Last

]

By Topic:

[

First

|

Previous

|

Next

|

Last

]

By Author:

[

First

|

Previous

|

Next

|

Last

]

Font:

Proportional Font

LISTSERV Archives

LISTSERV Archives

HPS-SVN Home

HPS-SVN Home

HPS-SVN  March 2015

HPS-SVN March 2015

Subject:

r2266 - in /java/trunk/analysis/src/main/java/org/hps/analysis/trigger: ./ event/ util/

From:

[log in to unmask]

Reply-To:

Notification of commits to the hps svn repository <[log in to unmask]>

Date:

Thu, 5 Mar 2015 18:59:24 -0000

Content-Type:

text/plain

Parts/Attachments:

Parts/Attachments

text/plain (4566 lines)

Author: [log in to unmask]
Date: Thu Mar  5 10:59:19 2015
New Revision: 2266

Log:
Trigger diagnostic package is being relocated to an official location now that it is approaching release status.

Added:
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DiagSnapshot.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchEvent.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchStatus.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchedPair.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterStatModule.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerEfficiencyModule.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchEvent.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchStatus.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchedPair.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerStatModule.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/ComponentUtils.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/OutputLogger.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Pair.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/PairTrigger.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/SinglesTrigger.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Trigger.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DiagSnapshot.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DiagSnapshot.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DiagSnapshot.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,169 @@
+package org.hps.analysis.trigger;
+
+import org.hps.analysis.trigger.event.ClusterMatchStatus;
+import org.hps.analysis.trigger.event.ClusterStatModule;
+import org.hps.analysis.trigger.event.TriggerEfficiencyModule;
+import org.hps.analysis.trigger.event.TriggerMatchStatus;
+import org.hps.analysis.trigger.event.TriggerStatModule;
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+
+
+/**
+ * Class <code>DiagSnapshot</code> creates a snapshot of the trigger
+ * diagnostics at a specific time that can be passed to other classes.
+ * It is entirely static and will not change after creation.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class DiagSnapshot {
+	public final ClusterStatModule clusterRunStatistics;
+	public final ClusterStatModule clusterLocalStatistics;
+	public final SinglesTriggerStatModule singlesRunStatistics;
+	public final SinglesTriggerStatModule singlesLocalStatistics;
+	public final PairTriggerStatModule pairRunStatistics;
+	public final PairTriggerStatModule pairLocalStatistics;
+	public final TriggerEfficiencyModule efficiencyRunStatistics;
+	public final TriggerEfficiencyModule efficiencyLocalStatistics;
+	
+	/**
+	 * Instantiates a new snapshot. The snapshot creates a copy of the
+	 * statistical information stored in the status objects and makes
+	 * a copy of this information available to other classes.
+	 * @param localCluster - The local cluster data object.
+	 * @param globalCluster - The run cluster data object.
+	 * @param localSingles - The local singles trigger data object.
+	 * @param globalSingles - The run singles trigger data object.
+	 * @param localPair - The local pair trigger data object.
+	 * @param globalPair - The run pair trigger data object.
+	 */
+	DiagSnapshot(ClusterMatchStatus localCluster, ClusterMatchStatus globalCluster,
+			TriggerMatchStatus localSingles, TriggerMatchStatus globalSingles,
+			TriggerMatchStatus localPair, TriggerMatchStatus globalPair,
+			TriggerEfficiencyModule localEfficiency, TriggerEfficiencyModule globalEfficiency) {
+		clusterRunStatistics = globalCluster.cloneStatModule();
+		clusterLocalStatistics = localCluster.cloneStatModule();
+		singlesRunStatistics = new SinglesTriggerStatModule(globalSingles);
+		singlesLocalStatistics = new SinglesTriggerStatModule(localSingles);
+		pairRunStatistics = new PairTriggerStatModule(globalPair);
+		pairLocalStatistics = new PairTriggerStatModule(localPair);
+		efficiencyRunStatistics = globalEfficiency.clone();
+		efficiencyLocalStatistics = localEfficiency.clone();
+	}
+	
+	/**
+	 * 
+	 * Class <code>SinglesTriggerStatModule</code> is a wrapper for the
+	 * generic <code>TriggerStatModule</code> that provides specific
+	 * methods to obtain cut results rather than needing to reference
+	 * a cut index.
+	 * 
+	 * @author Kyle McCarty <[log in to unmask]>
+	 */
+	public class SinglesTriggerStatModule extends TriggerStatModule {
+		/**
+		 * Instantiates a <code>SinglesTriggerStatModule</code> with
+		 * statistics cloned from the base object.
+		 * @param base - The source for the statistical data.
+		 */
+		SinglesTriggerStatModule(TriggerStatModule base) {
+			super(base);
+		}
+		
+		/**
+		 * Gets the number of times the cluster energy upper bound cut
+		 * failed to match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getEMaxFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.SINGLES_ENERGY_MAX);
+		}
+		
+		/**
+		 * Gets the number of times the cluster energy lower bound cut
+		 * failed to match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getEMinFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.SINGLES_ENERGY_MIN);
+		}
+		
+		/**
+		 * Gets the number of times the cluster hit count cut failed
+		 * to match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getHitCountFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.SINGLES_HIT_COUNT);
+		}
+	}
+	
+	/**
+	 * 
+	 * Class <code>PairTriggerStatModule</code> is a wrapper for the
+	 * generic <code>TriggerStatModule</code> that provides specific
+	 * methods to obtain cut results rather than needing to reference
+	 * a cut index.
+	 * 
+	 * @author Kyle McCarty <[log in to unmask]>
+	 */
+	public class PairTriggerStatModule extends TriggerStatModule {
+		/**
+		 * Instantiates a <code>PairTriggerStatModule</code> with
+		 * statistics cloned from the base object.
+		 * @param base - The source for the statistical data.
+		 */
+		PairTriggerStatModule(TriggerStatModule base) {
+			super(base);
+		}
+		
+		/**
+		 * Gets the number of times the pair energy sum cut failed to
+		 * match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getESumFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.PAIR_ENERGY_SUM);
+		}
+		
+		/**
+		 * Gets the number of times the pair energy difference cut failed
+		 * to match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getEDiffFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.PAIR_ENERGY_DIFF);
+		}
+		
+		/**
+		 * Gets the number of times the pair energy slope cut failed
+		 * to match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getESlopeFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.PAIR_ENERGY_SLOPE);
+		}
+		
+		/**
+		 * Gets the number of times the pair coplanarity cut failed to
+		 * match.
+		 * @param triggerNumber - The trigger for which to get the value.
+		 * @return Returns the number of times the cut failed as an
+		 * <code>int</code> primitive.
+		 */
+		public int getCoplanarityFailures(int triggerNumber) {
+			return getCutFailures(triggerNumber, TriggerDiagnosticUtil.PAIR_COPLANARITY);
+		}
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,1995 @@
+package org.hps.analysis.trigger;
+
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.hps.analysis.trigger.event.ClusterMatchEvent;
+import org.hps.analysis.trigger.event.ClusterMatchStatus;
+import org.hps.analysis.trigger.event.ClusterMatchedPair;
+import org.hps.analysis.trigger.event.TriggerEfficiencyModule;
+import org.hps.analysis.trigger.event.TriggerMatchEvent;
+import org.hps.analysis.trigger.event.TriggerMatchStatus;
+import org.hps.analysis.trigger.util.OutputLogger;
+import org.hps.analysis.trigger.util.Pair;
+import org.hps.analysis.trigger.util.PairTrigger;
+import org.hps.analysis.trigger.util.SinglesTrigger;
+import org.hps.analysis.trigger.util.Trigger;
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+import org.hps.readout.ecal.TriggerModule;
+import org.hps.readout.ecal.daqconfig.ConfigurationManager;
+import org.hps.readout.ecal.daqconfig.DAQConfig;
+import org.hps.readout.ecal.triggerbank.AbstractIntData;
+import org.hps.readout.ecal.triggerbank.SSPCluster;
+import org.hps.readout.ecal.triggerbank.SSPData;
+import org.hps.readout.ecal.triggerbank.SSPNumberedTrigger;
+import org.hps.readout.ecal.triggerbank.SSPPairTrigger;
+import org.hps.readout.ecal.triggerbank.SSPSinglesTrigger;
+import org.hps.readout.ecal.triggerbank.SSPTrigger;
+import org.hps.readout.ecal.triggerbank.TIData;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.Cluster;
+import org.lcsim.event.EventHeader;
+import org.lcsim.event.GenericObject;
+import org.lcsim.util.Driver;
+
+public class TriggerDiagnosticDriver extends Driver {
+	// Store the LCIO collection names for the needed objects.
+	private String hitCollectionName = "EcalCalHits";
+	private String bankCollectionName = "TriggerBank";
+	private String clusterCollectionName = "EcalClusters";
+	private String diagnosticCollectionName = "DiagnosticSnapshot";
+	
+	// Store the lists of parsed objects.
+	private TIData tiBank;
+	private SSPData sspBank;
+	private List<Cluster> reconClusters = new ArrayList<Cluster>();
+	private List<SSPCluster> sspClusters;
+	private List<List<PairTrigger<Cluster[]>>> reconPairsTriggers = new ArrayList<List<PairTrigger<Cluster[]>>>(2);
+	private List<List<PairTrigger<SSPCluster[]>>> sspPairsTriggers = new ArrayList<List<PairTrigger<SSPCluster[]>>>(2);
+	private List<List<SinglesTrigger<Cluster>>> reconSinglesTriggers = new ArrayList<List<SinglesTrigger<Cluster>>>(2);
+	private List<List<SinglesTrigger<SSPCluster>>> sspSinglesTriggers = new ArrayList<List<SinglesTrigger<SSPCluster>>>(2);
+	
+	// Trigger modules for performing trigger analysis.
+	private int activeTrigger = -1;
+	private TriggerModule[] singlesTrigger = new TriggerModule[2];
+	private TriggerModule[] pairsTrigger = new TriggerModule[2];
+	
+	// Verification settings.
+	private int nsa = 100;
+	private int nsb = 20;
+	private int windowWidth = 200;
+	private int hitAcceptance = 1;
+	private int noiseThreshold = 50;
+	private long localWindowStart = 0;
+	private boolean readDAQConfig = false;
+	private double energyAcceptance = 0.03;
+	private int localWindowThreshold = 30000;
+	private boolean performClusterVerification = true;
+	private boolean performSinglesTriggerVerification = true;
+	private boolean performPairTriggerVerification = true;
+	
+	// Efficiency tracking variables.
+	private ClusterMatchStatus clusterRunStats = new ClusterMatchStatus();
+	private ClusterMatchStatus clusterLocalStats = new ClusterMatchStatus();
+	private TriggerEfficiencyModule efficiencyRunStats = new TriggerEfficiencyModule();
+	private TriggerEfficiencyModule efficiencyLocalStats = new TriggerEfficiencyModule();
+	private TriggerMatchStatus[] triggerRunStats = { new TriggerMatchStatus(), new TriggerMatchStatus() };
+	private TriggerMatchStatus[] triggerLocalStats = { new TriggerMatchStatus(), new TriggerMatchStatus() };
+	
+	private int failedClusterEvents = 0;
+	private int failedSinglesEvents = 0;
+	private int failedPairEvents = 0;
+	private int totalEvents = 0;
+	private int noiseEvents = 0;
+    
+    // Verbose settings.
+    private boolean clusterFail = false;
+    private boolean singlesEfficiencyFail = false;
+    private boolean singlesInternalFail = false;
+    private boolean pairEfficiencyFail = false;
+    private boolean pairInternalFail = false;
+    private boolean verbose = false;
+    private boolean printClusterFail = true;
+    private boolean printSinglesTriggerEfficiencyFail = true;
+    private boolean printSinglesTriggerInternalFail = true;
+    private boolean printPairTriggerEfficiencyFail = true;
+    private boolean printPairTriggerInternalFail = true;
+    
+    // Cut index arrays for trigger verification.
+	private static final int ENERGY_MIN   = TriggerDiagnosticUtil.SINGLES_ENERGY_MIN;
+	private static final int ENERGY_MAX   = TriggerDiagnosticUtil.SINGLES_ENERGY_MAX;
+	private static final int HIT_COUNT    = TriggerDiagnosticUtil.SINGLES_HIT_COUNT;
+	private static final int ENERGY_SUM   = TriggerDiagnosticUtil.PAIR_ENERGY_SUM;
+	private static final int ENERGY_DIFF  = TriggerDiagnosticUtil.PAIR_ENERGY_DIFF;
+	private static final int ENERGY_SLOPE = TriggerDiagnosticUtil.PAIR_ENERGY_SLOPE;
+	private static final int COPLANARITY  = TriggerDiagnosticUtil.PAIR_COPLANARITY;
+    
+	/**
+	 * Define the trigger modules. This should be replaced by parsing
+	 * the DAQ configuration at some point.
+	 */
+	@Override
+	public void startOfData() {
+		// If the DAQ configuration should be read, attach a listener
+		// to track when it updates.
+		if(readDAQConfig) {
+			ConfigurationManager.addActionListener(new ActionListener() {
+				@Override
+				public void actionPerformed(ActionEvent e) {
+					// Get the DAQ configuration.
+					DAQConfig daq = ConfigurationManager.getInstance();
+					
+					// Load the DAQ settings from the configuration manager.
+					singlesTrigger[0].loadDAQConfiguration(daq.getSSPConfig().getSingles1Config());
+					singlesTrigger[1].loadDAQConfiguration(daq.getSSPConfig().getSingles2Config());
+					pairsTrigger[0].loadDAQConfiguration(daq.getSSPConfig().getPair1Config());
+					pairsTrigger[1].loadDAQConfiguration(daq.getSSPConfig().getPair2Config());
+					nsa = daq.getFADCConfig().getNSA();
+					nsb = daq.getFADCConfig().getNSB();
+					windowWidth = daq.getFADCConfig().getWindowWidth();
+					
+					// Print a DAQ configuration settings header.
+					System.out.println();
+					System.out.println();
+					System.out.println("======================================================================");
+					System.out.println("=== DAQ Configuration Settings =======================================");
+					System.out.println("======================================================================");
+					logSettings();
+				}
+			});
+		}
+		
+		// Print the cluster verification header.
+		System.out.println();
+		System.out.println();
+		System.out.println("======================================================================");
+		System.out.println("=== Cluster/Trigger Verification Settings ============================");
+		System.out.println("======================================================================");
+		
+		// Set the FADC settings.
+		nsa = 100;
+		nsb = 20;
+		windowWidth = 400;
+		
+		/*
+		// Define the first singles trigger.
+		singlesTrigger[0] = new TriggerModule();
+		singlesTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.010);
+		singlesTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 8.191);
+		singlesTrigger[0].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 2);
+		
+		// Define the second singles trigger.
+		singlesTrigger[1] = new TriggerModule();
+		singlesTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.010);
+		singlesTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 0.050);
+		singlesTrigger[1].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 2);
+		
+		// Define the first pairs trigger.
+		pairsTrigger[0] = new TriggerModule();
+		pairsTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.020);
+		pairsTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 0.055);
+		pairsTrigger[0].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 1);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SUM_LOW, 0.010);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SUM_HIGH, 2.000);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH, 1.200);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_LOW, 0.400);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F, 0.0055);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_COPLANARITY_HIGH, 40);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_TIME_COINCIDENCE, 16);
+		
+		// Define the second pairs trigger.
+		pairsTrigger[1] = new TriggerModule();
+		pairsTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.010);
+		pairsTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 1.800);
+		pairsTrigger[1].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 2);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SUM_LOW, 0.020);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SUM_HIGH, 2.000);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH, 1.200);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_LOW, 0.400);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F, 0.0055);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_COPLANARITY_HIGH, 40);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_TIME_COINCIDENCE, 16);
+		*/
+		
+		// Define the first singles trigger.
+		singlesTrigger[0] = new TriggerModule();
+		singlesTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.500);
+		singlesTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 8.191);
+		singlesTrigger[0].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 0);
+		
+		// Define the second singles trigger.
+		singlesTrigger[1] = new TriggerModule();
+		singlesTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.000);
+		singlesTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 8.191);
+		singlesTrigger[1].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 0);
+		
+		// Define the first pairs trigger.
+		pairsTrigger[0] = new TriggerModule();
+		pairsTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.000);
+		pairsTrigger[0].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 8.191);
+		pairsTrigger[0].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 0);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SUM_LOW, 0.000);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SUM_HIGH, 8.191);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH, 8.191);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_LOW, 0.000);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F, 0.001);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_COPLANARITY_HIGH, 180);
+		pairsTrigger[0].setCutValue(TriggerModule.PAIR_TIME_COINCIDENCE, 8);
+		
+		// Define the second pairs trigger.
+		pairsTrigger[1] = new TriggerModule();
+		pairsTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW, 0.000);
+		pairsTrigger[1].setCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH, 8.191);
+		pairsTrigger[1].setCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW, 0);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SUM_LOW, 0.000);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SUM_HIGH, 8.191);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH, 8.191);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_LOW, 0.000);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F, 0.001);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_COPLANARITY_HIGH, 180);
+		pairsTrigger[1].setCutValue(TriggerModule.PAIR_TIME_COINCIDENCE, 8);
+		
+		// Instantiate the triggers lists.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			reconPairsTriggers.add(new ArrayList<PairTrigger<Cluster[]>>());
+			sspPairsTriggers.add(new ArrayList<PairTrigger<SSPCluster[]>>());
+			reconSinglesTriggers.add(new ArrayList<SinglesTrigger<Cluster>>());
+			sspSinglesTriggers.add(new ArrayList<SinglesTrigger<SSPCluster>>());
+		}
+		
+		// Print the initial settings.
+		logSettings();
+	}
+	
+	/**
+	 * Prints the total run statistics.
+	 */
+	@Override
+	public void endOfData() {
+		// Print the cluster/trigger verification header.
+		System.out.println();
+		System.out.println();
+		System.out.println("======================================================================");
+		System.out.println("=== Cluster/Trigger Verification Results =============================");
+		System.out.println("======================================================================");
+		
+		// Print the general event failure rate.
+		System.out.println("Event Failure Rate:");
+		System.out.printf("\tNoise Events          :: %d / %d (%7.3f%%)%n",
+				noiseEvents, totalEvents, (100.0 * noiseEvents / totalEvents));
+		System.out.printf("\tCluster Events Failed :: %d / %d (%7.3f%%)%n",
+				failedClusterEvents, totalEvents, (100.0 * failedClusterEvents / totalEvents));
+		System.out.printf("\tSingles Events Failed :: %d / %d (%7.3f%%)%n",
+				failedSinglesEvents, totalEvents, (100.0 * failedSinglesEvents / totalEvents));
+		System.out.printf("\tPair Events Failed    :: %d / %d (%7.3f%%)%n",
+				failedPairEvents, totalEvents, (100.0 * failedPairEvents / totalEvents));
+		
+		// Print the cluster verification data.
+		System.out.println("Cluster Verification:");
+		System.out.printf("\tRecon Clusters        :: %d%n", clusterRunStats.getReconClusterCount());
+		System.out.printf("\tSSP Clusters          :: %d%n", clusterRunStats.getSSPClusterCount());
+		System.out.printf("\tClusters Matched      :: %d%n", clusterRunStats.getMatches());
+		System.out.printf("\tFailed (Position)     :: %d%n", clusterRunStats.getPositionFailures());
+		System.out.printf("\tFailed (Energy)       :: %d%n", clusterRunStats.getEnergyFailures());
+		System.out.printf("\tFailed (Hit Count)    :: %d%n", clusterRunStats.getHitCountFailures());
+		if(clusterRunStats.getReconClusterCount() == 0) { System.out.printf("\tCluster Efficiency    :: N/A%n"); }
+		else { System.out.printf("\tCluster Efficiency :: %7.3f%%%n", 100.0 * clusterRunStats.getMatches() / clusterRunStats.getReconClusterCount()); }
+		
+		// Print the trigger verification data.
+		for(int triggerType = 0; triggerType < 2; triggerType++) {
+			int spaces = getPrintSpaces(triggerRunStats[triggerType].getSSPSimTriggerCount(),
+					triggerRunStats[triggerType].getReconTriggerCount(), triggerRunStats[triggerType].getSSPBankTriggerCount(),
+					triggerRunStats[triggerType].getMatchedSSPTriggers(), triggerRunStats[triggerType].getMatchedReconTriggers());
+			System.out.println();
+			if(triggerType == 0) { System.out.println("Singles Trigger Verification:"); }
+			else { System.out.println("Pair Trigger Verification:"); }
+			System.out.printf("\tSSP Cluster Sim Triggers   :: %" + spaces + "d%n", triggerRunStats[triggerType].getSSPSimTriggerCount());
+			System.out.printf("\tRecon Cluster Sim Triggers :: %" + spaces + "d%n", triggerRunStats[triggerType].getReconTriggerCount());
+			System.out.printf("\tSSP Reported Triggers      :: %" + spaces + "d%n", triggerRunStats[triggerType].getSSPBankTriggerCount());
+			System.out.printf("\tExtra Reported Triggers    :: %" + spaces + "d%n", triggerRunStats[triggerType].getExtraSSPBankTriggers());
+			
+			System.out.printf("\tInternal Efficiency        :: %" + spaces + "d / %" + spaces + "d ",
+					triggerRunStats[triggerType].getMatchedSSPTriggers(), triggerRunStats[triggerType].getSSPSimTriggerCount());
+			if(triggerRunStats[triggerType].getSSPSimTriggerCount() == 0) { System.out.printf("(N/A)%n"); }
+			else {
+				System.out.printf("(%7.3f%%)%n", (100.0 * triggerRunStats[triggerType].getMatchedSSPTriggers() / triggerRunStats[triggerType].getSSPSimTriggerCount()));
+			}
+			
+			System.out.printf("\tTrigger Efficiency         :: %" + spaces + "d / %" + spaces + "d ",
+					triggerRunStats[triggerType].getMatchedReconTriggers(), triggerRunStats[triggerType].getReconTriggerCount());
+			if(triggerRunStats[triggerType].getReconTriggerCount() == 0) { System.out.printf("(N/A)%n"); }
+			else { System.out.printf("(%7.3f%%)%n" , (100.0 * triggerRunStats[triggerType].getMatchedReconTriggers() / triggerRunStats[triggerType].getReconTriggerCount())); }
+			
+			// Print the individual cut performances.
+			int halfSSPTriggers = triggerRunStats[triggerType].getSSPSimTriggerCount() / 2;
+			if(triggerType == 0) {
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+					System.out.println();
+					System.out.printf("\tTrigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
+					if(triggerRunStats[0].getSSPSimTriggerCount() == 0) {
+						System.out.printf("\t\tCluster Energy Lower Bound :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[0].getCutFailures(triggerNum, ENERGY_MIN), halfSSPTriggers);
+						System.out.printf("\t\tCluster Energy Upper Bound :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[0].getCutFailures(triggerNum, ENERGY_MAX), halfSSPTriggers);
+						System.out.printf("\t\tCluster Hit Count          :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[0].getCutFailures(triggerNum, HIT_COUNT), halfSSPTriggers);
+					} else {
+						System.out.printf("\t\tCluster Energy Lower Bound :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[0].getCutFailures(triggerNum, ENERGY_MIN), halfSSPTriggers,
+								(100.0 * triggerRunStats[0].getCutFailures(triggerNum, ENERGY_MIN) / halfSSPTriggers));
+						System.out.printf("\t\tCluster Energy Upper Bound :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[0].getCutFailures(triggerNum, ENERGY_MAX), halfSSPTriggers,
+								(100.0 * triggerRunStats[0].getCutFailures(triggerNum, ENERGY_MAX) / halfSSPTriggers));
+						System.out.printf("\t\tCluster Hit Count          :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[0].getCutFailures(triggerNum, HIT_COUNT), halfSSPTriggers,
+								(100.0 * triggerRunStats[0].getCutFailures(triggerNum, HIT_COUNT) / halfSSPTriggers));
+					}
+				}
+			} else {
+				for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+					System.out.println();
+					System.out.printf("\tTrigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
+					if(triggerRunStats[1].getSSPSimTriggerCount() == 0) {
+						System.out.printf("\t\tPair Energy Sum            :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[1].getCutFailures(triggerNum, ENERGY_SUM), halfSSPTriggers);
+						System.out.printf("\t\tPair Energy Difference     :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[1].getCutFailures(triggerNum, ENERGY_DIFF), halfSSPTriggers);
+						System.out.printf("\t\tPair Energy Slope          :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[1].getCutFailures(triggerNum, ENERGY_SLOPE), halfSSPTriggers);
+						System.out.printf("\t\tPair Coplanarity           :: %" + spaces + "d / %" + spaces + "d%n",
+								triggerRunStats[1].getCutFailures(triggerNum, COPLANARITY), halfSSPTriggers);
+					} else {
+						System.out.printf("\t\tPair Energy Sum            :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[1].getCutFailures(triggerNum, ENERGY_SUM), halfSSPTriggers,
+								(100.0 * triggerRunStats[1].getCutFailures(triggerNum, ENERGY_SUM) / halfSSPTriggers));
+						System.out.printf("\t\tPair Energy Difference     :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[1].getCutFailures(triggerNum, ENERGY_DIFF), halfSSPTriggers,
+								(100.0 * triggerRunStats[1].getCutFailures(triggerNum, ENERGY_DIFF) / halfSSPTriggers));
+						System.out.printf("\t\tPair Energy Slope          :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[1].getCutFailures(triggerNum, ENERGY_SLOPE), halfSSPTriggers,
+								(100.0 * triggerRunStats[1].getCutFailures(triggerNum, ENERGY_SLOPE) / halfSSPTriggers));
+						System.out.printf("\t\tPair Coplanarity           :: %" + spaces + "d / %" + spaces + "d (%7.3f%%)%n",
+								triggerRunStats[1].getCutFailures(triggerNum, COPLANARITY), halfSSPTriggers,
+								(100.0 * triggerRunStats[1].getCutFailures(triggerNum, COPLANARITY) / halfSSPTriggers));
+					}
+				}
+			}
+		}
+		
+		this.efficiencyRunStats.printModule();
+	}
+	
+	/**
+	 * Gets the banks and clusters from the event.
+	 */
+	@Override
+	public void process(EventHeader event) {
+		// ==========================================================
+		// ==== Initialize the Event ================================
+		// ==========================================================
+        
+		// If DAQ settings are to be used, check if they are initialized
+		// yet. If not, skip the event.
+		if(readDAQConfig) {
+			if(!ConfigurationManager.isInitialized()) {
+				return;
+			}
+		}
+		
+        // Print the verification header.
+		OutputLogger.printNewLine(2);
+		OutputLogger.println("======================================================================");
+		OutputLogger.println("==== Cluster/Trigger Verification ====================================");
+		OutputLogger.println("======================================================================");
+		
+		// Increment the total event count.
+		totalEvents++;
+		
+		// Reset the output buffer and print flags.
+		clusterFail = false;
+		singlesInternalFail = false;
+		singlesEfficiencyFail = false;
+		pairInternalFail = false;
+		pairEfficiencyFail = false;
+		
+		
+		
+		// ==========================================================
+		// ==== Obtain SSP and TI Banks =============================
+		// ==========================================================
+		
+		// Get the SSP clusters.
+		if(event.hasCollection(GenericObject.class, bankCollectionName)) {
+			// Get the bank list.
+			List<GenericObject> bankList = event.get(GenericObject.class, bankCollectionName);
+			
+			// Search through the banks and get the SSP and TI banks.
+			for(GenericObject obj : bankList) {
+				// If this is an SSP bank, parse it.
+				if(AbstractIntData.getTag(obj) == SSPData.BANK_TAG) {
+					sspBank = new SSPData(obj);
+				}
+				
+				// Otherwise, if this is a TI bank, parse it.
+				else if(AbstractIntData.getTag(obj) == TIData.BANK_TAG) {
+					tiBank = new TIData(obj);
+					
+					if(tiBank.isPulserTrigger()) {
+						OutputLogger.println("Trigger type :: Pulser");
+						activeTrigger = TriggerDiagnosticUtil.TRIGGER_PULSER;
+					} else if(tiBank.isSingle0Trigger()) {
+						OutputLogger.println("Trigger type :: Singles 1");
+						activeTrigger = TriggerDiagnosticUtil.TRIGGER_SINGLES_1;
+					} else if(tiBank.isSingle1Trigger()) {
+						OutputLogger.println("Trigger type :: Singles 2");
+						activeTrigger = TriggerDiagnosticUtil.TRIGGER_SINGLES_2;
+					} else if(tiBank.isPair0Trigger()) {
+						OutputLogger.println("Trigger type :: Pair 1");
+						activeTrigger = TriggerDiagnosticUtil.TRIGGER_PAIR_1;
+					} else if(tiBank.isPair1Trigger()) {
+						OutputLogger.println("Trigger type :: Pair 2");
+						activeTrigger = TriggerDiagnosticUtil.TRIGGER_PAIR_2;
+					} else if(tiBank.isCalibTrigger()) {
+						OutputLogger.println("Trigger type :: Cosmic");
+						activeTrigger = TriggerDiagnosticUtil.TRIGGER_COSMIC;
+					}
+				}
+			}
+			
+			// If there is an SSP bank, get the list of SSP clusters.
+			if(sspBank != null) {
+				sspClusters = sspBank.getClusters();
+				if(sspClusters.size() == 1) {
+					OutputLogger.println("1 SSP cluster found.");
+				} else {
+					OutputLogger.printf("%d SSP clusters found.%n", sspClusters.size());
+				}
+			}
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Establish Event Integrity ===========================
+		// ==========================================================
+		
+		// Check that all of the required objects are present.
+		if(sspBank == null) {
+			OutputLogger.println("No SSP bank found for this event. No verification will be performed.");
+			if(verbose) { OutputLogger.printLog(); }
+			return;
+		} if(tiBank == null) {
+			OutputLogger.println("No TI bank found for this event. No verification will be performed.");
+			if(verbose) { OutputLogger.printLog(); }
+			return;
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Check the Noise Level ===============================
+		// ==========================================================
+		
+		// Check if there are hits.
+		if(event.hasCollection(CalorimeterHit.class, hitCollectionName)) {
+			// Check if there are more hits than the noise threshold.
+			if(event.get(CalorimeterHit.class, hitCollectionName).size() >= noiseThreshold) {
+				noiseEvents++;
+				OutputLogger.println("Noise event detected. Skipping event...");
+				if(verbose) { OutputLogger.printLog(); }
+				return;
+			}
+		}
+        
+        
+        
+		// ==========================================================
+		// ==== Obtain Reconstructed Clusters =======================
+		// ==========================================================
+		
+		// Clear the list of triggers from previous events.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			sspSinglesTriggers.get(triggerNum).clear();
+			reconSinglesTriggers.get(triggerNum).clear();
+			sspPairsTriggers.get(triggerNum).clear();
+			reconPairsTriggers.get(triggerNum).clear();
+		}
+		
+		// Get the reconstructed clusters.
+		if(event.hasCollection(Cluster.class, clusterCollectionName)) {
+			// Get the reconstructed clusters.
+			List<Cluster> allClusters = event.get(Cluster.class, clusterCollectionName);
+			
+			// Keep only the clusters that can be verified.
+			OutputLogger.println();
+			OutputLogger.println("Process cluster for verifiability:");
+			reconClusters.clear();
+			for(Cluster reconCluster : allClusters) {
+				// Check that the cluster is within the safe region of the
+				// FADC readout window. If it is not, it will likely have
+				// inaccurate energy or hit values and may not produce the
+				// expected results.
+				OutputLogger.printf("\t%s", TriggerDiagnosticUtil.clusterToString(reconCluster));
+				if(isVerifiable(reconCluster)) {
+					reconClusters.add(reconCluster);
+					OutputLogger.println(" [  verifiable  ]");
+				} else { OutputLogger.println(" [ unverifiable ]"); }
+				
+			}
+			
+			// Output the number of verifiable clusters found.
+			if(reconClusters.size() == 1) { OutputLogger.println("1 verifiable reconstructed cluster found."); }
+			else { OutputLogger.printf("%d verifiable reconstructed clusters found.%n", reconClusters.size()); }
+			
+			// Output the number of unverifiable clusters found.
+			int unverifiableClusters = allClusters.size() - reconClusters.size();
+			if(unverifiableClusters == 1) { OutputLogger.println("1 unverifiable reconstructed cluster found."); }
+			else { OutputLogger.printf("%d unverifiable reconstructed clusters found.%n", unverifiableClusters); }
+		} else {
+			reconClusters = new ArrayList<Cluster>(0);
+			OutputLogger.printf("No reconstructed clusters were found for collection \"%s\" in this event.%n", clusterCollectionName);
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Perform Event Verification ==========================
+		// ==========================================================
+		
+		// Perform the cluster verification step.
+		if(performClusterVerification) { clusterVerification(); }
+		
+		// Construct lists of triggers for the SSP clusters and the
+		// reconstructed clusters.
+		if(performSinglesTriggerVerification) {
+			constructSinglesTriggers();
+			singlesTriggerVerification();
+		}
+		if(performPairTriggerVerification) {
+			constructPairTriggers();
+			pairTriggerVerification();
+		}
+		
+		// Track how many events failed due to each type of verification.
+		if(clusterFail) { failedClusterEvents++; }
+		if(pairInternalFail || pairEfficiencyFail) { failedPairEvents++; }
+		if(singlesInternalFail || singlesEfficiencyFail) { failedSinglesEvents++; }
+		
+		
+		
+		// ==========================================================
+		// ==== Perform Event Write-Out =============================
+		// ==========================================================
+		
+		if(verbose ||(clusterFail && printClusterFail) ||
+				(singlesInternalFail && printSinglesTriggerInternalFail) ||
+				(singlesEfficiencyFail && printSinglesTriggerEfficiencyFail) ||
+				(pairInternalFail && printPairTriggerInternalFail) ||
+				(pairEfficiencyFail && printPairTriggerEfficiencyFail)) {
+			OutputLogger.printLog();
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Process Local Tracked Variables =====================
+		// ==========================================================
+		if(Calendar.getInstance().getTimeInMillis() - localWindowStart > localWindowThreshold) {
+			// Write a snapshot of the driver to the event stream.
+			DiagSnapshot snapshot = new DiagSnapshot(clusterLocalStats, clusterRunStats,
+					triggerLocalStats[0], triggerRunStats[0], triggerLocalStats[1],
+					triggerRunStats[1], efficiencyRunStats, efficiencyLocalStats);
+			
+			// Push the snapshot to the data stream.
+			List<DiagSnapshot> snapshotCollection = new ArrayList<DiagSnapshot>(1);
+			snapshotCollection.add(snapshot);
+			event.put(diagnosticCollectionName, snapshotCollection);
+			
+			// Clear the local statistical data.
+			clusterLocalStats.clear();
+			triggerLocalStats[0].clear();
+			triggerLocalStats[1].clear();
+			efficiencyLocalStats.clear();
+			
+			// Update the last write time.
+			localWindowStart = Calendar.getInstance().getTimeInMillis();
+		}
+	}
+	
+	public void setPrintOnClusterFailure(boolean state) {
+		printClusterFail = state;
+	}
+	
+	public void setPrintOnSinglesEfficiencyFailure(boolean state) {
+		printSinglesTriggerEfficiencyFail = state;
+	}
+	
+	public void setPrintOnSinglesSSPFailure(boolean state) {
+		printSinglesTriggerInternalFail = state;
+	}
+	
+	public void setPrintOnPairEfficiencyFailure(boolean state) {
+		printPairTriggerEfficiencyFail = state;
+	}
+	
+	public void setPrintOnPairSSPFailure(boolean state) {
+		printPairTriggerInternalFail = state;
+	}
+	
+	public void setVerbose(boolean state) {
+		verbose = state;
+	}
+	
+	public void setHitCollectionName(String hitCollectionName) {
+		this.hitCollectionName = hitCollectionName;
+	}
+	
+	public void setClusterCollectionName(String clusterCollectionName) {
+		this.clusterCollectionName = clusterCollectionName;
+	}
+	
+	public void setBankCollectionName(String bankCollectionName) {
+		this.bankCollectionName = bankCollectionName;
+	}
+	
+	public void setNoiseThresholdCount(int noiseHits) {
+		noiseThreshold = noiseHits;
+	}
+	
+	public void setHitAcceptanceWindow(int window) {
+		hitAcceptance = window;
+	}
+	
+	public void setEnergyAcceptanceWindow(double window) {
+		energyAcceptance = window;
+	}
+	
+	public void setReadDAQConfig(boolean state) {
+		readDAQConfig = state;
+	}
+	
+	/**
+	 * Attempts to match all reconstructed clusters that are safely
+	 * within the integration window with clusters reported by the SSP.
+	 * Method also tracks the ratio of valid reconstructed clusters to
+	 * matches found.<br/>
+	 * <br/>
+	 * Note that unmatched SSP clusters are ignored. Since these may
+	 * or may not correspond to reconstructed clusters that occur in
+	 * the forbidden time region, it is impossible to say whether or
+	 * not these legitimately failed to match or not.
+	 */
+	private void clusterVerification() {
+		// ==========================================================
+		// ==== Initialize Cluster Verification =====================
+		// ==========================================================
+		
+		// Print the cluster verification header.
+		OutputLogger.printNewLine(2);
+		OutputLogger.println("======================================================================");
+		OutputLogger.println("=== Cluster Verification =============================================");
+		OutputLogger.println("======================================================================");
+		
+		// Track the number of cluster pairs that were matched and that
+		// failed by failure type.
+		ClusterMatchEvent event = new ClusterMatchEvent();
+		
+		
+		
+		// ==========================================================
+		// ==== Produce the Cluster Position Mappings ===============
+		// ==========================================================
+		
+		// Create maps to link cluster position to the list of clusters
+		// that were found at that location.
+		Map<Point, List<Cluster>> reconClusterMap = new HashMap<Point, List<Cluster>>(reconClusters.size());
+		Map<Point, List<SSPCluster>> sspClusterMap = new HashMap<Point, List<SSPCluster>>(reconClusters.size());
+		
+		// Populate the reconstructed cluster map.
+		for(Cluster reconCluster : reconClusters) {
+			// Get the cluster position.
+			Point position = new Point(TriggerDiagnosticUtil.getXIndex(reconCluster),
+					TriggerDiagnosticUtil.getYIndex(reconCluster));
+			
+			// Get the list for this cluster position.
+			List<Cluster> reconList = reconClusterMap.get(position);
+			if(reconList == null) {
+				reconList = new ArrayList<Cluster>();
+				reconClusterMap.put(position, reconList);
+			}
+			
+			// Add the cluster to the list.
+			reconList.add(reconCluster);
+		}
+		
+		// Populate the SSP cluster map.
+		for(SSPCluster sspCluster : sspClusters) {
+			// Get the cluster position.
+			Point position = new Point(sspCluster.getXIndex(), sspCluster.getYIndex());
+			
+			// Get the list for this cluster position.
+			List<SSPCluster> sspList = sspClusterMap.get(position);
+			if(sspList == null) {
+				sspList = new ArrayList<SSPCluster>();
+				sspClusterMap.put(position, sspList);
+			}
+			
+			// Add the cluster to the list.
+			sspList.add(sspCluster);
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Perform Cluster Matching ============================
+		// ==========================================================
+		
+		// For each reconstructed cluster, attempt to match the clusters
+		// with SSP clusters at the same position.
+		positionLoop:
+		for(Entry<Point, List<Cluster>> clusterSet : reconClusterMap.entrySet()) {
+			// Get the reconstructed and SSP clusters at this position.
+			List<Cluster> reconList = clusterSet.getValue();
+			List<SSPCluster> sspList = sspClusterMap.get(clusterSet.getKey());
+			
+			// Print the crystal position header.
+			OutputLogger.println();
+			OutputLogger.printf("Considering clusters at (%3d, %3d)%n", clusterSet.getKey().x, clusterSet.getKey().y);
+			
+			// If there are no SSP clusters, then matching fails by
+			// reason of position. The remainder of the loop may be
+			// skipped, since there is nothing to check.
+			if(sspList == null || sspList.isEmpty()) {
+				clusterFail = true;
+				for(Cluster cluster : reconList) {
+					event.pairFailPosition(cluster, null);
+				}
+				continue positionLoop;
+			}
+			
+			// If there are more reconstructed clusters than there are
+			// SSP clusters, than a number equal to the difference must
+			// fail by means of positions.
+			if(sspList.size() < reconList.size()) { clusterFail = true; }
+			
+			// Get all possible permutations of SSP clusters.
+			List<List<Pair<Cluster, SSPCluster>>> permutations = getPermutations(reconList, sspList);
+			
+			// Print the information for this crystal position.
+			OutputLogger.printf("\tRecon Clusters :: %d%n", reconList.size());
+			OutputLogger.printf("\tSSP Clusters   :: %d%n", sspList.size());
+			OutputLogger.printf("\tPermutations   :: %d%n", permutations.size());
+			
+			// Track the plotted values for the current best permutation.
+			ClusterMatchEvent bestPerm = null;
+			
+			// Iterate over the permutations and find the permutation
+			// that produces the best possible result when compared to
+			// the reconstructed clusters.
+			int permIndex = 0;
+			for(List<Pair<Cluster, SSPCluster>> pairs : permutations) {
+				// Update the current permutation number.
+				permIndex++;
+				
+				// Track the plot values for this permutation.
+				ClusterMatchEvent perm = new ClusterMatchEvent();
+				
+				// Try to match each pair.
+				pairLoop:
+				for(Pair<Cluster, SSPCluster> pair : pairs) {
+					// Print the current reconstructed/SSP cluster pair.
+					OutputLogger.printf("\tP%d :: %s --> %s", permIndex,
+							pair.getFirstElement() == null ? "None" : TriggerDiagnosticUtil.clusterToString(pair.getFirstElement()),
+							pair.getSecondElement() == null ? "None" : TriggerDiagnosticUtil.clusterToString(pair.getSecondElement()));
+					
+					// If either cluster in the pair is null, there
+					// are not enough clusters to perform this match.
+					if(pair.getFirstElement() == null || pair.getSecondElement() == null) {
+						OutputLogger.printf(" [ %18s ]%n", "failure: unpaired");
+						perm.pairFailPosition(pair.getFirstElement(), pair.getSecondElement());
+						continue pairLoop;
+					}
+					
+					// Check if the reconstructed cluster has an energy
+					// within the allotted threshold of the SSP cluster.
+					if(pair.getSecondElement().getEnergy() >= pair.getFirstElement().getEnergy() * (1 - energyAcceptance) &&
+							pair.getSecondElement().getEnergy() <= pair.getFirstElement().getEnergy() * (1 + energyAcceptance)) {
+						// Check that the hit count of the reconstructed
+						// is within the allotted threshold of the SSP
+						// cluster.
+						if(pair.getSecondElement().getHitCount() >= pair.getFirstElement().getCalorimeterHits().size() - hitAcceptance &&
+								pair.getSecondElement().getHitCount() <= pair.getFirstElement().getCalorimeterHits().size() + hitAcceptance) {
+							// Designate the pair as a match.
+							perm.pairMatch(pair.getFirstElement(), pair.getSecondElement());
+							OutputLogger.printf(" [ %18s ]%n", "success: matched");
+						} else {
+							perm.pairFailHitCount(pair.getFirstElement(), pair.getSecondElement());
+							OutputLogger.printf(" [ %18s ]%n", "failure: hit count");
+						} // End hit count check.
+					} else {
+						perm.pairFailEnergy(pair.getFirstElement(), pair.getSecondElement());
+						OutputLogger.printf(" [ %18s ]%n", "failure: energy");
+					} // End energy check.
+				} // End Pair Loop
+				
+				// Print the results of the permutation.
+				OutputLogger.printf("\t\tPermutation Matched   :: %d%n", perm.getMatches());
+				OutputLogger.printf("\t\tPermutation Energy    :: %d%n", perm.getEnergyFailures());
+				OutputLogger.printf("\t\tPermutation Hit Count :: %d%n", perm.getHitCountFailures());
+				
+				// Check whether the results from this permutation
+				// exceed the quality of the last best results. A
+				// greater number of matches is always better. If the
+				// matches are the same, select the one with fewer
+				// failures due to energy.
+				bestPerm = getBestPermutation(bestPerm, perm);
+			} // End Permutation Loop
+			
+			// Print the final results for the position.
+			OutputLogger.printf("\tPosition Matched   :: %d%n", bestPerm.getMatches());
+			OutputLogger.printf("\tPosition Energy    :: %d%n", bestPerm.getEnergyFailures());
+			OutputLogger.printf("\tPosition Hit Count :: %d%n", bestPerm.getHitCountFailures());
+			
+			// Add the results from the best-matched permutation
+			// to the event efficiency results.
+			event.addEvent(bestPerm);
+		} // End Crystal Position Loop
+		
+		// Add the event results to the global results.
+		clusterRunStats.addEvent(event, reconClusters, sspClusters);
+		clusterLocalStats.addEvent(event, reconClusters, sspClusters);
+		
+		
+		
+		// ==========================================================
+		// ==== Output Event Summary ================================
+		// ==========================================================
+		
+		// Print the valid reconstructed clusters and populate their
+		// distribution graphs.
+		OutputLogger.println();
+		OutputLogger.println("Verified Reconstructed Clusters:");
+		if(!reconClusters.isEmpty()) {
+			for(Cluster reconCluster : reconClusters) {
+				OutputLogger.printf("\t%s%n", TriggerDiagnosticUtil.clusterToString(reconCluster));
+			}
+		} else { OutputLogger.println("\tNone"); }
+		
+		// Print the SSP clusters and populate their distribution graphs.
+		OutputLogger.println("SSP Clusters:");
+		if(!sspClusters.isEmpty()) {
+			for(SSPCluster sspCluster : sspClusters) {
+				OutputLogger.printf("\t%s%n", TriggerDiagnosticUtil.clusterToString(sspCluster));
+			}
+		} else { OutputLogger.println("\tNone"); }
+		
+		// Print the matched clusters.
+		OutputLogger.println("Matched Clusters:");
+		if(event.getMatchedPairs().size() != 0) {
+			// Iterate over the matched pairs.
+			for(ClusterMatchedPair pair : event.getMatchedPairs()) {
+				// If the pair is a match, print it out.
+				if(pair.isMatch()) {
+					OutputLogger.printf("\t%s --> %s%n",
+							TriggerDiagnosticUtil.clusterToString(pair.getReconstructedCluster()),
+							TriggerDiagnosticUtil.clusterToString(pair.getSSPCluster()));
+				}
+			}
+		}
+		 else { OutputLogger.println("\tNone"); }
+		
+		// Get the number of position failures.
+		int failPosition = event.getPositionFailures();
+		if(sspClusters == null || sspClusters.isEmpty()) {
+			failPosition = (reconClusters == null ? 0 : reconClusters.size());
+		}
+		
+		// Print event statistics.
+		OutputLogger.println();
+		OutputLogger.println("Event Statistics:");
+		OutputLogger.printf("\tRecon Clusters     :: %d%n", reconClusters.size());
+		OutputLogger.printf("\tClusters Matched   :: %d%n", event.getMatches());
+		OutputLogger.printf("\tFailed (Position)  :: %d%n", failPosition);
+		OutputLogger.printf("\tFailed (Energy)    :: %d%n", event.getEnergyFailures());
+		OutputLogger.printf("\tFailed (Hit Count) :: %d%n", event.getHitCountFailures());
+		OutputLogger.printf("\tCluster Efficiency :: %3.0f%%%n", 100.0 * event.getMatches() / reconClusters.size());
+		
+		// Note whether there was a cluster match failure.
+		if(event.getMatches() - reconClusters.size() != 0) {
+			clusterFail = true;
+		}
+	}
+	
+	/**
+	 * Checks triggers simulated on SSP clusters against the SSP bank's
+	 * reported triggers to verify that the trigger is correctly applying
+	 * cuts to the clusters it sees. Additionally compares triggers
+	 * simulated on reconstructed clusters to measure trigger efficiency.
+	 */
+	private void singlesTriggerVerification() {
+		// Create lists of generic triggers.
+		List<List<? extends Trigger<?>>> sspTriggerList = new ArrayList<List<? extends Trigger<?>>>(2);
+		List<List<? extends Trigger<?>>> reconTriggerList = new ArrayList<List<? extends Trigger<?>>>(2);
+		
+		// Convert the simulated triggers to generic versions and add
+		// them to the generic list.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			// Get the generic trigger list.
+			List<? extends Trigger<?>> sspTriggers = sspSinglesTriggers.get(triggerNum);
+			List<? extends Trigger<?>> reconTriggers = reconSinglesTriggers.get(triggerNum);
+			
+			// Add it to the generic list.
+			sspTriggerList.add(sspTriggers);
+			reconTriggerList.add(reconTriggers);
+		}
+		
+		// Run generic trigger verification.
+		triggerVerification(sspTriggerList, reconTriggerList, true);
+	}
+	
+	/**
+	 * Checks triggers simulated on SSP clusters against the SSP bank's
+	 * reported triggers to verify that the trigger is correctly applying
+	 * cuts to the clusters it sees. Additionally compares triggers
+	 * simulated on reconstructed clusters to measure trigger efficiency.
+	 */
+	private void pairTriggerVerification() {
+		// Create lists of generic triggers.
+		List<List<? extends Trigger<?>>> sspTriggerList = new ArrayList<List<? extends Trigger<?>>>(2);
+		List<List<? extends Trigger<?>>> reconTriggerList = new ArrayList<List<? extends Trigger<?>>>(2);
+		
+		// Convert the simulated triggers to generic versions and add
+		// them to the generic list.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			// Get the generic trigger list.
+			List<? extends Trigger<?>> sspTriggers = sspPairsTriggers.get(triggerNum);
+			List<? extends Trigger<?>> reconTriggers = reconPairsTriggers.get(triggerNum);
+			
+			// Add it to the generic list.
+			sspTriggerList.add(sspTriggers);
+			reconTriggerList.add(reconTriggers);
+		}
+		
+		// Run generic trigger verification.
+		triggerVerification(sspTriggerList, reconTriggerList, false);
+	}
+	
+	/**
+	 * Performs trigger verification for both trigger types.
+	 * @param sspTriggerList - The list of SSP triggers.
+	 * @param reconTriggerList - The list of reconstructed triggers.
+	 * @param isSingles - Whether or not this is a singles trigger
+	 * verification.
+	 */
+	private void triggerVerification(List<List<? extends Trigger<?>>> sspTriggerList, 
+			List<List<? extends Trigger<?>>> reconTriggerList, boolean isSingles) {
+		
+		// ==========================================================
+		// ==== Initialize Trigger Verification =====================
+		// ==========================================================
+		
+		// Print the cluster verification header.
+		OutputLogger.println();
+		OutputLogger.println();
+		OutputLogger.println("======================================================================");
+		if(isSingles) { OutputLogger.println("=== Singles Trigger Verification ====================================="); }
+		else { OutputLogger.println("=== Pair Trigger Verification ========================================"); }
+		OutputLogger.println("======================================================================");
+		
+		// Track the number of triggers seen and the number found.
+		TriggerMatchEvent event = new TriggerMatchEvent();
+		
+		// ==========================================================
+		// ==== Output Event Summary ================================
+		// ==========================================================
+		
+		// Get the list of triggers reported by the SSP.
+		List<? extends SSPNumberedTrigger> sspTriggers;
+		if(isSingles) { sspTriggers = sspBank.getSinglesTriggers(); }
+		else { sspTriggers = sspBank.getPairTriggers(); }
+		
+		// Output the SSP cluster singles triggers.
+		OutputLogger.println();
+		OutputLogger.println("SSP Cluster " + (isSingles ? "Singles" : "Pair") + " Triggers");
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			for(Trigger<?> simTrigger : sspTriggerList.get(triggerNum)) {
+				OutputLogger.printf("\tTrigger %d :: %s :: %s%n",
+						(triggerNum + 1), triggerPositionString(simTrigger),
+						simTrigger.toString());
+			}
+		}
+		if(sspTriggerList.get(0).size() + sspTriggerList.get(1).size() == 0) {
+			OutputLogger.println("\tNone");
+		}
+		
+		// Output the reconstructed cluster singles triggers.
+		OutputLogger.println("Reconstructed Cluster " + (isSingles ? "Singles" : "Pair") + " Triggers");
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			for(Trigger<?> simTrigger : reconTriggerList.get(triggerNum)) {
+				OutputLogger.printf("\tTrigger %d :: %s :: %s%n",
+						(triggerNum + 1), triggerPositionString(simTrigger),
+						simTrigger.toString());
+			}
+		}
+		if(reconTriggerList.get(0).size() + reconTriggerList.get(1).size() == 0) {
+			OutputLogger.println("\tNone");
+		}
+		
+		// Output the SSP reported triggers.
+		OutputLogger.println("SSP Reported " + (isSingles ? "Singles" : "Pair") + " Triggers");
+		for(SSPTrigger sspTrigger : sspTriggers) {
+			OutputLogger.printf("\t%s%n", sspTrigger.toString());
+		}
+		if(sspTriggers.size() == 0) { OutputLogger.println("\tNone"); }
+		
+		
+		
+		// ==========================================================
+		// ==== SSP Internal Logic Verification =====================
+		// ==========================================================
+		
+		// Track which SSP triggers have been matched to avoid matching
+		// multiple reconstructed SSP cluster triggers to the same SSP
+		// trigger.
+		Set<SSPNumberedTrigger> sspTriggerSet = new HashSet<SSPNumberedTrigger>();
+		Set<Trigger<?>> simTriggerSet = new HashSet<Trigger<?>>();
+		
+		// Track the number of SSP reported triggers that are found in
+		// excess of the SSP simulated triggers.
+		int sspReportedExtras = sspTriggers.size() - (sspTriggerList.get(0).size() + sspTriggerList.get(1).size());
+		if(sspReportedExtras > 0) {
+			if(isSingles) { singlesInternalFail = true; }
+			else { pairInternalFail = true; }
+		}
+		
+		// Iterate over the triggers.
+		OutputLogger.println();
+		OutputLogger.println("SSP Reported Trigger --> SSP Cluster Trigger Match Status");
+		for(SSPNumberedTrigger sspTrigger : sspTriggers) {
+			// Get the trigger information.
+			int triggerNum = sspTrigger.isFirstTrigger() ? 0 : 1;
+			boolean matchedTrigger = false;
+			
+			// Iterate over the SSP cluster simulated triggers and
+			// look for a trigger that matches.
+			matchLoop:
+			for(Trigger<?> simTrigger : sspTriggerList.get(triggerNum)) {
+				// If the current SSP trigger has already been
+				// matched, skip it.
+				if(sspTriggerSet.contains(sspTrigger)) { continue matchLoop; }
+				
+				// Otherwise, check whether the reconstructed SSP
+				// cluster trigger matches the SSP trigger.
+				if(compareTriggers(sspTrigger, simTrigger)) {
+					matchedTrigger = true;
+					sspTriggerSet.add(sspTrigger);
+					simTriggerSet.add(simTrigger);
+					event.matchedSSPPair(simTrigger, sspTrigger);
+					break matchLoop;
+				}
+				
+				OutputLogger.printf("\t%s :: Matched: %5b%n", sspTrigger.toString(), matchedTrigger);
+			}
+		}
+		
+		// Iterate over the unmatched simulated triggers again and the
+		// unmatched SSP reported trigger that most closely matches it.
+		simLoop:
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			for(Trigger<?> simTrigger : sspTriggerList.get(triggerNum)) {
+				// Check whether this trigger has already been matched
+				// or not. If it has been matched, skip it.
+				if(simTriggerSet.contains(simTrigger)) { continue simLoop; }
+				
+				// Get the trigger time for the simulated trigger.
+				double simTime = getTriggerTime(simTrigger);
+				
+				// Track the match statistics for each reported trigger
+				// so that the closest match may be found.
+				int numMatched = -1;
+				boolean[] matchedCut = null;
+				SSPNumberedTrigger bestMatch = null;
+				
+				// Iterate over the reported triggers to find a match.
+				reportedLoop:
+				for(SSPNumberedTrigger sspTrigger : sspTriggers) {
+					// If the two triggers have different times, this
+					// trigger should be skipped.
+					if(sspTrigger.getTime() != simTime) {
+						continue reportedLoop;
+					}
+					
+					// If this reported trigger has been matched then
+					// it should be skipped.
+					if(sspTriggerSet.contains(sspTrigger)) { continue reportedLoop; }
+					
+					// Check each of the cuts.
+					boolean[] tempMatchedCut = triggerCutMatch(simTrigger, sspTrigger);
+					
+					// Check each cut and see if this is a closer match
+					// than the previous best match.
+					int tempNumMatched = 0;
+					for(boolean passed : tempMatchedCut) { if(passed) { tempNumMatched++; } }
+					
+					// If the number of matched cuts exceeds the old
+					// best result, this becomes the new best result.
+					if(tempNumMatched > numMatched) {
+						numMatched = tempNumMatched;
+						matchedCut = tempMatchedCut;
+						bestMatch = sspTrigger;
+					}
+				}
+				
+				// If there was no match found, it means that there were
+				// no triggers that were both unmatched and at the same
+				// time as this simulated trigger.
+				
+				if(bestMatch == null) {
+					if(isSingles) { singlesInternalFail = true; }
+					else { pairInternalFail = true; }
+				} else {
+					event.matchedSSPPair(simTrigger, bestMatch, matchedCut);
+				}
+			}
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Trigger Efficiency ==================================
+		// ==========================================================
+		
+		// Reset the SSP matched trigger set.
+		sspTriggerSet.clear();
+		
+		// Iterate over the reconstructed cluster singles triggers.
+		OutputLogger.println();
+		OutputLogger.println("Recon Cluster Trigger --> SSP Reported Trigger Match Status");
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			for(Trigger<?> simTrigger : reconTriggerList.get(triggerNum)) {
+				
+				OutputLogger.printf("\tTrigger %d :: %s :: %s%n", (triggerNum + 1),
+						triggerPositionString(simTrigger), simTrigger.toString());
+				
+				// Iterate over the SSP reported triggers and compare
+				// them to the reconstructed cluster simulated trigger.
+				matchLoop:
+				for(SSPNumberedTrigger sspTrigger : sspTriggers) {
+					OutputLogger.printf("\t\t\t%s", sspTrigger.toString());
+					
+					// Only compare triggers if they are from the
+					// same trigger source.
+					if((triggerNum == 0 && sspTrigger.isSecondTrigger())
+							|| (triggerNum == 1 && sspTrigger.isFirstTrigger())) {
+						OutputLogger.print(" [ fail; source    ]%n");
+						continue matchLoop;
+					}
+					
+					// Only compare the singles trigger if it was
+					// not already matched to another trigger.
+					if(sspTriggerSet.contains(sspTrigger)) {
+						OutputLogger.print(" [ fail; matched   ]%n");
+						continue matchLoop;
+					}
+					
+					// Test each cut.
+					String[][] cutNames = {
+							{ "E_min", "E_max", "hit count", "null" },
+							{ "E_sum", "E_diff", "E_slope", "coplanar" }
+					};
+					int typeIndex = isSingles ? 0 : 1;
+					boolean[] matchedCuts = triggerCutMatch(simTrigger, sspTrigger);
+					for(int cutIndex = 0; cutIndex < matchedCuts.length; cutIndex++) {
+						if(!matchedCuts[cutIndex]) {
+							OutputLogger.printf(" [ fail; %-9s ]%n", cutNames[typeIndex][cutIndex]);
+							continue matchLoop;
+						}
+					}
+					
+					// If all the trigger flags match, then the
+					// triggers are a match.
+					sspTriggerSet.add(sspTrigger);
+					event.matchedReconPair(simTrigger, sspTrigger);
+					OutputLogger.print(" [ success         ]%n");
+					break matchLoop;
+				}
+			}
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Output Event Results ================================
+		// ==========================================================
+		
+		// Get the number of SSP and reconstructed cluster simulated
+		// triggers.
+		
+		int sspSimTriggers = sspTriggerList.get(0).size() + sspTriggerList.get(1).size();
+		int reconSimTriggers = reconTriggerList.get(0).size() + reconTriggerList.get(1).size();
+		int halfSimTriggers = sspSimTriggers / 2;
+		
+		// Print event statistics.
+		OutputLogger.println();
+		OutputLogger.println("Event Statistics:");
+		OutputLogger.printf("\tSSP Cluster Sim Triggers   :: %d%n", sspSimTriggers);
+		OutputLogger.printf("\tRecon Cluster Sim Triggers :: %d%n", reconSimTriggers);
+		OutputLogger.printf("\tSSP Reported Triggers      :: %d%n", sspTriggers.size());
+		if(sspSimTriggers == 0) {
+			OutputLogger.printf("\tInternal Efficiency        :: %d / %d (N/A)%n",
+					event.getMatchedSSPTriggers(), sspSimTriggers);
+		} else {
+			OutputLogger.printf("\tInternal Efficiency        :: %d / %d (%3.0f%%)%n",
+					event.getMatchedSSPTriggers(), sspSimTriggers, (100.0 * event.getMatchedSSPTriggers() / sspSimTriggers));
+		}
+		if(reconSimTriggers == 0) {
+			OutputLogger.printf("\tTrigger Efficiency         :: %d / %d (N/A)%n",
+					event.getMatchedReconTriggers(), reconSimTriggers);
+		} else {
+			OutputLogger.printf("\tTrigger Efficiency         :: %d / %d (%3.0f%%)%n",
+					event.getMatchedReconTriggers(), reconSimTriggers, (100.0 * event.getMatchedReconTriggers() / reconSimTriggers));
+		}
+		
+		// Print the individual cut performances.
+		if(isSingles) {
+			OutputLogger.println();
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				OutputLogger.printf("Trigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
+				if(sspSimTriggers == 0) {
+					OutputLogger.printf("\tCluster Energy Lower Bound :: %d / %d%n", event.getCutFailures(triggerNum, ENERGY_MIN), halfSimTriggers);
+					OutputLogger.printf("\tCluster Energy Upper Bound :: %d / %d%n", event.getCutFailures(triggerNum, ENERGY_MAX), halfSimTriggers);
+					OutputLogger.printf("\tCluster Hit Count          :: %d / %d%n", event.getCutFailures(triggerNum, HIT_COUNT), halfSimTriggers);
+				} else {
+					OutputLogger.printf("\tCluster Energy Lower Bound :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, ENERGY_MIN), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, ENERGY_MIN) / halfSimTriggers));
+					OutputLogger.printf("\tCluster Energy Upper Bound :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, ENERGY_MAX), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, ENERGY_MAX) / halfSimTriggers));
+					OutputLogger.printf("\tCluster Hit Count          :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, HIT_COUNT), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, HIT_COUNT) / halfSimTriggers));
+				}
+				OutputLogger.printf("\tExcess Reported Triggers   :: %d%n", sspReportedExtras / 2);
+			}
+			
+			// Update the global trigger tracking variables.
+			triggerRunStats[0].addEvent(event, reconTriggerList, sspTriggerList, sspTriggers);
+			triggerLocalStats[0].addEvent(event, reconTriggerList, sspTriggerList, sspTriggers);
+			efficiencyRunStats.addSinglesTriggers(activeTrigger, reconTriggerList);
+			efficiencyLocalStats.addSinglesTriggers(activeTrigger, reconTriggerList);
+			efficiencyRunStats.addEvent(activeTrigger, event);
+			efficiencyLocalStats.addEvent(activeTrigger, event);
+		} else {
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				OutputLogger.println();
+				OutputLogger.printf("Trigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
+				if(sspSimTriggers == 0) {
+					OutputLogger.printf("\tPair Energy Sum            :: %d / %d%n", event.getCutFailures(triggerNum, ENERGY_SUM), halfSimTriggers);
+					OutputLogger.printf("\tPair Energy Difference     :: %d / %d%n", event.getCutFailures(triggerNum, ENERGY_DIFF), halfSimTriggers);
+					OutputLogger.printf("\tPair Energy Slope          :: %d / %d%n", event.getCutFailures(triggerNum, ENERGY_SLOPE), halfSimTriggers);
+					OutputLogger.printf("\tPair Coplanarity           :: %d / %d%n", event.getCutFailures(triggerNum, COPLANARITY), halfSimTriggers);
+				} else {
+					OutputLogger.printf("\tPair Energy Sum            :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, ENERGY_SUM), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, ENERGY_SUM) / halfSimTriggers));
+					OutputLogger.printf("\tPair Energy Difference     :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, ENERGY_DIFF), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, ENERGY_DIFF) / halfSimTriggers));
+					OutputLogger.printf("\tPair Energy Slope          :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, ENERGY_SLOPE), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, ENERGY_SLOPE) / halfSimTriggers));
+					OutputLogger.printf("\tPair Coplanarity           :: %d / %d (%3.0f%%)%n",
+							event.getCutFailures(triggerNum, COPLANARITY), halfSimTriggers, (100.0 * event.getCutFailures(triggerNum, COPLANARITY) / halfSimTriggers));
+				}
+				OutputLogger.printf("\tExcess Reported Triggers   :: %d%n", sspReportedExtras / 2);
+			}
+			
+			// Update the global trigger tracking variables.
+			triggerRunStats[1].addEvent(event, reconTriggerList, sspTriggerList, sspTriggers);
+			triggerLocalStats[1].addEvent(event, reconTriggerList, sspTriggerList, sspTriggers);
+			efficiencyRunStats.addPairTriggers(activeTrigger, reconTriggerList);
+			efficiencyLocalStats.addSinglesTriggers(activeTrigger, reconTriggerList);
+			efficiencyRunStats.addEvent(activeTrigger, event);
+			efficiencyLocalStats.addEvent(activeTrigger, event);
+		}
+		
+		// Note whether the was a trigger match failure.
+		if((event.getMatchedReconTriggers() - reconSimTriggers != 0) || (event.getMatchedSSPTriggers() - sspSimTriggers != 0)) {
+			if(isSingles) { singlesEfficiencyFail = true; }
+			else { pairEfficiencyFail = true; }
+		}
+	}
+	
+	/**
+	 * Generates and stores the singles triggers for both reconstructed
+	 * and SSP clusters.
+	 */
+	private void constructSinglesTriggers() {
+		// Run the SSP clusters through the singles trigger to determine
+		// whether they pass it or not.
+		for(SSPCluster cluster : sspClusters) {
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				// For a cluster to have formed it is assumed to have passed
+				// the cluster seed energy cuts. This can not be verified
+				// since the SSP bank does not report individual hit. 
+				boolean passSeedLow = true;
+				boolean passSeedHigh = true;
+				
+				// The remaining cuts may be acquired from trigger module.
+				boolean passClusterLow = singlesTrigger[triggerNum].clusterTotalEnergyCutLow(cluster);
+				boolean passClusterHigh = singlesTrigger[triggerNum].clusterTotalEnergyCutHigh(cluster);
+				boolean passHitCount = singlesTrigger[triggerNum].clusterHitCountCut(cluster);
+				
+				// Make a trigger to store the results.
+				SinglesTrigger<SSPCluster> trigger = new SinglesTrigger<SSPCluster>(cluster, triggerNum);
+				trigger.setStateSeedEnergyLow(passSeedLow);
+				trigger.setStateSeedEnergyHigh(passSeedHigh);
+				trigger.setStateClusterEnergyLow(passClusterLow);
+				trigger.setStateClusterEnergyHigh(passClusterHigh);
+				trigger.setStateHitCount(passHitCount);
+				
+				// Store the trigger.
+				sspSinglesTriggers.get(triggerNum).add(trigger);
+			}
+		}
+		
+		// Run the reconstructed clusters through the singles trigger
+		// to determine whether they pass it or not.
+		for(Cluster cluster : reconClusters) {
+			// Simulate each of the cluster singles triggers.
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				// For a cluster to have formed it is assumed to have passed
+				// the cluster seed energy cuts. This can not be verified
+				// since the SSP bank does not report individual hit. 
+				boolean passSeedLow = true;
+				boolean passSeedHigh = true;
+				
+				// The remaining cuts may be acquired from trigger module.
+				boolean passClusterLow = singlesTrigger[triggerNum].clusterTotalEnergyCutLow(cluster);
+				boolean passClusterHigh = singlesTrigger[triggerNum].clusterTotalEnergyCutHigh(cluster);
+				boolean passHitCount = singlesTrigger[triggerNum].clusterHitCountCut(cluster);
+				
+				// Make a trigger to store the results.
+				SinglesTrigger<Cluster> trigger = new SinglesTrigger<Cluster>(cluster, triggerNum);
+				trigger.setStateSeedEnergyLow(passSeedLow);
+				trigger.setStateSeedEnergyHigh(passSeedHigh);
+				trigger.setStateClusterEnergyLow(passClusterLow);
+				trigger.setStateClusterEnergyHigh(passClusterHigh);
+				trigger.setStateHitCount(passHitCount);
+				
+				// Store the trigger.
+				reconSinglesTriggers.get(triggerNum).add(trigger);
+			}
+		}
+	}
+	
+	/**
+	 * Generates and stores the pair triggers for both reconstructed
+	 * and SSP clusters.
+	 */
+	private void constructPairTriggers() {
+		// Store cluster pairs.
+		List<Cluster> topReconClusters = new ArrayList<Cluster>();
+		List<Cluster> bottomReconClusters = new ArrayList<Cluster>();
+		List<Cluster[]> reconPairs = new ArrayList<Cluster[]>();
+		List<SSPCluster> topSSPClusters = new ArrayList<SSPCluster>();
+		List<SSPCluster> bottomSSPClusters = new ArrayList<SSPCluster>();
+		List<SSPCluster[]> sspPairs = new ArrayList<SSPCluster[]>();
+		
+		// Split the clusters into lists of top and bottom clusters.
+		for(Cluster reconCluster : reconClusters) {
+			if(reconCluster.getCalorimeterHits().get(0).getIdentifierFieldValue("iy") > 0) {
+				topReconClusters.add(reconCluster);
+			} else {
+				bottomReconClusters.add(reconCluster);
+			}
+		}
+		for(SSPCluster sspCluster : sspClusters) {
+			if(sspCluster.getYIndex() > 0) {
+				topSSPClusters.add(sspCluster);
+			} else {
+				bottomSSPClusters.add(sspCluster);
+			}
+		}
+		
+		// Form all possible top/bottom cluster pairs.
+		for(Cluster topReconCluster : topReconClusters) {
+			for(Cluster bottomReconCluster : bottomReconClusters) {
+				Cluster[] reconPair = new Cluster[2];
+				reconPair[0] = topReconCluster;
+				reconPair[1] = bottomReconCluster;
+				reconPairs.add(reconPair);
+			}
+		}
+		for(SSPCluster topSSPCluster : topSSPClusters) {
+			for(SSPCluster bottomSSPCluster : bottomSSPClusters) {
+				SSPCluster[] sspPair = new SSPCluster[2];
+				sspPair[0] = topSSPCluster;
+				sspPair[1] = bottomSSPCluster;
+				sspPairs.add(sspPair);
+			}
+		}
+		
+		// Simulate the pair triggers and record the results.
+		for(Cluster[] reconPair : reconPairs) {
+			// Simulate each of the cluster pair triggers.
+			reconTriggerLoop:
+			for(int triggerIndex = 0; triggerIndex < 2; triggerIndex++) {
+				// Check that the pair passes the time coincidence cut.
+				// If it does not, it is not a valid pair and should be
+				// destroyed.
+				if(!pairsTrigger[triggerIndex].pairTimeCoincidenceCut(reconPair)) {
+					continue reconTriggerLoop;
+				}
+				
+				// For a cluster to have formed it is assumed to have passed
+				// the cluster seed energy cuts. This can not be verified
+				// since the SSP bank does not report individual hit. 
+				boolean passSeedLow = true;
+				boolean passSeedHigh = true;
+				
+				// The remaining cuts may be acquired from trigger module.
+				boolean passClusterLow = pairsTrigger[triggerIndex].clusterTotalEnergyCutLow(reconPair[0])
+						&& pairsTrigger[triggerIndex].clusterTotalEnergyCutLow(reconPair[1]);
+				boolean passClusterHigh = pairsTrigger[triggerIndex].clusterTotalEnergyCutHigh(reconPair[0])
+						&& pairsTrigger[triggerIndex].clusterTotalEnergyCutHigh(reconPair[1]);
+				boolean passHitCount = pairsTrigger[triggerIndex].clusterHitCountCut(reconPair[0])
+						&& pairsTrigger[triggerIndex].clusterHitCountCut(reconPair[1]);
+				boolean passPairEnergySumLow = pairsTrigger[triggerIndex].pairEnergySumCutLow(reconPair);
+				boolean passPairEnergySumHigh = pairsTrigger[triggerIndex].pairEnergySumCutHigh(reconPair);
+				boolean passPairEnergyDifference = pairsTrigger[triggerIndex].pairEnergyDifferenceCut(reconPair);
+				boolean passPairEnergySlope = pairsTrigger[triggerIndex].pairEnergySlopeCut(reconPair);
+				boolean passPairCoplanarity = pairsTrigger[triggerIndex].pairCoplanarityCut(reconPair);
+				boolean passTimeCoincidence = pairsTrigger[triggerIndex].pairTimeCoincidenceCut(reconPair);
+				
+				// Create a trigger from the results.
+				PairTrigger<Cluster[]> trigger = new PairTrigger<Cluster[]>(reconPair, triggerIndex);
+				trigger.setStateSeedEnergyLow(passSeedLow);
+				trigger.setStateSeedEnergyHigh(passSeedHigh);
+				trigger.setStateClusterEnergyLow(passClusterLow);
+				trigger.setStateClusterEnergyHigh(passClusterHigh);
+				trigger.setStateHitCount(passHitCount);
+				trigger.setStateEnergySumLow(passPairEnergySumLow);
+				trigger.setStateEnergySumHigh(passPairEnergySumHigh);
+				trigger.setStateEnergyDifference(passPairEnergyDifference);
+				trigger.setStateEnergySlope(passPairEnergySlope);
+				trigger.setStateCoplanarity(passPairCoplanarity);
+				trigger.setStateTimeCoincidence(passTimeCoincidence);
+				
+				// Add the trigger to the list.
+				reconPairsTriggers.get(triggerIndex).add(trigger);
+			}
+		}
+		
+		for(SSPCluster[] sspPair : sspPairs) {
+			pairTriggerLoop:
+			for(int triggerIndex = 0; triggerIndex < 2; triggerIndex++) {
+				// Check that the pair passes the time coincidence cut.
+				// If it does not, it is not a valid pair and should be
+				// destroyed.
+				if(!pairsTrigger[triggerIndex].pairTimeCoincidenceCut(sspPair)) {
+					continue pairTriggerLoop;
+				}
+				
+				// For a cluster to have formed it is assumed to have passed
+				// the cluster seed energy cuts. This can not be verified
+				// since the SSP bank does not report individual hit. 
+				boolean passSeedLow = true;
+				boolean passSeedHigh = true;
+				
+				// The remaining cuts may be acquired from trigger module.
+				boolean passClusterLow = pairsTrigger[triggerIndex].clusterTotalEnergyCutLow(sspPair[0])
+						&& pairsTrigger[triggerIndex].clusterTotalEnergyCutLow(sspPair[1]);
+				boolean passClusterHigh = pairsTrigger[triggerIndex].clusterTotalEnergyCutHigh(sspPair[0])
+						&& pairsTrigger[triggerIndex].clusterTotalEnergyCutHigh(sspPair[1]);
+				boolean passHitCount = pairsTrigger[triggerIndex].clusterHitCountCut(sspPair[0])
+						&& pairsTrigger[triggerIndex].clusterHitCountCut(sspPair[1]);
+				boolean passPairEnergySumLow = pairsTrigger[triggerIndex].pairEnergySumCutLow(sspPair);
+				boolean passPairEnergySumHigh = pairsTrigger[triggerIndex].pairEnergySumCutHigh(sspPair);
+				boolean passPairEnergyDifference = pairsTrigger[triggerIndex].pairEnergyDifferenceCut(sspPair);
+				boolean passPairEnergySlope = pairsTrigger[triggerIndex].pairEnergySlopeCut(sspPair);
+				boolean passPairCoplanarity = pairsTrigger[triggerIndex].pairCoplanarityCut(sspPair);
+				boolean passTimeCoincidence = pairsTrigger[triggerIndex].pairTimeCoincidenceCut(sspPair);
+				
+				// Create a trigger from the results.
+				PairTrigger<SSPCluster[]> trigger = new PairTrigger<SSPCluster[]>(sspPair, triggerIndex);
+				trigger.setStateSeedEnergyLow(passSeedLow);
+				trigger.setStateSeedEnergyHigh(passSeedHigh);
+				trigger.setStateClusterEnergyLow(passClusterLow);
+				trigger.setStateClusterEnergyHigh(passClusterHigh);
+				trigger.setStateHitCount(passHitCount);
+				trigger.setStateEnergySumLow(passPairEnergySumLow);
+				trigger.setStateEnergySumHigh(passPairEnergySumHigh);
+				trigger.setStateEnergyDifference(passPairEnergyDifference);
+				trigger.setStateEnergySlope(passPairEnergySlope);
+				trigger.setStateCoplanarity(passPairCoplanarity);
+				trigger.setStateTimeCoincidence(passTimeCoincidence);
+				
+				// Add the trigger to the list.
+				sspPairsTriggers.get(triggerIndex).add(trigger);
+			}
+		}
+	}
+	
+	/**
+	 * Outputs all of the verification parameters currently in use by
+	 * the software. A warning will be issued if the values for NSA and
+	 * NSB, along with the FADC window, preclude clusters from being
+	 * verified.
+	 */
+	private void logSettings() {
+		// Output general settings.
+		System.out.println("Cluster Verification Settings");
+		System.out.printf("\tEnergy Threshold       :: %1.2f%%%n", energyAcceptance);
+		System.out.printf("\tHit Threshold          :: %1d%n", hitAcceptance);
+		
+		// Output window settings.
+		System.out.println("FADC Timing Window Settings");
+		System.out.printf("\tNSB                    :: %3d ns%n", nsb);
+		System.out.printf("\tNSA                    :: %3d ns%n", nsa);
+		System.out.printf("\tFADC Window            :: %3d ns%n", windowWidth);
+		
+		// Calculate the valid clustering window.
+		int start = nsb;
+		int end = windowWidth - nsa;
+		if(start < end) {
+			System.out.printf("\tValid Cluster Window   :: [ %3d ns, %3d ns ]%n", start, end);
+			performClusterVerification = true;
+		} else {
+			System.out.println("\tNSB, NSA, and FADC window preclude a valid cluster verification window.");
+			System.out.println("\tCluster verification will not be performed!");
+			performClusterVerification = false;
+		}
+		
+		// Output the singles trigger settings.
+		for(int i = 0; i < 2; i++) {
+			System.out.printf("Singles Trigger %d Settings%n", (i + 1));
+			System.out.printf("\tCluster Energy Low     :: %.3f GeV%n", singlesTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW));
+			System.out.printf("\tCluster Energy High    :: %.3f GeV%n", singlesTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH));
+			System.out.printf("\tCluster Hit Count      :: %.0f hits%n", singlesTrigger[i].getCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW));
+		}
+		
+		// Output the pair trigger settings.
+		for(int i = 0; i < 2; i++) {
+			System.out.printf("Pairs Trigger %d Settings%n", (i + 1));
+			System.out.printf("\tCluster Energy Low     :: %.3f GeV%n", pairsTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW));
+			System.out.printf("\tCluster Energy High    :: %.3f GeV%n", pairsTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH));
+			System.out.printf("\tCluster Hit Count      :: %.0f hits%n", pairsTrigger[i].getCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW));
+			System.out.printf("\tPair Energy Sum Low    :: %.3f GeV%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SUM_LOW));
+			System.out.printf("\tPair Energy Sum Low    :: %.3f GeV%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SUM_HIGH));
+			System.out.printf("\tPair Energy Difference :: %.3f GeV%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH));
+			System.out.printf("\tPair Energy Slope      :: %.3f GeV%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SLOPE_LOW));
+			System.out.printf("\tPair Energy Slope F    :: %.3f GeV / mm%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F));
+			System.out.printf("\tPair Coplanarity       :: %.0f Degrees%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_COPLANARITY_HIGH));
+			System.out.printf("\tPair Time Coincidence  :: %.0f ns%n", pairsTrigger[i].getCutValue(TriggerModule.PAIR_TIME_COINCIDENCE));
+		}
+	}
+	
+	/**
+	 * Checks whether all of the hits in a cluster are within the safe
+	 * region of the FADC output window.
+	 * @param reconCluster - The cluster to check.
+	 * @return Returns <code>true</code> if the cluster is safe and
+	 * returns <code>false</code> otherwise.
+	 */
+	private final boolean isVerifiable(Cluster reconCluster) {
+		return TriggerDiagnosticUtil.isVerifiable(reconCluster, nsa, nsb, windowWidth);
+	}
+	
+	/**
+	 * Compares an SSP trigger with a simulated trigger. Note that only
+	 * certain class combinations are supported. Triggers of the type
+	 * <code>SSPSinglesTrigger</code> may be compared with triggers of
+	 * the type <code>SinglesTrigger<SSPCluster></code> and triggers of
+	 * the type <code>SSPPairTrigger</code> may be compared to either
+	 * <code>PairTrigger<SSPCluster[]></code> triggers objects.
+	 * @param bankTrigger - The SSP bank trigger.
+	 * @param simTrigger - The simulated trigger.
+	 * @return Returns <code>true</code> if the triggers are valid
+	 * matches and <code>false</code> if they are not.
+	 * @throws IllegalArgumentException Occurs if the trigger types
+	 * are not of a supported type.
+	 */
+	@SuppressWarnings("unchecked")
+	private static final boolean compareTriggers(SSPNumberedTrigger bankTrigger, Trigger<?> simTrigger) throws IllegalArgumentException {
+		// Get the classes of the arguments. This is used to check the
+		// generic type of the Trigger<?> object, and means that the
+		// "unchecked" warnings can be safely ignored.
+		Object source = simTrigger.getTriggerSource();
+		
+		// If the combination of classes is supported, pass the triggers
+		// to the appropriate handler.
+		if(bankTrigger instanceof SSPSinglesTrigger && simTrigger instanceof SinglesTrigger && source instanceof SSPCluster) {
+			return compareSSPSinglesTriggers((SSPSinglesTrigger) bankTrigger, (SinglesTrigger<SSPCluster>) simTrigger);
+		} else if(bankTrigger instanceof SSPPairTrigger && simTrigger instanceof PairTrigger && source instanceof SSPCluster[]) {
+			return compareSSPPairTriggers((SSPPairTrigger) bankTrigger, (PairTrigger<SSPCluster[]>) simTrigger);
+		}
+		
+		// Otherwise, the trigger combination is not supported. Produce
+		// and exception.
+		throw new IllegalArgumentException(String.format("Trigger type \"%s\" can not be compared to trigger type \"%s\" with source type \"%s\".",
+				bankTrigger.getClass().getSimpleName(), simTrigger.getClass().getSimpleName(), source.getClass().getSimpleName()));
+	}
+	
+	/**
+	 * Compares a trigger from the SSP bank to a trigger simulated on
+	 * an SSP cluster.
+	 * @param bankTrigger - The trigger from the SSP bank.
+	 * @param simTrigger - The trigger from the simulation.
+	 * @return Returns <code>true</code> if the triggers match and
+	 * <code>false</code> if they do not.
+	 */
+	private static final boolean compareSSPSinglesTriggers(SSPSinglesTrigger bankTrigger, SinglesTrigger<SSPCluster> simTrigger) {
+		// The bank trigger and simulated trigger must have the same
+		// time. This is equivalent to the time of the triggering cluster.
+		if(bankTrigger.getTime() != simTrigger.getTriggerSource().getTime()) {
+			return false;
+		}
+		
+		// If the time stamp is the same, check that the trigger flags
+		// are all the same. Start with cluster energy low.
+		if(bankTrigger.passCutEnergyMin() != simTrigger.getStateClusterEnergyLow()) {
+			return false;
+		}
+		
+		// Check cluster energy high.
+		if(bankTrigger.passCutEnergyMax() != simTrigger.getStateClusterEnergyHigh()) {
+			return false;
+		}
+		
+		// Check cluster hit count.
+		if(bankTrigger.passCutHitCount() != simTrigger.getStateHitCount()) {
+			return false;
+		}
+		
+		// If all of the tests are successful, the triggers match.
+		return true;
+	}
+	
+	/**
+	 * Compares a trigger from the SSP bank to a trigger simulated on
+	 * an SSP cluster.
+	 * @param bankTrigger - The trigger from the SSP bank.
+	 * @param simTrigger - The trigger from the simulation.
+	 * @return Returns <code>true</code> if the triggers match and
+	 * <code>false</code> if they do not.
+	 */
+	private static final boolean compareSSPPairTriggers(SSPPairTrigger bankTrigger, PairTrigger<SSPCluster[]> simTrigger) {
+		// Get the time of the bottom cluster in the pair.
+		int simTime = 0;
+		if(simTrigger.getTriggerSource()[0].getYIndex() < 0) {
+			simTime = simTrigger.getTriggerSource()[0].getTime();
+		} else {
+			simTime = simTrigger.getTriggerSource()[1].getTime();
+		}
+		
+		// The bank trigger and simulated trigger must have the same
+		// time. This is equivalent to the time of the triggering cluster.
+		if(bankTrigger.getTime() != simTime) { return false; }
+		
+		// If the time stamp is the same, check that the trigger flags
+		// are all the same. Start with energy sum.
+		if(bankTrigger.passCutEnergySum() != simTrigger.getStateEnergySum()) {
+			return false;
+		}
+		
+		// Check pair energy difference.
+		if(bankTrigger.passCutEnergyDifference() != simTrigger.getStateEnergyDifference()) {
+			return false;
+		}
+		
+		// Check pair energy slope.
+		if(bankTrigger.passCutEnergySlope() != simTrigger.getStateEnergySlope()) {
+			return false;
+		}
+		
+		// Check pair coplanarity.
+		if(bankTrigger.passCutCoplanarity() != simTrigger.getStateCoplanarity()) {
+			return false;
+		}
+		
+		// If all of the tests are successful, the triggers match.
+		return true;
+	}
+	
+	/**
+	 * Generates a <code>List</code> collection that contains a set
+	 * of <code>ArrayList</code> collections representing a unique
+	 * permutation of the entries in the argument.
+	 * @param values - A collection of the entries to be permuted.
+	 * @return Returns a list of lists representing the permutations.
+	 */
+	private static final List<List<Pair<Cluster, SSPCluster>>> getPermutations(List<Cluster> reconClusters, List<SSPCluster> sspClusters) {
+		// Store the SSP cluster permutations.
+		List<List<SSPCluster>> permList = new ArrayList<List<SSPCluster>>();
+		
+		// Make sure that the two lists are the same size.
+		int reconSize = reconClusters.size();
+		int sspSize = sspClusters.size();
+		while(sspClusters.size() < reconClusters.size()) {
+			sspClusters.add(null);
+		}
+		while(reconClusters.size() < sspClusters.size()) {
+			reconClusters.add(null);
+		}
+		
+		// Get the SSP cluster permutations.
+		permute(new ArrayList<SSPCluster>(0), sspClusters, permList);
+		
+		// Create pairs from the permutations.
+		List<List<Pair<Cluster, SSPCluster>>> pairList = new ArrayList<List<Pair<Cluster, SSPCluster>>>();
+		for(List<SSPCluster> permutation : permList) {
+			List<Pair<Cluster, SSPCluster>> pairs = new ArrayList<Pair<Cluster, SSPCluster>>(reconClusters.size());
+			
+			for(int clusterIndex = 0; (clusterIndex < reconClusters.size() && clusterIndex < permutation.size()); clusterIndex++) {
+				pairs.add(new Pair<Cluster, SSPCluster>(reconClusters.get(clusterIndex), permutation.get(clusterIndex)));
+			}
+			
+			pairList.add(pairs);
+		}
+		
+		// Remove the extra values.
+		for(int i = sspClusters.size() - 1; i >= sspSize; i--) { sspClusters.remove(i); }
+		for(int i = reconClusters.size() - 1; i >= reconSize; i--) { reconClusters.remove(i); }
+		
+		// Return the pairs.
+		return pairList;
+	}
+	
+	/**
+	 * Recursive method for permuting all entries in the argument
+	 * collection <code>remainingValues</code> into the argument
+	 * <code>permutedValues</code> values. Completed permutations are
+	 * placed in the argument <code>permList</code>.
+	 * @param permutedValues - List to store entries that have already
+	 * been permuted.
+	 * @param remainingValues - List to store  entries that need to be
+	 * permuted.
+	 * @param permList - List to store completed permutations.
+	 */
+	private static final void permute(List<SSPCluster> permutedValues, List<SSPCluster> remainingValues, List<List<SSPCluster>> permList) {
+		// If the list of entries that still need to be sorted is empty,
+		// then there is nothing to sort. Just return and empty list.
+		if(remainingValues.isEmpty()) { return; }
+		
+		// If there is only one value left in the list of entries that
+		// still need to be sorted, then just add it to the permutation
+		// list and return it.
+		else if(remainingValues.size() <= 1) {
+			// Add the last entry.
+			permutedValues.add(remainingValues.get(0));
+			
+			// Add the permutation to the list of completed permutations.
+			permList.add(permutedValues);
+		}
+		
+		// Otherwise, continue to get all possible permutations.
+		else {
+			// Iterate over the entries that have not been permuted.
+			for(int i = 0; i < remainingValues.size(); i++) {
+				// Make new lists to contain the permutations.
+				List<SSPCluster> newPermList = new ArrayList<SSPCluster>(permutedValues.size() + 1);
+				List<SSPCluster> newRemainList = new ArrayList<SSPCluster>(remainingValues.size());
+				
+				// Copy the current permuted entries to the new list
+				// and one value from the list of entries that have
+				// not been permuted yet.
+				newPermList.addAll(permutedValues);
+				newPermList.add(remainingValues.get(i));
+				
+				// The new list of entries that have not been permuted
+				// should be identical, except it should now be missing
+				// the entry that was moved.
+				for(int index = 0; index < remainingValues.size(); index++) {
+					if(index != i) { newRemainList.add(remainingValues.get(index)); }
+				}
+				
+				// Repeat the process with the new lists.
+				permute(newPermList, newRemainList, permList);
+			}
+		}
+	}
+	
+	/**
+	 * Compares two cluster matching events and finds the one that has
+	 * the better results. Note that this will only return results that
+	 * make sense if both of the events represent different permutations
+	 * of the same set of clusters. Comparing events with different sets
+	 * of clusters will produce meaningless results.
+	 * @param firstEvent - The first cluster matching event,
+	 * @param secondEvent - The second cluster matching event.
+	 * @return Returns the cluster matching event that is better.
+	 */
+	private static final ClusterMatchEvent getBestPermutation(ClusterMatchEvent firstEvent, ClusterMatchEvent secondEvent) {
+		// If both permutations are null, return that.
+		if(firstEvent == null && secondEvent == null) {
+			return null;
+		}
+		
+		// If one permutation is null, it is not the best.
+		if(firstEvent == null) { return secondEvent; }
+		else if(secondEvent == null) { return firstEvent; }
+		
+		// A permutation is better if it has more matches.
+		if(firstEvent.getMatches() > secondEvent.getMatches()) { return firstEvent; }
+		else if(secondEvent.getMatches() > firstEvent.getMatches()) { return secondEvent; }
+		
+		// Otherwise, the permutation with the least energy failures is
+		// the better permutation.
+		if(firstEvent.getEnergyFailures() < secondEvent.getEnergyFailures()) { return firstEvent; }
+		else if(secondEvent.getEnergyFailures() < firstEvent.getEnergyFailures()) { return secondEvent; }
+		
+		// If both these values are the same, then the events are identical.
+		return firstEvent;
+	}
+	
+	/**
+	 * Determines the number of spaces needed to render the longest of
+	 * a series of integers as a string.
+	 * @param vals - The series of integers.
+	 * @return Returns the number of spaces needed to render the longest
+	 * integer as a base-10 string.
+	 */
+	private static final int getPrintSpaces(int... vals) {
+		// Track the largest value.
+		int largest = 0;
+		
+		// Iterate over the arguments and find the largest.
+		for(int val : vals) {
+			// Get the length of the string.
+			int length = TriggerDiagnosticUtil.getDigits(val);
+			
+			// If it is larger, track it.
+			if(length > largest) { largest = length; }
+		}
+		
+		// Return the longer one.
+		return largest;
+	}
+	
+	/**
+	 * Gets the position of the source of a <code>Trigger</code> object
+	 * as text. This method only supports trigger sources of the types
+	 * <code>SSPCluster</code>, <code>Cluster</code>, and arrays of size
+	 * two of either type.
+	 * @param trigger - The trigger from which to obtain the source.
+	 * @return Returns the source of the trigger as a <code>String</code>
+	 * object.
+	 * @throws IllegalArgumentException Occurs if the source of the
+	 * trigger is not any of the supported types.
+	 */
+	private static final String triggerPositionString(Trigger<?> trigger) throws IllegalArgumentException {
+		// Get the trigger source.
+		Object source = trigger.getTriggerSource();
+		
+		// Handle valid trigger sources.
+		if(source instanceof SSPCluster) {
+			return TriggerDiagnosticUtil.clusterPositionString((SSPCluster) source);
+		} else if(source instanceof Cluster) {
+			return TriggerDiagnosticUtil.clusterPositionString((Cluster) source);
+		} else if(source instanceof SSPCluster[]) {
+			SSPCluster[] sourcePair = (SSPCluster[]) source;
+			if(sourcePair.length == 2) {
+				return String.format("%s, %s", TriggerDiagnosticUtil.clusterPositionString(sourcePair[0]),
+						TriggerDiagnosticUtil.clusterPositionString(sourcePair[1]));
+			}
+		} else if(source instanceof Cluster[]) {
+			Cluster[] sourcePair = (Cluster[]) source;
+			if(sourcePair.length == 2) {
+				return String.format("%s, %s", TriggerDiagnosticUtil.clusterPositionString(sourcePair[0]),
+						TriggerDiagnosticUtil.clusterPositionString(sourcePair[1]));
+			}
+		}
+		
+		// Otherwise, the source type is unrecognized. Throw an error.
+		throw new IllegalArgumentException(String.format("Trigger source type \"%s\" is not supported.",
+				trigger.getTriggerSource().getClass().getSimpleName()));
+	}
+	
+	/**
+	 * Gets the time of a simulated trigger object. Method supports
+	 * triggers with source objects of type <code>SSPCluster</code>,
+	 * <code>Cluster</code>, and arrays of size two composed of either
+	 * object type.
+	 * @param trigger - The trigger.
+	 * @return Returns the time at which the trigger occurred.
+	 * @throws IllegalArgumentException Occurs if the trigger source
+	 * is not a supported type.
+	 */
+	private static final double getTriggerTime(Trigger<?> trigger) throws IllegalArgumentException {
+		// Get the trigger source.
+		Object source = trigger.getTriggerSource();
+		
+		// Get the trigger time for supported trigger types.
+		if(source instanceof SSPCluster) {
+			return ((SSPCluster) source).getTime();
+		} else if(source instanceof Cluster) {
+			return TriggerDiagnosticUtil.getClusterTime((Cluster) source);
+		} else if(source instanceof SSPCluster[]) {
+			// Get the pair.
+			SSPCluster[] sourcePair = (SSPCluster[]) source;
+			
+			// Get the time of the bottom cluster.
+			if(sourcePair.length == 2) {
+				if(sourcePair[0].getYIndex() < 0) { return sourcePair[0].getTime(); }
+				else if(sourcePair[1].getYIndex() < 0) { return sourcePair[1].getTime(); }
+				else { throw new IllegalArgumentException("Cluster pairs must be formed of a top/bottom pair."); }
+			}
+			else { throw new IllegalArgumentException("Cluster pairs must be of size 2."); }
+		} else if(source instanceof Cluster[]) {
+			// Get the pair.
+			Cluster[] sourcePair = (Cluster[]) source;
+			int[] iy = {
+				TriggerDiagnosticUtil.getYIndex(sourcePair[0]),
+				TriggerDiagnosticUtil.getYIndex(sourcePair[1])
+			};
+			
+			// Get the time of the bottom cluster.
+			if(sourcePair.length == 2) {
+				if(iy[0] < 0) { return TriggerDiagnosticUtil.getClusterTime(sourcePair[0]); }
+				else if(iy[1] < 0) { return TriggerDiagnosticUtil.getClusterTime(sourcePair[1]); }
+				else { throw new IllegalArgumentException("Cluster pairs must be formed of a top/bottom pair."); }
+			}
+			else { throw new IllegalArgumentException("Cluster pairs must be of size 2."); }
+		}
+		
+		// If the source type is unrecognized, throw an exception.
+		throw new IllegalArgumentException(String.format("Trigger source type \"%\" is not supported.",
+				source.getClass().getSimpleName()));
+	}
+	
+	/**
+	 * Checks if a simulated trigger and an SSP trigger match. Note
+	 * that only certain types can be compared. These are:
+	 * <ul><li><code>SinglesTrigger<?> --> SSPSinglesTrigger</code></li>
+	 * <li><code>PairTrigger<?> --> SSPPairTrigger</code></li></ul>
+	 * @param simTrigger - The simulated trigger.
+	 * @param sspTrigger - The SSP bank trigger.
+	 * @return Returns an array of <code>boolean</code> primitives that
+	 * indicate which cuts passed and which failed.
+	 */
+	private static final boolean[] triggerCutMatch(Trigger<?> simTrigger, SSPTrigger sspTrigger) {
+		// Check that the cuts match for supported trigger types.
+		if(simTrigger instanceof SinglesTrigger && sspTrigger instanceof SSPSinglesTrigger) {
+			// Create an array to store the cut checks.
+			boolean[] cutMatch = new boolean[3];
+			
+			// Cast the triggers.
+			SinglesTrigger<?> simSingles = (SinglesTrigger<?>) simTrigger;
+			SSPSinglesTrigger sspSingles = (SSPSinglesTrigger) sspTrigger;
+			
+			// Perform the check.
+			cutMatch[ENERGY_MIN] = (simSingles.getStateClusterEnergyLow()  == sspSingles.passCutEnergyMin());
+			cutMatch[ENERGY_MAX] = (simSingles.getStateClusterEnergyHigh() == sspSingles.passCutEnergyMax());
+			cutMatch[HIT_COUNT] = (simSingles.getStateHitCount()          == sspSingles.passCutHitCount());
+			
+			// Return the match array.
+			return cutMatch;
+		} else if(simTrigger instanceof PairTrigger && sspTrigger instanceof SSPPairTrigger) {
+			// Create an array to store the cut checks.
+			boolean[] cutMatch = new boolean[4];
+			
+			// Cast the triggers.
+			PairTrigger<?> simPair = (PairTrigger<?>) simTrigger;
+			SSPPairTrigger sspPair = (SSPPairTrigger) sspTrigger;
+			
+			// Perform the check.
+			cutMatch[ENERGY_SUM] = (simPair.getStateEnergySum()        == sspPair.passCutEnergySum());
+			cutMatch[ENERGY_DIFF] = (simPair.getStateEnergyDifference() == sspPair.passCutEnergyDifference());
+			cutMatch[ENERGY_SLOPE] = (simPair.getStateEnergySlope()      == sspPair.passCutEnergySlope());
+			cutMatch[COPLANARITY] = (simPair.getStateCoplanarity()      == sspPair.passCutCoplanarity());
+			
+			// Return the match array.
+			return cutMatch;
+		}
+		
+		// If this point is reached, the triggers are not of a supported
+		// type for cut comparison. Produce an exception.
+		throw new IllegalArgumentException(String.format("Triggers of type \"%s\" can not be cut-matched with triggers of type \"%s\".",
+				simTrigger.getClass().getSimpleName(), sspTrigger.getClass().getSimpleName()));
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchEvent.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchEvent.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchEvent.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,145 @@
+package org.hps.analysis.trigger.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hps.readout.ecal.triggerbank.SSPCluster;
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+import org.lcsim.event.Cluster;
+
+/**
+ * Class <code>ClusterMatchEvent</code> tracks reconstructed/SSP cluster
+ * pairs for the purpose of cluster matching. It maintains a list of
+ * all pairs that have been seen as well as their match states. It can
+ * additionally provide the total number of each type of match state.
+ * 
+ * @author Kyle McCarty
+ */
+public class ClusterMatchEvent {
+	// Track the number of state instances.
+	private int matched = 0;
+	private int failPosition = 0;
+	private int failEnergy = 0;
+	private int failHitCount = 0;
+	
+	// Store all of the pairs.
+	private List<ClusterMatchedPair> pairList = new ArrayList<ClusterMatchedPair>();
+	
+	/**
+	 * Fuses another <code>ClusterMatchEvent</code> with this object.
+	 * The other event's cluster pairs and states will be added to those
+	 * already in this event.
+	 * @param event - The event to fuse.
+	 */
+	public void addEvent(ClusterMatchEvent event) {
+		// If the event is null, do nothing.
+		if(event == null) { return; }
+		
+		// Iterate over the new event's matched pairs and add them into
+		// this event's statistics.
+		for(ClusterMatchedPair cmp : event.pairList) {
+			// Add the current pair to this pair list.
+			pairList.add(cmp);
+			
+			// Increment the statistics counters based on the pair state.
+			if(cmp.isMatch()) { matched++; }
+			if(cmp.isPositionFailState()) { failPosition++; }
+			if(cmp.isEnergyFailState()) { failEnergy++; }
+			if(cmp.isHitCountFailState()) { failHitCount++; }
+		}
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with energy fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getEnergyFailures() {
+		return failEnergy;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with hit count fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getHitCountFailures() {
+		return failHitCount;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with match states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public List<ClusterMatchedPair> getMatchedPairs() {
+		return pairList;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with position fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getMatches() {
+		return matched;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with position fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getPositionFailures() {
+		return failPosition;
+	}
+	
+	/**
+	 * Adds a reconstructed/SSP cluster pair and marks it as having an
+	 * energy fail state.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @param sspCluster - The SSP cluster.
+	 */
+	public void pairFailEnergy(Cluster reconCluster, SSPCluster sspCluster) {
+		failEnergy++;
+		pairList.add(new ClusterMatchedPair(reconCluster, sspCluster, TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_ENERGY));
+	}
+	
+	/**
+	 * Adds a reconstructed/SSP cluster pair and marks it as having a
+	 * hit count fail state.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @param sspCluster - The SSP cluster.
+	 */
+	public void pairFailHitCount(Cluster reconCluster, SSPCluster sspCluster) {
+		failHitCount++;
+		pairList.add(new ClusterMatchedPair(reconCluster, sspCluster, TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_HIT_COUNT));
+	}
+	
+	/**
+	 * Adds a reconstructed/SSP cluster pair and marks it as having a
+	 * position fail state.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @param sspCluster - The SSP cluster.
+	 */
+	public void pairFailPosition(Cluster reconCluster, SSPCluster sspCluster) {
+		failPosition++;
+		pairList.add(new ClusterMatchedPair(reconCluster, sspCluster, TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_POSITION));
+	}
+	
+	/**
+	 * Adds a reconstructed/SSP cluster pair and marks it as having a
+	 * match state.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @param sspCluster - The SSP cluster.
+	 */
+	public void pairMatch(Cluster reconCluster, SSPCluster sspCluster) {
+		matched++;
+		pairList.add(new ClusterMatchedPair(reconCluster, sspCluster, TriggerDiagnosticUtil.CLUSTER_STATE_MATCHED));
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchStatus.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchStatus.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchStatus.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,200 @@
+package org.hps.analysis.trigger.event;
+
+import java.awt.Point;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+import org.hps.readout.ecal.triggerbank.SSPCluster;
+import org.lcsim.event.Cluster;
+
+/**
+ * Tracks the status of cluster matching for the purposes of trigger
+ * verification.
+ * 
+ * @author Kyle McCarty
+ */
+public class ClusterMatchStatus extends ClusterStatModule {
+	// Plot binning values.
+	private static final int TIME_BIN = 4;
+	private static final double ENERGY_BIN = 0.01;
+	private static final int TIME_BIN_HALF = TIME_BIN / 2;
+	private static final double ENERGY_BIN_HALF = ENERGY_BIN / 2;
+	
+	// Track plotting values for reconstructed and SSP clusters.
+	private Map<Integer, Integer> sspHitCountBins = new HashMap<Integer, Integer>();
+	private Map<Integer, Integer> reconHitCountBins = new HashMap<Integer, Integer>();
+	private Map<Point, Integer> sspPositionBins = new HashMap<Point, Integer>();
+	private Map<Point, Integer> reconPositionBins = new HashMap<Point, Integer>();
+	private Map<Integer, Integer> sspEnergyBins = new HashMap<Integer, Integer>();
+	private Map<Integer, Integer> reconEnergyBins = new HashMap<Integer, Integer>();
+	
+	// Track plotting values for cluster matching results.
+	private Map<Point, Integer> failPositionBins = new HashMap<Point, Integer>();
+	private Map<Point, Integer> allHenergyBins = new HashMap<Point, Integer>();
+	private Map<Point, Integer> failHenergyBins = new HashMap<Point, Integer>();
+	private Map<Integer, Integer> allTimeBins = new HashMap<Integer, Integer>();
+	private Map<Integer, Integer> failTimeBins = new HashMap<Integer, Integer>();
+	
+    public void addEvent(ClusterMatchEvent event, List<Cluster> reconClusters, List<SSPCluster> sspClusters) {
+    	// Update the number of reconstructed and SSP clusters
+		// that have been seen so far.
+		int sspCount = sspClusters == null ? 0 : sspClusters.size();
+		int reconCount = reconClusters == null ? 0 : reconClusters.size();
+		this.sspClusters   += sspCount;
+		this.reconClusters += reconCount;
+		
+		// Update the pair state information.
+		matches      += event.getMatches();
+		failEnergy   += event.getEnergyFailures();
+		failHitCount += event.getHitCountFailures();
+		failPosition += event.getPositionFailures();
+		
+		// In the special case that there are no SSP clusters, no pairs
+		// will be listed. All possible fails are known to have failed
+		// due to position.
+		if(sspClusters == null || sspClusters.isEmpty()) {
+			failPosition += (reconClusters == null ? 0 : reconClusters.size());
+		}
+    	
+    	// Update the plotting information for reconstructed clusters.
+		for(Cluster cluster : reconClusters) {
+			// Update the hit count bin data.
+			Integer hitCountCount = reconHitCountBins.get(cluster.getCalorimeterHits().size());
+			if(hitCountCount == null) { reconHitCountBins.put(cluster.getCalorimeterHits().size(), 1); }
+			else { reconHitCountBins.put(cluster.getCalorimeterHits().size(), hitCountCount + 1); }
+			
+			// Update the position bin data.
+			Point clusterPosition = TriggerDiagnosticUtil.getClusterPosition(cluster);
+			Integer positionCount = reconPositionBins.get(clusterPosition);
+			if(positionCount == null) { reconPositionBins.put(clusterPosition, 1); }
+			else { reconPositionBins.put(clusterPosition, positionCount + 1); }
+			
+			// Update the energy bin data.
+			int energyBin = (int) Math.floor(cluster.getEnergy() / ENERGY_BIN);
+			Integer energyCount = reconEnergyBins.get(energyBin);
+			if(energyCount == null) { reconEnergyBins.put(energyBin, 1); }
+			else { reconEnergyBins.put(energyBin, energyCount + 1); }
+		}
+		
+    	// Update the plotting information for SSP clusters.
+		for(SSPCluster cluster : sspClusters) {
+			// Update the hit count bin data.
+			Integer hitCountCount = sspHitCountBins.get(cluster.getHitCount());
+			if(hitCountCount == null) { sspHitCountBins.put(cluster.getHitCount(), 1); }
+			else { sspHitCountBins.put(cluster.getHitCount(), hitCountCount + 1); }
+			
+			// Update the position bin data.
+			Point clusterPosition = TriggerDiagnosticUtil.getClusterPosition(cluster);
+			Integer positionCount = sspPositionBins.get(clusterPosition);
+			if(positionCount == null) { sspPositionBins.put(clusterPosition, 1); }
+			else { sspPositionBins.put(clusterPosition, positionCount + 1); }
+			
+			// Update the energy bin data.
+			int energyBin = (int) Math.floor(cluster.getEnergy() / ENERGY_BIN);
+			Integer energyCount = sspEnergyBins.get(energyBin);
+			if(energyCount == null) { sspEnergyBins.put(energyBin, 1); }
+			else { sspEnergyBins.put(energyBin, energyCount + 1); }
+		}
+		
+		// Update the plotting information for SSP/reconstructed cluster
+		// pairs.
+		pairLoop:
+		for(ClusterMatchedPair pair : event.getMatchedPairs()) {
+			// If one of the pairs is null, then it is unmatched cluster
+			// and may be skipped.
+			if(pair.getReconstructedCluster() == null || pair.getSSPCluster() == null) {
+				continue pairLoop;
+			}
+			
+			// Populate the bins for the "all" plots.
+			// Update the match time plots.
+			int timeBin = (int) Math.floor(TriggerDiagnosticUtil.getClusterTime(pair.getReconstructedCluster()) / TIME_BIN);
+			Integer timeCount = allTimeBins.get(timeBin);
+			if(timeCount == null) { allTimeBins.put(timeBin, 1); }
+			else { allTimeBins.put(timeBin, timeCount + 1); }
+			
+			// Update the energy/hit difference plots.
+			int hitBin = getHitCountDifference(pair.getSSPCluster(), pair.getReconstructedCluster());
+			int energyBin = (int) Math.floor(getEnergyPercentDifference(pair.getSSPCluster(), pair.getReconstructedCluster()) / ENERGY_BIN);
+			Point henergyBin = new Point(hitBin, energyBin);
+			Integer henergyCount = allHenergyBins.get(henergyBin);
+			if(henergyCount == null) { allHenergyBins.put(henergyBin, 1); }
+			else { allHenergyBins.put(henergyBin, henergyCount + 1); }
+			
+			// Populate the bins for the "fail" plots.
+			if(!pair.isMatch()) {
+				// Update the failed cluster position bins.
+				Point clusterPosition = TriggerDiagnosticUtil.getClusterPosition(pair.getReconstructedCluster());
+				Integer positionCount = failPositionBins.get(clusterPosition);
+				if(positionCount == null) { failPositionBins.put(clusterPosition, 1); }
+				else { failPositionBins.put(clusterPosition, positionCount + 1); }
+				
+				// Update the failed match time plots.
+				timeCount = failTimeBins.get(timeBin);
+				if(timeCount == null) { failTimeBins.put(timeBin, 1); }
+				else { failTimeBins.put(timeBin, timeCount + 1); }
+				
+				// Update the failed energy/hit difference plots.
+				henergyCount = failHenergyBins.get(henergyBin);
+				if(henergyCount == null) { failHenergyBins.put(henergyBin, 1); }
+				else { failHenergyBins.put(henergyBin, henergyCount + 1); }
+			}
+		}
+    }
+    
+	/**
+	 * Clears all statistical information and resets the object of its
+	 * default, empty state.
+	 */
+    @Override
+	public void clear() {
+		// Clear statistical data.
+		super.clear();
+		
+		// Clear plot collections.
+		sspHitCountBins.clear();
+		reconHitCountBins.clear();
+		sspPositionBins.clear();
+		reconPositionBins.clear();
+		sspEnergyBins.clear();
+		reconEnergyBins.clear();
+		failPositionBins.clear();
+		allHenergyBins.clear();
+		failHenergyBins.clear();
+		allTimeBins.clear();
+		failTimeBins.clear();
+	}
+    
+    /**
+     * Gets a copy of the statistical data stored in the object.
+     * @return Returns the data in a <code>ClusterStatModule</code>
+     * object.
+     */
+    public ClusterStatModule cloneStatModule() {
+    	return new ClusterStatModule(this);
+    }
+	
+	/**
+	 * Solves the equation <code>|E_ssp / E_recon|</code>.
+	 * @param sspCluster - The SSP cluster.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @return Returns the solution to the equation as a <code>double
+	 * </code> primitive.
+	 */
+	private static final double getEnergyPercentDifference(SSPCluster sspCluster, Cluster reconCluster) {
+		return Math.abs((sspCluster.getEnergy() / reconCluster.getEnergy()));
+	}
+    
+	/**
+	 * Gets the difference in hit count between an SSP cluster and a
+	 * reconstructed cluster.
+	 * @param sspCluster - The SSP cluster.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @return Returns the difference as an <code>int</code> primitive.
+	 */
+	private static final int getHitCountDifference(SSPCluster sspCluster, Cluster reconCluster) {
+		return sspCluster.getHitCount() - TriggerDiagnosticUtil.getHitCount(reconCluster);
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchedPair.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchedPair.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterMatchedPair.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,117 @@
+package org.hps.analysis.trigger.event;
+
+import org.hps.analysis.trigger.util.Pair;
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+import org.hps.readout.ecal.triggerbank.SSPCluster;
+import org.lcsim.event.Cluster;
+
+/**
+ * Class <code>ClusterMatchedPair</code> stores a reconstructed cluster
+ * and an SSP bank reported cluster which have been compared for the
+ * purpose of cluster matching. It also tracks what the match state of
+ * the two clusters is.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class ClusterMatchedPair extends Pair<Cluster, SSPCluster> {
+	// CLass variables.
+	private final byte state;
+	
+	/**
+	 * Instantiates a new <code>ClusterMatchedPair</code> object from
+	 * the two indicated clusters and marks their match state.
+	 * @param reconCluster - The reconstructed cluster.
+	 * @param sspCluster - The SSP cluster.
+	 * @param state - The pair match state.
+	 */
+	public ClusterMatchedPair(Cluster reconCluster, SSPCluster sspCluster, byte state) {
+		// Set the cluster pairs.
+		super(reconCluster, sspCluster);
+		
+		// If the state is defined, set it. Otherwise, it is unknown.
+		if(state == TriggerDiagnosticUtil.CLUSTER_STATE_MATCHED
+				|| state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_POSITION
+				|| state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_ENERGY
+				|| state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_HIT_COUNT) {
+			this.state = state;
+		} else {
+			this.state = TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_UNKNOWN;
+		}
+	}
+	
+	/**
+	 * Gets the reconstructed cluster of the pair.
+	 * @return Returns the reconstructed cluster a <code>Cluster</cod>
+	 * object.
+	 */
+	public Cluster getReconstructedCluster() {
+		return getFirstElement();
+	}
+	
+	/**
+	 * Gets the SSP cluster of the pair.
+	 * @return Returns the SSP cluster as an <code>SSPCluster</code>
+	 * object.
+	 */
+	public SSPCluster getSSPCluster() {
+		return getSecondElement();
+	}
+	
+	/**
+	 * Gets the raw state identifier.
+	 * @return Returns the state identifier as a <code>byte</code>
+	 * primitive. Valid identifiers are defined in the class
+	 * <code>TriggerDiagnosticUtil</code>.
+	 */
+	public byte getState() {
+		return state;
+	}
+	
+	/**
+	 * Indicates whether the recon/SSP pair failed to not being close
+	 * enough in energy.
+	 * @return Returns <code>true</code> if the pair match state is an
+	 * energy fail state and <code>false</code> otherwise.
+	 */
+	public boolean isEnergyFailState() {
+		return (state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_ENERGY);
+	}
+	
+	/**
+	 * Indicates whether the recon/SSP pair failed to match due to not
+	 * being close enough in hit count.
+	 * @return Returns <code>true</code> if the pair match state is a
+	 * hit count fail state and <code>false</code> otherwise.
+	 */
+	public boolean isHitCountFailState() {
+		return (state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_HIT_COUNT);
+	}
+	
+	/**
+	 * Indicates whether the recon/SSP pair matched.
+	 * @return Returns <code>true</code> if the pair match state is a
+	 * match state and <code>false</code> otherwise.
+	 */
+	public boolean isMatch() {
+		return (state == TriggerDiagnosticUtil.CLUSTER_STATE_MATCHED);
+	}
+	
+	/**
+	 * Indicates whether the recon/SSP pair failed to match due to a
+	 * the cluster positions not aligning.
+	 * @return Returns <code>true</code> if the pair match state is a
+	 * position fail state and <code>false</code> otherwise.
+	 */
+	public boolean isPositionFailState() {
+		return (state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_POSITION);
+	}
+	
+	/**
+	 * Indicates whether the recon/SSP pair has no known match state.
+	 * @return Returns <code>true</code> if the pair match state is
+	 * unknown and <code>false</code> otherwise.
+	 */
+	public boolean isUnknownState() {
+		return (state == TriggerDiagnosticUtil.CLUSTER_STATE_FAIL_UNKNOWN);
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterStatModule.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterStatModule.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/ClusterStatModule.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,109 @@
+package org.hps.analysis.trigger.event;
+
+/**
+ * Class <code>ClusterStatModule</code> stores the statistical data
+ * for trigger diagnostic cluster matching.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class ClusterStatModule {
+	// Track cluster statistics.
+	protected int sspClusters   = 0;
+	protected int reconClusters = 0;
+	protected int matches       = 0;
+	protected int failEnergy    = 0;
+	protected int failPosition  = 0;
+	protected int failHitCount  = 0;
+	
+	/**
+	 * Instantiates a <code>ClusterStatModule</code> with no statistics
+	 * stored.
+	 */
+	ClusterStatModule() {  }
+	
+	/**
+	 * Instantiates a <code>ClusterStatModule</code> with no statistics
+	 * cloned from the base object.
+	 * @param base - The source for the statistical data.
+	 */
+	ClusterStatModule(ClusterStatModule base) {
+		// Copy the statistical data into it.
+		sspClusters = base.sspClusters;
+		reconClusters = base.reconClusters;
+		matches = base.matches;
+		failEnergy = base.failEnergy;
+		failPosition = base.failPosition;
+		failHitCount = base.failHitCount;
+	}
+	
+	/**
+	 * Clears all statistical information and resets the object of its
+	 * default, empty state.
+	 */
+	void clear() {
+		sspClusters   = 0;
+		reconClusters = 0;
+		matches       = 0;
+		failEnergy    = 0;
+		failPosition  = 0;
+		failHitCount  = 0;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with energy fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getEnergyFailures() {
+		return failEnergy;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with hit count fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getHitCountFailures() {
+		return failHitCount;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with position fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getMatches() {
+		return matches;
+	}
+	
+	/**
+	 * Gets the number of cluster pairs stored in this event that are
+	 * marked with position fail states.
+	 * @return Returns the number of instances of this state as an
+	 * <code>int</code> primitive.
+	 */
+	public int getPositionFailures() {
+		return failPosition;
+	}
+	
+	/**
+	 * Gets the total number of verifiable reconstructed clusters seen.
+     * @return Returns the cluster count as an <code>int</code>
+     * primitive.
+	 */
+    public int getReconClusterCount() {
+    	return reconClusters;
+    }
+    
+    /**
+     * Gets the total number of SSP bank clusters seen.
+     * @return Returns the cluster count as an <code>int</code>
+     * primitive.
+     */
+    public int getSSPClusterCount() {
+    	return sspClusters;
+    }
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerEfficiencyModule.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerEfficiencyModule.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerEfficiencyModule.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,264 @@
+package org.hps.analysis.trigger.event;
+
+import java.util.List;
+
+import org.hps.analysis.trigger.util.ComponentUtils;
+import org.hps.analysis.trigger.util.Pair;
+import org.hps.analysis.trigger.util.PairTrigger;
+import org.hps.analysis.trigger.util.SinglesTrigger;
+import org.hps.readout.ecal.triggerbank.SSPNumberedTrigger;
+import org.hps.analysis.trigger.util.Trigger;
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+
+public class TriggerEfficiencyModule {
+	// Store the statistics.
+	protected int[][] triggersSeenByType = new int[6][6];
+	protected int[][] triggersMatchedByType = new int[6][6];
+	
+	/**
+	 * Adds the number of matched triggers from the event to the total
+	 * seen for each of the appropriate trigger types.
+	 * @param eventTriggerType - The trigger type ID for the trigger that
+	 * caused the event readout.
+	 * @param event - A trigger statistical event.
+	 */
+	public void addEvent(int eventTriggerType, TriggerMatchEvent event) {
+		// Iterate over the matched triggers and track how many were
+		// found of each trigger type.
+		List<Pair<Trigger<?>, SSPNumberedTrigger>> pairList = event.getMatchedReconPairs();
+		for(Pair<Trigger<?>, SSPNumberedTrigger> pair : pairList) {
+			// Update the appropriate counter based on the trigger type.
+			int triggerType = getTriggerType(pair.getFirstElement());
+			triggersMatchedByType[eventTriggerType][triggerType]++;
+		}
+	}
+	
+	/**
+	 * Adds singles triggers to the list of triggers seen.
+	 * @param eventTriggerType - The trigger type ID for the event
+	 * trigger type.
+	 * @param singlesTriggers - A list of size two containing the
+	 * triggers seen for each of the two singles triggers.
+	 */
+	public void addSinglesTriggers(int eventTriggerType, List<List<? extends Trigger<?>>> singlesTriggers) {
+		// Note the trigger type.
+		int[] triggerType = { TriggerDiagnosticUtil.TRIGGER_SINGLES_1, TriggerDiagnosticUtil.TRIGGER_SINGLES_2 };
+		
+		// Track the total number of singles triggers seen.
+		addTriggers(eventTriggerType, singlesTriggers, triggerType);
+	}
+	
+	/**
+	 * Adds pair triggers to the list of triggers seen.
+	 * @param eventTriggerType - The trigger type ID for the event
+	 * trigger type.
+	 * @param pairTriggers - A list of size two containing the
+	 * triggers seen for each of the two pair triggers.
+	 */
+	public void addPairTriggers(int eventTriggerType, List<List<? extends Trigger<?>>> pairTriggers) {
+		// Note the trigger type.
+		int[] triggerType = { TriggerDiagnosticUtil.TRIGGER_PAIR_1, TriggerDiagnosticUtil.TRIGGER_PAIR_2 };
+		
+		// Track the total number of singles triggers seen.
+		addTriggers(eventTriggerType, pairTriggers, triggerType);
+	}
+	
+	/**
+	 * Clears the data stored in the module.
+	 */
+	public void clear() {
+		triggersSeenByType = new int[6][6];
+		triggersMatchedByType = new int[6][6];
+	}
+	
+	@Override
+	public TriggerEfficiencyModule clone() {
+		// Create a new module.
+		TriggerEfficiencyModule clone = new TriggerEfficiencyModule();
+		
+		// Clone the data.
+		clone.triggersMatchedByType = triggersMatchedByType.clone();
+		clone.triggersSeenByType = triggersSeenByType.clone();
+		
+		// Return the clone.
+		return clone;
+	}
+	
+	/**
+	 * Gets the number of triggers matched in events that were caused
+	 * by trigger <code>eventTriggerID</code> for <code>seenTriggerID
+	 * </code> trigger.
+	 * @param eventTriggerID - The trigger that caused the event.
+	 * @param seenTriggerID - The trigger that was seen in the event.
+	 * @return Returns the number of matches as an <code>int</code>.
+	 */
+	public int getTriggersMatched(int eventTriggerID, int seenTriggerID) {
+		return triggersMatchedByType[eventTriggerID][seenTriggerID];
+	}
+	
+	/**
+	 * Gets the number of triggers seen in events that were caused
+	 * by trigger <code>eventTriggerID</code> for <code>seenTriggerID
+	 * </code> trigger.
+	 * @param eventTriggerID - The trigger that caused the event.
+	 * @param seenTriggerID - The trigger that was seen in the event.
+	 * @return Returns the number of triggers as an <code>int</code>.
+	 */
+	public int getTriggersSeen(int eventTriggerID, int seenTriggerID) {
+		return triggersSeenByType[eventTriggerID][seenTriggerID];
+	}
+	
+	/**
+	 * Prints the trigger statistics to the terminal as a table.
+	 */
+	public void printModule() {
+		// Define constant spacing variables.
+		int columnSpacing = 3;
+		
+		// Define table headers.
+		String sourceName = "Source";
+		String seenName = "Trigger Efficiency";
+		
+		// Get the longest column header name.
+		int longestHeader = -1;
+		for(String triggerName : TriggerDiagnosticUtil.TRIGGER_NAME) {
+			longestHeader = ComponentUtils.max(longestHeader, triggerName.length());
+		}
+		longestHeader = ComponentUtils.max(longestHeader, sourceName.length());
+		
+		// Determine the spacing needed to display the largest numerical
+		// cell value.
+		int numWidth = -1;
+		int longestCell = -1;
+		for(int eventTriggerID = 0; eventTriggerID < 6; eventTriggerID++) {
+			for(int seenTriggerID = 0; seenTriggerID < 6; seenTriggerID++) {
+				int valueSize = ComponentUtils.getDigits(triggersSeenByType[eventTriggerID][seenTriggerID]);
+				int cellSize = valueSize * 2 + 3;
+				if(cellSize > longestCell) {
+					longestCell = cellSize;
+					numWidth = valueSize;
+				}
+			}
+		}
+		
+		// The total column width can then be calculated from the
+		// longer of the header and cell values.
+		int columnWidth = ComponentUtils.max(longestCell, longestHeader);
+		
+		// Calculate the total width of the table value header columns.
+		int headerTotalWidth = (TriggerDiagnosticUtil.TRIGGER_NAME.length * columnWidth)
+				+ ((TriggerDiagnosticUtil.TRIGGER_NAME.length - 1) * columnSpacing);
+		
+		// Write the table header.
+		String spacingText = ComponentUtils.getChars(' ', columnSpacing);
+		System.out.println(ComponentUtils.getChars(' ', columnWidth) + spacingText
+				+ getCenteredString(seenName, headerTotalWidth));
+		
+		// Create the format strings for the cell values.
+		String headerFormat = "%-" + columnWidth + "s" + spacingText;
+		String cellFormat = "%" + numWidth + "d / %" + numWidth + "d";
+		String nullText = getCenteredString(ComponentUtils.getChars('-', numWidth) + " / "
+				+ ComponentUtils.getChars('-', numWidth), columnWidth) + spacingText;
+		
+		// Print the column headers.
+		System.out.printf(headerFormat, sourceName);
+		for(String header : TriggerDiagnosticUtil.TRIGGER_NAME) {
+			System.out.print(getCenteredString(header, columnWidth) + spacingText);
+		}
+		System.out.println();
+		
+		// Write out the value columns.
+		for(int eventTriggerID = 0; eventTriggerID < 6; eventTriggerID++) {
+			// Print out the row header.
+			System.out.printf(headerFormat, TriggerDiagnosticUtil.TRIGGER_NAME[eventTriggerID]);
+			
+			// Print the cell values.
+			for(int seenTriggerID = 0; seenTriggerID < 6; seenTriggerID++) {
+				if(seenTriggerID == eventTriggerID) { System.out.print(nullText); }
+				else {
+					String cellText = String.format(cellFormat, triggersMatchedByType[eventTriggerID][seenTriggerID],
+							triggersSeenByType[eventTriggerID][seenTriggerID]);
+					System.out.print(getCenteredString(cellText, columnWidth) + spacingText);
+				}
+			}
+			
+			// Start a new line.
+			System.out.println();
+		}
+	}
+	
+	/**
+	 * Adds triggers in a generic way to the number of triggers seen.
+	 * @param eventTriggerType - The trigger type ID for the event
+	 * trigger type.
+	 * @param triggerList - A list of size two containing the
+	 * triggers seen for each of the two triggers of its type.
+	 * @param triggerTypeID - The two trigger IDs corresponding to the
+	 * list entries.
+	 */
+	private void addTriggers(int eventTriggerType, List<List<? extends Trigger<?>>> triggerList, int[] triggerTypeID) {
+		// Track the total number of singles triggers seen.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			List<? extends Trigger<?>> triggers = triggerList.get(triggerNum);
+			triggersSeenByType[eventTriggerType][triggerTypeID[triggerNum]] += triggers.size();
+		}
+	}
+	
+	/**
+	 * Produces a <code>String</code> of the indicated length with the
+	 * text <code>value</code> centered in the middle. Extra length is
+	 * filled through spaces before and after the text.
+	 * @param value - The text to display.
+	 * @param width - The number of spaces to include.
+	 * @return Returns a <code>String</code> of the specified length,
+	 * or the argument text if it is longer.
+	 */
+	private static final String getCenteredString(String value, int width) {
+		// The method can not perform as intended if the argument text
+		// exceeds the requested string length. Just return the text.
+		if(width <= value.length()) {
+			return value;
+		}
+		
+		// Otherwise, get the amount of buffering needed to center the
+		// text and add it around the text to produce the string.
+		else {
+			int buffer = (width - value.length()) / 2;
+			return ComponentUtils.getChars(' ', buffer) + value
+					+ ComponentUtils.getChars(' ', width - buffer - value.length());
+		}
+	}
+	
+	/**
+	 * Gets the trigger type identifier from a trigger object.
+	 * @param trigger - A trigger.
+	 * @return Returns the trigger type ID of the argument trigger.
+	 */
+	private static final int getTriggerType(Trigger<?> trigger) {
+		// Choose the appropriate trigger type ID based on the class
+		// of the trigger.
+		if(trigger instanceof PairTrigger) {
+			// Use the trigger number to determine which of the two
+			// triggers this is. Note that this assumes that the trigger
+			// number is stored as either 0 or 1.
+			if(trigger.getTriggerNumber() == 0) {
+				return TriggerDiagnosticUtil.TRIGGER_PAIR_1;
+			} else {
+				return TriggerDiagnosticUtil.TRIGGER_PAIR_2;
+			}
+		} else if(trigger instanceof SinglesTrigger) {
+			// Use the trigger number to determine which of the two
+			// triggers this is. Note that this assumes that the trigger
+			// number is stored as either 0 or 1.
+			if(trigger.getTriggerNumber() == 0) {
+				return TriggerDiagnosticUtil.TRIGGER_SINGLES_1;
+			} else {
+				return TriggerDiagnosticUtil.TRIGGER_SINGLES_2;
+			}
+		}
+		
+		// If the trigger type is not supported, throw an exception.
+		throw new IllegalArgumentException(String.format("Trigger type \"%s\" is not supported.",
+				trigger.getClass().getSimpleName()));
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchEvent.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchEvent.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchEvent.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,172 @@
+package org.hps.analysis.trigger.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hps.analysis.trigger.util.Pair;
+import org.hps.analysis.trigger.util.Trigger;
+import org.hps.readout.ecal.triggerbank.SSPNumberedTrigger;
+
+/**
+ * Tracks trigger pairs that were matched within an event. This can also
+ * track simulated SSP cluster pairs along with specifically which cuts
+ * passed and which did not.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TriggerMatchEvent {
+	// Track trigger matching statistics.
+	private int[] sspInternalMatched = new int[2];
+	private int[] reconTriggersMatched = new int[2];
+	private int[][] triggerComp = new int[4][2];
+	
+	// Track the matched trigger pairs.
+	private List<TriggerMatchedPair> sspPairList = new ArrayList<TriggerMatchedPair>();
+	private List<Pair<Trigger<?>, SSPNumberedTrigger>> reconPairList = new ArrayList<Pair<Trigger<?>, SSPNumberedTrigger>>();
+	
+	/**
+	 * Gets the number of times a cut of the given cut ID failed when
+	 * SSP simulated triggers were compared to SSP bank triggers.
+	 * @param triggerNumber - The trigger for which to get the value.
+	 * @param cutID - The ID of the cut.
+	 * @return Returns the number of times the cut failed as an
+	 * <code>int</code> primitive.
+	 */
+	public int getCutFailures(int triggerNumber, int cutID) {
+		// Validate the arguments.
+		if(triggerNumber !=0 && triggerNumber != 1) {
+			throw new IndexOutOfBoundsException("Trigger number must be 0 or 1.");
+		} if(cutID < 0 || cutID > 3) {
+			throw new IndexOutOfBoundsException(String.format("Cut ID \"%d\" is not valid.", cutID));
+		}
+		
+		// Return the requested cut value.
+		return triggerComp[cutID][triggerNumber];
+	}
+	
+	/**
+	 * Gets the number of reconstructed cluster triggers that were
+	 * matched successfully.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedReconTriggers() {
+		return reconPairList.size();
+	}
+	
+	/**
+	 * Gets the number of reconstructed cluster triggers that were
+	 * matched successfully for a specific trigger.
+	 * @param triggerNumber - The trigger number.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedReconTriggers(int triggerNumber) {
+		// Validate the arguments.
+		if(triggerNumber !=0 && triggerNumber != 1) {
+			throw new IndexOutOfBoundsException("Trigger number must be 0 or 1.");
+		}
+		
+		// Return the trigger count.
+		return reconTriggersMatched[triggerNumber];
+	}
+	
+	/**
+	 * Gets the number of simulated SSP cluster triggers that were
+	 * matched successfully.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedSSPTriggers() {
+		return sspPairList.size();
+	}
+	
+	/**
+	 * Gets the number of simulated SSP cluster triggers that were
+	 * matched successfully for a specific trigger.
+	 * @param triggerNumber - The trigger number.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedSSPTriggers(int triggerNumber) {
+		// Validate the arguments.
+		if(triggerNumber !=0 && triggerNumber != 1) {
+			throw new IndexOutOfBoundsException("Trigger number must be 0 or 1.");
+		}
+		
+		// Return the trigger count.
+		return sspInternalMatched[triggerNumber];
+	}
+	
+	/**
+	 * Gets a list containing all reconstructed cluster triggers and
+	 * their matched SSP bank triggers.
+	 * @return Returns the trigger pairs as a <code>List</code>
+	 * collection of <code>Pair</code> objects.
+	 */
+	public List<Pair<Trigger<?>, SSPNumberedTrigger>> getMatchedReconPairs() {
+		return reconPairList;
+	}
+	
+	/**
+	 * Adds a reconstructed trigger and SSP bank trigger pair that is
+	 * marked as matched for all trigger cuts.
+	 * @param reconTrigger - The reconstructed cluster trigger.
+	 * @param sspTrigger - The SSP bank trigger.
+	 */
+	public void matchedReconPair(Trigger<?> reconTrigger, SSPNumberedTrigger sspTrigger) {
+		reconTriggersMatched[sspTrigger.isFirstTrigger() ? 0 : 1]++;
+		reconPairList.add(new Pair<Trigger<?>, SSPNumberedTrigger>(reconTrigger, sspTrigger));
+	}
+	
+	/**
+	 * Adds a simulated SSP trigger and SSP bank trigger pair that is
+	 * marked as matched for all trigger cuts.
+	 * @param simTrigger - The simulated SSP cluster trigger.
+	 * @param sspTrigger - The SSP bank trigger.
+	 */
+	public void matchedSSPPair(Trigger<?> simTrigger, SSPNumberedTrigger sspTrigger) {
+		// A null SSP trigger means that no match was found. This is
+		// treated as a failure due to time, which is not tracked.
+		if(sspTrigger != null) {
+			sspInternalMatched[sspTrigger.isFirstTrigger() ? 0 : 1]++;
+			sspPairList.add(new TriggerMatchedPair(simTrigger, sspTrigger, new boolean[] { true, true, true, true, true }));
+		}
+	}
+	
+	/**
+	 * Adds a simulated SSP trigger and SSP bank trigger pair along
+	 * with the cuts that matched and did not.
+	 * @param simTrigger - The simulated SSP cluster trigger.
+	 * @param sspTrigger - The SSP bank trigger.
+	 * @param cutsMatched - An array indicating which cuts matched and
+	 * which did not.
+	 */
+	public void matchedSSPPair(Trigger<?> simTrigger, SSPNumberedTrigger sspTrigger, boolean[] cutsMatched) {
+		// Store the full cut array.
+		boolean[] cutArray = cutsMatched;
+		
+		// If the array is size 3, it is the a singles trigger. Update
+		// it to an array of size 4 for compatibility.
+		if(cutsMatched.length == 3) {
+			boolean[] tempArray = { true, true, true, true };
+			for(int cutIndex = 0; cutIndex < cutsMatched.length; cutIndex++) {
+				tempArray[cutIndex] = cutsMatched[cutIndex];
+			}
+			cutArray = tempArray;
+		}
+		
+		// Add the trigger pair to the list.
+		TriggerMatchedPair triggerPair = new TriggerMatchedPair(simTrigger, sspTrigger, cutArray);
+		sspPairList.add(triggerPair);
+		
+		// Track which cuts have failed.
+		boolean isMatched = true;
+		int triggerNum = triggerPair.isFirstTrigger() ? 0 : 1;
+		for(int cutIndex = 0; cutIndex < cutsMatched.length; cutIndex++) {
+			if(!cutsMatched[cutIndex]) {
+				triggerComp[cutIndex][triggerNum]++;
+				isMatched = false;
+			}
+		}
+		
+		// If all the cuts are true, then the trigger pair is a match.
+		if(isMatched) { sspInternalMatched[triggerNum]++; }
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchStatus.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchStatus.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchStatus.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,65 @@
+package org.hps.analysis.trigger.event;
+
+import java.util.List;
+
+import org.hps.analysis.trigger.util.Trigger;
+import org.hps.readout.ecal.triggerbank.SSPNumberedTrigger;
+
+/**
+ * Tracks the trigger diagnostic statistics for trigger matching.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TriggerMatchStatus extends TriggerStatModule {
+	/**
+	 * Adds the statistical data stored in a trigger comparison event
+	 * into this status tracking module.
+	 * @param event - The event object.
+	 * @param reconTriggers - A list of reconstructed cluster triggers.
+	 * @param sspSimTriggers - A list of simulated SSP cluster triggers.
+	 * @param sspBankTriggers - A list of SSP bank triggers.
+	 */
+	public void addEvent(TriggerMatchEvent event, List<List<? extends Trigger<?>>> reconTriggers,
+			List<List<? extends Trigger<?>>> sspSimTriggers, List<? extends SSPNumberedTrigger> sspBankTriggers) {
+		// Check if there are more bank triggers than there are
+		// simulated SSP triggers.
+		int sspTriggerDiff = sspBankTriggers.size() - sspSimTriggers.size();
+		if(sspTriggerDiff > 0) {
+			reportedExtras += sspTriggerDiff;
+		}
+		
+		// Increment the number of triggers of each type that have been
+		// seen so far.
+		sspTriggers += sspSimTriggers.get(0).size() + sspSimTriggers.get(1).size();
+		this.reconTriggers += reconTriggers.get(0).size() + reconTriggers.get(1).size();
+		reportedTriggers += sspBankTriggers.size();
+		
+		// Increment the count for each cut failure type.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			for(int cutIndex = 0; cutIndex < 4; cutIndex++) {
+				triggerComp[cutIndex][triggerNum] += event.getCutFailures(triggerNum, cutIndex);
+			}
+		}
+		
+		// Increment the total triggers found.
+		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			sspInternalMatched[triggerNum] += event.getMatchedSSPTriggers(triggerNum);
+			reconTriggersMatched[triggerNum] += event.getMatchedReconTriggers(triggerNum);
+		}
+	}
+	
+	@Override
+	public void clear() {
+		// Clear the statistics module.
+		super.clear();
+	}
+	
+    /**
+     * Gets a copy of the statistical data stored in the object.
+     * @return Returns the data in a <code>TriggerStatModule</code>
+     * object.
+     */
+	public TriggerStatModule cloneStatModule() {
+    	return new TriggerStatModule(this);
+    }
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchedPair.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchedPair.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerMatchedPair.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,140 @@
+package org.hps.analysis.trigger.event;
+
+import org.hps.analysis.trigger.util.Pair;
+import org.hps.analysis.trigger.util.PairTrigger;
+import org.hps.analysis.trigger.util.SinglesTrigger;
+import org.hps.analysis.trigger.util.Trigger;
+import org.hps.readout.ecal.triggerbank.SSPNumberedTrigger;
+import org.hps.readout.ecal.triggerbank.SSPPairTrigger;
+import org.hps.readout.ecal.triggerbank.SSPSinglesTrigger;
+
+/**
+ * Stores a pair of one simulated trigger and one SSP bank trigger that
+ * have been matched, along with which cuts are the same and which are
+ * different.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TriggerMatchedPair extends Pair<Trigger<?>, SSPNumberedTrigger> {
+	// Cut IDs.
+	public static final int SINGLES_ENERGY_MIN = 0;
+	public static final int SINGLES_ENERGY_MAX = 1;
+	public static final int SINGLES_HIT_COUNT = 2;
+	public static final int PAIR_ENERGY_SUM = 0;
+	public static final int PAIR_ENERGY_DIFF = 1;
+	public static final int PAIR_ENERGY_SLOPE = 2;
+	public static final int PAIR_COPLANARITY = 3;
+	
+	// Store the trigger result.
+	private boolean[] matchedCut = new boolean[4];
+	
+	/**
+	 * Creates a matched trigger pair of a simulated trigger and an SSP
+	 * bank trigger.
+	 * @param firstElement - The simulated trigger.
+	 * @param secondElement - The SSP bank trigger.
+	 * @param cutsMatched - An array indicating which cuts match and
+	 * which do not.
+	 */
+	public TriggerMatchedPair(Trigger<?> firstElement, SSPNumberedTrigger secondElement, boolean[] cutsMatched) {
+		// Store the trigger objects.
+		super(firstElement, secondElement);
+		
+		// Set the matched cuts.
+		if(cutsMatched.length != 4) {
+			throw new IllegalArgumentException(String.format("The matched cuts array must be of size 4. Seen size = %d.", cutsMatched.length));
+		} else {
+			matchedCut[0] = cutsMatched[0];
+			matchedCut[1] = cutsMatched[1];
+			matchedCut[2] = cutsMatched[2];
+			matchedCut[3] = cutsMatched[3];
+		}
+	}
+	
+	/**
+	 * Gets whether the cut of the given cut ID passed the trigger.
+	 * @param cutID - The cut ID.
+	 * @return Returns <code>true</code> if the cut passed and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getCutResult(int cutID) {
+		if(cutID > matchedCut.length || cutID < 0) {
+			throw new IndexOutOfBoundsException(String.format("Cut ID \"%d\" is not valid.", cutID));
+		} else {
+			return matchedCut[cutID];
+		}
+	}
+	
+	/**
+	 * Gets the simulated trigger from the pair.
+	 * @return Returns the simulated trigger as an object of the generic
+	 * type <code>Trigger<?></code>.
+	 */
+	public Trigger<?> getSimulatedTrigger() {
+		return getFirstElement();
+	}
+	
+	/**
+	 * Gets the type of cluster on which the trigger was simulated.
+	 * @return Returns the type of cluster as a <code>Class<?></code>
+	 * object.
+	 */
+	public Class<?> getSimulatedTriggerType() {
+		return getFirstElement().getTriggerSource().getClass();
+	}
+	
+	/**
+	 * Gets the SSP bank trigger from the pair.
+	 * @return Returns the SSP bank trigger as an object of the generic
+	 * type <code>SSPNumberedTrigger</code>.
+	 */
+	public SSPNumberedTrigger getSSPTrigger() {
+		return getSecondElement();
+	}
+	
+	/**
+	 * Returns whether the triggers in this pair are from the first
+	 * trigger or not.
+	 * @return Returns <code>true</code> if the triggers are from the
+	 * first trigger and <code>false</code> otherwise.
+	 */
+	public boolean isFirstTrigger() {
+		return getSecondElement().isFirstTrigger();
+	}
+	
+	/**
+	 * Indicates whether this is a pair of pair triggers.
+	 * @return Returns <code>true</code> if the triggers are pair
+	 * triggers and <code>false</code> otherwise.
+	 */
+	public boolean isPairTrigger() {
+		if(getFirstElement() instanceof PairTrigger && getSecondElement() instanceof SSPPairTrigger) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	/**
+	 * Returns whether the triggers in this pair are from the second
+	 * trigger or not.
+	 * @return Returns <code>true</code> if the triggers are from the
+	 * second trigger and <code>false</code> otherwise.
+	 */
+	public boolean isSecondTrigger() {
+		return getSecondElement().isSecondTrigger();
+	}
+	
+	/**
+	 * Indicates whether this is a pair of singles triggers.
+	 * @return Returns <code>true</code> if the triggers are singles
+	 * triggers and <code>false</code> otherwise.
+	 */
+	public boolean isSinglesTrigger() {
+		if(getFirstElement() instanceof SinglesTrigger && getSecondElement() instanceof SSPSinglesTrigger) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerStatModule.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerStatModule.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/event/TriggerStatModule.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,166 @@
+package org.hps.analysis.trigger.event;
+
+/**
+ * Class <code>TriggerStatModule</code> stores the statistical data
+ * for trigger diagnostic trigger matching.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TriggerStatModule {
+	// Track trigger verification statistical information.
+	protected int sspTriggers = 0;
+	protected int reconTriggers = 0;
+	protected int reportedTriggers = 0;
+	protected int reportedExtras = 0;
+	protected int[] sspInternalMatched = new int[2];
+	protected int[] reconTriggersMatched = new int[2];
+	protected int[][] triggerComp = new int[4][2];
+	
+	
+	/**
+	 * Instantiates a <code>TriggerStatModule</code> with no statistics
+	 * stored.
+	 */
+	TriggerStatModule() {  }
+	
+	/**
+	 * Instantiates a <code>TriggerStatModule</code> with no statistics
+	 * cloned from the base object.
+	 * @param base - The source for the statistical data.
+	 */
+	protected TriggerStatModule(TriggerStatModule base) {
+		// Copy all of the values to the clone.
+		sspTriggers = base.sspTriggers;
+		reconTriggers = base.reconTriggers;
+		reportedTriggers = base.reportedTriggers;
+		reportedExtras = base.reportedExtras;
+		for(int i = 0; i < sspInternalMatched.length; i++) {
+			sspInternalMatched[i] = base.sspInternalMatched[i];
+		}
+		for(int i = 0; i < reconTriggersMatched.length; i++) {
+			reconTriggersMatched[i] = base.reconTriggersMatched[i];
+		}
+		for(int i = 0; i < triggerComp.length; i++) {
+			for(int j = 0; j < triggerComp[i].length; j++) {
+				triggerComp[i][j] = base.triggerComp[i][j];
+			}
+		}
+	}
+	
+	/**
+	 * Clears all the tracked values and resets them to the default
+	 * empty value.
+	 */
+	void clear() {
+		sspTriggers = 0;
+		reconTriggers = 0;
+		reportedTriggers = 0;
+		reportedExtras = 0;
+		sspInternalMatched = new int[2];
+		reconTriggersMatched = new int[2];
+		triggerComp = new int[4][2];
+	}
+	
+	/**
+	 * Gets the number of times a cut of the given cut ID failed when
+	 * SSP simulated triggers were compared to SSP bank triggers.
+	 * @param triggerNumber - The trigger for which to get the value.
+	 * @param cutID - The ID of the cut.
+	 * @return Returns the number of times the cut failed as an
+	 * <code>int</code> primitive.
+	 */
+	public int getCutFailures(int triggerNumber, int cutID) {
+		// Validate the arguments.
+		if(triggerNumber !=0 && triggerNumber != 1) {
+			throw new IndexOutOfBoundsException("Trigger number must be 0 or 1.");
+		} if(cutID < 0 || cutID > 3) {
+			throw new IndexOutOfBoundsException(String.format("Cut ID \"%d\" is not valid.", cutID));
+		}
+		
+		// Return the requested cut value.
+		return triggerComp[cutID][triggerNumber];
+	}
+	
+	/**
+	 * Gets the number of SSP bank triggers that were reported in excess
+	 * of the number of simulated SSP triggers seen.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getExtraSSPBankTriggers() {
+		return reportedExtras;
+	}
+	
+	/**
+	 * Gets the number of reconstructed triggers that matched bank SSP
+	 * triggers for both triggers.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedReconTriggers() {
+		return reconTriggersMatched[0] + reconTriggersMatched[1];
+	}
+	
+	/**
+	 * Gets the number of reconstructed triggers that matched bank SSP
+	 * triggers.
+	 * @param triggerNumber - The trigger for which to get the value.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedReconTriggers(int triggerNumber) {
+		// Validate the arguments.
+		if(triggerNumber !=0 && triggerNumber != 1) {
+			throw new IndexOutOfBoundsException("Trigger number must be 0 or 1.");
+		}
+		
+		// Return the trigger count.
+		return reconTriggersMatched[triggerNumber];
+	}
+	
+	/**
+	 * Gets the number of simulated SSP triggers that matched bank SSP
+	 * triggers for both triggers.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedSSPTriggers() {
+		return sspInternalMatched[0] + sspInternalMatched[1];
+	}
+	
+	/**
+	 * Gets the number of simulated SSP triggers that matched bank SSP
+	 * triggers.
+	 * @param triggerNumber - The trigger for which to get the value.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getMatchedSSPTriggers(int triggerNumber) {
+		// Validate the arguments.
+		if(triggerNumber !=0 && triggerNumber != 1) {
+			throw new IndexOutOfBoundsException("Trigger number must be 0 or 1.");
+		}
+		
+		// Return the trigger count.
+		return sspInternalMatched[triggerNumber];
+	}
+	
+	/**
+	 * Gets the number of reconstructed cluster triggers seen.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getReconTriggerCount() {
+		return reconTriggers;
+	}
+	
+	/**
+	 * Gets the number of simulated SSP cluster triggers seen.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getSSPSimTriggerCount() {
+		return sspTriggers;
+	}
+	
+	/**
+	 * Gets the number of triggers reported by the SSP bank.
+	 * @return Returns the value as an <code>int</code> primitive.
+	 */
+	public int getSSPBankTriggerCount() {
+		return reportedTriggers;
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/ComponentUtils.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/ComponentUtils.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/ComponentUtils.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,126 @@
+package org.hps.analysis.trigger.util;
+
+import java.awt.Component;
+
+import org.hps.analysis.trigger.util.TriggerDiagnosticUtil;
+
+/**
+ * Class <code>ComponentUtils</code> is a list of utility methods used
+ * by the trigger diagnostic GUI.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class ComponentUtils {
+	/** The default spacing used between a horizontal edge of one
+	 * component and the horizontal edge of another. */
+	static final int hinternal = 10;
+	/** The default spacing used between a vertical edge of one
+	 * component and the vertical edge of another. */
+	static final int vinternal = 10;
+	/** The default spacing used between a horizontal edge of one
+	 * component and the edge of its parent component. */
+	static final int hexternal = 0;
+	/** The default spacing used between a vertical edge of one
+	 * component and the edge of its parent component. */
+	static final int vexternal = 0;
+	
+	/**
+	 * Gets a <code>String</code> composed of a number of instances of
+	 * character <code>c</code> equal to <code>number</code>.
+	 * @param c - The character to repeat.
+	 * @param number - The number of repetitions.
+	 * @return Returns the repeated character as a <code>String</code>.
+	 */
+	public static final String getChars(char c, int number) {
+		// Create a buffer to store the characters in.
+		StringBuffer s = new StringBuffer();
+		
+		// Add the indicated number of instances.
+		for(int i = 0; i < number; i++) {
+			s.append(c);
+		}
+		
+		// Return the string.
+		return s.toString();
+	}
+	
+	/**
+	 * Gets the number of digits in the base-10 String representation
+	 * of an integer primitive. Negative signs are not included in the
+	 * digit count.
+	 * @param value - The value of which to obtain the length.
+	 * @return Returns the number of digits in the String representation
+	 * of the argument value.
+	 */
+	public static final int getDigits(int value) {
+		return TriggerDiagnosticUtil.getDigits(value);
+	}
+	
+	/**
+	 * Gets the maximum value from a list of values.
+	 * @param values - The values to compare.
+	 * @return Returns the largest of the argument values.
+	 * @throws IllegalArgumentException Occurs if no values are given.
+	 */
+	public static final int max(int... values) throws IllegalArgumentException {
+		// Throw an error if no arguments are provided.
+		if(values == null || values.length == 0) {
+			throw new IllegalArgumentException("Can not determine maximum value from a list of 0 values.");
+		}
+		
+		// If there is only one value, return it.
+		if(values.length == 1) { return values[0]; }
+		
+		// Otherwise, get the largest value.
+		int largest = Integer.MIN_VALUE;
+		for(int value : values) {
+			if(value > largest) { largest = value; }
+		}
+		
+		// Return the result.
+		return largest;
+	}
+	
+	/**
+	 * Gets the x-coordinate immediately to the right of the given
+	 * component.
+	 * @param c - The component of which to find the edge.
+	 * @return Returns the x-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextX(Component c) {
+		return getNextX(c, 0);
+	}
+	
+	/**
+	 * Gets the x-coordinate a given distance to the right edge of the
+	 * argument component.
+	 * @param c - The component of which to find the edge.
+	 * @param spacing - The additional spacing past the edge of the
+	 * component to add.
+	 * @return Returns the x-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextX(Component c, int spacing) {
+		return c.getX() + c.getWidth() + spacing;
+	}
+	
+	/**
+	 * Gets the y-coordinate immediately below the given component.
+	 * @param c - The component of which to find the edge.
+	 * @return Returns the y-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextY(Component c) {
+		return getNextY(c, 0);
+	}
+	
+	/**
+	 * Gets the y-coordinate a given distance below the bottom edge
+	 * of the argument component.
+	 * @param c - The component of which to find the edge.
+	 * @param spacing - The additional spacing past the edge of the
+	 * component to add.
+	 * @return Returns the y-coordinate as an <code>int</code> value.
+	 */
+	static final int getNextY(Component c, int spacing) {
+		return c.getY() + c.getHeight() + spacing;
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/OutputLogger.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/OutputLogger.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/OutputLogger.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,30 @@
+package org.hps.analysis.trigger.util;
+
+public class OutputLogger {
+    private static StringBuffer outputBuffer = new StringBuffer();
+    
+	public static final void printf(String text, Object... args) {
+		outputBuffer.append(String.format(text, args));
+	}
+	
+	public static final void println() { printf(String.format("%n")); }
+	
+	public static final void println(String text) { printf(String.format("%s%n", text)); }
+	
+	public static final void print(String text) { printf(text); }
+	
+	public static final void printLog() {
+		System.out.println(outputBuffer.toString());
+		clearLog();
+	}
+	
+	public static final void printNewLine() { println(); }
+	
+	public static final void printNewLine(int quantity) {
+		for(int i = 0; i < quantity; i++) { println(); }
+	}
+	
+	public static final void clearLog() {
+		outputBuffer = new StringBuffer();
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Pair.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Pair.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Pair.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,39 @@
+package org.hps.analysis.trigger.util;
+
+/**
+ * Class <code>Pair</code> represents a pair of two objects.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @param <E> - The object type of the first element in the pair.
+ * @param <F> - The object type of the second element in the pair.
+ */
+public class Pair<E, F> {
+	private final E firstObject;
+	private final F secondObject;
+	
+	/**
+	 * Creates a pair of the two indicated objects.
+	 * @param firstObject - The first object.
+	 * @param secondObject - The second object.
+	 */
+	public Pair(E firstElement, F secondElement) {
+		this.firstObject = firstElement;
+		this.secondObject = secondElement;
+	}
+	
+	/**
+	 * Gets the first element of the pair.
+	 * @return Returns the first element.
+	 */
+	public E getFirstElement() {
+		return firstObject;
+	}
+	
+	/**
+	 * Gets the second element of the pair.
+	 * @return Returns the second element.
+	 */
+	public F getSecondElement() {
+		return secondObject;
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/PairTrigger.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/PairTrigger.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/PairTrigger.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,166 @@
+package org.hps.analysis.trigger.util;
+
+import org.hps.readout.ecal.TriggerModule;
+
+public class PairTrigger<E> extends SinglesTrigger<E> {
+	// Define the supported trigger cuts.
+	private static final String PAIR_ENERGY_SUM_LOW = TriggerModule.PAIR_ENERGY_SUM_LOW;
+	private static final String PAIR_ENERGY_SUM_HIGH = TriggerModule.PAIR_ENERGY_SUM_HIGH;
+	private static final String PAIR_ENERGY_DIFFERENCE_HIGH = TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH;
+	private static final String PAIR_ENERGY_SLOPE_LOW = TriggerModule.PAIR_ENERGY_SLOPE_LOW;
+	private static final String PAIR_COPLANARITY_HIGH = TriggerModule.PAIR_COPLANARITY_HIGH;
+    private static final String PAIR_TIME_COINCIDENCE = "pairTimeCoincidence";
+	
+	/**
+	 * Instantiates a new <code>PairTrigger</code> with all cut
+	 * states set to <code>false</code> and with the trigger source
+	 * defined according to the specified object.
+	 * @param source - The object from which the trigger cut states
+	 * are derived.
+	 */
+	public PairTrigger(E source, int triggerNum) {
+		// Instantiate the superclass.
+		super(source, triggerNum);
+		
+		// Add the supported cuts types.
+		addValidCut(PAIR_ENERGY_SUM_LOW);
+		addValidCut(PAIR_ENERGY_SUM_HIGH);
+		addValidCut(PAIR_ENERGY_DIFFERENCE_HIGH);
+		addValidCut(PAIR_ENERGY_SLOPE_LOW);
+		addValidCut(PAIR_COPLANARITY_HIGH);
+		addValidCut(PAIR_TIME_COINCIDENCE);
+	}
+	
+	/**
+	 * Gets whether the pair energy sum lower bound cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateEnergySumLow() {
+		return getCutState(PAIR_ENERGY_SUM_LOW);
+	}
+	
+	/**
+	 * Gets whether the pair energy sum upper bound cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateEnergySumHigh() {
+		return getCutState(PAIR_ENERGY_SUM_HIGH);
+	}
+	
+	/**
+	 * Gets whether both the pair energy sum upper and lower bound cuts
+	 * were met.
+	 * @return Returns <code>true</code> if the cuts were met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateEnergySum() {
+		return getCutState(PAIR_ENERGY_SUM_HIGH);
+	}
+	
+	/**
+	 * Gets whether the pair energy difference cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateEnergyDifference() {
+		return getCutState(PAIR_ENERGY_SUM_HIGH);
+	}
+	
+	/**
+	 * Gets whether the pair energy slope cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateEnergySlope() {
+		return getCutState(PAIR_ENERGY_SLOPE_LOW);
+	}
+	
+	/**
+	 * Gets whether the pair coplanarity cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateCoplanarity() {
+		return getCutState(PAIR_COPLANARITY_HIGH);
+	}
+	
+	/**
+	 * Gets whether the time coincidence cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateTimeCoincidence() {
+		return getCutState(PAIR_TIME_COINCIDENCE);
+	}
+	
+	/**
+	 * Sets whether the conditions for the pair energy sum lower bound
+	 * cut were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateEnergySumLow(boolean state) {
+		setCutState(PAIR_ENERGY_SUM_LOW, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the pair energy sum upper bound
+	 * cut were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateEnergySumHigh(boolean state) {
+		setCutState(PAIR_ENERGY_SUM_HIGH, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the pair energy difference cut
+	 * were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateEnergyDifference(boolean state) {
+		setCutState(PAIR_ENERGY_DIFFERENCE_HIGH, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the pair energy slope cut were
+	 * met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateEnergySlope(boolean state) {
+		setCutState(PAIR_ENERGY_SLOPE_LOW, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the pair coplanarity cut were
+	 * met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateCoplanarity(boolean state) {
+		setCutState(PAIR_COPLANARITY_HIGH, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the time coincidence cut were
+	 * met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateTimeCoincidence(boolean state) {
+		setCutState(PAIR_TIME_COINCIDENCE, state);
+	}
+	
+	@Override
+	public String toString() {
+		return String.format("EClusterLow: %d; EClusterHigh %d; HitCount: %d; ESumLow: %d, ESumHigh: %d, EDiff: %d, ESlope: %d, Coplanarity: %d",
+				getStateClusterEnergyLow() ? 1 : 0, getStateClusterEnergyHigh() ? 1 : 0,
+				getStateHitCount() ? 1 : 0, getStateEnergySumLow() ? 1 : 0,
+				getStateEnergySumHigh() ? 1 : 0, getStateEnergyDifference() ? 1 : 0,
+				getStateEnergySlope() ? 1 : 0, getStateCoplanarity() ? 1 : 0);
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/SinglesTrigger.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/SinglesTrigger.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/SinglesTrigger.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,153 @@
+package org.hps.analysis.trigger.util;
+
+import org.hps.readout.ecal.TriggerModule;
+
+public class SinglesTrigger<E> extends Trigger<E> {
+	// Define the supported trigger cuts.
+	private static final String CLUSTER_HIT_COUNT_LOW = TriggerModule.CLUSTER_HIT_COUNT_LOW;
+	private static final String CLUSTER_SEED_ENERGY_LOW = TriggerModule.CLUSTER_SEED_ENERGY_LOW;
+	private static final String CLUSTER_SEED_ENERGY_HIGH = TriggerModule.CLUSTER_SEED_ENERGY_HIGH;
+	private static final String CLUSTER_TOTAL_ENERGY_LOW = TriggerModule.CLUSTER_TOTAL_ENERGY_LOW;
+	private static final String CLUSTER_TOTAL_ENERGY_HIGH = TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH;
+	
+	/**
+	 * Instantiates a new <code>SinglesTrigger</code> with all cut
+	 * states set to <code>false</code> and with the trigger source
+	 * defined according to the specified object.
+	 * @param source - The object from which the trigger cut states
+	 * are derived.
+	 */
+	public SinglesTrigger(E source, int triggerNum) {
+		// Instantiate the superclass.
+		super(source, triggerNum);
+		
+		// Add the supported cuts types.
+		addValidCut(CLUSTER_HIT_COUNT_LOW);
+		addValidCut(CLUSTER_SEED_ENERGY_LOW);
+		addValidCut(CLUSTER_SEED_ENERGY_HIGH);
+		addValidCut(CLUSTER_TOTAL_ENERGY_LOW);
+		addValidCut(CLUSTER_TOTAL_ENERGY_HIGH);
+	}
+	
+	/**
+	 * Gets whether the cluster hit count cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateHitCount() {
+		return getCutState(CLUSTER_HIT_COUNT_LOW);
+	}
+	
+	/**
+	 * Gets whether the cluster seed energy lower bound cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateSeedEnergyLow() {
+		return getCutState(CLUSTER_SEED_ENERGY_LOW);
+	}
+	
+	/**
+	 * Gets whether the cluster seed energy upper bound cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateSeedEnergyHigh() {
+		return getCutState(CLUSTER_SEED_ENERGY_HIGH);
+	}
+	
+	/**
+	 * Gets whether both the cluster seed energy upper and lower bound
+	 * cuts were met.
+	 * @return Returns <code>true</code> if the cuts were met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateSeedEnergy() {
+		return getCutState(CLUSTER_SEED_ENERGY_LOW) && getCutState(CLUSTER_SEED_ENERGY_HIGH);
+	}
+	
+	/**
+	 * Gets whether the cluster total energy lower bound cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateClusterEnergyLow() {
+		return getCutState(CLUSTER_TOTAL_ENERGY_LOW);
+	}
+	
+	/**
+	 * Gets whether the cluster total energy upper bound cut was met.
+	 * @return Returns <code>true</code> if the cut was met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateClusterEnergyHigh() {
+		return getCutState(CLUSTER_TOTAL_ENERGY_HIGH);
+	}
+	
+	/**
+	 * Gets whether both the cluster total energy upper and lower bound
+	 * cuts were met.
+	 * @return Returns <code>true</code> if the cuts were met and
+	 * <code>false</code> otherwise.
+	 */
+	public boolean getStateClusterEnergy() {
+		return getCutState(CLUSTER_TOTAL_ENERGY_LOW) && getCutState(CLUSTER_TOTAL_ENERGY_HIGH);
+	}
+	
+	/**
+	 * Sets whether the conditions for the cluster hit count cut were
+	 * met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateHitCount(boolean state) {
+		setCutState(CLUSTER_HIT_COUNT_LOW, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the cluster seed energy lower
+	 * bound cut were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateSeedEnergyLow(boolean state) {
+		setCutState(CLUSTER_SEED_ENERGY_LOW, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the cluster seed energy upper
+	 * bound cut were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateSeedEnergyHigh(boolean state) {
+		setCutState(CLUSTER_SEED_ENERGY_HIGH, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the cluster total energy lower
+	 * bound cut were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateClusterEnergyLow(boolean state) {
+		setCutState(CLUSTER_TOTAL_ENERGY_LOW, state);
+	}
+	
+	/**
+	 * Sets whether the conditions for the cluster total energy upper
+	 * bound cut were met.
+	 * @param state - <code>true</code> indicates that the cut conditions
+	 * were met and <code>false</code> that they were not.
+	 */
+	public void setStateClusterEnergyHigh(boolean state) {
+		setCutState(CLUSTER_TOTAL_ENERGY_HIGH, state);
+	}
+	
+	@Override
+	public String toString() {
+		return String.format("EClusterLow: %d; EClusterHigh %d; HitCount: %d",
+				getStateClusterEnergyLow() ? 1 : 0, getStateClusterEnergyHigh() ? 1 : 0,
+				getStateHitCount() ? 1 : 0);
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Trigger.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Trigger.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/Trigger.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,152 @@
+package org.hps.analysis.trigger.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Class <code>Trigger</code> stores a set cut states indicating whether
+ * specific cut conditions associated with a trigger were met or not as
+ * well as the state of the overall trigger. It is the responsibility of
+ * implementing classes to specify the supported cut states and also
+ * to define when the trigger conditions are met.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public abstract class Trigger<E> {
+	// Track whether the trigger conditions were met.
+	private boolean passTrigger = false;
+	// Store the cut condition states.
+	private Map<String, Boolean> passMap = new HashMap<String, Boolean>();
+	// Store the cluster associated with the trigger.
+	private final E source;
+	// Store the trigger number.
+	private final int triggerNum;
+	
+	/**
+	 * Creates a new <code>Trigger</code> object with the argument
+	 * specifying the object from whence the trigger state is derived.
+	 * @param source - The trigger source object.
+	 */
+	protected Trigger(E source) {
+		this(source, -1);
+	}
+	
+	/**
+	 * Creates a new <code>Trigger</code> object with the argument
+	 * specifying the object from whence the trigger state is derived.
+	 * @param source - The trigger source object.
+	 * @param triggerNum - The number of the trigger.
+	 */
+	protected Trigger(E source, int triggerNum) {
+		this.source = source;
+		this.triggerNum = triggerNum;
+	}
+	
+	/**
+	 * Adds a cut to the set of cuts tracked by this trigger.
+	 * @param cut - The identifier for the cut.
+	 */
+	protected void addValidCut(String cut) {
+		passMap.put(cut, new Boolean(false));
+	}
+	
+	/**
+	 * Gets the state of the specified cut.
+	 * @param cut - The identifier for the cut.
+	 * @return Returns <code>true</code> if the conditions for the
+	 * specified cut were met and <code>false</code> otherwise.
+	 * @throws IllegalArgumentException Occurs if the specified cut
+	 * is not supported by the object.
+	 */
+	protected boolean getCutState(String cut) throws IllegalArgumentException {
+		if(passMap.containsKey(cut)) {
+			return passMap.get(cut);
+		} else {
+			throw new IllegalArgumentException(String.format("Trigger cut \"%s\" is not a supported trigger cut.", cut));
+		}
+	}
+	
+	/**
+	 * Gets the number of the trigger. If the trigger has no number,
+	 * it will return <code>-1</code>.
+	 * @return Returns the trigger number as an <code>int</code>.
+	 */
+	public int getTriggerNumber() {
+		return triggerNum;
+	}
+	
+	/**
+	 * Gets the object to which the trigger cuts are applied.
+	 * @return Returns the trigger source object.
+	 */
+	public E getTriggerSource() { return source; }
+	
+	/**
+	 * Gets whether the conditions for the trigger were met.
+	 * @return Returns <code>true</code> if the conditions for the
+	 * trigger were met and <code>false</code> if they were not.
+	 */
+	public boolean getTriggerState() {
+		return passTrigger;
+	}
+	
+	/**
+	 * Removes a cut from the set of cuts tracked by the trigger.
+	 * @param cut - The identifier for the cut.
+	 */
+	protected void removeValidCut(String cut) {
+		passMap.remove(cut);
+	}
+	
+	/**
+	 * Checks whether the all of the trigger cut conditions were met.
+	 * @return Returns <code>true</code> if all of the cut conditions
+	 * were met and <code>false</code> otherwise.
+	 */
+	private boolean isValidTrigger() {
+		// Iterate over all of the cuts and look for any that have not
+		// been met.
+		for(Entry<String, Boolean> cut : passMap.entrySet()) {
+			if(!cut.getValue()) { return false; }
+		}
+		
+		// If there are no cut conditions that have not been met, then
+		// the trigger is valid.
+		return true;
+	}
+	
+	/**
+	 * Sets whether the conditions for the specified cut were met.
+	 * @param cut - The identifier for the cut.
+	 * @param state - <code>true</code> indicates that the conditions
+	 * for the cut were met and <code>false</code> that they were not.
+	 * @throws IllegalArgumentException Occurs if the specified cut
+	 * is not supported by the object.
+	 */
+	protected void setCutState(String cut, boolean state) throws IllegalArgumentException {
+		if(passMap.containsKey(cut)) {
+			// Set the cut state.
+			passMap.put(cut, state);
+			
+			// If the cut state is true, then all cut conditions may have
+			// been met. Check whether this is true and, if so, set the
+			// trigger state accordingly.
+			if(state && isValidTrigger()) { passTrigger = true; }
+			else { passTrigger = false; }
+		} else {
+			throw new IllegalArgumentException(String.format("Trigger cut \"%s\" is not a supported trigger cut.", cut));
+		}
+	}
+	
+	/**
+	 * Indicates whether the specified cut state is tracked by this
+	 * object or not.
+	 * @param cut - The identifier for the cut.
+	 * @return Returns <code>true</code> if the cut state is tracked
+	 * by this object and <code>false</code> otherwise.
+	 */
+	protected boolean supportsCut(String cut) {
+		return passMap.containsKey(cut);
+	}
+}

Added: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java	(added)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java	Thu Mar  5 10:59:19 2015
@@ -0,0 +1,220 @@
+package org.hps.analysis.trigger.util;
+
+import java.awt.Point;
+
+import org.hps.readout.ecal.triggerbank.SSPCluster;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.Cluster;
+
+/**
+ * Class <code>TriggerDiagnosticUtil</code> contains a series of
+ * utility methods that are used at various points throughout the
+ * trigger diagnostic package.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TriggerDiagnosticUtil {
+	// Cluster match state variables.
+	public static final byte CLUSTER_STATE_MATCHED        = 0;
+	public static final byte CLUSTER_STATE_FAIL_POSITION  = 1;
+	public static final byte CLUSTER_STATE_FAIL_ENERGY    = 2;
+	public static final byte CLUSTER_STATE_FAIL_HIT_COUNT = 3;
+	public static final byte CLUSTER_STATE_FAIL_UNKNOWN   = 4;
+	
+	// Trigger match cut IDs.
+	public static final int SINGLES_ENERGY_MIN = 0;
+	public static final int SINGLES_ENERGY_MAX = 1;
+	public static final int SINGLES_HIT_COUNT = 2;
+	public static final int PAIR_ENERGY_SUM = 0;
+	public static final int PAIR_ENERGY_DIFF = 1;
+	public static final int PAIR_ENERGY_SLOPE = 2;
+	public static final int PAIR_COPLANARITY = 3;
+	
+	// Trigger type variables.
+	public static final int TRIGGER_PULSER    = 0;
+	public static final int TRIGGER_COSMIC    = 1;
+	public static final int TRIGGER_SINGLES_1 = 2;
+	public static final int TRIGGER_SINGLES_2 = 3;
+	public static final int TRIGGER_PAIR_1    = 4;
+	public static final int TRIGGER_PAIR_2    = 5;
+	public static final String[] TRIGGER_NAME = { "Pulser", "Cosmic", "Singles 1", "Singles 2", "Pair 1", "Pair 2" };
+	
+	/**
+	 * Convenience method that writes the position of a cluster in the
+	 * form (ix, iy).
+	 * @param cluster - The cluster.
+	 * @return Returns the cluster position as a <code>String</code>.
+	 */
+	public static final String clusterPositionString(Cluster cluster) {
+		return String.format("(%3d, %3d)",
+				cluster.getCalorimeterHits().get(0).getIdentifierFieldValue("ix"),
+				cluster.getCalorimeterHits().get(0).getIdentifierFieldValue("iy"));
+	}
+	
+	/**
+	 * Convenience method that writes the position of a cluster in the
+	 * form (ix, iy).
+	 * @param cluster - The cluster.
+	 * @return Returns the cluster position as a <code>String</code>.
+	 */
+	public static final String clusterPositionString(SSPCluster cluster) {
+		return String.format("(%3d, %3d)", cluster.getXIndex(), cluster.getYIndex());
+	}
+	
+	/**
+	 * Convenience method that writes the information in a cluster to
+	 * a <code>String</code>.
+	 * @param cluster - The cluster.
+	 * @return Returns the cluster information as a <code>String</code>.
+	 */
+	public static final String clusterToString(Cluster cluster) {
+		return String.format("Cluster at (%3d, %3d) with %.3f GeV and %d hits at %4.0f ns.",
+				cluster.getCalorimeterHits().get(0).getIdentifierFieldValue("ix"),
+				cluster.getCalorimeterHits().get(0).getIdentifierFieldValue("iy"),
+				cluster.getEnergy(), cluster.getCalorimeterHits().size(),
+				cluster.getCalorimeterHits().get(0).getTime());
+	}
+	
+	/**
+	 * Convenience method that writes the information in a cluster to
+	 * a <code>String</code>.
+	 * @param cluster - The cluster.
+	 * @return Returns the cluster information as a <code>String</code>.
+	 */
+	public static final String clusterToString(SSPCluster cluster) {
+		return String.format("Cluster at (%3d, %3d) with %.3f GeV and %d hits at %4d ns.",
+				cluster.getXIndex(), cluster.getYIndex(), cluster.getEnergy(),
+				cluster.getHitCount(), cluster.getTime());
+	}
+	
+	/**
+	 * Gets the x/y-indices of the cluster.
+	 * @param cluster -  The cluster of which to obtain the indices.
+	 * @return Returns the indices as a <code>Point</code> object.
+	 */
+	public static final Point getClusterPosition(Cluster cluster) {
+		return new Point(getXIndex(cluster), getYIndex(cluster));
+	}
+	
+	/**
+	 * Gets the x/y-indices of the cluster.
+	 * @param cluster -  The cluster of which to obtain the indices.
+	 * @return Returns the indices as a <code>Point</code> object.
+	 */
+	public static final Point getClusterPosition(SSPCluster cluster) {
+		return new Point(cluster.getXIndex(), cluster.getYIndex());
+	}
+	
+	/**
+	 * Gets the time stamp of the cluster in nanoseconds.
+	 * @param cluster - The cluster.
+	 * @return Returns the time-stamp.
+	 */
+	public static final double getClusterTime(Cluster cluster) {
+		return cluster.getCalorimeterHits().get(0).getTime();
+	}
+	
+	/**
+	 * Gets the time stamp of the cluster in nanoseconds.
+	 * @param cluster - The cluster.
+	 * @return Returns the time-stamp.
+	 */
+	public static final int getClusterTime(SSPCluster cluster) {
+		return cluster.getTime();
+	}
+	
+	/**
+	 * Gets the number of digits in the base-10 String representation
+	 * of an integer primitive. Negative signs are not included in the
+	 * digit count.
+	 * @param value - The value of which to obtain the length.
+	 * @return Returns the number of digits in the String representation
+	 * of the argument value.
+	 */
+	public static final int getDigits(int value) {
+		if(value < 0) { return Integer.toString(value).length() - 1; }
+		else { return Integer.toString(value).length(); }
+	}
+	
+	/**
+	 * Gets the number of hits in a cluster.
+	 * @param cluster - The cluster.
+	 * @return Returns the number of hits in the cluster.
+	 */
+	public static final int getHitCount(Cluster cluster) {
+		return cluster.getCalorimeterHits().size();
+	}
+	
+	/**
+	 * Gets the number of hits in a cluster.
+	 * @param cluster - The cluster.
+	 * @return Returns the number of hits in the cluster.
+	 */
+	public static final int getHitCount(SSPCluster cluster) {
+		return cluster.getHitCount();
+	}
+	
+	/**
+	 * Gets the x-index of the cluster's seed hit.
+	 * @param cluster - The cluster.
+	 * @return Returns the x-index.
+	 */
+	public static final int getXIndex(Cluster cluster) {
+		return cluster.getCalorimeterHits().get(0).getIdentifierFieldValue("ix");
+	}
+	
+	/**
+	 * Gets the x-index of the cluster's seed hit.
+	 * @param cluster - The cluster.
+	 * @return Returns the x-index.
+	 */
+	public static final int getXIndex(SSPCluster cluster) {
+		return cluster.getXIndex();
+	}
+	
+	/**
+	 * Gets the y-index of the cluster's seed hit.
+	 * @param cluster - The cluster.
+	 * @return Returns the y-index.
+	 */
+	public static final int getYIndex(Cluster cluster) {
+		return cluster.getCalorimeterHits().get(0).getIdentifierFieldValue("iy");
+	}
+	
+	/**
+	 * Gets the y-index of the cluster's seed hit.
+	 * @param cluster - The cluster.
+	 * @return Returns the y-index.
+	 */
+	public static final int getYIndex(SSPCluster cluster) {
+		return cluster.getYIndex();
+	}
+	
+	/**
+	 * Checks whether all of the hits in a cluster are within the safe
+	 * region of the FADC output window.
+	 * @param reconCluster - The cluster to check.
+	 * @return Returns <code>true</code> if the cluster is safe and
+	 * returns <code>false</code> otherwise.
+	 */
+	public static final boolean isVerifiable(Cluster reconCluster, int nsa, int nsb, int windowWidth) {
+		// Iterate over the hits in the cluster.
+		for(CalorimeterHit hit : reconCluster.getCalorimeterHits()) {
+			// Check that none of the hits are within the disallowed
+			// region of the FADC readout window.
+			if(hit.getTime() <= nsb || hit.getTime() >= (windowWidth - nsa)) {
+				return false;
+			}
+			
+			// Also check to make sure that the cluster does not have
+			// any negative energy hits. These are, obviously, wrong.
+			if(hit.getCorrectedEnergy() < 0.0) {
+				return false;
+			}
+		}
+		
+		// If all of the cluster hits pass the time cut, the cluster
+		// is valid.
+		return true;
+	}
+}

Top of Message | Previous Page | Permalink

Advanced Options


Options

Log In

Log In

Get Password

Get Password


Search Archives

Search Archives


Subscribe or Unsubscribe

Subscribe or Unsubscribe


Archives

November 2017
August 2017
July 2017
January 2017
December 2016
November 2016
October 2016
September 2016
August 2016
July 2016
June 2016
May 2016
April 2016
March 2016
February 2016
January 2016
December 2015
November 2015
October 2015
September 2015
August 2015
July 2015
June 2015
May 2015
April 2015
March 2015
February 2015
January 2015
December 2014
November 2014
October 2014
September 2014
August 2014
July 2014
June 2014
May 2014
April 2014
March 2014
February 2014
January 2014
December 2013
November 2013

ATOM RSS1 RSS2



LISTSERV.SLAC.STANFORD.EDU

Secured by F-Secure Anti-Virus CataList Email List Search Powered by the LISTSERV Email List Manager

Privacy Notice, Security Notice and Terms of Use