Print

Print


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;
+	}
+}