LISTSERV mailing list manager LISTSERV 16.5

Help for HPS-SVN Archives


HPS-SVN Archives

HPS-SVN Archives


HPS-SVN@LISTSERV.SLAC.STANFORD.EDU


View:

Message:

[

First

|

Previous

|

Next

|

Last

]

By Topic:

[

First

|

Previous

|

Next

|

Last

]

By Author:

[

First

|

Previous

|

Next

|

Last

]

Font:

Proportional Font

LISTSERV Archives

LISTSERV Archives

HPS-SVN Home

HPS-SVN Home

HPS-SVN  March 2015

HPS-SVN March 2015

Subject:

r2235 - in /java/trunk/users/src/main/java/org/hps/users/kmccarty: ./ diagpanel/

From:

[log in to unmask]

Reply-To:

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

Date:

Wed, 4 Mar 2015 00:01:59 -0000

Content-Type:

text/plain

Parts/Attachments:

Parts/Attachments

text/plain (2534 lines)

Author: [log in to unmask]
Date: Tue Mar  3 16:01:55 2015
New Revision: 2235

Log:
Updated trigger diagnostics. Basic GUI component added. Still under construction.

Added:
    java/trunk/users/src/main/java/org/hps/users/kmccarty/ClusterMatchStatus.java
    java/trunk/users/src/main/java/org/hps/users/kmccarty/DiagSnapshot.java   (with props)
    java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticGUIDriver.java   (with props)
    java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/
    java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/AbstractTablePanel.java   (with props)
    java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ClusterTablePanel.java   (with props)
    java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ComponentUtils.java   (with props)
    java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/DiagnosticUpdatable.java   (with props)
    java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/TableTextModel.java   (with props)
Modified:
    java/trunk/users/src/main/java/org/hps/users/kmccarty/PairTrigger.java
    java/trunk/users/src/main/java/org/hps/users/kmccarty/SinglesTrigger.java
    java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticDriver.java

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/ClusterMatchStatus.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/ClusterMatchStatus.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/ClusterMatchStatus.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,254 @@
+package org.hps.users.kmccarty;
+
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.hps.readout.ecal.triggerbank.SSPCluster;
+import org.lcsim.event.Cluster;
+
+public class ClusterMatchStatus {
+	// Track cluster statistics.
+	private int sspClusters   = 0;
+	private int reconClusters = 0;
+	private int matches       = 0;
+	private int failEnergy    = 0;
+	private int failPosition  = 0;
+	private int failHitCount  = 0;
+	
+	// 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.
+		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;
+			}
+			
+			// 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 ot its
+	 * default, empty state.
+	 */
+	public void clear() {
+		// Clear statistical data.
+		sspClusters   = 0;
+		reconClusters = 0;
+		matches       = 0;
+		failEnergy    = 0;
+		failPosition  = 0;
+		failHitCount  = 0;
+		
+		// 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 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;
+    }
+    
+	/**
+	 * 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);
+	}
+	
+	/**
+	 * 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()));
+	}
+}

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/DiagSnapshot.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/DiagSnapshot.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/DiagSnapshot.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,8 @@
+package org.hps.users.kmccarty;
+
+public class DiagSnapshot {
+	// UNDER CONSTRUCTION
+	// This is an empty file for now; it is still being built, but the
+	// table and driver won't work unless this object exists, so the
+	// empty class is included for the time being.
+}

Modified: java/trunk/users/src/main/java/org/hps/users/kmccarty/PairTrigger.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/PairTrigger.java	(original)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/PairTrigger.java	Tue Mar  3 16:01:55 2015
@@ -154,4 +154,13 @@
 	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);
+	}
 }

Modified: java/trunk/users/src/main/java/org/hps/users/kmccarty/SinglesTrigger.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/SinglesTrigger.java	(original)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/SinglesTrigger.java	Tue Mar  3 16:01:55 2015
@@ -143,4 +143,11 @@
 	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);
+	}
 }

Modified: java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticDriver.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticDriver.java	(original)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticDriver.java	Tue Mar  3 16:01:55 2015
@@ -17,6 +17,7 @@
 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;
@@ -62,16 +63,8 @@
 	private boolean performPairTriggerVerification = true;
 	
 	// Efficiency tracking variables.
-	private static final int GLOBAL      = 0;
-	private static final int LOCAL       = 1;
-	private static final int EVENT       = 2;
-	
-	private int[] clusterMatched      = new int[3];
-	private int[] clusterSSPCount     = new int[3];
-	private int[] clusterFailEnergy   = new int[3];
-	private int[] clusterReconCount   = new int[3];
-	private int[] clusterFailPosition = new int[3];
-	private int[] clusterFailHitCount = new int[3];
+	private ClusterMatchStatus clusterRunStats = new ClusterMatchStatus();
+	private ClusterMatchStatus clusterLocalStats = new ClusterMatchStatus();
 	
 	private int singlesSSPTriggers = 0;
 	private int singlesReconMatched = 0;
@@ -127,6 +120,16 @@
     private boolean printPairTriggerInternalFail = true;
     private StringBuffer outputBuffer = new StringBuffer();
     
+    // Cut index arrays for trigger verification.
+	private static final int ENERGY_MIN = 0;
+	private static final int ENERGY_MAX = 1;
+	private static final int HIT_COUNT = 2;
+	private static final int ENERGY_SUM = 0;
+	private static final int ENERGY_DIFF = 1;
+	private static final int ENERGY_SLOPE = 2;
+	private static final int COPLANARITY = 3;
+	private static final int TIME = 4;
+    
 	/**
 	 * Define the trigger modules. This should be replaced by parsing
 	 * the DAQ configuration at some point.
@@ -151,12 +154,12 @@
 		System.out.println("=== Cluster/Trigger Verification Settings ============================");
 		System.out.println("======================================================================");
 		
-		/* Runs 2040+
 		// Set the FADC settings.
-		nsa = 240;
-		nsb = 12;
+		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);
@@ -271,14 +274,15 @@
 		
 		// Print the cluster verification data.
 		System.out.println("Cluster Verification:");
-		System.out.printf("\tRecon Clusters        :: %d%n", clusterReconCount[GLOBAL]);
-		System.out.printf("\tSSP Clusters          :: %d%n", clusterSSPCount[GLOBAL]);
-		System.out.printf("\tClusters Matched      :: %d%n", clusterMatched[GLOBAL]);
-		System.out.printf("\tFailed (Position)     :: %d%n", clusterFailPosition[GLOBAL]);
-		System.out.printf("\tFailed (Energy)       :: %d%n", clusterFailEnergy[GLOBAL]);
-		System.out.printf("\tFailed (Hit Count)    :: %d%n", clusterFailHitCount[GLOBAL]);
-		if(clusterReconCount[GLOBAL] == 0) { System.out.printf("\tCluster Efficiency    :: N/A%n"); }
-		else { System.out.printf("\tCluster Efficiency :: %7.3f%%%n", 100.0 * clusterMatched[GLOBAL] / clusterReconCount[GLOBAL]); }
+	
+		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 singles trigger verification data.
 		int spaces = getPrintSpaces(singlesSSPTriggers, singlesReconTriggers,
@@ -382,6 +386,16 @@
 		// ==========================================================
 		// ==== Initialize the Event ================================
 		// ==========================================================
+        
+        // Print the verification header.
+		println();
+		println();
+		println("======================================================================");
+		println("==== Cluster/Trigger Verification ====================================");
+		println("======================================================================");
+		
+		// Increment the total event count.
+		totalEvents++;
 		
 		// Reset the output buffer and print flags.
 		outputBuffer = new StringBuffer();
@@ -426,19 +440,86 @@
             }
         }
         */
+		
+		
+		
+		// ==========================================================
+		// ==== 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()) { println("Trigger type :: Pulser"); }
+					else if(tiBank.isSingle0Trigger() || tiBank.isSingle1Trigger()) { println("Trigger type :: Singles"); }
+					else if(tiBank.isPair0Trigger() || tiBank.isPair1Trigger()) { println("Trigger type :: Pair"); }
+					else if(tiBank.isCalibTrigger()) { println("Trigger type :: Cosmic"); }
+				}
+			}
+			
+			// If there is an SSP bank, get the list of SSP clusters.
+			if(sspBank != null) {
+				sspClusters = sspBank.getClusters();
+				if(sspClusters.size() == 1) {
+					println("1 SSP cluster found.");
+				} else {
+					printf("%d SSP clusters found.%n", sspClusters.size());
+				}
+			}
+		}
+		
+		
+		
+		// ==========================================================
+		// ==== Establish Event Integrity ===========================
+		// ==========================================================
+		
+		// Check that all of the required objects are present.
+		if(sspBank == null) {
+			println("No SSP bank found for this event. No verification will be performed.");
+			if(verbose) { System.out.println(outputBuffer.toString()); }
+			return;
+		} if(tiBank == null) {
+			println("No TI bank found for this event. No verification will be performed.");
+			if(verbose) { System.out.println(outputBuffer.toString()); }
+			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++;
+				println("Noise event detected. Skipping event...");
+				if(verbose) { System.out.println(outputBuffer.toString()); }
+				return;
+			}
+		}
         
         
         
 		// ==========================================================
 		// ==== Obtain Reconstructed Clusters =======================
 		// ==========================================================
-        
-        // Print the verification header.
-		println();
-		println();
-		println("======================================================================");
-		println("==== Cluster/Trigger Verification ====================================");
-		println("======================================================================");
 		
 		// Clear the list of triggers from previous events.
 		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
@@ -519,80 +600,6 @@
 		
 		
 		// ==========================================================
-		// ==== 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()) { println("Trigger type :: Pulser"); }
-					else if(tiBank.isSingle0Trigger() || tiBank.isSingle1Trigger()) { println("Trigger type :: Singles"); }
-					else if(tiBank.isPair0Trigger() || tiBank.isPair1Trigger()) { println("Trigger type :: Pair"); }
-					else if(tiBank.isCalibTrigger()) { println("Trigger type :: Cosmic"); }
-				}
-			}
-			
-			// If there is an SSP bank, get the list of SSP clusters.
-			if(sspBank != null) {
-				sspClusters = sspBank.getClusters();
-				if(sspClusters.size() == 1) {
-					println("1 SSP cluster found.");
-				} else {
-					printf("%d SSP clusters found.%n", sspClusters.size());
-				}
-			}
-		}
-		
-		
-		
-		// ==========================================================
-		// ==== Establish Event Integrity ===========================
-		// ==========================================================
-		
-		// Check that all of the required objects are present.
-		if(sspBank == null) {
-			println("No SSP bank found for this event. No verification will be performed.");
-			return;
-		} if(tiBank == null) {
-			println("No TI bank found for this event. No verification will be performed.");
-			return;
-		}
-		
-		
-		
-		// ==========================================================
-		// ==== Check the Noise Level ===============================
-		// ==========================================================
-		
-		// Increment the total event count.
-		totalEvents++;
-		
-		// 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++;
-				println("Noise event detected. Skipping event...");
-				return;
-			}
-		}
-		
-		
-		
-		// ==========================================================
 		// ==== Perform Event Verification ==========================
 		// ==========================================================
 		
@@ -638,13 +645,8 @@
 			// Write a snapshot of the driver to the event stream.
 			// TODO: Readout the snapshot!!
 			
-			// Reset the local variables to zero.
-			clusterMatched[LOCAL]      = 0;
-			clusterSSPCount[LOCAL]     = 0;
-			clusterFailEnergy[LOCAL]   = 0;
-			clusterReconCount[LOCAL]   = 0;
-			clusterFailPosition[LOCAL] = 0;
-			clusterFailHitCount[LOCAL] = 0;
+			// Clear the local statistical data.
+			clusterLocalStats.clear();
 		}
 	}
 	
@@ -721,8 +723,6 @@
 		
 		// Track the number of cluster pairs that were matched and that
 		// failed by failure type.
-		clusterSSPCount[EVENT]     = 0;
-		clusterReconCount[EVENT]   = 0;
 		ClusterMatchEvent event = new ClusterMatchEvent();
 		
 		
@@ -751,9 +751,6 @@
 			
 			// Add the cluster to the list.
 			reconList.add(reconCluster);
-			
-			// Count the reconstructed clusters.
-			clusterReconCount[EVENT]++;
 		}
 		
 		// Populate the SSP cluster map.
@@ -770,9 +767,6 @@
 			
 			// Add the cluster to the list.
 			sspList.add(sspCluster);
-			
-			// Count the SSP clusters.
-			clusterSSPCount[EVENT]++;
 		}
 		
 		
@@ -797,18 +791,17 @@
 			// reason of position. The remainder of the loop may be
 			// skipped, since there is nothing to check.
 			if(sspList == null || sspList.isEmpty()) {
-				clusterFailPosition[EVENT] += reconList.size();
 				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;
-				clusterFailPosition[EVENT] += (sspList.size() - reconList.size());
-			}
+			if(sspList.size() < reconList.size()) { clusterFail = true; }
 			
 			// Get all possible permutations of SSP clusters.
 			List<List<Pair>> permutations = getPermutations(reconList, sspList);
@@ -835,9 +828,6 @@
 				// Try to match each pair.
 				pairLoop:
 				for(Pair pair : pairs) {
-					// Track the state of the current pair.
-					//int pairState = STATE_CLUSTER_UNDEFINED;
-					
 					// Print the current reconstructed/SSP cluster pair.
 					printf("\tP%d :: %s --> %s", permIndex,
 							pair.reconCluster == null ? "None" : clusterToString(pair.reconCluster),
@@ -861,16 +851,13 @@
 						if(pair.sspCluster.getHitCount() >= pair.reconCluster.getCalorimeterHits().size() - hitAcceptance &&
 								pair.sspCluster.getHitCount() <= pair.reconCluster.getCalorimeterHits().size() + hitAcceptance) {
 							// Designate the pair as a match.
-							//pairState = STATE_CLUSTER_SUCCESS_MATCH;
 							perm.pairMatch(pair.reconCluster, pair.sspCluster);
 							printf(" [ %18s ]%n", "success: matched");
 						} else {
-							//pairState = STATE_CLUSTER_FAIL_HIT_COUNT;
 							perm.pairFailHitCount(pair.reconCluster, pair.sspCluster);
 							printf(" [ %18s ]%n", "failure: hit count");
 						} // End hit count check.
 					} else {
-						//pairState = STATE_CLUSTER_FAIL_ENERGY;
 						perm.pairFailEnergy(pair.reconCluster, pair.sspCluster);
 						printf(" [ %18s ]%n", "failure: energy");
 					} // End energy check.
@@ -900,20 +887,8 @@
 		} // End Crystal Position Loop
 		
 		// Add the event results to the global results.
-		clusterMatched[GLOBAL]      += event.getMatches();
-		clusterFailPosition[GLOBAL] += clusterFailPosition[EVENT];
-		clusterFailEnergy[GLOBAL]   += event.getEnergyFailures();
-		clusterFailHitCount[GLOBAL] += event.getHitCountFailures();
-		clusterReconCount[GLOBAL]   += clusterReconCount[EVENT];
-		clusterSSPCount[GLOBAL]     += clusterSSPCount[EVENT];
-		
-		// Add the event results to the local results.
-		clusterMatched[LOCAL]      += event.getMatches();
-		clusterFailPosition[LOCAL] += clusterFailPosition[EVENT];
-		clusterFailEnergy[LOCAL]   += event.getEnergyFailures();
-		clusterFailHitCount[LOCAL] += event.getHitCountFailures();
-		clusterReconCount[LOCAL]   += clusterReconCount[EVENT];
-		clusterSSPCount[LOCAL]     += clusterSSPCount[EVENT];
+		clusterRunStats.addEvent(event, reconClusters, sspClusters);
+		clusterLocalStats.addEvent(event, reconClusters, sspClusters);
 		
 		
 		
@@ -954,18 +929,24 @@
 		}
 		 else { 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.
 		println();
 		println("Event Statistics:");
 		printf("\tRecon Clusters     :: %d%n", reconClusters.size());
 		printf("\tClusters Matched   :: %d%n", event.getMatches());
-		printf("\tFailed (Position)  :: %d%n", clusterFailPosition[EVENT]);
+		printf("\tFailed (Position)  :: %d%n", failPosition);
 		printf("\tFailed (Energy)    :: %d%n", event.getEnergyFailures());
 		printf("\tFailed (Hit Count) :: %d%n", event.getHitCountFailures());
 		printf("\tCluster Efficiency :: %3.0f%%%n", 100.0 * event.getMatches() / reconClusters.size());
 		
 		// Note whether there was a cluster match failure.
-		if(clusterMatched[EVENT] - reconClusters.size() != 0) {
+		if(event.getMatches() - reconClusters.size() != 0) {
 			clusterFail = true;
 		}
 	}
@@ -977,15 +958,66 @@
 	 * simulated on reconstructed clusters to measure trigger efficiency.
 	 */
 	private void singlesTriggerVerification() {
-		// ==========================================================
-		// ==== Initialize Singles Trigger Verification =============
+		// 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);
+	}
+	
+	private void triggerVerification(List<List<? extends Trigger<?>>> sspTriggerList, 
+			List<List<? extends Trigger<?>>> reconTriggerList, boolean isSingles) {
+		
+		// ==========================================================
+		// ==== Initialize Trigger Verification =====================
 		// ==========================================================
 		
 		// Print the cluster verification header.
 		println();
 		println();
 		println("======================================================================");
-		println("=== Singles Trigger Verification =====================================");
+		if(isSingles) { println("=== Singles Trigger Verification ====================================="); }
+		else { println("=== Pair Trigger Verification ========================================"); }
 		println("======================================================================");
 		
 		// Track the number of triggers seen and the number found.
@@ -996,10 +1028,7 @@
 		
 		// Track the number of times a given cut caused a trigger to
 		// fail to match.
-		int[] eventEnergyMin = new int[2];
-		int[] eventEnergyMax = new int[2];
-		int[] eventHitCount = new int[2];
-		int[] eventTime = new int[2];
+		int[][] triggerComp = new int[5][2];
 		
 		
 		
@@ -1008,52 +1037,45 @@
 		// ==========================================================
 		
 		// Get the list of triggers reported by the SSP.
-		List<SSPSinglesTrigger> sspTriggers = sspBank.getSinglesTriggers();
+		List<? extends SSPNumberedTrigger> sspTriggers;
+		if(isSingles) { sspTriggers = sspBank.getSinglesTriggers(); }
+		else { sspTriggers = sspBank.getPairTriggers(); }
 		
 		// Output the SSP cluster singles triggers.
 		println();
-		println("SSP Cluster Singles Triggers");
+		println("SSP Cluster " + (isSingles ? "Singles" : "Pair") + " Triggers");
 		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(SinglesTrigger<SSPCluster> simTrigger : sspSinglesTriggers.get(triggerNum)) {
-				printf("\tTrigger %d :: %s :: EClusterLow: %d; EClusterHigh %d; HitCount: %d%n",
-						(triggerNum + 1), clusterPositionString(simTrigger.getTriggerSource()),
-						simTrigger.getStateClusterEnergyLow() ? 1 : 0,
-						simTrigger.getStateClusterEnergyHigh() ? 1 : 0,
-						simTrigger.getStateHitCount() ? 1 : 0);
-			}
-		}
-		if(sspSinglesTriggers.get(0).size() + sspSinglesTriggers.get(1).size() == 0) {
+			for(Trigger<?> simTrigger : sspTriggerList.get(triggerNum)) {
+				printf("\tTrigger %d :: %s :: %s%n",
+						(triggerNum + 1), triggerPositionString(simTrigger),
+						simTrigger.toString());
+			}
+		}
+		if(sspTriggerList.get(0).size() + sspTriggerList.get(1).size() == 0) {
 			println("\tNone");
 		}
 		
 		// Output the reconstructed cluster singles triggers.
-		println("Reconstructed Cluster Singles Triggers");
+		println("Reconstructed Cluster " + (isSingles ? "Singles" : "Pair") + " Triggers");
 		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(SinglesTrigger<Cluster> simTrigger : reconSinglesTriggers.get(triggerNum)) {
-				printf("\tTrigger %d :: %s :: EClusterLow: %d; EClusterHigh %d; HitCount: %d%n",
-						(triggerNum + 1), clusterPositionString(simTrigger.getTriggerSource()),
-						simTrigger.getStateClusterEnergyLow() ? 1 : 0,
-						simTrigger.getStateClusterEnergyHigh() ? 1 : 0,
-						simTrigger.getStateHitCount() ? 1 : 0);
-			}
-		}
-		if(reconSinglesTriggers.get(0).size() + reconSinglesTriggers.get(1).size() == 0) {
+			for(Trigger<?> simTrigger : reconTriggerList.get(triggerNum)) {
+				printf("\tTrigger %d :: %s :: %s%n",
+						(triggerNum + 1), triggerPositionString(simTrigger),
+						simTrigger.toString());
+			}
+		}
+		if(reconTriggerList.get(0).size() + reconTriggerList.get(1).size() == 0) {
 			println("\tNone");
 		}
 		
 		// Output the SSP reported triggers.
-		println("SSP Reported Singles Triggers");
-		for(SSPSinglesTrigger sspTrigger : sspTriggers) {
+		println("SSP Reported " + (isSingles ? "Singles" : "Pair") + " Triggers");
+		for(SSPTrigger sspTrigger : sspTriggers) {
 			// Increment the number of SSP cluster singles triggers.
 			sspReportedTriggers++;
 			
-			// Get the trigger properties.
-			int triggerNum = sspTrigger.isFirstTrigger() ? 1 : 2;
-			
 			// Print the trigger.
-			printf("\tTrigger %d :: %3d ns :: EClusterLow: %d; EClusterHigh %d; HitCount: %d%n",
-					triggerNum, sspTrigger.getTime(), sspTrigger.passCutEnergyMin() ? 1 : 0,
-					sspTrigger.passCutEnergyMax() ? 1 : 0, sspTrigger.passCutHitCount() ? 1 : 0);
+			printf("\t%s%n", sspTrigger.toString());
 		}
 		if(sspReportedTriggers == 0) { println("\tNone"); }
 		
@@ -1066,21 +1088,22 @@
 		// Track which SSP triggers have been matched to avoid matching
 		// multiple reconstructed SSP cluster triggers to the same SSP
 		// trigger.
-		Set<SSPSinglesTrigger> sspTriggerSet = new HashSet<SSPSinglesTrigger>();
-		Set<SinglesTrigger<SSPCluster>> simTriggerSet = new HashSet<SinglesTrigger<SSPCluster>>();
+		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 extraTriggers = sspTriggers.size() - (sspSinglesTriggers.get(0).size() + sspSinglesTriggers.get(1).size());
+		int extraTriggers = sspTriggers.size() - (sspTriggerList.get(0).size() + sspTriggerList.get(1).size());
 		if(extraTriggers > 0) {
 			sspReportedExtras += extraTriggers;
-			singlesInternalFail = true;
+			if(isSingles) { singlesInternalFail = true; }
+			else { pairInternalFail = true; }
 		}
 		
 		// Iterate over the triggers.
 		println();
 		println("SSP Reported Trigger --> SSP Cluster Trigger Match Status");
-		for(SSPSinglesTrigger sspTrigger : sspTriggers) {
+		for(SSPNumberedTrigger sspTrigger : sspTriggers) {
 			// Get the trigger information.
 			int triggerNum = sspTrigger.isFirstTrigger() ? 0 : 1;
 			boolean matchedTrigger = false;
@@ -1088,14 +1111,14 @@
 			// Iterate over the SSP cluster simulated triggers and
 			// look for a trigger that matches.
 			matchLoop:
-			for(SinglesTrigger<SSPCluster> simTrigger : sspSinglesTriggers.get(triggerNum)) {
+			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(compareSSPSinglesTriggers(sspTrigger, simTrigger)) {
+				if(compareTriggers(sspTrigger, simTrigger)) {
 					matchedTrigger = true;
 					sspTriggerSet.add(sspTrigger);
 					simTriggerSet.add(simTrigger);
@@ -1103,10 +1126,7 @@
 					break matchLoop;
 				}
 				
-				printf("\tTrigger %d :: %3d :: EClusterLow: %d; EClusterHigh %d; HitCount: %d :: Matched: %5b%n",
-						(triggerNum + 1), sspTrigger.getTime(), sspTrigger.passCutEnergyMin() ? 1 : 0,
-						sspTrigger.passCutEnergyMax() ? 1 : 0, sspTrigger.passCutHitCount() ? 1 : 0,
-						matchedTrigger);
+				printf("\t%s :: Matched: %5b%n", sspTrigger.toString(), matchedTrigger);
 			}
 		}
 		
@@ -1114,23 +1134,25 @@
 		// unmatched SSP reported trigger that most closely matches it.
 		simLoop:
 		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(SinglesTrigger<SSPCluster> simTrigger : sspSinglesTriggers.get(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 foundBest = false;
-				boolean[] matchedCut = new boolean[3];
+				boolean[] matchedCut = null;
 				
 				// Iterate over the reported triggers to find a match.
 				reportedLoop:
-				for(SSPSinglesTrigger sspTrigger : sspTriggers) {
+				for(SSPNumberedTrigger sspTrigger : sspTriggers) {
 					// If the two triggers have different times, this
 					// trigger should be skipped.
-					if(sspTrigger.getTime() != simTrigger.getTriggerSource().getTime()) {
+					if(sspTrigger.getTime() != simTime) {
 						continue reportedLoop;
 					}
 					
@@ -1139,10 +1161,7 @@
 					if(sspTriggerSet.contains(sspTrigger)) { continue reportedLoop; }
 					
 					// Check each of the cuts.
-					boolean[] tempMatchedCut = new boolean[3];
-					tempMatchedCut[0] = (simTrigger.getStateClusterEnergyLow()  == sspTrigger.passCutEnergyMin());
-					tempMatchedCut[1] = (simTrigger.getStateClusterEnergyHigh() == sspTrigger.passCutEnergyMax());
-					tempMatchedCut[2] = (simTrigger.getStateHitCount()          == sspTrigger.passCutHitCount());
+					boolean[] tempMatchedCut = triggerCutMatch(simTrigger, sspTrigger);
 					
 					// Check each cut and see if this is a closer match
 					// than the previous best match.
@@ -1152,7 +1171,6 @@
 					// If the number of matched cuts exceeds the old
 					// best result, this becomes the new best result.
 					if(tempNumMatched > numMatched) {
-						foundBest = true;
 						numMatched = tempNumMatched;
 						matchedCut = tempMatchedCut;
 					}
@@ -1160,18 +1178,19 @@
 				
 				// If some match was found, note what caused it to not
 				// qualify as a complete match.
-				if(foundBest) {
-					if(!matchedCut[0]) { eventEnergyMin[triggerNum]++; }
-					if(!matchedCut[1]) { eventEnergyMax[triggerNum]++; }
-					if(!matchedCut[2]) { eventHitCount[triggerNum]++; }
+				if(matchedCut != null) {
+					for(int cutIndex = 0; cutIndex < matchedCut.length; cutIndex++) {
+						if(!matchedCut[cutIndex]) { triggerComp[cutIndex][triggerNum]++; }
+					}
 				}
 				
 				// 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.
 				else {
-					eventTime[triggerNum]++;
-					singlesInternalFail = true;
+					triggerComp[TIME][triggerNum]++;
+					if(isSingles) { singlesInternalFail = true; }
+					else { pairInternalFail = true; }
 				}
 			}
 		}
@@ -1179,7 +1198,7 @@
 		
 		
 		// ==========================================================
-		// ==== SSP Singles Trigger Efficiency ======================
+		// ==== Trigger Efficiency ==================================
 		// ==========================================================
 		
 		// Reset the SSP matched trigger set.
@@ -1189,70 +1208,52 @@
 		println();
 		println("Recon Cluster Trigger --> SSP Reported Trigger Match Status");
 		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(SinglesTrigger<Cluster> simTrigger : reconSinglesTriggers.get(triggerNum)) {
-				
-				printf("\tTrigger %d :: %s :: EClusterLow: %d; EClusterHigh %d; HitCount: %d%n",
-						(triggerNum + 1), clusterPositionString(simTrigger.getTriggerSource()),
-						simTrigger.getStateClusterEnergyLow() ? 1 : 0,
-						simTrigger.getStateClusterEnergyHigh() ? 1 : 0,
-						simTrigger.getStateHitCount() ? 1 : 0);
-				printf("\t\tCluster Energy :: %.3f GeV; Hit Count :: %d%n", simTrigger.getTriggerSource().getEnergy(),
-						simTrigger.getTriggerSource().getCalorimeterHits().size());
-				printf("\t\tCluster Energy Cut :: [ %.3f GeV, %.3f GeV ]; Hit Count :: [ %.0f, INF )%n",
-						singlesTrigger[triggerNum].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW),
-						singlesTrigger[triggerNum].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH),
-						singlesTrigger[triggerNum].getCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW));
+			for(Trigger<?> simTrigger : reconTriggerList.get(triggerNum)) {
+				
+				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(SSPTrigger sspTrigger : sspTriggers) {
-					// Only compare singles triggers.
-					if(sspTrigger instanceof SSPSinglesTrigger) {
-						// Cast the trigger.
-						SSPSinglesTrigger sspSingles = (SSPSinglesTrigger) sspTrigger;
-						
-						printf("\t\t\tTrigger %d :: %3d ns :: EClusterLow: %d; EClusterHigh %d; HitCount: %d",
-								sspSingles.isFirstTrigger() ? 1 : 2,
-								sspSingles.getTime(),
-								sspSingles.passCutEnergyMin() ? 1 : 0,
-								sspSingles.passCutEnergyMax() ? 1 : 0,
-								sspSingles.passCutHitCount() ? 1 : 0);
-						
-						// Only compare triggers if they are from the
-						// same trigger source.
-						if((triggerNum == 0 && sspSingles.isSecondTrigger())
-								|| (triggerNum == 1 && sspSingles.isFirstTrigger())) {
-							print(" [ fail; source    ]%n");
+				for(SSPNumberedTrigger sspTrigger : sspTriggers) {
+					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())) {
+						print(" [ fail; source    ]%n");
+						continue matchLoop;
+					}
+					
+					// Only compare the singles trigger if it was
+					// not already matched to another trigger.
+					if(sspTriggerSet.contains(sspTrigger)) {
+						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]) {
+							printf(" [ fail; %-9s ]%n", cutNames[typeIndex][cutIndex]);
 							continue matchLoop;
 						}
-						
-						// Only compare the singles trigger if it was
-						// not already matched to another trigger.
-						if(sspTriggerSet.contains(sspSingles)) {
-							print(" [ fail; matched   ]%n");
-							continue matchLoop;
-						}
-						
-						// Test each cut.
-						if(sspSingles.passCutEnergyMin() != simTrigger.getStateClusterEnergyLow()) {
-							print(" [ fail; E_min     ]%n");
-							continue matchLoop;
-						} if(sspSingles.passCutEnergyMax() != simTrigger.getStateClusterEnergyHigh()) {
-							print(" [ fail; E_max     ]%n");
-							continue matchLoop;
-						} if(sspSingles.passCutHitCount() != simTrigger.getStateHitCount()) {
-							print(" [ fail; hit count ]%n");
-							continue matchLoop;
-						}
-						
-						// If all the trigger flags match, then the
-						// triggers are a match.
-						reconTriggersMatched++;
-						sspTriggerSet.add(sspSingles);
-						print(" [ success         ]%n");
-						break matchLoop;
 					}
+					
+					// If all the trigger flags match, then the
+					// triggers are a match.
+					reconTriggersMatched++;
+					sspTriggerSet.add(sspTrigger);
+					print(" [ success         ]%n");
+					break matchLoop;
 				}
 			}
 		}
@@ -1265,8 +1266,9 @@
 		
 		// Get the number of SSP and reconstructed cluster simulated
 		// triggers.
-		int sspSimTriggers = sspSinglesTriggers.get(0).size() + sspSinglesTriggers.get(1).size();
-		int reconSimTriggers = reconSinglesTriggers.get(0).size() + reconSinglesTriggers.get(1).size();
+		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.
 		println();
@@ -1290,402 +1292,85 @@
 		}
 		
 		// Print the individual cut performances.
-		println();
-		int halfSimTriggers = sspSimTriggers / 2;
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-		printf("Trigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
-			if(sspSimTriggers == 0) {
-				printf("\tCluster Energy Lower Bound :: %d / %d%n", eventEnergyMin[triggerNum], halfSimTriggers);
-				printf("\tCluster Energy Upper Bound :: %d / %d%n", eventEnergyMax[triggerNum], halfSimTriggers);
-				printf("\tCluster Hit Count          :: %d / %d%n", eventHitCount[triggerNum], halfSimTriggers);
-			} else {
-				printf("\tCluster Energy Lower Bound :: %d / %d (%3.0f%%)%n",
-						eventEnergyMin[triggerNum], halfSimTriggers, (100.0 * eventEnergyMin[triggerNum] / halfSimTriggers));
-				printf("\tCluster Energy Upper Bound :: %d / %d (%3.0f%%)%n",
-						eventEnergyMax[triggerNum], halfSimTriggers, (100.0 * eventEnergyMax[triggerNum] / halfSimTriggers));
-				printf("\tCluster Hit Count          :: %d / %d (%3.0f%%)%n",
-						eventHitCount[triggerNum], halfSimTriggers, (100.0 * eventHitCount[triggerNum] / halfSimTriggers));
-			}
-			printf("\tExcess Reported Triggers   :: %d%n", sspReportedExtras / 2);
-		}
-		
-		// Update the global trigger tracking variables.
-		singlesSSPTriggers += sspSimTriggers;
-		singlesReconMatched += reconTriggersMatched;
-		singlesReconTriggers += reconSimTriggers;
-		singlesInternalMatched += sspInternalMatched;
-		singlesReportedTriggers += sspReportedTriggers;
-		singlesReportedExtras += sspReportedExtras;
-		
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			globalEnergyMinCut[triggerNum] += eventEnergyMin[triggerNum];
-			globalEnergyMaxCut[triggerNum] += eventEnergyMax[triggerNum];
-			globalHitCountCut[triggerNum] += eventHitCount[triggerNum];
-			globalSinglesTimeCut[triggerNum] += eventTime[triggerNum];
-		}
-		
-		// Note whether the was a singles trigger match failure.
+		if(isSingles) {
+			println();
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+			printf("Trigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
+				if(sspSimTriggers == 0) {
+					printf("\tCluster Energy Lower Bound :: %d / %d%n", triggerComp[ENERGY_MIN][triggerNum], halfSimTriggers);
+					printf("\tCluster Energy Upper Bound :: %d / %d%n", triggerComp[ENERGY_MAX][triggerNum], halfSimTriggers);
+					printf("\tCluster Hit Count          :: %d / %d%n", triggerComp[HIT_COUNT][triggerNum], halfSimTriggers);
+				} else {
+					printf("\tCluster Energy Lower Bound :: %d / %d (%3.0f%%)%n",
+							triggerComp[ENERGY_MIN][triggerNum], halfSimTriggers, (100.0 * triggerComp[ENERGY_MIN][triggerNum] / halfSimTriggers));
+					printf("\tCluster Energy Upper Bound :: %d / %d (%3.0f%%)%n",
+							triggerComp[ENERGY_MAX][triggerNum], halfSimTriggers, (100.0 * triggerComp[ENERGY_MAX][triggerNum] / halfSimTriggers));
+					printf("\tCluster Hit Count          :: %d / %d (%3.0f%%)%n",
+							triggerComp[HIT_COUNT][triggerNum], halfSimTriggers, (100.0 * triggerComp[HIT_COUNT][triggerNum] / halfSimTriggers));
+				}
+				printf("\tExcess Reported Triggers   :: %d%n", sspReportedExtras / 2);
+			}
+			
+			// Update the global trigger tracking variables.
+			singlesSSPTriggers += sspSimTriggers;
+			singlesReconMatched += reconTriggersMatched;
+			singlesReconTriggers += reconSimTriggers;
+			singlesInternalMatched += sspInternalMatched;
+			singlesReportedTriggers += sspReportedTriggers;
+			singlesReportedExtras += sspReportedExtras;
+			
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				globalEnergyMinCut[triggerNum] += triggerComp[ENERGY_MIN][triggerNum];
+				globalEnergyMaxCut[triggerNum] += triggerComp[ENERGY_MAX][triggerNum];
+				globalHitCountCut[triggerNum] += triggerComp[HIT_COUNT][triggerNum];
+				globalSinglesTimeCut[triggerNum] += triggerComp[TIME][triggerNum];
+			}
+		} else {
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				println();
+				printf("Trigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
+				if(sspSimTriggers == 0) {
+					printf("\tPair Energy Sum            :: %d / %d%n", triggerComp[ENERGY_SUM][triggerNum], halfSimTriggers);
+					printf("\tPair Energy Difference     :: %d / %d%n", triggerComp[ENERGY_DIFF][triggerNum], halfSimTriggers);
+					printf("\tPair Energy Slope          :: %d / %d%n", triggerComp[ENERGY_SLOPE][triggerNum], halfSimTriggers);
+					printf("\tPair Coplanarity           :: %d / %d%n", triggerComp[COPLANARITY][triggerNum], halfSimTriggers);
+					printf("\tPair Trigger Time          :: %d / %d%n", triggerComp[TIME][triggerNum], halfSimTriggers);
+				} else {
+					printf("\tPair Energy Sum            :: %d / %d (%3.0f%%)%n",
+							triggerComp[ENERGY_SUM][triggerNum], halfSimTriggers, (100.0 * triggerComp[ENERGY_SUM][triggerNum] / halfSimTriggers));
+					printf("\tPair Energy Difference     :: %d / %d (%3.0f%%)%n",
+							triggerComp[ENERGY_DIFF][triggerNum], halfSimTriggers, (100.0 * triggerComp[ENERGY_DIFF][triggerNum] / halfSimTriggers));
+					printf("\tPair Energy Slope          :: %d / %d (%3.0f%%)%n",
+							triggerComp[ENERGY_SLOPE][triggerNum], halfSimTriggers, (100.0 * triggerComp[ENERGY_SLOPE][triggerNum] / halfSimTriggers));
+					printf("\tPair Coplanarity           :: %d / %d (%3.0f%%)%n",
+							triggerComp[COPLANARITY][triggerNum], halfSimTriggers, (100.0 * triggerComp[COPLANARITY][triggerNum] / halfSimTriggers));
+					printf("\tPair Trigger Time          :: %d / %d (%3.0f%%)%n",
+							triggerComp[TIME][triggerNum], halfSimTriggers, (100.0 * triggerComp[TIME][triggerNum] / halfSimTriggers));
+				}
+				printf("\tExcess Reported Triggers   :: %d%n", sspReportedExtras / 2);
+			}
+			
+			// Update the global trigger tracking variables.
+			pairSSPTriggers += sspSimTriggers;
+			pairReconMatched += reconTriggersMatched;
+			pairReconTriggers += reconSimTriggers;
+			pairInternalMatched += sspInternalMatched;
+			pairReportedTriggers += sspReportedTriggers;
+			pairReportedExtras += sspReportedExtras;
+			
+			for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
+				globalEnergySumCut[triggerNum] += triggerComp[ENERGY_SUM][triggerNum];
+				globalEnergyDiffCut[triggerNum] += triggerComp[ENERGY_DIFF][triggerNum];
+				globalEnergySlopeCut[triggerNum] += triggerComp[ENERGY_SLOPE][triggerNum];
+				globalCoplanarityCut[triggerNum] += triggerComp[COPLANARITY][triggerNum];
+				globalPairTimeCut[triggerNum] += triggerComp[TIME][triggerNum];
+			}
+		}
+		
+		// Note whether the was a trigger match failure.
 		if((reconTriggersMatched - reconSimTriggers != 0) || (sspInternalMatched - sspSimTriggers != 0)) {
-			singlesEfficiencyFail = 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() {
-		// ==========================================================
-		// ==== Initialize Pair Trigger Verification ===============
-		// ==========================================================
-		
-		// Print the cluster verification header.
-		println();
-		println();
-		println("======================================================================");
-		println("=== Pair Trigger Verification ========================================");
-		println("======================================================================");
-		
-		// Track the number of triggers seen and the number found.
-		int sspReportedTriggers = 0;
-		int sspInternalMatched = 0;
-		int reconTriggersMatched = 0;
-		int sspReportedExtras = 0;
-		
-		int[] eventEnergySum = new int[2];
-		int[] eventEnergyDiff = new int[2];
-		int[] eventEnergySlope = new int[2];
-		int[] eventCoplanarity = new int[2];
-		int[] eventTime = new int[2];
-		
-		
-		
-		// ==========================================================
-		// ==== Output Event Summary ================================
-		// ==========================================================
-		
-		// Get the list of triggers reported by the SSP.
-		List<SSPPairTrigger> sspTriggers = sspBank.getPairTriggers();
-		
-		// Output the SSP cluster pair triggers.
-		println();
-		println("SSP Cluster Pair Triggers");
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(PairTrigger<SSPCluster[]> simTrigger : sspPairsTriggers.get(triggerNum)) {
-				printf("\tTrigger %d :: %s, %s :: EClusterLow: %d; EClusterHigh %d; HitCount: %d; ESumLow: %d, ESumHigh: %d, EDiff: %d, ESlope: %d, Coplanarity: %d%n",
-						(triggerNum + 1), clusterPositionString(simTrigger.getTriggerSource()[0]),
-						clusterPositionString(simTrigger.getTriggerSource()[1]),
-						simTrigger.getStateClusterEnergyLow() ? 1 : 0,
-						simTrigger.getStateClusterEnergyHigh() ? 1 : 0,
-						simTrigger.getStateHitCount() ? 1 : 0,
-						simTrigger.getStateEnergySumLow() ? 1 : 0,
-						simTrigger.getStateEnergySumHigh() ? 1 : 0,
-						simTrigger.getStateEnergyDifference() ? 1 : 0,
-						simTrigger.getStateEnergySlope() ? 1 : 0,
-						simTrigger.getStateCoplanarity() ? 1 : 0);
-			}
-		}
-		if(sspPairsTriggers.get(0).size() + sspPairsTriggers.get(1).size() == 0) {
-			println("\tNone");
-		}
-		
-		// Output the reconstructed cluster singles triggers.
-		println("Reconstructed Cluster Pair Triggers");
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(PairTrigger<Cluster[]> simTrigger : reconPairsTriggers.get(triggerNum)) {
-				printf("\tTrigger %d :: %s, %s :: EClusterLow: %d; EClusterHigh %d; HitCount: %d; ESumLow: %d, ESumHigh: %d, EDiff: %d, ESlope: %d, Coplanarity: %d%n",
-						(triggerNum + 1), clusterPositionString(simTrigger.getTriggerSource()[0]),
-						clusterPositionString(simTrigger.getTriggerSource()[1]),
-						simTrigger.getStateClusterEnergyLow() ? 1 : 0,
-						simTrigger.getStateClusterEnergyHigh() ? 1 : 0,
-						simTrigger.getStateHitCount() ? 1 : 0,
-						simTrigger.getStateEnergySumLow() ? 1 : 0,
-						simTrigger.getStateEnergySumHigh() ? 1 : 0,
-						simTrigger.getStateEnergyDifference() ? 1 : 0,
-						simTrigger.getStateEnergySlope() ? 1 : 0,
-						simTrigger.getStateCoplanarity() ? 1 : 0);
-			}
-		}
-		if(reconPairsTriggers.get(0).size() + reconPairsTriggers.get(1).size() == 0) {
-			println("\tNone");
-		}
-		
-		// Output the SSP reported triggers.
-		println("SSP Reported Pair Triggers");
-		for(SSPPairTrigger sspTrigger : sspTriggers) {
-			// Increment the number of SSP cluster singles triggers.
-			sspReportedTriggers++;
-			
-			// Get the trigger properties.
-			int triggerNum = sspTrigger.isFirstTrigger() ? 1 : 2;
-			
-			// Print the trigger.
-			printf("\tTrigger %d :: %3d ns :: ESum: %d, EDiff: %d, ESlope: %d, Coplanarity: %d%n",
-					triggerNum, sspTrigger.getTime(),
-					sspTrigger.passCutEnergySum() ? 1 : 0, sspTrigger.passCutEnergyDifference() ? 1 : 0,
-					sspTrigger.passCutEnergySlope() ? 1 : 0, sspTrigger.passCutCoplanarity() ? 1 : 0);
-		}
-		if(sspReportedTriggers == 0) { 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<SSPPairTrigger> sspTriggerSet = new HashSet<SSPPairTrigger>();
-		Set<PairTrigger<SSPCluster[]>> simTriggerSet = new HashSet<PairTrigger<SSPCluster[]>>();
-		
-		// Track the number of SSP reported triggers that are found in
-		// excess of the SSP simulated triggers.
-		int extraTriggers = sspTriggers.size() - (sspPairsTriggers.get(0).size() + sspPairsTriggers.get(1).size());
-		if(extraTriggers > 0) {
-			sspReportedExtras += extraTriggers;
-			pairInternalFail = true;
-		}
-		
-		// Iterate over the triggers.
-		println();
-		println("SSP Reported Trigger --> SSP Cluster Trigger Match Status");
-		for(SSPPairTrigger 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(PairTrigger<SSPCluster[]> simTrigger : sspPairsTriggers.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(compareSSPPairTriggers(sspTrigger, simTrigger)) {
-					matchedTrigger = true;
-					sspTriggerSet.add(sspTrigger);
-					simTriggerSet.add(simTrigger);
-					sspInternalMatched++;
-					break matchLoop;
-				}
-				
-				printf("\tTrigger %d :: %3d ns :: ESum: %d, EDiff: %d, ESlope: %d, Coplanarity: %d :: Matched: %5b%n",
-						triggerNum, sspTrigger.getTime(), sspTrigger.passCutEnergySum() ? 1 : 0,
-						sspTrigger.passCutEnergyDifference() ? 1 : 0, sspTrigger.passCutEnergySlope() ? 1 : 0,
-						sspTrigger.passCutCoplanarity() ? 1 : 0, 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(PairTrigger<SSPCluster[]> simTrigger : sspPairsTriggers.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.
-				int simTime = 0;
-				if(simTrigger.getTriggerSource()[0].getYIndex() < 0) {
-					simTime = simTrigger.getTriggerSource()[0].getTime();
-				} else { simTime = simTrigger.getTriggerSource()[1].getTime(); }
-				
-				// Track the match statistics for each reported trigger
-				// so that the closest match may be found.
-				int numMatched = -1;
-				boolean foundBest = false;
-				boolean[] matchedCut = new boolean[4];
-				
-				// Iterate over the reported triggers to find a match.
-				reportedLoop:
-				for(SSPPairTrigger 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 = new boolean[4];
-					tempMatchedCut[0] = (simTrigger.getStateEnergySum()        == sspTrigger.passCutEnergySum());
-					tempMatchedCut[1] = (simTrigger.getStateEnergyDifference() == sspTrigger.passCutEnergyDifference());
-					tempMatchedCut[2] = (simTrigger.getStateEnergySlope()      == sspTrigger.passCutEnergySlope());
-					tempMatchedCut[3] = (simTrigger.getStateCoplanarity()      == sspTrigger.passCutCoplanarity());
-					
-					// 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) {
-						foundBest = true;
-						numMatched = tempNumMatched;
-						matchedCut = tempMatchedCut;
-					}
-				}
-				
-				// If some match was found, note what caused it to not
-				// qualify as a complete match.
-				if(foundBest) {
-					if(!matchedCut[0]) { eventEnergySum[triggerNum]++; }
-					if(!matchedCut[1]) { eventEnergyDiff[triggerNum]++; }
-					if(!matchedCut[2]) { eventEnergySlope[triggerNum]++; }
-					if(!matchedCut[3]) { eventCoplanarity[triggerNum]++; }
-				}
-				
-				// 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.
-				else {
-					eventTime[triggerNum]++;
-					pairInternalFail = true;
-				}
-			}
-		}
-		
-		
-		
-		// ==========================================================
-		// ==== SSP Pair Trigger Efficiency =========================
-		// ==========================================================
-		
-		// Reset the SSP matched trigger set.
-		sspTriggerSet.clear();
-		
-		// Iterate over the reconstructed cluster pair triggers.
-		println();
-		println("Recon Cluster Trigger --> SSP Reported Trigger Match Status");
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			for(PairTrigger<Cluster[]> simTrigger : reconPairsTriggers.get(triggerNum)) {
-				// Track whether the trigger was matched.
-				boolean matchedTrigger = false;
-				
-				// Iterate over the SSP reported triggers and compare
-				// them to the reconstructed cluster simulated trigger.
-				matchLoop:
-				for(SSPTrigger sspTrigger : sspTriggers) {
-					// Only compare pair triggers.
-					if(sspTrigger instanceof SSPPairTrigger) {
-						// Cast the trigger.
-						SSPPairTrigger sspPair = (SSPPairTrigger) sspTrigger;
-						
-						// Only compare the pair trigger if it was
-						// not already matched to another trigger.
-						if(sspTriggerSet.contains(sspPair)) { continue matchLoop; }
-						
-						// Compare the triggers.
-						if(compareReconPairTriggers(sspPair, simTrigger)) {
-							reconTriggersMatched++;
-							matchedTrigger = true;
-							sspTriggerSet.add(sspPair);
-							break matchLoop;
-						}
-					}
-				}
-				
-				// Print the trigger matching status.
-				printf("\tTrigger %d :: %s, %s :: EClusterLow: %d; EClusterHigh %d; HitCount: %d; ESumLow: %d, ESumHigh: %d, EDiff: %d, ESlope: %d, Coplanarity: %d :: Matched: %5b%n",
-						(triggerNum + 1), clusterPositionString(simTrigger.getTriggerSource()[0]),
-						clusterPositionString(simTrigger.getTriggerSource()[1]),
-						simTrigger.getStateClusterEnergyLow() ? 1 : 0,
-						simTrigger.getStateClusterEnergyHigh() ? 1 : 0,
-						simTrigger.getStateHitCount() ? 1 : 0,
-						simTrigger.getStateEnergySumLow() ? 1 : 0,
-						simTrigger.getStateEnergySumHigh() ? 1 : 0,
-						simTrigger.getStateEnergyDifference() ? 1 : 0,
-						simTrigger.getStateEnergySlope() ? 1 : 0,
-						simTrigger.getStateCoplanarity() ? 1 : 0, matchedTrigger);
-			}
-		}
-		
-		
-		
-		// ==========================================================
-		// ==== Output Event Results ================================
-		// ==========================================================
-		
-		// Get the number of SSP and reconstructed cluster simulated
-		// triggers.
-		int sspSimTriggers = sspPairsTriggers.get(0).size() + sspPairsTriggers.get(1).size();
-		int reconSimTriggers = reconPairsTriggers.get(0).size() + reconPairsTriggers.get(1).size();
-		int halfSimTriggers = sspSimTriggers / 2;
-		
-		// Print event statistics.
-		println();
-		println("Event Statistics:");
-		printf("\tSSP Cluster Sim Triggers   :: %d%n", sspSimTriggers);
-		printf("\tRecon Cluster Sim Triggers :: %d%n", reconSimTriggers);
-		printf("\tSSP Reported Triggers      :: %d%n", sspReportedTriggers);
-		if(sspSimTriggers == 0) {
-			printf("\tInternal Efficiency        :: %d / %d (N/A)%n",
-					sspInternalMatched, sspSimTriggers);
-		} else {
-			printf("\tInternal Efficiency        :: %d / %d (%3.0f%%)%n",
-					sspInternalMatched, sspSimTriggers, (100.0 * sspInternalMatched / sspSimTriggers));
-		}
-		if(reconSimTriggers == 0) {
-			printf("\tTrigger Efficiency         :: %d / %d (N/A)%n",
-					reconTriggersMatched, reconSimTriggers);
-		} else {
-			printf("\tTrigger Efficiency         :: %d / %d (%3.0f%%)%n",
-					reconTriggersMatched, reconSimTriggers, (100.0 * reconTriggersMatched / reconSimTriggers));
-		}
-		
-		// Print the individual cut performances.
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			println();
-			printf("Trigger %d Individual Cut Failure Rate:%n", (triggerNum + 1));
-			if(sspSimTriggers == 0) {
-				printf("\tPair Energy Sum            :: %d / %d%n", eventEnergySum[triggerNum], halfSimTriggers);
-				printf("\tPair Energy Difference     :: %d / %d%n", eventEnergyDiff[triggerNum], halfSimTriggers);
-				printf("\tPair Energy Slope          :: %d / %d%n", eventEnergySlope[triggerNum], halfSimTriggers);
-				printf("\tPair Coplanarity           :: %d / %d%n", eventCoplanarity[triggerNum], halfSimTriggers);
-				printf("\tPair Trigger Time          :: %d / %d%n", eventTime[triggerNum], halfSimTriggers);
-			} else {
-				printf("\tPair Energy Sum            :: %d / %d (%3.0f%%)%n",
-						eventEnergySum[triggerNum], halfSimTriggers, (100.0 * eventEnergySum[triggerNum] / halfSimTriggers));
-				printf("\tPair Energy Difference     :: %d / %d (%3.0f%%)%n",
-						eventEnergyDiff[triggerNum], halfSimTriggers, (100.0 * eventEnergyDiff[triggerNum] / halfSimTriggers));
-				printf("\tPair Energy Slope          :: %d / %d (%3.0f%%)%n",
-						eventEnergySlope[triggerNum], halfSimTriggers, (100.0 * eventEnergySlope[triggerNum] / halfSimTriggers));
-				printf("\tPair Coplanarity           :: %d / %d (%3.0f%%)%n",
-						eventCoplanarity[triggerNum], halfSimTriggers, (100.0 * eventCoplanarity[triggerNum] / halfSimTriggers));
-				printf("\tPair Trigger Time          :: %d / %d (%3.0f%%)%n",
-						eventTime[triggerNum], halfSimTriggers, (100.0 * eventTime[triggerNum] / halfSimTriggers));
-			}
-			printf("\tExcess Reported Triggers   :: %d%n", sspReportedExtras / 2);
-		}
-		
-		// Update the global trigger tracking variables.
-		pairSSPTriggers += sspSimTriggers;
-		pairReconMatched += reconTriggersMatched;
-		pairReconTriggers += reconSimTriggers;
-		pairInternalMatched += sspInternalMatched;
-		pairReportedTriggers += sspReportedTriggers;
-		pairReportedExtras += sspReportedExtras;
-		
-		for(int triggerNum = 0; triggerNum < 2; triggerNum++) {
-			globalEnergySumCut[triggerNum] += eventEnergySum[triggerNum];
-			globalEnergyDiffCut[triggerNum] += eventEnergyDiff[triggerNum];
-			globalEnergySlopeCut[triggerNum] += eventEnergySlope[triggerNum];
-			globalCoplanarityCut[triggerNum] += eventCoplanarity[triggerNum];
-			globalPairTimeCut[triggerNum] += eventTime[triggerNum];
-		}
-		
-		// Note whether the was a singles trigger match failure.
-		if((reconTriggersMatched - reconSimTriggers != 0) || (sspInternalMatched - sspSimTriggers != 0)) {
-			pairEfficiencyFail = true;
+			if(isSingles) { singlesEfficiencyFail = true; }
+			else { pairEfficiencyFail = true; }
 		}
 	}
 	
@@ -2031,6 +1716,41 @@
 	}
 	
 	/**
+	 * 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.
@@ -2088,40 +1808,6 @@
 		
 		// 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;
-	}
-	
-	/**
-	 * Compares a trigger from the SSP bank to a trigger simulated on
-	 * an reconstructed 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 compareReconPairTriggers(SSPPairTrigger bankTrigger, PairTrigger<Cluster[]> simTrigger) {
-		// Check that the trigger flags are all the same. Start with
-		// energy sum.
 		if(bankTrigger.passCutEnergySum() != simTrigger.getStateEnergySum()) {
 			return false;
 		}
@@ -2301,6 +1987,138 @@
 	}
 	
 	/**
+	 * 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 clusterPositionString((SSPCluster) source);
+		} else if(source instanceof Cluster) {
+			return clusterPositionString((Cluster) source);
+		} else if(source instanceof SSPCluster[]) {
+			SSPCluster[] sourcePair = (SSPCluster[]) source;
+			if(sourcePair.length == 2) {
+				return String.format("%s, %s", clusterPositionString(sourcePair[0]),
+						clusterPositionString(sourcePair[1]));
+			}
+		} else if(source instanceof Cluster[]) {
+			Cluster[] sourcePair = (Cluster[]) source;
+			if(sourcePair.length == 2) {
+				return String.format("%s, %s", clusterPositionString(sourcePair[0]),
+						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()));
+	}
+	
+	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()));
+	}
+	
+	/**
 	 * Class <code>Pair</code> provides a convenient means of putting
 	 * a reconstructed cluster and an SSP cluster in the same object
 	 * for cluster matching.

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticGUIDriver.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticGUIDriver.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/TriggerDiagnosticGUIDriver.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,43 @@
+package org.hps.users.kmccarty;
+
+import java.util.List;
+
+import javax.swing.JFrame;
+
+import org.hps.users.kmccarty.diagpanel.ClusterTablePanel;
+import org.lcsim.event.EventHeader;
+import org.lcsim.util.Driver;
+
+public class TriggerDiagnosticGUIDriver extends Driver {
+	private JFrame window = new JFrame();
+	private ClusterTablePanel clusterTable = new ClusterTablePanel();
+	private String diagnosticCollectionName = "DiagnosticSnapshot";
+	
+	@Override
+	public void startOfData() {
+		window.add(clusterTable);
+		window.setVisible(true);
+		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		window.setSize(500, 400);
+	}
+	
+	@Override
+	public void process(EventHeader event) {
+		// Updates are only performed if a diagnostic snapshot object
+		// exists. Otherwise, do nothing.
+		if(event.hasCollection(DiagSnapshot.class, diagnosticCollectionName)) {
+			// Get the snapshot collection.
+			List<DiagSnapshot> snapshotList = event.get(DiagSnapshot.class, diagnosticCollectionName);
+			
+			// Get the snapshot. There will only ever be one.
+			DiagSnapshot snapshot = snapshotList.get(0);
+			
+			// Feed it to the table.
+			clusterTable.updatePanel(snapshot);
+		}
+	}
+	
+	public void setDiagnosticCollectionName(String name) {
+		diagnosticCollectionName = name;
+	}
+}

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/AbstractTablePanel.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/AbstractTablePanel.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/AbstractTablePanel.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,230 @@
+package org.hps.users.kmccarty.diagpanel;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+
+/**
+ * Class <code>AbstractTablePanel</code> displays two <code>JTable</code>
+ * objects side-by-side with headers above them. The left table displays
+ * statistical data for recent events processed with trigger diagnostics
+ * while the right table displays the same, but over the course of the
+ * entire run.<br/>
+ * <br/>
+ * This implements the interface <code>DiagnosticUpdatable</code>.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see JPanel
+ * @see DiagnosticUpdatable
+ */
+public abstract class AbstractTablePanel extends JPanel implements DiagnosticUpdatable {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	
+	// Table models.
+	private final TableTextModel localModel;
+	private final TableTextModel globalModel;
+	
+	// Components.
+	private JTable localTable;
+	private JLabel localHeader;
+	private JTable globalTable;
+	private JLabel globalHeader;
+	private Dimension defaultPrefSize = new Dimension(0, 0);
+	private Dimension userPrefSize = null;
+	
+	// Table model mappings.
+	private static final int COL_TITLE = 0;
+	private static final int COL_VALUE = 1;
+	
+	/**
+	 * Instantiates an <code>AbstractTablePanel</code> with a number
+	 * of rows equal to the length of the argument array. Note that
+	 * the panel requires that there be at least one row.
+	 * @param rowNames - An array of <code>String</code> objects that
+	 * are to be displayed for the names of the table rows.
+	 */
+	public AbstractTablePanel(String[] rowNames) {
+		// Initialize the table models. They should have two columns
+		// (one for values and one for headers) and a number of rows
+		// equal to the number of row names.
+		localModel = new TableTextModel(rowNames.length, 2);
+		globalModel = new TableTextModel(rowNames.length, 2);
+		
+		// Initialize the titles.
+		for(int i = 0; i < rowNames.length; i++) {
+			localModel.setValueAt(rowNames[i], i, COL_TITLE);
+			globalModel.setValueAt(rowNames[i], i, COL_TITLE);
+		}
+		updatePanel(null);
+		
+		// Define the panel layout.
+		//SpringLayout layout = new SpringLayout();
+		setLayout(null);
+		
+		// Create header labels for the tables.
+		localHeader = new JLabel("Local Statistics");
+		localHeader.setHorizontalAlignment(JLabel.CENTER);
+		add(localHeader);
+		
+		globalHeader = new JLabel("Run Statistics");
+		globalHeader.setHorizontalAlignment(JLabel.CENTER);
+		add(globalHeader);
+		
+		// Create JTable objects to display the data.
+		localTable = new JTable(localModel);
+		localTable.setRowSelectionAllowed(false);
+		localTable.setColumnSelectionAllowed(false);
+		localTable.setCellSelectionEnabled(false);
+		add(localTable);
+		
+		globalTable = new JTable(globalModel);
+		globalTable.setRowSelectionAllowed(false);
+		globalTable.setColumnSelectionAllowed(false);
+		globalTable.setCellSelectionEnabled(false);
+		add(globalTable);
+		
+		// Track when the component changes size and reposition the
+		// components accordingly.
+		addComponentListener(new ComponentAdapter() {
+			@Override
+			public void componentResized(ComponentEvent e) { positionComponents(); }
+		});
+		
+		// Define the component preferred size.
+		defaultPrefSize.width = localTable.getPreferredSize().width +
+				ComponentUtils.hinternal + globalTable.getPreferredSize().width;
+		defaultPrefSize.height = localTable.getPreferredSize().height +
+				ComponentUtils.vinternal + globalTable.getPreferredSize().height;
+	}
+	
+	@Override
+	public Dimension getPreferredSize() {
+		// If there is a user-specified preferred size, return that.
+		if(userPrefSize == null) { return defaultPrefSize; }
+		
+		// Otherwise, return the default calculated preferred size.
+		else { return userPrefSize; }
+	}
+	
+	@Override
+	public void setBackground(Color bg) {
+		// Set the base component background.
+		super.setBackground(bg);
+		
+		// If the components have been initialized, pass the background
+		// color change to them as appropriate. Note that the tables
+		// will always retain the same background color.
+		if(localTable != null) {
+			// Set the header backgrounds.
+			localHeader.setBackground(bg);
+			globalHeader.setBackground(bg);
+		}
+	}
+	
+	@Override
+	public void setFont(Font font) {
+		// Set the base component font.
+		super.setFont(font);
+		
+		// If the components have been initialized, pass the font change
+		// to them as appropriate.
+		if(localTable != null) {
+			// Set the table fonts.
+			localTable.setFont(font);
+			globalTable.setFont(font);
+			
+			// Set the header fonts.
+			Font headerFont = font.deriveFont(Font.BOLD, (float) Math.ceil(font.getSize2D() * 1.3));
+			localHeader.setFont(headerFont);
+			globalHeader.setFont(headerFont);
+		}
+	}
+	
+	@Override
+	public void setForeground(Color fg) {
+		// Set the base component foreground.
+		super.setForeground(fg);
+		
+		// If the components have been initialized, pass the foreground
+		// color change to them as appropriate. Note that the tables
+		// will always retain the same foreground color.
+		if(localTable != null) {
+			// Set the header foregrounds.
+			localHeader.setForeground(fg);
+			globalHeader.setForeground(fg);
+		}
+	}
+	
+	@Override
+	public void setPreferredSize(Dimension preferredSize) {
+		userPrefSize = preferredSize;
+	}
+	
+	/**
+	 * Sets the value of the indicated row for the global statistical
+	 * table.
+	 * @param rowIndex - The row.
+	 * @param value - The new value.
+	 */
+	protected void setGlobalRowValue(int rowIndex, String value) {
+		globalModel.setValueAt(value, rowIndex, COL_VALUE);
+	}
+	
+	/**
+	 * Sets the value of the indicated row for the local statistical
+	 * table.
+	 * @param rowIndex - The row.
+	 * @param value - The new value.
+	 */
+	protected void setLocalRowValue(int rowIndex, String value) {
+		localModel.setValueAt(value, rowIndex, COL_VALUE);
+	}
+	
+	/**
+	 * Repositions the components to the correct places on the parent
+	 * <code>JPanel</code>. This should be run whenever the panel
+	 * changes size.
+	 */
+	private void positionComponents() {
+		// Do not update if the components have not been initialized.
+		if(localHeader == null) { return; }
+		
+		// The local components get the left half of the panel and the
+		// global components the right. Find half of the panel width,
+		// accounting for the internal spacing. This is an internal
+		// component, so it does not employ additional spacing between
+		// itself and the parent component's edges.
+		int compWidth = (getWidth() - 10) / 2;
+		
+		// If there is any width remaining, it goes to the spacing.
+		int horizontal = ComponentUtils.hinternal + (getWidth() - 10) % 2;
+		
+		// Place the header labels. These are given their preferred
+		// height. Note that this means a very small panel may cut off
+		// some of the components. First, get the preferred height of
+		// the label with the larger preferred height. These should be
+		// the same thing, but just in case...
+		int labelHeight = localHeader.getPreferredSize().height;
+		if(labelHeight < globalHeader.getPreferredSize().height) {
+			labelHeight = globalHeader.getPreferredSize().height;
+		}
+		
+		// Set the label sizes and positions.
+		localHeader.setBounds(0, 0, compWidth, labelHeight);
+		globalHeader.setLocation(ComponentUtils.getNextX(localHeader, horizontal), 0);
+		globalHeader.setSize(compWidth, labelHeight);
+		
+		// The tables go under their respective labels and should fill
+		// the remainder of the label height.
+		int tableY = ComponentUtils.getNextY(localHeader, ComponentUtils.vinternal);
+		localTable.setBounds(0, tableY, compWidth, localTable.getPreferredSize().height);
+		globalTable.setBounds(globalHeader.getX(), tableY, compWidth, globalTable.getPreferredSize().height);
+	}
+}

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ClusterTablePanel.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ClusterTablePanel.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ClusterTablePanel.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,111 @@
+package org.hps.users.kmccarty.diagpanel;
+
+import org.hps.users.kmccarty.DiagSnapshot;
+
+/**
+ * Class <code>ClusterTablePanel</code> is an implementation of class
+ * <code>AbstractTablePanel</code> for cluster statistical data.<br/>
+ * <br/>
+ * This implements the interface <code>DiagnosticUpdatable</code>.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see AbstractTablePanel
+ */
+public class ClusterTablePanel extends AbstractTablePanel {
+	// Static variables.
+	private static final long serialVersionUID = 0L;
+	private static final String[] TABLE_TITLES = { "Recon Clusters:", "SSP Clusters", "Matched Clusters",
+			"Failed (Position)", "Failed (Energy)", "Failed (Hit Count)" };
+	
+	// Table model mappings.
+	private static final int ROW_RECON_COUNT      = 0;
+	private static final int ROW_SSP_COUNT        = 1;
+	private static final int ROW_MATCHED          = 2;
+	private static final int ROW_FAILED_POSITION  = 3;
+	private static final int ROW_FAILED_ENERGY    = 4;
+	private static final int ROW_FAILED_HIT_COUNT = 5;
+	
+	/**
+	 * Instantiate a new <code>ClusterTablePanel</code>.
+	 */
+	public ClusterTablePanel() { super(TABLE_TITLES); }
+	
+	@Override
+	public void updatePanel(DiagSnapshot snapshot) {
+		// If the snapshot is null, all values should be "N/A."
+		if(snapshot == null) {
+			// Output cluster count data.
+			String scalerNullValue = "---";
+			setLocalRowValue(ROW_RECON_COUNT,  scalerNullValue);
+			setLocalRowValue(ROW_SSP_COUNT,    scalerNullValue);
+			setGlobalRowValue(ROW_RECON_COUNT, scalerNullValue);
+			setGlobalRowValue(ROW_SSP_COUNT,   scalerNullValue);
+			
+			// Output the tracked statistical data.
+			String percentNullValue = "--- / --- (---%)";
+			setLocalRowValue(ROW_MATCHED,           percentNullValue);
+			setLocalRowValue(ROW_FAILED_POSITION,   percentNullValue);
+			setLocalRowValue(ROW_FAILED_ENERGY,     percentNullValue);
+			setLocalRowValue(ROW_FAILED_HIT_COUNT,  percentNullValue);
+			setGlobalRowValue(ROW_MATCHED,          percentNullValue);
+			setGlobalRowValue(ROW_FAILED_POSITION,  percentNullValue);
+			setGlobalRowValue(ROW_FAILED_ENERGY,    percentNullValue);
+			setGlobalRowValue(ROW_FAILED_HIT_COUNT, percentNullValue);
+		}
+		
+		// Otherwise, populate the table with the diagnostic data.
+		else {
+			/*
+			 * This is disabled until the snapshot object is stable and
+			 * is subject to change. It will not work if enabled now.
+			// Get the largest number of digits in any of the values.
+			int mostDigits = 0;
+			for(int valueID = 0; valueID < DiagSnapshot.CL_BANK_SIZE; valueID++) {
+				int localDigits = ComponentUtils.getDigits(snapshot.getClusterValue(LOCAL, valueID));
+				int globalDigits = ComponentUtils.getDigits(snapshot.getClusterValue(GLOBAL, valueID));
+				mostDigits = ComponentUtils.max(mostDigits, localDigits, globalDigits);
+			}
+			
+			// Put the number of reconstructed and SSP clusters into
+			// the tables.
+			int[] clusterValue = {
+					snapshot.getClusterValue(LOCAL,  DiagSnapshot.CL_VALUE_RECON_CLUSTERS),
+					snapshot.getClusterValue(LOCAL,  DiagSnapshot.CL_VALUE_SSP_CLUSTERS),
+					snapshot.getClusterValue(GLOBAL, DiagSnapshot.CL_VALUE_RECON_CLUSTERS),
+					snapshot.getClusterValue(GLOBAL, DiagSnapshot.CL_VALUE_SSP_CLUSTERS)
+			};
+			String countFormat = "%" + mostDigits + "d";
+			setLocalRowValue(ROW_RECON_COUNT,  String.format(countFormat, clusterValue[0]));
+			setLocalRowValue(ROW_SSP_COUNT,    String.format(countFormat, clusterValue[1]));
+			setGlobalRowValue(ROW_RECON_COUNT, String.format(countFormat, clusterValue[2]));
+			setGlobalRowValue(ROW_SSP_COUNT,   String.format(countFormat, clusterValue[3]));
+			
+			// Output the tracked statistical data.
+			int total;
+			String percentFormat = "%" + mostDigits + "d / %" + mostDigits + "d (%7.3f)";
+			int[] statValue = {
+					snapshot.getClusterValue(DiagSnapshot.TYPE_LOCAL, DiagSnapshot.CL_VALUE_MATCHED),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_LOCAL, DiagSnapshot.CL_VALUE_FAIL_POSITION),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_LOCAL, DiagSnapshot.CL_VALUE_FAIL_ENERGY),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_LOCAL, DiagSnapshot.CL_VALUE_FAIL_HIT_COUNT),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_GLOBAL, DiagSnapshot.CL_VALUE_MATCHED),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_GLOBAL, DiagSnapshot.CL_VALUE_FAIL_POSITION),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_GLOBAL, DiagSnapshot.CL_VALUE_FAIL_ENERGY),
+					snapshot.getClusterValue(DiagSnapshot.TYPE_GLOBAL, DiagSnapshot.CL_VALUE_FAIL_HIT_COUNT)
+			};
+			
+			total = snapshot.getClusterValue(DiagSnapshot.TYPE_LOCAL, DiagSnapshot.CL_VALUE_RECON_CLUSTERS);
+			setLocalRowValue(ROW_MATCHED,          String.format(percentFormat, statValue[0], total, 100.0 * statValue[0] / total));
+			setLocalRowValue(ROW_FAILED_POSITION,  String.format(percentFormat, statValue[1], total, 100.0 * statValue[1] / total));
+			setLocalRowValue(ROW_FAILED_ENERGY,    String.format(percentFormat, statValue[2], total, 100.0 * statValue[2] / total));
+			setLocalRowValue(ROW_FAILED_HIT_COUNT, String.format(percentFormat, statValue[3], total, 100.0 * statValue[3] / total));
+			
+			total = snapshot.getClusterValue(DiagSnapshot.TYPE_GLOBAL, DiagSnapshot.CL_VALUE_RECON_CLUSTERS);
+			setGlobalRowValue(ROW_MATCHED,          String.format(percentFormat, statValue[4], total, 100.0 * statValue[4] / total));
+			setGlobalRowValue(ROW_FAILED_POSITION,  String.format(percentFormat, statValue[5], total, 100.0 * statValue[5] / total));
+			setGlobalRowValue(ROW_FAILED_ENERGY,    String.format(percentFormat, statValue[6], total, 100.0 * statValue[6] / total));
+			setGlobalRowValue(ROW_FAILED_HIT_COUNT, String.format(percentFormat, statValue[7], total, 100.0 * statValue[7] / total));
+			*/
+		}
+	}
+}

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ComponentUtils.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ComponentUtils.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/ComponentUtils.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,106 @@
+package org.hps.users.kmccarty.diagpanel;
+
+import java.awt.Component;
+
+import org.hps.users.kmccarty.TriggerDiagnosticUtil;
+
+/**
+ * Class <code>ComponentUtils</code> is a list of utility methods used
+ * by the trigger diagnostic GUI.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+class ComponentUtils {
+	/** The default spacing used between a horizontal edge of one
+	 * component and the horizontal edge of another. */
+	public static final int hinternal = 10;
+	/** The default spacing used between a vertical edge of one
+	 * component and the vertical edge of another. */
+	public static final int vinternal = 10;
+	/** The default spacing used between a horizontal edge of one
+	 * component and the edge of its parent component. */
+	public static final int hexternal = 0;
+	/** The default spacing used between a vertical edge of one
+	 * component and the edge of its parent component. */
+	public static final int vexternal = 0;
+	
+	/**
+	 * 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 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;
+	}
+	
+	/**
+	 * 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;
+	}
+}

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/DiagnosticUpdatable.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/DiagnosticUpdatable.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/DiagnosticUpdatable.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,22 @@
+package org.hps.users.kmccarty.diagpanel;
+
+import org.hps.users.kmccarty.DiagSnapshot;
+
+/**
+ * Interface <code>DiagnosticUpdatable</code> defines a class of objects
+ * that can be updated with information from a trigger diagnostic driver.
+ * They can take snapshots of the driver results and use this in order to
+ * alter their displayed or constituent values.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ * @see DiagSnapshot
+ */
+public interface DiagnosticUpdatable {
+	/**
+	 * Updates the object with information from the trigger diagnostic
+	 * snapshot in the argument.
+	 * @param snapshot - The snapshot containing information with which
+	 * to update the object.
+	 */
+	public void updatePanel(DiagSnapshot snapshot);
+}

Added: java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/TableTextModel.java
 =============================================================================
--- java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/TableTextModel.java	(added)
+++ java/trunk/users/src/main/java/org/hps/users/kmccarty/diagpanel/TableTextModel.java	Tue Mar  3 16:01:55 2015
@@ -0,0 +1,103 @@
+package org.hps.users.kmccarty.diagpanel;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Class <code>TableTextModel</code> is a simple implementation of
+ * <code>AbstractTableModel</code> that supports a definable number
+ * of rows and columns which must be populated with <code>String</code>
+ * data.
+ * 
+ * @author Kyle McCarty <[log in to unmask]>
+ */
+public class TableTextModel extends AbstractTableModel {
+	// Serial UID.
+	private static final long serialVersionUID = 0L;
+	
+	// Stored values.
+	private final int rows, columns;
+	private final String[][] values;
+	
+	/**
+	 * Instantiates a new <code>TableTextModel</code> with the indicated
+	 * number of rows and columns.
+	 * @param rows - The number of rows.
+	 * @param columns - The number of columns.
+	 */
+	public TableTextModel(int rows, int columns) {
+		// Make sure that the arguments for rows and columns are valid.
+		if(rows < 1) {
+			throw new IllegalArgumentException("TableTextModel must have at least one row.");
+		} else 	if(columns < 1) {
+			throw new IllegalArgumentException("TableTextModel must have at least one column.");
+		}
+		
+		// Define the number of rows and columns.
+		this.rows = rows;
+		this.columns = columns;
+		
+		// Instantiate the data storage array.
+		values = new String[rows][columns];
+	}
+	
+	@Override
+	public int getRowCount() { return rows; }
+	
+	@Override
+	public int getColumnCount() { return columns; }
+	
+	@Override
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		// Ensure that the value is within the allowed range.
+		validateIndex(rowIndex, columnIndex);
+		
+		// Return the value.
+		return values[rowIndex][columnIndex];
+	}
+	
+	@Override
+	public void setValueAt(Object value, int rowIndex, int columnIndex) {
+		// If the object is a string, pass it to the preferred handler.
+		// This can also be performed if the value is null.
+		if(value == null || value instanceof String) {
+			setValueAt((String) value, rowIndex, columnIndex);
+		}
+		
+		// Otherwise, cast the object to a string and use that instead.
+		else { setValueAt(value.toString(), rowIndex, columnIndex); }
+	}
+	
+	/**
+	 * Sets the text for the indicated column and row of the table.
+	 * @param value - The new text.
+	 * @param rowIndex - The row.
+	 * @param columnIndex - The column.
+	 * @throws IndexOutOfBoundsException Occurs if the row and column
+	 * are not a valid member of table model.
+	 */
+	public void setValueAt(String value, int rowIndex, int columnIndex) throws IndexOutOfBoundsException {
+		// Ensure that the value is within the allowed range.
+		validateIndex(rowIndex, columnIndex);
+		
+		// Set the value.
+		values[rowIndex][columnIndex] = value;
+	}
+	
+	/**
+	 * Checks to make sure that a given row/column pointer refers to
+	 * an extant position in the data array. In the event that the row
+	 * and column values are not valid, an <code>IndexOutOfBounds</code>
+	 * exception is thrown.
+	 * @param rowIndex - The row index.
+	 * @param columnIndex - The column index.
+	 * @throws IndexOutOfBoundsException Occurs if the row and column
+	 * are not a valid member of the data array.
+	 */
+	private void validateIndex(int rowIndex, int columnIndex) throws IndexOutOfBoundsException {
+		if(rowIndex < 0 || rowIndex >= getRowCount()) {
+			throw new IndexOutOfBoundsException(String.format("Row index %d is out of bounds.", rowIndex));
+		} else if(columnIndex < 0 || columnIndex >= getColumnCount()) {
+			throw new IndexOutOfBoundsException(String.format("Column index %d is out of bounds.", columnIndex));
+		}
+	}
+}

Top of Message | Previous Page | Permalink

Advanced Options


Options

Log In

Log In

Get Password

Get Password


Search Archives

Search Archives


Subscribe or Unsubscribe

Subscribe or Unsubscribe


Archives

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

ATOM RSS1 RSS2



LISTSERV.SLAC.STANFORD.EDU

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

Privacy Notice, Security Notice and Terms of Use