LCOV - code coverage report
Current view: top level - triggeralgs/src - TAMakerPlaneCoincidenceAlgorithm.cpp (source / functions) Coverage Total Hit
Test: code.result Lines: 0.8 % 131 1
Test Date: 2025-12-21 13:07:08 Functions: 9.1 % 11 1

            Line data    Source code
       1              : /**
       2              :  * @file TAMakerPlaneCoincidenceAlgorithm.cpp
       3              :  *
       4              :  * This is part of the DUNE DAQ Application Framework, copyright 2021.
       5              :  * Licensing/copyright details are in the COPYING file that you should have
       6              :  * received with this code.
       7              :  */
       8              : 
       9              : #include "triggeralgs/PlaneCoincidence/TAMakerPlaneCoincidenceAlgorithm.hpp"
      10              : #include "TRACE/trace.h"
      11              : #define TRACE_NAME "TAMakerPlaneCoincidenceAlgorithm"
      12              : #include <vector>
      13              : 
      14              : using namespace triggeralgs;
      15              : 
      16              : using Logging::TLVL_DEBUG_MEDIUM;
      17              : 
      18              : void
      19            0 : TAMakerPlaneCoincidenceAlgorithm::process(const TriggerPrimitive& input_tp, std::vector<TriggerActivity>& output_ta)
      20              : {
      21              : 
      22              :   // Get the plane from which this hit arrived: 
      23              :   // U (induction) = 0, Y (induction) = 1, Z (collection) = 2, unconnected channel = 9999  
      24            0 :   uint plane = channelMap->get_plane_from_offline_channel(input_tp.channel);
      25            0 :   bool isU = plane == 0;  // Induction1 = U
      26            0 :   bool isY = plane == 1;  // Induction2 = Y
      27            0 :   bool isZ = plane == 2;  // Collection = Z
      28              :  
      29              :   // The first time operator() is called, reset the window object(s).
      30            0 :   if (m_induction1_window.is_empty() && isU) { m_induction1_window.reset(input_tp); m_primitive_count++; return; }
      31            0 :   if (m_induction2_window.is_empty() && isY) { m_induction2_window.reset(input_tp); m_primitive_count++; return; }
      32            0 :   if (m_collection_window.is_empty() && isZ) { m_collection_window.reset(input_tp); m_primitive_count++; return; }
      33              :   
      34              :   // If the difference between the current TP's start time and the start of the window
      35              :   // is less than the specified window size, add the TP to the corresponding window.
      36            0 :   if (isU && (input_tp.time_start - m_induction1_window.time_start) < m_window_length) m_induction1_window.add(input_tp);
      37            0 :   if (isY && (input_tp.time_start - m_induction2_window.time_start) < m_window_length) m_induction2_window.add(input_tp);
      38            0 :   if (isZ && (input_tp.time_start - m_collection_window.time_start) < m_window_length) m_collection_window.add(input_tp); 
      39              : 
      40              :   // ISSUE - We are checking the collection plane window too early: Every time we are NOT receiving a collection plane
      41              :   // TP, we're checking the trigger conditions. Fix this immediately and rerun trigger runs to test.
      42              : 
      43              :   // ===================================================================================
      44              :   // Below this line, we begin our hierarchy of checks for a low energy event,
      45              :   // taking advantage of the newly gained induction hits window.
      46              :   // ===================================================================================
      47              : 
      48              :   // Our windows have ADC Sum, Time Over Threshold, Multiplicity & Adjacency properties.
      49              :   // Take advantage of these to screen the activities passed to more involved checks.
      50              : 
      51              :   // 1) REQUIRE ADC SPIKE FROM INDUCTION AND CHECK ADJACENCY ===========================
      52              :   // We're looking for a localised spike of ADC (short time window) and then a short
      53              :   // adjacency corresponding to an electron track/shower.
      54              :   
      55              :   // ISSUE - We are checking the collection plane window too early and too frequently probably:
      56              :   // Every time we are NOT receiving a collection plane TP, we're checking the trigger conditions. 
      57              :   // Fix this immediately and rerun trigger runs to test.
      58              :   // Introduce bool to check for collection window completeness:
      59            0 :   bool collectionComplete = (input_tp.time_start - m_collection_window.time_start) > m_window_length;
      60              :   // Then require that the collection window be complete in the adjacency checks
      61            0 :   if (!collectionComplete) { } // Do nothing
      62            0 :   else if (collectionComplete && (m_induction1_window.adc_integral + m_induction2_window.adc_integral + m_collection_window.adc_integral)
      63            0 :             > m_adc_threshold && check_adjacency(m_collection_window) >= m_adjacency_threshold){
      64              : 
      65            0 :           TLOG_DEBUG(TLVL_DEBUG_MEDIUM) << "[TAM:PC] Emitting low energy trigger with " << m_induction1_window.adc_integral << " U "
      66            0 :                   << m_induction2_window.adc_integral << " Y induction ADC sums and "
      67            0 :                   << check_adjacency(m_collection_window) << " adjacent collection hits.";
      68              :    
      69              :           // Initial studies - output the TPs of the collection plane window that caused this trigger
      70            0 :           add_window_to_record(m_collection_window);
      71            0 :           dump_window_record();
      72            0 :           m_window_record.clear();     
      73              : 
      74              :           // Initial studies - Also dump the TPs that have contributed to this TA decision
      75            0 :           for(auto tp : m_collection_window.inputs) dump_tp(tp);
      76              :  
      77              :           // We have fulfilled our trigger condition, construct a TA and reset/flush the windows
      78              :           // to ensure they're all in the same "time zone"!
      79            0 :           output_ta.push_back(construct_ta(m_collection_window));
      80            0 :           if (isZ) m_collection_window.reset(input_tp);
      81            0 :           else m_collection_window.clear();
      82            0 :           if (isU) m_induction1_window.reset(input_tp); 
      83            0 :           else m_induction1_window.clear();
      84            0 :           if (isY) m_induction2_window.reset(input_tp);   
      85            0 :           else m_induction2_window.clear();
      86              :   }
      87              : 
      88              :   // Otherwise, slide the relevant window along using the current TP.
      89              :   else {
      90            0 :     if (isU) m_induction1_window.move(input_tp, m_window_length);
      91            0 :     else if (isY) m_induction2_window.move(input_tp, m_window_length);
      92            0 :     else if (isZ) m_collection_window.move(input_tp, m_window_length);
      93              :   }
      94            0 :   m_primitive_count++;
      95              : 
      96            0 :   return;
      97              : }
      98              : 
      99              : void
     100            0 : TAMakerPlaneCoincidenceAlgorithm::configure(const nlohmann::json& config)
     101              : {
     102            0 :   TriggerActivityMaker::configure(config);
     103              : 
     104            0 :   if (config.is_object()) {
     105            0 :     if (config.contains("adc_threshold"))
     106            0 :       m_adc_threshold = config["adc_threshold"];
     107            0 :     if (config.contains("window_length"))
     108            0 :       m_window_length = config["window_length"];
     109            0 :     if (config.contains("adjacency_tolerance"))
     110            0 :       m_adj_tolerance = config["adjacency_tolerance"];
     111            0 :     if (config.contains("adjacency_threshold"))
     112            0 :       m_adjacency_threshold = config["adjacency_threshold"];
     113              :   }
     114              : 
     115            0 : }
     116              : 
     117              : TriggerActivity
     118            0 : TAMakerPlaneCoincidenceAlgorithm::construct_ta(TPWindow m_current_window) const
     119              : {
     120              : 
     121            0 :   TriggerPrimitive latest_tp_in_window = m_current_window.inputs.back();
     122              : 
     123            0 :   TriggerActivity ta;
     124            0 :   ta.time_start = m_current_window.time_start;
     125            0 :   ta.time_end = latest_tp_in_window.time_start + latest_tp_in_window.samples_over_threshold * 32;  // FIXME: Replace the hard-coded SOT to TOT scaling.
     126            0 :   ta.time_peak = latest_tp_in_window.samples_to_peak * 32 + latest_tp_in_window.time_start;  // FIXME: Replace hard-coded STP to `time_peak` conversion.
     127            0 :   ta.time_activity = ta.time_peak;
     128            0 :   ta.channel_start = latest_tp_in_window.channel;
     129            0 :   ta.channel_end = latest_tp_in_window.channel;
     130            0 :   ta.channel_peak = latest_tp_in_window.channel;
     131            0 :   ta.adc_integral = m_current_window.adc_integral;
     132            0 :   ta.adc_peak = latest_tp_in_window.adc_peak;
     133            0 :   ta.detid = latest_tp_in_window.detid;
     134            0 :   ta.type = TriggerActivity::Type::kTPC;
     135            0 :   ta.algorithm = TriggerActivity::Algorithm::kPlaneCoincidence;
     136            0 :   ta.inputs = m_current_window.inputs;
     137              : 
     138            0 :   return ta;
     139            0 : }
     140              : 
     141              : uint16_t
     142            0 : TAMakerPlaneCoincidenceAlgorithm::check_adjacency(TPWindow window) const
     143              : {
     144              :   /* This function returns the adjacency value for the current window, where adjacency
     145              :   *  is defined as the maximum number of consecutive wires containing hits. It accepts
     146              :   *  a configurable tolerance paramter, which allows up to adj_tolerance missing hits
     147              :   *  on adjacent wires before restarting the adjacency count. */
     148              : 
     149            0 :   uint16_t adj = 1;              // Initialise adjacency, 1 for the first wire.
     150            0 :   uint16_t max = 0;              // Maximum adjacency of window, which this function returns
     151            0 :   unsigned int channel = 0;      // Current channel ID
     152            0 :   unsigned int next_channel = 0; // Next channel ID
     153            0 :   unsigned int next = 0;         // The next position in the hit channels vector
     154            0 :   unsigned int tol_count = 0;    // Tolerance count, should not pass adj_tolerance
     155              : 
     156              :   /* Generate a channelID ordered list of hit channels for this window */
     157            0 :   std::vector<int> chanList;
     158            0 :   for (auto tp : window.inputs) {
     159            0 :     chanList.push_back(tp.channel);
     160              :   }
     161            0 :   std::sort(chanList.begin(), chanList.end());
     162              : 
     163              :   /* ADAJACENCY LOGIC ====================================================================
     164              :   *  Adjcancency Tolerance = Number of times prepared to skip missed hits before resetting 
     165              :   *  the adjacency count. This accounts for things like dead channels / missed TPs. */
     166            0 :   for (size_t i = 0; i < chanList.size(); ++i) {
     167              : 
     168            0 :     next = (i + 1) % chanList.size(); // Loops back when outside of channel list range
     169            0 :     channel = chanList.at(i);
     170            0 :     next_channel = chanList.at(next); // Next channel with a hit
     171              : 
     172              :     // End of vector condition.
     173            0 :     if (next_channel == 0) { next_channel = channel - 1; }
     174              : 
     175              :     // Skip same channel hits.
     176            0 :     if (next_channel == channel) { continue; }
     177              : 
     178              :     // If next hit is on next channel, increment the adjacency count.
     179            0 :     else if (next_channel == channel + 1){ ++adj; }
     180              : 
     181              :     // If next channel is not on the next hit, but the 'second next', increase adjacency 
     182              :     // but also tally up with the tolerance counter.
     183            0 :     else if ((next_channel == channel + 2 || next_channel == channel + 3) && (tol_count < m_adj_tolerance)) { 
     184            0 :         ++adj;
     185            0 :         for (size_t i = 0 ; i < next_channel-channel ; ++i) ++tol_count;
     186              :     }
     187              : 
     188              :     // If next hit isn't within reach, end the adjacency count and check for a new max.
     189              :     // Reset variables for next iteration.
     190              :     else {
     191            0 :       if (adj > max) { max = adj; } 
     192              :       adj = 1;
     193              :       tol_count = 0;
     194              :     }
     195              :   }
     196              : 
     197            0 :   return max;
     198            0 : }
     199              : 
     200              : // =====================================================================================
     201              : // Functions below this line are for debugging and performance study purposes.
     202              : // =====================================================================================
     203              : void
     204            0 : TAMakerPlaneCoincidenceAlgorithm::add_window_to_record(TPWindow window)
     205              : {
     206            0 :   m_window_record.push_back(window);
     207            0 :   return;
     208              : }
     209              : 
     210              : // Function to dump the details of the TA window currently on record
     211              : void
     212            0 : TAMakerPlaneCoincidenceAlgorithm::dump_window_record()
     213              : {
     214            0 :   std::ofstream outfile;
     215            0 :   outfile.open("window_record_tam.csv", std::ios_base::app);
     216              : 
     217            0 :   for (auto window : m_window_record) {
     218            0 :     outfile << window.time_start << ",";
     219            0 :     outfile << window.inputs.back().time_start << ",";
     220            0 :     outfile << window.inputs.back().time_start - window.time_start << ",";
     221            0 :     outfile << window.adc_integral << ",";
     222            0 :     outfile << window.n_channels_hit() << ",";             // Number of unique channels with hits
     223            0 :     outfile << window.inputs.size() << ",";                // Number of TPs in TPWindow
     224            0 :     outfile << window.inputs.back().channel << ",";        // Last TP Channel ID
     225            0 :     outfile << window.inputs.back().time_start << ",";     // Last TP start time
     226            0 :     outfile << window.inputs.front().channel << ",";       // First TP Channel ID
     227            0 :     outfile << window.inputs.front().time_start << ",";    // First TP start time 
     228            0 :     outfile << check_adjacency(window) << ",";             // New adjacency value for the window
     229            0 :     outfile << check_sot(window) << std::endl;             // Summed window SOT
     230            0 :   }
     231              : 
     232            0 :   outfile.close();
     233            0 :   m_window_record.clear();
     234              : 
     235            0 :   return;
     236            0 : }
     237              : 
     238              : // Function to add current TP details to a text file for testing and debugging.
     239              : void
     240            0 : TAMakerPlaneCoincidenceAlgorithm::dump_tp(TriggerPrimitive const& input_tp)
     241              : {
     242            0 :   std::ofstream outfile;
     243            0 :   outfile.open("triggered_coldbox_tps.txt", std::ios_base::app);
     244              : 
     245              :   // Output relevant TP information to file
     246            0 :   outfile << input_tp.time_start << " ";          
     247            0 :   outfile << input_tp.samples_over_threshold << " "; // 50MHz ticks
     248            0 :   outfile << input_tp.samples_to_peak << " ";           
     249            0 :   outfile << input_tp.channel << " ";             // Offline channel ID
     250            0 :   outfile << input_tp.adc_integral << " ";        
     251            0 :   outfile << input_tp.adc_peak << " ";            
     252            0 :   outfile << input_tp.detid << " ";               // Det ID - Identifies detector element
     253            0 :   outfile.close();
     254              : 
     255            0 :   return;
     256            0 : }
     257              : 
     258              : int
     259            0 : TAMakerPlaneCoincidenceAlgorithm::check_sot(TPWindow m_current_window) const
     260              : {
     261              :   // Here, we just want to sum up all the sot values for each TP within window,
     262              :   // and return this sot of the window.
     263            0 :   int window_sot = 0; 
     264            0 :   for (auto tp : m_current_window.inputs) {
     265            0 :     window_sot += tp.samples_over_threshold;
     266              :   }
     267              : 
     268            0 :   return window_sot;
     269              : }
     270              : 
     271              : // Regiser algo in TA Factory
     272           12 : REGISTER_TRIGGER_ACTIVITY_MAKER(TRACE_NAME, TAMakerPlaneCoincidenceAlgorithm)
     273              : // END OF TA MAKER - LOW ENERGY EVENTS
        

Generated by: LCOV version 2.0-1