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  December 2016

HPS-SVN December 2016

Subject:

r4601 - in /java/trunk/analysis/src/main/java/org/hps/analysis/trigger: ClusterDiagnosticDriver.java DataTriggerSimDriver.java TriggerDiagnosticDriver.java util/TriggerDiagnosticUtil.java

From:

[log in to unmask]

Reply-To:

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

Date:

Wed, 7 Dec 2016 03:29:15 -0000

Content-Type:

text/plain

Parts/Attachments:

Parts/Attachments

text/plain (1257 lines)

Author: [log in to unmask]
Date: Tue Dec  6 19:29:12 2016
New Revision: 4601

Log:
Updated trigger diagnostics.

Modified:
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/ClusterDiagnosticDriver.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DataTriggerSimDriver.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java
    java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java

Modified: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/ClusterDiagnosticDriver.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/ClusterDiagnosticDriver.java	(original)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/ClusterDiagnosticDriver.java	Tue Dec  6 19:29:12 2016
@@ -1,11 +1,14 @@
 package org.hps.analysis.trigger;
 
+import java.awt.Point;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.hps.analysis.trigger.util.ClusterMatchedPair;
@@ -80,6 +83,21 @@
      * not verify because no two clusters were found with a matching
      * seed position. */
     private int failedMatchPosition = 0;
+    /** Indicates the number of failed cluster events that have a seed
+     * hit near the seed energy threshold. */
+    private int failedNearSeedThreshold = 0;
+    /** Indicates the number of failed cluster events that occur within
+     * the dead time window of the event start and also have a t = 0
+     * cluster with a hit on the same channel. */
+    private int failedPositionDeadtime = 0;
+    /** Indicates the number of failed cluster events that may have
+     * lost a hit due to deadtime issues. */
+    private int failedHitCountDeadtime = 0;
+    /** Indicates the number of failed cluster events where the energies
+     * were identical, but hit count differed. */
+    private int failedNegativeEnergyHit = 0;
+    // TODO: Add description.
+    private int failedCloneBug = 0;
     
     // === Local window values. =========================================================
     // ==================================================================================
@@ -181,12 +199,77 @@
     private static final String matchedClustersEnergyDiffPlot = MODULE_HEADER + "Matched Clusters Energy Difference Distribution";
     /** Plots the difference in hit count between matched clusters. */
     private static final String matchedClustersHitDiffPlot = MODULE_HEADER + "Matched Clusters Hit Count Difference Distribution";
+    /** Plots the difference in energy between matched clusters which failed due to energy. */
+    private static final String failedEnergyEnergyDiffPlot = MODULE_HEADER + "Energy-Match Failure Energy Difference Distribution";
+    /** Plots the difference in energy between matched clusters which failed due to hit count. */
+    private static final String failedHitCountEnergyDiffPlot = MODULE_HEADER + "Hit Count-Match Failure Energy Difference Distribution";
+    /** Plots the difference in hit count between matched clusters which failed due to energy. */
+    private static final String failedEnergyHitDiffPlot = MODULE_HEADER + "Energy-Match Failure Hit Count Difference Distribution";
+    /** Plots the difference in hit count between matched clusters which failed due to hit count. */
+    private static final String failedHitCountHitDiffPlot = MODULE_HEADER + "Hit Count-Match Failure Hit Count Difference Distribution";
     /** Plots the 2D difference in energy between matched clusters. */
     private static final String matchedClusters2DEnergyDiffPlot = MODULE_HEADER + "Matched Clusters 2D Energy Difference Distribution";
     /** Plots the 2D difference in hit count between matched clusters. */
     private static final String matchedClusters2DHitDiffPlot = MODULE_HEADER + "Matched Clusters 2D Hit Count Difference Distribution";
-    
+    /** Plots the efficiency over the course of the run. **/
     private static final String runtimeEfficiencyPlot = MODULE_HEADER + "Matched Clusters Run Time Efficiency";
+    
+    private static final Set<Point> getClusterHitIndices(Cluster cluster) {
+        // Store the possible hits in a set. The seed hit is excluded.
+        Set<Point> hitSet = new HashSet<Point>(8);
+        
+        // Iterate over the cluster hits and add any existing positions
+        // to the hit set.
+        for(CalorimeterHit hit : cluster.getCalorimeterHits()) {
+            hitSet.add(new Point(hit.getIdentifierFieldValue("ix"), hit.getIdentifierFieldValue("iy")));
+        }
+        
+        // Return the set of constituent hits.
+        return hitSet;
+    }
+    
+    private static final Set<Point> getClusterPossibleHitIndices(Cluster cluster) {
+        // Get the cluster seed position.
+        int ix = TriggerModule.getClusterXIndex(cluster);
+        int iy = TriggerModule.getClusterYIndex(cluster);
+        
+        // Store the possible hits in a set. The seed hit is excluded.
+        Set<Point> hitSet = new HashSet<Point>(8);
+        
+        // Get all eight adjacent hits.
+        xLoop:
+        for(int xMod = -1; xMod <= 2; xMod++) {
+            // Get the modified x position.
+            int hix = ix + xMod;
+            
+            // Values where |x| > 23 do not exist.
+            if(Math.abs(hix) > 23) { continue xLoop; }
+            
+            // Values of x = 0 do not exist. x = 1 and x = -1 are to
+            // be treated as adjacent.
+            if(hix == 0) { hix = ix == -1 ? 1 : -1; }
+            
+            yLoop:
+            for(int yMod = -1; yMod <= 2; yMod++) {
+                // Get the modified y position.
+                int hiy = iy + yMod;
+                
+                // Values of y = 0 and |y| > 5 do not exist.
+                if(hiy == 0 || Math.abs(hiy) > 5) { continue yLoop; }
+                
+                // Add the potential hit position to the set.
+                hitSet.add(new Point(hix, hiy));
+            }
+        }
+        
+        // Return the set of possible hit positions.
+        return hitSet;
+    }
+    
+    private static final double getRatioError(double num, double sigmaNum, double den, double sigmaDen) {
+        double ratio = num / den;
+        return Math.abs(ratio) * Math.sqrt(Math.pow(sigmaNum / num, 2) + Math.pow(sigmaDen / den, 2));
+    }
     
     /**
      * Prints out final run verification statistics and generates
@@ -202,20 +285,108 @@
         AIDA.defaultInstance().histogramFactory().divide(matchedClustersEnergyEfficiencyPlot,
                 AIDA.defaultInstance().histogram1D(matchedClustersEnergyPlot), AIDA.defaultInstance().histogram1D(softwareClustersEnergyPlot));
         
+        // Calculate errors for efficiencies and failure rates.
+        double sigmaSimCount         = Math.sqrt(goodSimulatedClusterCount);
+        double sigmaMatched          = Math.sqrt(matchedClusters);
+        double sigmaFailedPosition   = Math.sqrt(failedMatchPosition);
+        double sigmaFailedTime       = Math.sqrt(failedMatchTime);
+        double sigmaFailedHitCount   = Math.sqrt(failedMatchHitCount);
+        double sigmaFailedEnergy     = Math.sqrt(failedMatchEnergy);
+        double sigmaFailedNearSeed   = Math.sqrt(failedNearSeedThreshold);
+        double sigmaFailedDeadtime   = Math.sqrt(failedPositionDeadtime);
+        double sigmaFailedNegative   = Math.sqrt(failedNegativeEnergyHit);
+        double sigmaFailedHCDeadtime = Math.sqrt(failedHitCountDeadtime);
+        double sigmaFailedCloneBug   = Math.sqrt(failedCloneBug);
+        
+        // Get the maximum number of digits needed to display the
+        // largest of the counts. This should always be either the
+        // total number hardware or simulated clusters.
+        String countDisp = "%" + TriggerDiagnosticUtil.getDigits(Math.max(simulatedClusterCount, goodSimulatedClusterCount)) + "d";
+        
         // Print the global run statistics for cluster verification.
         System.out.println("Cluster Verification:");
-        System.out.printf("\tSimulated Clusters     :: %d%n", simulatedClusterCount);
-        System.out.printf("\tUnclipped Sim Clusters :: %d%n", goodSimulatedClusterCount);
-        System.out.printf("\tHardware Clusters      :: %d%n", hardwareClusterCount);
-        System.out.printf("\tClusters Matched       :: %d%n", matchedClusters);
-        System.out.printf("\tFailed (Position)      :: %d%n", failedMatchPosition);
-        System.out.printf("\tFailed (Time)          :: %d%n", failedMatchTime);
-        System.out.printf("\tFailed (Energy)        :: %d%n", failedMatchEnergy);
-        System.out.printf("\tFailed (Hit Count)     :: %d%n", failedMatchHitCount);
-        if(simulatedClusterCount == 0) {
-            System.out.printf("\tCluster Efficiency     :: N/A%n");
+        System.out.printf("\tSimulated Clusters     :: " + countDisp + "%n", simulatedClusterCount);
+        System.out.printf("\tUnclipped Sim Clusters :: " + countDisp + "%n", goodSimulatedClusterCount);
+        System.out.printf("\tHardware Clusters      :: " + countDisp + "%n", hardwareClusterCount);
+        System.out.printf("\tClusters Matched       :: " + countDisp + "%n", matchedClusters);
+        
+        System.out.printf("\tFailed (Position)      :: " + countDisp, failedMatchPosition);
+        if(failedMatchPosition == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
         } else {
-            System.out.printf("\tCluster Efficiency     :: %7.3f%%%n", 100.0 * matchedClusters / goodSimulatedClusterCount);
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedMatchPosition / goodSimulatedClusterCount,
+                    getRatioError(failedMatchPosition, sigmaFailedPosition, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\t> Failed (Deadtime)    :: " + countDisp, failedPositionDeadtime);
+        if(failedPositionDeadtime == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedPositionDeadtime / goodSimulatedClusterCount,
+                    getRatioError(failedPositionDeadtime, sigmaFailedDeadtime, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\t> Failed (Clone Bug)   :: " + countDisp, failedCloneBug);
+        if(failedPositionDeadtime == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedCloneBug / goodSimulatedClusterCount,
+                    getRatioError(failedCloneBug, sigmaFailedCloneBug, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\t> Failed (Seed Thresh) :: " + countDisp, failedNearSeedThreshold);
+        if(failedNearSeedThreshold == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedNearSeedThreshold / goodSimulatedClusterCount,
+                    getRatioError(failedNearSeedThreshold, sigmaFailedNearSeed, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\tFailed (Time)          :: " + countDisp, failedMatchTime);
+        if(failedMatchTime == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedMatchTime / goodSimulatedClusterCount,
+                    getRatioError(failedMatchTime, sigmaFailedTime, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\tFailed (Hit Count)     :: " + countDisp, failedMatchHitCount);
+        if(failedMatchHitCount == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedMatchHitCount / goodSimulatedClusterCount,
+                    getRatioError(failedMatchHitCount, sigmaFailedHitCount, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\t> Failed (Deadtime)    :: " + countDisp, failedHitCountDeadtime);
+        if(failedPositionDeadtime == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedHitCountDeadtime / goodSimulatedClusterCount,
+                    getRatioError(failedHitCountDeadtime, sigmaFailedHCDeadtime, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\t> Failed (Negative En) :: " + countDisp, failedNegativeEnergyHit);
+        if(failedNegativeEnergyHit == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedNegativeEnergyHit / goodSimulatedClusterCount,
+                    getRatioError(failedNegativeEnergyHit, sigmaFailedNegative, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        System.out.printf("\tFailed (Energy)        :: " + countDisp, failedMatchEnergy);
+        if(failedMatchEnergy == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 0.0, 0.0);
+        } else {
+            System.out.printf("   (%7.3f%% ± %7.3f%%)%n", 100.0 * failedMatchEnergy / goodSimulatedClusterCount,
+                    getRatioError(failedMatchEnergy, sigmaFailedEnergy, goodSimulatedClusterCount, sigmaSimCount));
+        }
+        
+        if(simulatedClusterCount == 0 || goodSimulatedClusterCount == 0) {
+            System.out.printf("\tCluster Efficiency     :: %7.3f%% ± %7.3f%%%n", 0.0, 0.0);
+        } else {
+            System.out.printf("\tCluster Efficiency     :: %7.3f%% ± %7.3f%%%n", 100.0 * matchedClusters / goodSimulatedClusterCount,
+                    100.0 * getRatioError(matchedClusters, sigmaMatched, goodSimulatedClusterCount, sigmaSimCount));
         }
         
         // Create and populate the efficiency over time plot.
@@ -303,7 +474,7 @@
         if(hits.isEmpty()) { logger.println("\tNone"); }
         
         // Output the simulated clusters from the software.
-        logger.printNewLine(2);
+        logger.printNewLine(1);
         logger.println("Software Clusters:");
         for(Cluster cluster : simulatedClusters) {
             logger.printf("\t%s%n", TriggerDiagnosticUtil.clusterToString(cluster));
@@ -314,7 +485,7 @@
         if(simulatedClusters.isEmpty()) { logger.println("\tNone"); }
         
         // Output the reported clusters from the hardware.
-        logger.printNewLine(2);
+        logger.printNewLine(1);
         logger.println("Hardware Clusters:");
         for(SSPCluster cluster : hardwareClusters) {
             logger.printf("\t%s%n", TriggerDiagnosticUtil.clusterToString(cluster));
@@ -337,7 +508,6 @@
         // Iterate through each simulated cluster and keep only the
         // clusters safe from pulse-clipping.
         List<Cluster> goodSimulatedClusters = new ArrayList<Cluster>();
-        logger.printNewLine(2);
         logger.println("Simulated Cluster Pulse-Clipping Check:");
         for(Cluster cluster : simulatedClusters) {
             boolean isSafe = TriggerDiagnosticUtil.isVerifiable(cluster, nsa, nsb, windowWidth);
@@ -374,8 +544,161 @@
             if(pair.isMatch()) { matchedClusters++; }
             if(pair.isTimeFailState()) { failedMatchTime++; }
             if(pair.isEnergyFailState()) { failedMatchEnergy++; }
-            if(pair.isHitCountFailState()) { failedMatchHitCount++; }
-            if(pair.isPositionFailState()) { failedMatchPosition++; }
+            if(pair.isHitCountFailState()) {
+                // Increment the general hit count fail state count.
+                failedMatchHitCount++;
+                
+                // If the simulated cluster is different in hit count,
+                // but has the same energy, it is likely caused by a
+                // near-zero energy hit that registered as negative to
+                // the simulation, and was consequently ignored.
+                if(Math.abs(TriggerModule.getValueClusterTotalEnergy(pair.getReconstructedCluster())
+                        - TriggerModule.getValueClusterTotalEnergy(pair.getSSPCluster())) <= energyVerificationThreshold) {
+                    failedNegativeEnergyHit++;
+                }
+                
+                // Deadtime can also cause a failure in hit count. A
+                // simple check of this is to see if either the cluster
+                // occurred within 32 ns of the event start, or if any
+                // channel that would be included by the cluster for
+                // which there is no hit has a hit within this time
+                // frame that is also within 32 ns of the cluster.
+                else {
+                    // Check for the case that the cluster itself is
+                    // within the deadtime uncertainty region.
+                    if(TriggerModule.getClusterTime(pair.getReconstructedCluster()) < 32) {
+                        failedHitCountDeadtime++;
+                    }
+                    
+                    // Otherwise, determine which hits are not included
+                    // in the cluster and see if a hit exists which could
+                    // produce a deadtime error in that channel.
+                    else {
+                        // Get a list of the hits that could exist in
+                        // the cluster.
+                        Set<Point> actualHits = getClusterHitIndices(pair.getReconstructedCluster());
+                        Set<Point> possibleHits = getClusterPossibleHitIndices(pair.getReconstructedCluster());
+                        
+                        // Iterate over each hit and look for one that
+                        // could produce a deadtime error.
+                        hitLoop:
+                        for(CalorimeterHit hit : hits) {
+                            // The hit is required to be within the
+                            // first 32 of the event.
+                            if(hit.getTime() >= 32) { continue hitLoop; }
+                            
+                            // The hit must be within 32 ns of the
+                            // cluster  time and also occur before
+                            // the cluster time or simultaneously.
+                            if((TriggerModule.getClusterTime(pair.getReconstructedCluster()) < hit.getTime()) 
+                                    || (TriggerModule.getClusterTime(pair.getReconstructedCluster()) - hit.getTime() >= 32)) {
+                                continue hitLoop;
+                            }
+                            
+                            // The hit must occur on a channel that
+                            // falls within the 3x3 cluster range, and
+                            // there must not already be a hit on that
+                            // channel in the cluster.
+                            Point ixy = new Point(hit.getIdentifierFieldValue("ix"), hit.getIdentifierFieldValue("iy"));
+                            if(possibleHits.contains(ixy) && !actualHits.contains(ixy)) {
+                                failedHitCountDeadtime++;
+                                break hitLoop;
+                            }
+                        }
+                    }
+                }
+            }
+            if(pair.isPositionFailState()) {
+                // Increment the general position fail state count.
+                failedMatchPosition++;
+                
+                // If the simulated cluster is within the allowed energy
+                // range of the seed energy threshold, it is likely to
+                // have failed because of energy differences between the
+                // hardware and the simulation. Track these.
+                double energyDifference = Math.abs(TriggerModule.getValueClusterTotalEnergy(pair.getReconstructedCluster())
+                        - ConfigurationManager.getInstance().getGTPConfig().getSeedEnergyCutConfig().getLowerBound());
+                if(energyDifference <= energyVerificationThreshold) {
+                    failedNearSeedThreshold++;
+                }
+                
+                // If the simulated cluster has a seed hit which is
+                // less than 32 ns in time, it is possible that it was
+                // missed by the hardware due to a dead time issue.
+                // This is most likely to the case for any cluster where
+                // there exists a t = 0 cluster with a hit on the same
+                // channel. Check for this case and track the instances
+                // where it occurred.
+                else if(TriggerModule.getClusterTime(pair.getReconstructedCluster()) < 32) {
+                    // Get the cluster channel.
+                    int ix = TriggerModule.getClusterXIndex(pair.getReconstructedCluster());
+                    int iy = TriggerModule.getClusterYIndex(pair.getReconstructedCluster());
+                    
+                    // Check for a t = 0 cluster with a hit on the same
+                    // channel.
+                    deadtimeLoop:
+                    for(Cluster cluster : simulatedClusters) {
+                        if(TriggerModule.getClusterTime(cluster) == 0) {
+                            if(TriggerModule.getClusterXIndex(cluster) == ix && TriggerModule.getClusterYIndex(cluster) == iy) {
+                                failedPositionDeadtime++;
+                                break deadtimeLoop;
+                            }
+                        }
+                    }
+                }
+                
+                // Check for more complicated fail state causes.
+                else {
+                    // The "clone bug" occurs when the hardware reports
+                    // a fake cluster at the same position as an existing
+                    // cluster, but at the same time as a cluster which
+                    // should exist, but is missed.
+                    // Check if more than SSP cluster exists at the
+                    // same position as another cluster, and one of them
+                    // matches in time.
+                    double time = TriggerModule.getClusterTime(pair.getReconstructedCluster());
+                    Map<Point, Integer> countMap = new HashMap<Point, Integer>();
+                    Map<Point, SSPCluster> timeMap = new HashMap<Point, SSPCluster>();
+                    for(SSPCluster sspCluster : hardwareClusters) {
+                        Point ixy = new Point(TriggerModule.getClusterXIndex(sspCluster), TriggerModule.getClusterYIndex(sspCluster));
+                        if(countMap.containsKey(ixy)) { countMap.put(ixy, countMap.get(ixy) + 1); }
+                        else { countMap.put(ixy, new Integer(1)); }
+                        if(TriggerModule.getClusterTime(sspCluster) == time) { timeMap.put(ixy, sspCluster); }
+                    }
+                    
+                    // Check each cluster position. If there are more
+                    // clusters than one at that position, and one of
+                    // them has the same time as the position failure
+                    // cluster, the clone bug could have occurred.
+                    unmatchedLoop:
+                    for(Map.Entry<Point, Integer> entry : countMap.entrySet()) {
+                    	// Check that at least two clusters exist at
+                    	// this position and that one of them matched
+                    	// the fail state cluster in time.
+                        if(entry.getValue() >= 2 && timeMap.containsKey(entry.getKey())) {
+                        	// Get the matching cluster.
+                            SSPCluster cloneTestCluster = timeMap.get(entry.getKey());
+                            
+                            // The cluster that matched the fail state
+                            // cluster in time must be itself unmatched.
+                            for(ClusterMatchedPair match : matchedPairs) {
+                            	// If the cluster matching the fail state
+                            	// cluster in time is matched, this is
+                            	// not the clone bug.
+                                if(match.getSSPCluster() == cloneTestCluster) {
+                                    continue unmatchedLoop;
+                                }
+                            }
+                            
+                            // If not match was found for the cluster
+                            // that matches the fail state cluster in
+                            // time, this is probably the clone bug.
+                            failedCloneBug++;
+                            break unmatchedLoop;
+                        }
+                    }
+                }
+            }
         }
         
         // Increment the statistics.
@@ -504,12 +827,16 @@
         
         // The hit count comparison plots are binned by individual hit
         // and run from 0 to 9 hits.
+        AIDA.defaultInstance().histogram1D(failedEnergyHitDiffPlot, 10, -0.5, 9.5);
+        AIDA.defaultInstance().histogram1D(failedHitCountHitDiffPlot, 10, -0.5, 9.5);
         AIDA.defaultInstance().histogram1D(matchedClustersHitDiffPlot, 10, -0.5, 9.5);
         AIDA.defaultInstance().histogram2D(matchedClusters2DHitDiffPlot, 10, -0.5, 9.5, 10, -0.5, 9.5);
         
         // The energy difference plots are binned on a reduced energy
         // scale, as hits typically are close in energy.
-        AIDA.defaultInstance().histogram1D(matchedClustersEnergyDiffPlot, 200, 0, 0.100);
+        AIDA.defaultInstance().histogram1D(failedEnergyEnergyDiffPlot, 67, 0, 0.201);
+        AIDA.defaultInstance().histogram1D(failedHitCountEnergyDiffPlot, 67, 0, 0.201);
+        AIDA.defaultInstance().histogram1D(matchedClustersEnergyDiffPlot, 67, 0, 0.201);
         AIDA.defaultInstance().histogram2D(matchedClusters2DEnergyDiffPlot, 34, 0, 0.102, 34, 0, 0.102);
     }
     
@@ -579,6 +906,29 @@
         
         // Return the simulated cluster collection.
         return simulatedClusters;
+    }
+    
+    /**
+     * Performs the verification check for cluster hit counts.
+     * @param simCluster - The simulated cluster to check.
+     * @param sspCluster - The hardware cluster to check.
+     * @param hitWindow - The range by which hit counts are allowed to
+     * differ between the clusters.
+     * @return Returns <code>true</code> if the hit counts match to
+     * within threshold and <code>false</code> otherwise.
+     */
+    private static final boolean isHitMatch(Cluster simCluster, SSPCluster sspCluster, int hitWindow) {
+        // Get the hit counts for both clusters.
+        double simHitCount = TriggerModule.getClusterHitCount(simCluster);
+        double sspHitCount = TriggerModule.getClusterHitCount(sspCluster);
+        
+        // The hardware does not store cluster hit counts as higher than
+        // 7, so if the software hit count is 8 or 9, we must treat it
+        // as 7 instead to match this behavior.
+        if(simHitCount > 7) { simHitCount = 7; }
+        
+        // Perform the hit count check.
+        return (sspHitCount >= simHitCount - hitWindow && sspHitCount <= simHitCount + hitWindow);
     }
     
     /**
@@ -685,16 +1035,17 @@
                 // While time and position matched clusters are considered
                 // to be the same cluster, the clusters must have similar
                 // energies and hit counts to be properly verified. First
-                // perform the energy check. The hardware cluster must
-                // match the simulated cluster energy to within a given
+                // perform the hit count check. The hardware cluster must
+                // match the simulated cluster hit count to within a given
                 // bound.
-                if(TriggerModule.getValueClusterTotalEnergy(hardwareCluster) >= TriggerModule.getValueClusterTotalEnergy(simCluster) - energyWindow
-                        && TriggerModule.getValueClusterTotalEnergy(hardwareCluster) <= TriggerModule.getValueClusterTotalEnergy(simCluster) + energyWindow) {
+                //if(TriggerModule.getClusterHitCount(hardwareCluster) >= TriggerModule.getClusterHitCount(simCluster) - hitWindow &&
+                //        TriggerModule.getClusterHitCount(hardwareCluster) <= TriggerModule.getClusterHitCount(simCluster) + hitWindow) {
+                if(isHitMatch(simCluster, hardwareCluster, hitWindow)) {
                     // Next, check that the hardware cluster matches the
-                    // simulated cluster in hit count to within a given
+                    // simulated cluster in energy to within a given
                     // bound.
-                    if(TriggerModule.getClusterHitCount(hardwareCluster) >= TriggerModule.getClusterHitCount(simCluster) - hitWindow &&
-                            TriggerModule.getClusterHitCount(hardwareCluster) <= TriggerModule.getClusterHitCount(simCluster) + hitWindow) {
+                    if(TriggerModule.getValueClusterTotalEnergy(hardwareCluster) >= TriggerModule.getValueClusterTotalEnergy(simCluster) - energyWindow
+                            && TriggerModule.getValueClusterTotalEnergy(hardwareCluster) <= TriggerModule.getValueClusterTotalEnergy(simCluster) + energyWindow) {
                         // The cluster is a match.
                         pairList.add(new ClusterMatchedPair(simCluster, hardwareCluster, ClusterMatchedPair.CLUSTER_STATE_MATCHED));
                         logger.printf("[ %7s; %9s ]%n", "success", "matched");
@@ -708,22 +1059,30 @@
                         continue softwareLoop;
                     }
                     
-                    // If the hit counts of the two clusters are not
+                    // If the energies of the two clusters are not
                     // sufficiently close, the clusters fail to verify.
                     else {
-                        pairList.add(new ClusterMatchedPair(simCluster, hardwareCluster, ClusterMatchedPair.CLUSTER_STATE_FAIL_HIT_COUNT));
-                        logger.printf("[ %7s; %9s ]%n", "fail", "hit count");
+                        pairList.add(new ClusterMatchedPair(simCluster, hardwareCluster, ClusterMatchedPair.CLUSTER_STATE_FAIL_ENERGY));
+                        logger.printf("[ %7s; %9s ]%n", "fail", "energy");
+                        AIDA.defaultInstance().histogram1D(failedEnergyHitDiffPlot).fill(Math.abs(TriggerModule.getClusterHitCount(simCluster)
+                                - TriggerModule.getClusterHitCount(hardwareCluster)));
+                        AIDA.defaultInstance().histogram1D(failedEnergyEnergyDiffPlot).fill(Math.abs(TriggerModule.getValueClusterTotalEnergy(simCluster)
+                                - TriggerModule.getValueClusterTotalEnergy(hardwareCluster)));
                         continue softwareLoop;
-                    } // End hit count check.
+                    } // End energy check.
                 }
                 
-                // If the energies of the two clusters are not
+                // If the hit counts of the two clusters are not
                 // sufficiently close, the clusters fail to verify.
                 else {
-                    pairList.add(new ClusterMatchedPair(simCluster, hardwareCluster, ClusterMatchedPair.CLUSTER_STATE_FAIL_ENERGY));
-                    logger.printf("[ %7s; %9s ]%n", "fail", "energy");
+                    pairList.add(new ClusterMatchedPair(simCluster, hardwareCluster, ClusterMatchedPair.CLUSTER_STATE_FAIL_HIT_COUNT));
+                    logger.printf("[ %7s; %9s ]%n", "fail", "hit count");
+                    AIDA.defaultInstance().histogram1D(failedHitCountHitDiffPlot).fill(Math.abs(TriggerModule.getClusterHitCount(simCluster)
+                            - TriggerModule.getClusterHitCount(hardwareCluster)));
+                    AIDA.defaultInstance().histogram1D(failedHitCountEnergyDiffPlot).fill(Math.abs(TriggerModule.getValueClusterTotalEnergy(simCluster)
+                            - TriggerModule.getValueClusterTotalEnergy(hardwareCluster)));
                     continue softwareLoop;
-                } // End energy check.
+                } // End hit count check.
             } // End hardware loop.
             
             // This point may only be reached if a cluster failed to

Modified: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DataTriggerSimDriver.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DataTriggerSimDriver.java	(original)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/DataTriggerSimDriver.java	Tue Dec  6 19:29:12 2016
@@ -214,9 +214,6 @@
                     pairCutsEnabled[i][3 + ENERGY_SLOPE] = pairs[i].getEnergySlopeCutConfig().isEnabled();
                     pairCutsEnabled[i][3 + COPLANARITY] = pairs[i].getCoplanarityCutConfig().isEnabled();
                 }
-                
-                // Output the DAQ settings.
-                logSettings();
             }
         });
     }
@@ -519,6 +516,7 @@
                                 TriggerModule.getClusterTime(reconPair[1]));
                     }
                     
+                    // Perform each trigger cut.
                     passClusterLow = pairsTrigger[triggerIndex].clusterTotalEnergyCutLow(reconPair[0])
                             && pairsTrigger[triggerIndex].clusterTotalEnergyCutLow(reconPair[1]);
                     passClusterHigh = pairsTrigger[triggerIndex].clusterTotalEnergyCutHigh(reconPair[0])
@@ -725,64 +723,4 @@
         // trigger type.
         return Double.MIN_VALUE;
     }
-    
-    /**
-     * Outputs all of the verification parameters currently in use by
-     * the software. A warning will be issued if the values for NSA and
-     * NSB, along with the FADC window, preclude clusters from being
-     * verified.
-     */
-    private void logSettings() {
-        // Print a DAQ configuration settings header.
-        System.out.println();
-        System.out.println();
-        System.out.println("======================================================================");
-        System.out.println("=== DAQ Configuration Settings =======================================");
-        System.out.println("======================================================================");
-        
-        // Output window settings.
-        System.out.println("FADC Timing Window Settings");
-        System.out.printf("\tNSB                    :: %3d ns%n", nsb);
-        System.out.printf("\tNSA                    :: %3d ns%n", nsa);
-        System.out.printf("\tFADC Window            :: %3d ns%n", windowWidth);
-        
-        // Output the singles trigger settings.
-        for(int i = 0; i < 2; i++) {
-            // Print the settings.
-            System.out.printf("Singles Trigger %d Settings%23s[%5b]%n", (i + 1), "", singlesTriggerEnabled[i]);
-            System.out.printf("\tCluster Energy Low     :: %.3f GeV      [%5b]%n",
-                    singlesTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW), singlesCutsEnabled[i][0]);
-            System.out.printf("\tCluster Energy High    :: %.3f GeV      [%5b]%n",
-                    singlesTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH), singlesCutsEnabled[i][1]);
-            System.out.printf("\tCluster Hit Count      :: %.0f hit(s)       [%5b]%n",
-                    singlesTrigger[i].getCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW), singlesCutsEnabled[i][2]);
-            System.out.println();
-        }
-        
-        // Output the pair trigger settings.
-        for(int i = 0; i < 2; i++) {
-            System.out.printf("Pairs Trigger %d Settings%25s[%5b]%n", (i + 1), "", pairTriggerEnabled[i]);
-            System.out.printf("\tCluster Energy Low     :: %.3f GeV      [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_LOW), pairCutsEnabled[i][0]);
-            System.out.printf("\tCluster Energy High    :: %.3f GeV      [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.CLUSTER_TOTAL_ENERGY_HIGH), pairCutsEnabled[i][1]);
-            System.out.printf("\tCluster Hit Count      :: %.0f hit(s)       [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.CLUSTER_HIT_COUNT_LOW), pairCutsEnabled[i][2]);
-            System.out.printf("\tPair Energy Sum Low    :: %.3f GeV      [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SUM_LOW), pairCutsEnabled[i][3]);
-            System.out.printf("\tPair Energy Sum High   :: %.3f GeV      [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SUM_HIGH), pairCutsEnabled[i][3]);
-            System.out.printf("\tPair Energy Difference :: %.3f GeV      [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_DIFFERENCE_HIGH), pairCutsEnabled[i][4]);
-            System.out.printf("\tPair Energy Slope      :: %.3f GeV      [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SLOPE_LOW), pairCutsEnabled[i][5]);
-            System.out.printf("\tPair Energy Slope F    :: %.4f GeV / mm%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F));
-            System.out.printf("\tPair Coplanarity       :: %3.0f Degrees    [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_COPLANARITY_HIGH), pairCutsEnabled[i][6]);
-            System.out.printf("\tPair Time Coincidence  :: %2.0f ns          [%5b]%n",
-                    pairsTrigger[i].getCutValue(TriggerModule.PAIR_TIME_COINCIDENCE), true);
-            System.out.println();
-        }
-    }
 }

Modified: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java	(original)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/TriggerDiagnosticDriver.java	Tue Dec  6 19:29:12 2016
@@ -89,6 +89,50 @@
      * array index corresponding to the trigger of the same trigger number. */
     private TriggerModule[] pairTrigger = new TriggerModule[2];
     
+    // === Plotting variables. ==========================================================
+    // ==================================================================================
+    /** Defines the basic directory structure for all plots used in the
+     * class. This is instantiated in <code>startOfData</code>. */
+    private String moduleHeader;
+    /** Stores the number of bins used by the efficiency plots for each
+     * conventional trigger cut. Array index corresponds to the ordinal
+     * value of the <code>CutType</code> enumerable for all values for
+     * which <code>isSpecial()</code> is false. Note that this variable
+     * is defined as a function of the variable arrays <code>xMax</code>
+     * and <code>binSize</code> during <code>startOfData()</code>. */
+    private int[] bins = new int[8];
+    /** Stores the x-axis maximum used by the efficiency plots for each
+     * conventional trigger cut. Array index corresponds to the ordinal
+     * value of the <code>CutType</code> enumerable for all values for
+     * which <code>isSpecial()</code> is false. */
+    private double[] xMax = {
+            2.200,          // Seed energy,        xMax = 2.2 GeV
+            2.200,          // Cluster energy,     xMax = 2.2 GeV
+            9.5,            // Hit count,          xMax = 9.5 hits
+            2.200,          // Energy sum,         xMax = 2.2 GeV
+            2.200,          // Energy difference,  xMax = 2.2 GeV
+            4.000,          // Energy slope,       xMax = 4.0 GeV
+            180.0,          // Coplanarity,        xMax = 180 degrees
+            30.0            // Time coincidence,   xMax = 30 ns
+    };
+    /** Store the size of a bin used by the efficiency plots for each
+     * conventional trigger cut. Array index corresponds to the ordinal
+     * value of the <code>CutType</code> enumerable for all values for
+     * which <code>isSpecial()</code> is false. */
+    private double[] binSize = {
+            0.050,          // Seed energy,        binSize = 50 MeV
+            0.050,          // Cluster energy,     binSize = 50 MeV
+            1,              // Hit count,          binSize = 1 hit
+            0.050,          // Energy sum,         binSize = 50 MeV
+            0.050,          // Energy difference,  binSize = 50 MeV
+            0.050,          // Energy slope,       binSize = 50 MeV
+            5,              // Coplanarity,        binSize = 5 degrees
+            4               // Time coincidence,   binSize = 2 ns
+    };
+    /** Stores a list of all trigger types that are used for plotting
+     * efficiency plots. This is filled in <code>startOfData</code>. */
+    private List<TriggerType> triggerTypes = new ArrayList<TriggerType>(TriggerType.values().length + 1);
+    
     // === Trigger matching statistics. =================================================
     // ==================================================================================
     private static final int SOURCE_SIM_CLUSTER = 0;
@@ -158,13 +202,13 @@
     // ==================================================================================
     /** The number of samples before a pulse-crossing event to integrate
      * during hit formation. Used to determine the risk of pulse-clipping. */
-    private int nsb =  20;
+    private int nsb =  -1;
     /** The number of samples after a pulse-crossing event to integrate
      * during hit formation. Used to determine the risk of pulse-clipping. */
-    private int nsa = 100;
+    private int nsa = -1;
     /** The width of the pulse integration window used to form hits.
      * Used to determine the risk of pulse-clipping. */
-    private int windowWidth = 200;
+    private int windowWidth = -1;
     /** The number of hits that must be present in event in order for
      * it to be ignored as a "noise event." */
     private int noiseEventThreshold = 50;
@@ -203,6 +247,94 @@
      */
     private List<Pair<Long, int[][]>> efficiencyPlotEntries = new ArrayList<Pair<Long, int[][]>>();
     
+    /**
+     * Enumerable <code>CutType</code> represents a type of cut which
+     * against which trigger efficiency may be plotted. It also provides
+     * mechanisms by which a human-readable name may be acquired and
+     * also whether or not the cut is a real trigger cut, or a special
+     * cut used for plotting efficiency only.
+     * 
+     * @author Kyle McCarty <[log in to unmask]>
+     */
+    private enum CutType {
+        CLUSTER_SEED_ENERGY("Cluster Seed Energy", true, false), CLUSTER_TOTAL_ENERGY("Cluster Total Energy", true, false),
+        CLUSTER_HIT_COUNT("Cluster Hit Count", true, false), PAIR_ENERGY_SUM("Pair Energy Sum", false, true),
+        PAIR_ENERGY_DIFF("Pair Energy Difference", false, true), PAIR_ENERGY_SLOPE("Pair Energy Slope", false, true),
+        PAIR_COPLANARITY("Pair Coplanarity", false, true), PAIR_TIME_COINCIDENCE("Pair Time Coincidence", false, true),
+        PAIR_LOW_ENERGY("Pair Lower Cluster Energy", false, true, true), PAIR_HIGH_ENERGY("Pair Upper Cluster Energy", false, true, true),
+        EVENT_TIME("Event Time", true, true, true);
+        
+        private final String name;
+        private final boolean isPair;
+        private final boolean isSpecial;
+        private final boolean isSingles;
+        
+        /**
+         * Instantiates a cut. The cut is assumed to be a real trigger
+         * cut, and not a "special cut" included for plotting purposes.
+         * @param name - The name of the cut in a human-readable form.
+         * @param isSingles - Whether or not this is a singles cut.
+         * <code>true</code> means that it is and <code>false</code>
+         * that it is not.
+         * @param isPair - Whether or not this is a pair cut.
+         * <code>true</code> means that it is and <code>false</code>
+         * that it is not.
+         */
+        private CutType(String name, boolean isSingles, boolean isPair) {
+            this.name = name;
+            isSpecial = false;
+            this.isPair = isPair;
+            this.isSingles = isSingles;
+        }
+        
+        /**
+         * Instantiates a cut.
+         * @param name - The name of the cut in a human-readable form.
+         * @param isSingles - Whether or not this is a singles cut.
+         * <code>true</code> means that it is and <code>false</code>
+         * that it is not.
+         * @param isPair - Whether or not this is a pair cut.
+         * <code>true</code> means that it is and <code>false</code>
+         * that it is not.
+         * @param isSpecial - Whether or not the cut is a real trigger
+         * cut or not. <code>true</code> indicates that it is a trigger
+         * cut and <code>false</code> that it is not.
+         */
+        private CutType(String name, boolean isSingles, boolean isPair, boolean isSpecial) {
+            this.name = name;
+            this.isPair = isPair;
+            this.isSingles = isSingles;
+            this.isSpecial = isSpecial;
+        }
+        
+        /**
+         * Indicates whether this is a singles cut.
+         * @return Returns <code>true</code> to indicate that it is
+         * and <code>false</code> that it is not.
+         */
+        public boolean isSingles() { return isSingles; }
+        
+        /**
+         * Indicates whether this is a pair cut.
+         * @return Returns <code>true</code> to indicate that it is
+         * and <code>false</code> that it is not.
+         */
+        public boolean isPair() { return isPair; }
+        
+        /**
+         * Indicates whether this is an actual trigger cut or not. Some
+         * "special cuts" are included because they provide useful data
+         * when efficiency is plotted against them, but they are not
+         * properly trigger cuts.
+         * @return Returns whether or not the cut is a real trigger cut
+         * or not. <code>true</code> indicates that it is a trigger cut
+         * and <code>false</code> that it is not.
+         */
+        public boolean isSpecial() { return isSpecial; }
+        
+        @Override
+        public String toString() { return name; }
+    }
     
     /**
      * Enumerable <code>TriggerType</code> represents the supported
@@ -249,6 +381,15 @@
         public boolean isSinglesTrigger() { return (this.equals(SINGLES0) || this.equals(SINGLES1)); }
         
         /**
+         * Indicates whether this trigger type is a pair trigger.
+         * @return Returns <code>true</code> if the trigger is of type
+         * <code>TriggerType.PAIR0</code> or
+         * <code>TriggerType.PAIR1</code>. Otherwise, returns
+         * <code>false</code>.
+         */
+        public boolean isPairTrigger() { return (this.equals(PAIR0) || this.equals(PAIR1)); }
+        
+        /**
          * Gets the trigger number for this trigger type.
          * @return Returns either <code>0</code> or <code>1</code> as
          * appropriate for singles and pair trigger types. For cosmic
@@ -270,7 +411,7 @@
         int largestValue = max(hardwareTriggerCount[ALL_TRIGGERS], simTriggerCount[SOURCE_SIM_CLUSTER][ALL_TRIGGERS],
                 simTriggerCount[SOURCE_SSP_CLUSTER][ALL_TRIGGERS], matchedTriggerCount[SOURCE_SIM_CLUSTER][ALL_TRIGGERS],
                 matchedTriggerCount[SOURCE_SSP_CLUSTER][ALL_TRIGGERS]);
-        int maxChars = getDigits(largestValue);
+        int maxChars = TriggerDiagnosticUtil.getDigits(largestValue);
         String charDisplay = "%" + maxChars + "d";
         
         // Calculate the efficiencies and determine the display value.
@@ -319,7 +460,7 @@
                     matchedTriggerCount[SOURCE_SIM_CLUSTER][trigger.ordinal()], simTriggerCount[SOURCE_SSP_CLUSTER][trigger.ordinal()],
                     matchedTriggerCount[SOURCE_SSP_CLUSTER][trigger.ordinal()]);
         }
-        int tiMaxChars = getDigits(tiMaxValue);
+        int tiMaxChars = TriggerDiagnosticUtil.getDigits(tiMaxValue);
         
         // Define the column width and column headers for the TI-bit
         // specific efficiencies.
@@ -381,7 +522,6 @@
         }
         
         // Create and populate the efficiency over time plot.
-        final String moduleHeader = "Trigger Diagnostics/Trigger Verification/" + triggerType.toString() + "/";
         AIDA.defaultInstance().cloud2D(moduleHeader + "Software Sim Trigger Efficiency", efficiencyPlotEntries.size());
         AIDA.defaultInstance().cloud2D(moduleHeader + "Hardware Sim Trigger Efficiency", efficiencyPlotEntries.size());
         for(Pair<Long, int[][]> entry : efficiencyPlotEntries) {
@@ -399,6 +539,24 @@
                 AIDA.defaultInstance().cloud2D(moduleHeader + "Software Sim Trigger Efficiency").fill(time, softwareEfficiency);
             } if(!Double.isNaN(hardwareEfficiency)) {
                 AIDA.defaultInstance().cloud2D(moduleHeader + "Hardware Sim Trigger Efficiency").fill(time, hardwareEfficiency);
+            }
+        }
+        
+        // Create the efficiency plots from the observed and verified
+        // trigger plots, as appropriate.
+        for(TriggerType trigger : triggerTypes) {
+            for(CutType cut : CutType.values()) {
+                // Only process plots appropriate to the trigger type.
+                if((triggerType.isSinglesTrigger() && !cut.isSingles()) || (triggerType.isPairTrigger() && !cut.isPair())) {
+                    continue;
+                }
+                
+                // Define the plot for the current TI-bit and cut.
+                for(int type = SOURCE_SIM_CLUSTER; type <= SOURCE_SSP_CLUSTER; type++) {
+                    AIDA.defaultInstance().histogramFactory().divide(getPlotNameEfficiency(cut, trigger, type),
+                            AIDA.defaultInstance().histogram1D(getPlotNameVerified(cut, trigger, type)),
+                            AIDA.defaultInstance().histogram1D(getPlotNameTotal(cut, trigger, type)));
+                }
             }
         }
     }
@@ -582,6 +740,22 @@
             public void actionPerformed(ActionEvent e) {
                 // Get the DAQ configuration.
                 DAQConfig daq = ConfigurationManager.getInstance();
+                
+                // If the event time plots are not instantiated, do so.
+                if(nsa == -1) {
+                    // Calculate the bin count and x-axis maximum.
+                    int bins = (daq.getFADCConfig().getWindowWidth() / 4) + 1;
+                    int xMax = daq.getFADCConfig().getWindowWidth() + 2;
+                    
+                    // Instantiate the plots for each trigger bit.
+                    for(TriggerType trigger : triggerTypes) {
+                        for(int type = SOURCE_SIM_CLUSTER; type <= SOURCE_SSP_CLUSTER; type++) {
+                            AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.EVENT_TIME, trigger, type), bins, -2, xMax);
+                            AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.EVENT_TIME, trigger, type), bins, -2, xMax);
+                            AIDA.defaultInstance().histogram1D(getPlotNameEfficiency(CutType.EVENT_TIME, trigger, type), bins, -2, xMax);
+                        }
+                    }
+                }
                 
                 // Load the DAQ settings from the configuration manager.
                 singlesTrigger[0].loadDAQConfiguration(daq.getSSPConfig().getSingles1Config());
@@ -631,6 +805,72 @@
         pairTrigger[1].setCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F, 0.001);
         pairTrigger[1].setCutValue(TriggerModule.PAIR_COPLANARITY_HIGH, 180);
         pairTrigger[1].setCutValue(TriggerModule.PAIR_TIME_COINCIDENCE, 8);
+        
+        // Set the trigger plots module name.
+        moduleHeader = "Trigger Diagnostics/Trigger Verification/" + triggerType.toString() + "/";
+        
+        // Instantiate the trigger efficiency plots. Note that the time
+        // coincidence plot is instantiated in the ConfigurationManager
+        // listener, as it needs to know the event readout window size.
+        for(TriggerType trigger : TriggerType.values()) { triggerTypes.add(trigger); }
+        triggerTypes.add(null);
+        for(TriggerType trigger : triggerTypes) {
+            for(CutType cut : CutType.values()) {
+                // Skip "special" plotting cuts. These are defined in
+                // other locations.
+                if(cut.isSpecial()) { continue; }
+                
+                // Make sure that the maximum x-axis values for the efficiency
+                // plots are evenly divisible by the bin size.
+                if(Math.floor(1.0 * xMax[cut.ordinal()] / binSize[cut.ordinal()]) != (xMax[cut.ordinal()] / binSize[cut.ordinal()])) {
+                    xMax[cut.ordinal()] = Math.ceil(xMax[cut.ordinal()] / binSize[cut.ordinal()]) * binSize[cut.ordinal()];
+                }
+                
+                // Define the bin counts for each plot.
+                bins[cut.ordinal()] = (int) Math.ceil(xMax[cut.ordinal()] / binSize[cut.ordinal()]);
+                
+                // Only generate plots appropriate to the trigger type.
+                if((triggerType.isSinglesTrigger() && !cut.isSingles()) || (triggerType.isPairTrigger() && !cut.isPair())) {
+                    continue;
+                }
+                
+                // Define the plot for the current TI-bit and cut.
+                for(int type = SOURCE_SIM_CLUSTER; type <= SOURCE_SSP_CLUSTER; type++) {
+                    AIDA.defaultInstance().histogram1D(getPlotNameTotal(cut, trigger, type), bins[cut.ordinal()],
+                            0.0, bins[cut.ordinal()] * binSize[cut.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameVerified(cut, trigger, type), bins[cut.ordinal()],
+                            0.0, bins[cut.ordinal()] * binSize[cut.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameEfficiency(cut, trigger, type), bins[cut.ordinal()],
+                            0.0, bins[cut.ordinal()] * binSize[cut.ordinal()]);
+                }
+            }
+            
+            // Define the pair cluster high and low energy plots. These
+            // use the same values as the cluster total energy plot.
+            // These plots are only initialized for pair triggers.
+            if(triggerType.isPairTrigger()) {
+                for(int type = SOURCE_SIM_CLUSTER; type <= SOURCE_SSP_CLUSTER; type++) {
+                    AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_LOW_ENERGY, trigger, type),
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()], 0.0,
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()] * binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_LOW_ENERGY, trigger, type),
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()], 0.0,
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()] * binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameEfficiency(CutType.PAIR_LOW_ENERGY, trigger, type),
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()], 0.0,
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()] * binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_HIGH_ENERGY, trigger, type),
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()], 0.0,
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()] * binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_HIGH_ENERGY, trigger, type),
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()], 0.0,
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()] * binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]);
+                    AIDA.defaultInstance().histogram1D(getPlotNameEfficiency(CutType.PAIR_HIGH_ENERGY, trigger, type),
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()], 0.0,
+                            bins[CutType.CLUSTER_TOTAL_ENERGY.ordinal()] * binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]);
+                }
+            }
+        }
     }
     
     /**
@@ -771,6 +1011,9 @@
                 logger.printf(" [ trigger verified   ]%n");
                 matchedTriggers.add(hardwareTrigger);
                 
+                // Plot the trigger for the verified plots.
+                plotTrigger(simTrigger, tiFlags, true);
+                
                 // Update the verified count for each type of trigger
                 // for the local and global windows.
                 if(clusterType == Cluster.class || clusterType == Cluster[].class) {
@@ -879,19 +1122,6 @@
     }
     
     /**
-     * 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.
-     */
-    private static final int getDigits(int value) {
-        if(value < 0) { return Integer.toString(value).length() - 1; }
-        else { return Integer.toString(value).length(); }
-    }
-    
-    /**
      * A helper method associated with <code>getTriggerTime</code> that
      * handles pair triggers, which have either <code>Cluster[]</code>
      * or <code>SSPCluster[]</code> objects as their source type. <b>This
@@ -934,6 +1164,52 @@
         } else {
             throw new IllegalArgumentException("Ambiguous cluster pair; both top clusters.");
         }
+    }
+    
+    private final String getPlotName(String footer, CutType cut, TriggerType tiBit, int sourceType) {
+        // Make sure that a cut was defined.
+        if(cut == null) {
+            throw new NullPointerException("Plot cut type was not defined.");
+        }
+        
+        // Make sure a valid source type is defined.
+        if(sourceType != SOURCE_SIM_CLUSTER && sourceType != SOURCE_SSP_CLUSTER) {
+            throw new NullPointerException("\"" + sourceType + "\" is not a valid source type index.");
+        }
+        
+        // Get the appropriate name for the TI bit.
+        String tiName = getPlotTIName(tiBit);
+        
+        // Define the source type name.
+        String sourceName;
+        if(sourceType == SOURCE_SIM_CLUSTER) { sourceName = "Software Sim Distributions/"; }
+        else { sourceName = "Hardware Sim Distributions/"; }
+        
+        // Return the name of the coplanarity plot.
+        return moduleHeader + sourceName + tiName + "/" + cut.toString() + footer;
+    }
+    
+    private final String getPlotNameEfficiency(CutType cut, TriggerType tiBit, int sourceType) {
+        return getPlotName(" Efficiency", cut, tiBit, sourceType);
+    }
+    
+    private final String getPlotNameTotal(CutType cut, TriggerType tiBit, int sourceType) {
+        return getPlotName(" (Observed)", cut, tiBit, sourceType);
+    }
+    
+    private final String getPlotNameVerified(CutType cut, TriggerType tiBit, int sourceType) {
+        return getPlotName(" (Verified)", cut, tiBit, sourceType);
+    }
+    
+    /**
+     * Returns the name of the trigger type in the argument, or "All"
+     * if a null argument is given.
+     * @param tiBit - The trigger type.
+     * @return Returns either the name of the trigger type or "All."
+     */
+    private static final String getPlotTIName(TriggerType tiBit) {
+        if(tiBit == null) { return "All"; }
+        else { return tiBit.toString(); }
     }
     
     /**
@@ -1160,6 +1436,139 @@
         
         // Return the result.
         return maxValue;
+    }
+    
+    private void plotTrigger(Trigger<?> trigger, boolean[] activeTIBits, boolean verified) {
+        // Which plots are to be populated depends on the type of
+        // trigger. First, handle singles triggers.
+        if(trigger.getTriggerSource() instanceof Cluster || trigger.getTriggerSource() instanceof SSPCluster) {
+            // Define the plot values.
+            int sourceType;
+            double clusterEnergy;
+            double hitCount;
+            double eventTime = getTriggerTime(trigger);
+            
+            // Get the values. This will depend on the cluster type.
+            if(trigger.getTriggerSource() instanceof Cluster) {
+                // Fill the plot value variables.
+                Cluster cluster = (Cluster) trigger.getTriggerSource();
+                clusterEnergy = TriggerModule.getValueClusterTotalEnergy(cluster);
+                hitCount = TriggerModule.getClusterHitCount(cluster);
+                
+                // Note that the source type is a sim cluster.
+                sourceType = SOURCE_SIM_CLUSTER;
+                
+                // Seed energy is also plotted here, as it does not exist
+                // for SSP clusters.
+                double seedEnergy = TriggerModule.getValueClusterSeedEnergy(cluster);
+                for(TriggerType tiBit : triggerTypes) {
+                    if(tiBit == null || activeTIBits[tiBit.ordinal()]) {
+                        if(verified) {
+                            AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.CLUSTER_SEED_ENERGY, tiBit, sourceType)).fill(seedEnergy);
+                        } else {
+                            AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.CLUSTER_SEED_ENERGY, tiBit, sourceType)).fill(seedEnergy);
+                        }
+                    }
+                }
+            } else if(trigger.getTriggerSource() instanceof SSPCluster) {
+                // Fill the plot value variables.
+                SSPCluster cluster = (SSPCluster) trigger.getTriggerSource();
+                clusterEnergy = TriggerModule.getValueClusterTotalEnergy(cluster);
+                hitCount = TriggerModule.getClusterHitCount(cluster);
+                
+                // Note that the source type is an SSP cluster.
+                sourceType = SOURCE_SSP_CLUSTER;
+            } else {
+                throw new IllegalArgumentException("Trigger source " + trigger.getTriggerSource().getClass().getSimpleName() + " is not recognized.");
+            }
+            
+            // Populate the appropriate trigger plot.
+            for(TriggerType tiBit : triggerTypes) {
+                if(tiBit == null || activeTIBits[tiBit.ordinal()]) {
+                    if(verified) {
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.EVENT_TIME, tiBit, sourceType)).fill(eventTime);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.CLUSTER_HIT_COUNT, tiBit, sourceType)).fill(hitCount);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.CLUSTER_TOTAL_ENERGY, tiBit, sourceType)).fill(clusterEnergy);
+                    } else {
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.EVENT_TIME, tiBit, sourceType)).fill(eventTime);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.CLUSTER_HIT_COUNT, tiBit, sourceType)).fill(hitCount);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.CLUSTER_TOTAL_ENERGY, tiBit, sourceType)).fill(clusterEnergy);
+                    }
+                }
+            }
+        } else if(trigger.getTriggerSource() instanceof Cluster[] || trigger.getTriggerSource() instanceof SSPCluster[]) {
+            // Define the plot values.
+            int sourceType;
+            double energySum;
+            double energyDiff;
+            double energySlope;
+            double coplanarity;
+            double timeCoincidence;
+            double clusterLow;
+            double clusterHigh;
+            double eventTime = getTriggerTime(trigger);
+            
+            // How the values are filled depends on the source cluster
+            // type.
+            if(trigger.getTriggerSource() instanceof Cluster[]) {
+                // Fill the plot value variables.
+                Cluster[] pair = (Cluster[]) trigger.getTriggerSource();
+                energySum = TriggerModule.getValueEnergySum(pair);
+                energyDiff = TriggerModule.getValueEnergyDifference(pair);
+                energySlope = TriggerModule.getValueEnergySlope(pair,
+                        pairTrigger[triggerType.getTriggerNumber()].getCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F));
+                coplanarity = TriggerModule.getValueCoplanarity(pair);
+                timeCoincidence = TriggerModule.getValueTimeCoincidence(pair);
+                clusterLow = Math.min(TriggerModule.getValueClusterTotalEnergy(pair[0]), TriggerModule.getValueClusterTotalEnergy(pair[1]));
+                clusterHigh = Math.max(TriggerModule.getValueClusterTotalEnergy(pair[0]), TriggerModule.getValueClusterTotalEnergy(pair[1]));
+                
+                // Note that the source type is a sim cluster.
+                sourceType = SOURCE_SIM_CLUSTER;
+            } else if(trigger.getTriggerSource() instanceof SSPCluster[]) {
+                // Fill the plot value variables.
+                SSPCluster[] pair = (SSPCluster[]) trigger.getTriggerSource();
+                energySum = TriggerModule.getValueEnergySum(pair);
+                energyDiff = TriggerModule.getValueEnergyDifference(pair);
+                energySlope = TriggerModule.getValueEnergySlope(pair,
+                        pairTrigger[triggerType.getTriggerNumber()].getCutValue(TriggerModule.PAIR_ENERGY_SLOPE_F));
+                coplanarity = TriggerModule.getValueCoplanarity(pair);
+                timeCoincidence = TriggerModule.getValueTimeCoincidence(pair);
+                clusterLow = Math.min(TriggerModule.getValueClusterTotalEnergy(pair[0]), TriggerModule.getValueClusterTotalEnergy(pair[1]));
+                clusterHigh = Math.max(TriggerModule.getValueClusterTotalEnergy(pair[0]), TriggerModule.getValueClusterTotalEnergy(pair[1]));
+                
+                // Note that the source type is an SSP cluster.
+                sourceType = SOURCE_SSP_CLUSTER;
+            } else {
+                throw new IllegalArgumentException("Trigger source " + trigger.getTriggerSource().getClass().getSimpleName() + " is not recognized.");
+            }
+            
+            // Fill the appropriate plots.
+            for(TriggerType tiBit : triggerTypes) {
+                if(tiBit == null || activeTIBits[tiBit.ordinal()]) {
+                    if(verified) {
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.EVENT_TIME, tiBit, sourceType)).fill(eventTime);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_ENERGY_SUM, tiBit, sourceType)).fill(energySum);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_ENERGY_DIFF, tiBit, sourceType)).fill(energyDiff);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_ENERGY_SLOPE, tiBit, sourceType)).fill(energySlope);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_COPLANARITY, tiBit, sourceType)).fill(coplanarity);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_TIME_COINCIDENCE, tiBit, sourceType)).fill(timeCoincidence);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_LOW_ENERGY, tiBit, sourceType)).fill(clusterLow);
+                        AIDA.defaultInstance().histogram1D(getPlotNameVerified(CutType.PAIR_HIGH_ENERGY, tiBit, sourceType)).fill(clusterHigh);
+                    } else {
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.EVENT_TIME, tiBit, sourceType)).fill(eventTime);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_ENERGY_SUM, tiBit, sourceType)).fill(energySum);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_ENERGY_DIFF, tiBit, sourceType)).fill(energyDiff);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_ENERGY_SLOPE, tiBit, sourceType)).fill(energySlope);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_COPLANARITY, tiBit, sourceType)).fill(coplanarity);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_TIME_COINCIDENCE, tiBit, sourceType)).fill(timeCoincidence);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_LOW_ENERGY, tiBit, sourceType)).fill(clusterLow);
+                        AIDA.defaultInstance().histogram1D(getPlotNameTotal(CutType.PAIR_HIGH_ENERGY, tiBit, sourceType)).fill(clusterHigh);
+                    }
+                }
+            }
+        } else {
+            throw new IllegalArgumentException("Trigger type " + trigger.getClass().getSimpleName() + " is not recognized.");
+        }
     }
     
     /**
@@ -1312,6 +1721,10 @@
             }
         }
         
+        // Print the observed trigger distributions.
+        for(Trigger<?> trigger : softwareSimTriggers) { plotTrigger(trigger, tiFlags, false); }
+        for(Trigger<?> trigger : hardwareSimTriggers) { plotTrigger(trigger, tiFlags, false); }
+        
         // Run the trigger verification for each simulated trigger type.
         logger.printNewLine(2);
         logger.println("=== Performing Trigger Verification ==================================");
@@ -1467,4 +1880,22 @@
     public void setVerbose(boolean state) {
         verbose = state;
     }
+    
+    public void setClusterSeedEnergyXMax(double value)   { xMax[CutType.CLUSTER_SEED_ENERGY.ordinal()]   = value; }
+    public void setClusterTotalEnergyXMax(double value)  { xMax[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]  = value; }
+    public void setClusterHitCountXMax(double value)     { xMax[CutType.CLUSTER_HIT_COUNT.ordinal()]     = value; }
+    public void setPairEnergySumXMax(double value)       { xMax[CutType.PAIR_ENERGY_SUM.ordinal()]       = value; }
+    public void setPairEnergyDiffXMax(double value)      { xMax[CutType.PAIR_ENERGY_DIFF.ordinal()]      = value; }
+    public void setPairEnergySlopeXMax(double value)     { xMax[CutType.PAIR_ENERGY_SLOPE.ordinal()]     = value; }
+    public void setPairCoplanarityXMax(double value)     { xMax[CutType.PAIR_COPLANARITY.ordinal()]      = value; }
+    public void setPairTimeCoincidenceXMax(double value) { xMax[CutType.PAIR_TIME_COINCIDENCE.ordinal()] = value; }
+    
+    public void setClusterSeedEnergyBinSize(double value)   { binSize[CutType.CLUSTER_SEED_ENERGY.ordinal()]   = value; }
+    public void setClusterTotalEnergyBinSize(double value)  { binSize[CutType.CLUSTER_TOTAL_ENERGY.ordinal()]  = value; }
+    public void setClusterHitCountBinSize(double value)     { binSize[CutType.CLUSTER_HIT_COUNT.ordinal()]     = value; }
+    public void setPairEnergySumBinSize(double value)       { binSize[CutType.PAIR_ENERGY_SUM.ordinal()]       = value; }
+    public void setPairEnergyDiffBinSize(double value)      { binSize[CutType.PAIR_ENERGY_DIFF.ordinal()]      = value; }
+    public void setPairEnergySlopeBinSize(double value)     { binSize[CutType.PAIR_ENERGY_SLOPE.ordinal()]     = value; }
+    public void setPairCoplanarityBinSize(double value)     { binSize[CutType.PAIR_COPLANARITY.ordinal()]      = value; }
+    public void setPairTimeCoincidenceBinSize(double value) { binSize[CutType.PAIR_TIME_COINCIDENCE.ordinal()] = value; }
 }

Modified: java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java
 =============================================================================
--- java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java	(original)
+++ java/trunk/analysis/src/main/java/org/hps/analysis/trigger/util/TriggerDiagnosticUtil.java	Tue Dec  6 19:29:12 2016
@@ -60,6 +60,19 @@
     }
     
     /**
+     * Gets the number of digits in the base-10 String representation
+     * of an integer primitive. Negative signs are not included in the
+     * digit count.
+     * @param value - The value of which to obtain the length.
+     * @return Returns the number of digits in the String representation
+     * of the argument value.
+     */
+    public static final int getDigits(int value) {
+        if(value < 0) { return Integer.toString(value).length() - 1; }
+        else { return Integer.toString(value).length(); }
+    }
+    
+    /**
      * Checks whether a cluster is within the safe region of the FADC
      * output window.
      * @param sspCluster - The cluster to check.

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