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
|