All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
TriggerEfficiencyPlots_module.cc
Go to the documentation of this file.
1 /**
2  * @file TriggerEfficiencyPlots_module.cc
3  * @brief Plots of trigger efficiency.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date January 9, 2020
6  */
7 
8 
9 // ICARUS libraries
14 #include "icaruscode/PMT/Trigger/Utilities/TriggerDataUtils.h" // FillTriggerGates()
16 #include "icarusalg/Utilities/ROOTutils.h" // util::ROOT
18 
19 // LArSoft libraries
21 #include "lardata/Utilities/TensorIndices.h" // util::MatrixIndices
23 #include "larcore/CoreUtils/ServiceUtil.h" // lar::providerFrom()
25 #include "lardataalg/DetectorInfo/DetectorTimingTypes.h" // simulation_time
27 #include "lardataalg/MCDumpers/MCDumperUtils.h" // sim::TruthInteractionTypeName
28 #include "lardataalg/Utilities/quantities/spacetime.h" // microseconds, ...
29 #include "lardataalg/Utilities/quantities/energy.h" // megaelectronvolt, ...
30 #include "lardataalg/Utilities/intervals_fhicl.h" // microseconds from FHiCL
33 #include "larcorealg/CoreUtils/values.h" // util::const_values()
36 #include "larcorealg/CoreUtils/StdUtils.h" // util::to_string()
39 #if 0
40 // #include "larcorealg/CoreUtils/DebugUtils.h" // lar::debug::::static_assert_on<>
42 #endif // 0
43 
44 // nutools libraries
45 #include "nusimdata/SimulationBase/MCTruth.h"
46 #include "nusimdata/SimulationBase/MCNeutrino.h"
47 #include "nusimdata/SimulationBase/MCParticle.h"
48 
49 // framework libraries
50 #include "art_root_io/TFileService.h"
51 #include "art_root_io/TFileDirectory.h"
52 #include "art/Framework/Services/Registry/ServiceHandle.h"
53 #include "art/Framework/Core/EDAnalyzer.h"
54 #include "art/Framework/Core/ModuleMacros.h"
55 #include "art/Framework/Principal/Event.h"
56 #include "art/Framework/Principal/Handle.h"
57 #include "canvas/Persistency/Common/Assns.h"
58 #include "canvas/Utilities/InputTag.h"
59 #include "canvas/Utilities/Exception.h"
60 #include "messagefacility/MessageLogger/MessageLogger.h"
61 #include "fhiclcpp/types/Sequence.h"
62 #include "fhiclcpp/types/Atom.h"
63 #include "fhiclcpp/types/OptionalAtom.h"
64 #include "cetlib_except/exception.h"
65 #if 0
66 #include "fhiclcpp/types/DelegatedParameter.h"
67 #include "fhiclcpp/ParameterSet.h"
68 #endif // 0
69 
70 // ROOT libraries
71 #include "TEfficiency.h"
72 #include "TH1F.h"
73 #include "TH2F.h"
74 #include "TTree.h"
75 
76 // C/C++ standard libraries
77 #include <ostream>
78 #include <atomic>
79 #include <map>
80 #include <vector>
81 #include <string>
82 #include <functional> // std::function<>, std::reference_wrapper<>
83 #include <memory> // std::unique_ptr
84 #include <utility> // std::pair<>, std::move()
85 #include <cstddef> // std::size_t
86 
87 
88 //------------------------------------------------------------------------------
91 
92 using namespace util::quantities::time_literals; // ""_ns ...
93 
94 
95 //------------------------------------------------------------------------------
96 /// Information about the event.
97 struct EventInfo_t {
98 
99  /// Constructor. As if nobody noticed.
100  EventInfo_t() { fInteractions.fill(0U); }
101 
102  // --- BEGIN Query interface -----------------------------------------------
103  /// @name Query interface
104  /// @{
105 
106  /// Returns the number of weak charged current interactions in the event.
107  unsigned int nWeakChargedCurrentInteractions() const
108  { return fInteractions[itWCC]; }
109 
110  /// Returns the number of weak neutral current interactions in the event.
111  unsigned int nWeakNeutralCurrentInteractions() const
112  { return fInteractions[itWNC]; }
113 
114  /// Returns the number of weak current interactions in the event.
115  unsigned int nWeakCurrentInteractions() const
116  {
117  return
118  nWeakChargedCurrentInteractions() + nWeakNeutralCurrentInteractions();
119  }
120 
121  /// Returns whether the event is generated as a neutrino CC interaction.
122  bool isWeakChargedCurrent() const
123  { return nWeakChargedCurrentInteractions() > 0U; }
124 
125  /// Returns whether the event is generated as a neutrino NC interaction.
126  bool isWeakNeutralCurrent() const
127  { return nWeakNeutralCurrentInteractions() > 0U; }
128 
129  /// Returns whether the event is generated as a neutrino interaction.
130  bool isNeutrino() const
131  { return isWeakChargedCurrent() || isWeakNeutralCurrent(); }
132 
133  /// Returns which neutrino flavor is present in an event
134  bool isNu_mu() const { return nu_mu; }
135  bool isNu_e() const { return nu_e; }
136 
137  /// Returns the neutrino PDG code
138  int NeutrinoPDG() const { return fNeutrinoPDG; }
139 
140  /// Returns the total energy deposited in the detector during the event [GeV]
141  GeV DepositedEnergy() const { return fEnergyDepTotal; }
142 
143  /// Returns the total energy deposited in the detector during beam [GeV]
144  GeV DepositedEnergyInSpill() const { return fEnergyDepSpill; }
145 
146  /// Returns the energy deposited in the active volume during the event [GeV]
147  GeV DepositedEnergyInActiveVolume() const { return fEnergyDepActive; }
148 
149  /// Returns the energy deposited in the active volume during the beam [GeV]
151  { return fEnergyDepSpillActive; }
152 
153  // Returns the neutrino energy [GeV]
154  GeV NeutrinoEnergy() const { return fNeutrinoEnergy; }
155 
156  // Returns the lepton energy [GeV]
157  GeV LeptonEnergy() const { return fLeptonEnergy; }
158 
159  // Returns the interaction type
160  int InteractionType() const { return fInteractionType; }
161 
162  /// Returns whether this type of event has a known vertex.
163  bool hasVertex() const { return !fVertices.empty(); }
164 
165  /// Returns whether there is an interaction within the active volume.
166  bool isInActiveVolume() const { return fInActiveVolume; }
167 
168  /// @}
169  // --- END Query interface -------------------------------------------------
170 
171 
172  // --- BEGIN Set interface -------------------------------------------------
173  /// @name Set interface
174  /// @{
175 
176  /// Marks this event as including _n_ more weak charged current interactions.
177  void AddWeakChargedCurrentInteractions(unsigned int n = 1U)
178  { fInteractions[itWCC] += n; }
179 
180  /// Marks this event as including _n_ more weak neutral current interactions.
181  void AddWeakNeutralCurrentInteractions(unsigned int n = 1U)
182  { fInteractions[itWNC] += n; }
183 
184  /// Marks the neutrino flavor
185  void SetNu_mu(bool numu) { nu_mu = numu; }
186  void SetNu_e(bool nue) { nu_e = nue; }
187 
188  /// Marks this event's neutrino type
189  void SetNeutrinoPDG(int NU) { fNeutrinoPDG = NU; }
190 
191  /// Sets the total deposited energy of the event [GeV]
192  void SetDepositedEnergy(GeV e) { fEnergyDepTotal = e; }
193 
194  /// Sets the energy of the event deposited during beam gate [GeV]
195  void SetDepositedEnergyInSpill(GeV e) { fEnergyDepSpill = e; }
196 
197  /// Sets the total deposited energy of the event in active volume [GeV]
198  void SetDepositedEnergyInActiveVolume(GeV e) { fEnergyDepActive = e; }
199 
200  /// Sets the energy of the event deposited during beam gate in active volume
201  /// [GeV]
203  { fEnergyDepSpillActive = e; }
204 
205  // Sets the neutrino energy.
206  void SetNeutrinoEnergy(GeV eNu) { fNeutrinoEnergy = eNu; }
207 
208  // Sets the lepton energy.
209  void SetLeptonEnergy(GeV eL) { fLeptonEnergy = eL; }
210 
211  // Sets the interaction type
212  void SetInteractionType(int type) { fInteractionType = type; }
213 
214  /// Set whether the event has relevant activity in the active volume.
215  void SetInActiveVolume(bool active = true) { fInActiveVolume = active; }
216 
217  /// Adds a point to the list of interaction vertices in the event.
218  void AddVertex(geo::Point_t const& vertex) { fVertices.push_back(vertex); }
219 
220  std::vector<geo::Point_t> fVertices; ///< Position of all vertices.
221 
222  /// @}
223  // --- END Set interface ---------------------------------------------------
224 
225  /// Prints the content of the object into a stream.
226  void dump(std::ostream& out) const
227  {
228  out << "Event contains:";
229  if (isNeutrino()) {
230  if (nWeakChargedCurrentInteractions())
231  out << " " << nWeakChargedCurrentInteractions() << " CC";
232  if (nWeakNeutralCurrentInteractions())
233  out << " " << nWeakNeutralCurrentInteractions() << " NC";
234  if (isNu_mu()) out << " nu_mu";
235  if (isNu_e()) out << " nu_e";
236  out << "\nThe first neutrino has E=" << NeutrinoEnergy()
237  << " and becomes a lepton with E=" << LeptonEnergy()
238  << " with a " << sim::TruthInteractionTypeName(InteractionType())
239  << " interaction"
240  ;
241  }
242  else {
243  out << " no neutrino interaction";
244  }
245  out << "\nTotal deposited energy: " << DepositedEnergy()
246  << ", of which in spill " << DepositedEnergyInSpill()
247  << ", in active volume " << DepositedEnergyInActiveVolume()
248  << ", in active volume and in spill "
249  << DepositedEnergyInSpillInActiveVolume();
250  if (fVertices.empty()) {
251  out << "\nNo interaction vertex found.";
252  }
253  else {
254  auto iVertex = fVertices.begin();
255  auto const vend = fVertices.end();
256  out
257  << "\n" << fVertices.size() << " interaction vertices: " << *iVertex;
258  while (++iVertex != vend) out << "; " << *iVertex;
259  out << ".";
260  }
261  out << "\nThe event is" << (isInActiveVolume()? "": " NOT")
262  << " marked as in the active volume of the detector.";
263  out << "\n";
264  out.flush();
265  } // dump()
266 
267  private:
268 
269  // --- BEGIN interaction type constants ------------------------------------
270 
271  static constexpr std::size_t itWCC { 0U }; ///< Charged weak current.
272  static constexpr std::size_t itWNC { 1U }; ///< Neutral weak current.
273  static constexpr std::size_t NInteractionTypes { 2U };
274 
275  // --- END interaction type constants --------------------------------------
276 
277  std::array<unsigned int, NInteractionTypes> fInteractions;
278 
279  GeV fEnergyDepTotal { 0.0 }; ///< Total deposited energy.
280  GeV fEnergyDepSpill { 0.0 }; ///< Energy deposited in spill.
281  GeV fEnergyDepActive { 0.0 }; ///< Energy deposited in active volume.
282  /// Energy deposited in active volume in spill.
283  GeV fEnergyDepSpillActive { 0.0 };
284 
285  int fNeutrinoPDG { 0 };
286  int fInteractionType { 0 };
287 
288  GeV fNeutrinoEnergy { 0.0 };
289  GeV fLeptonEnergy { 0.0 };
290  //GeV fNucleonEnergy { 0.0 };
291 
292  bool nu_mu { false };
293  bool nu_e { false };
294 
295  /// Whether the event has activity inside the active volume.
296  bool fInActiveVolume { false };
297 
298  //std::vector<geo::Point_t> fVertices; ///< Position of all vertices.
299 
300 }; // struct EventInfo_t
301 
302 std::ostream& operator<< (std::ostream& out, EventInfo_t const& info)
303  { info.dump(out); return out; }
304 
305 
306 //------------------------------------------------------------------------------
308 
309  public:
310 
311  /// Type of test function.
312  using QualifyFunc_t = std::function<bool(EventInfo_t const&)>;
313 
314 
315  /// Constructor from category name and test function.
317  std::string name, std::string descr = {},
318  QualifyFunc_t&& test = [](EventInfo_t const&){ return true; }
319  )
320  : fName(std::move(name)), fDescr(std::move(descr)), fTest(std::move(test))
321  {}
322 
323  /// Returns the name of the category.
324  std::string const& name() const { return fName; }
325 
326  /// Returns the description of the category.
327  std::string const& description() const { return fDescr; }
328 
329  /// Returns whether the event belong to this category.
330  bool test(EventInfo_t const& info) const { return fTest(info); }
331 
332  operator std::string() const { return name(); }
333  bool operator() (EventInfo_t const& info) const { return test(info); }
334 
335  private:
336 
337  std::string fName;
338  std::string fDescr;
340 
341 }; // class PlotCategory
342 using PlotCategories_t = std::vector<PlotCategory>;
343 
344 /**
345  * @brief List of event categories.
346  *
347  * category name | condition
348  * -------------- | ------------------------------------------------------------
349  * `All` | any event
350  * ---Nu_mu |
351  * ---Nu_e |
352  * `NuCC` | at least one generated charged current neutrino interaction
353  * ---Nu_mu |
354  * ---Nu_e |
355  * `NuNC` | at least one generated neutral current neutrino interaction
356  * ---Nu_mu |
357  * ---Nu_e |
358  *
359  *
360  */
362 
363  PlotCategory{
364  "All"
365  },
366 
367  PlotCategory{
368  "All nu_mu", "nu_mu",
369  [](EventInfo_t const& info){ return info.isNu_mu(); }
370  },
371 
372  PlotCategory{
373  "All nu_e", "nu_e",
374  [](EventInfo_t const& info){ return info.isNu_e(); }
375  },
376 
377  PlotCategory{
378  "NuCC", "CC",
379  [](EventInfo_t const& info){ return info.isWeakChargedCurrent(); }
380  },
381 
382  PlotCategory{
383  "NuCC_mu", "CC_mu",
384  [](EventInfo_t const& info){ return (info.isWeakChargedCurrent() & info.isNu_mu()); }
385  },
386 
387  PlotCategory{
388  "NuCC_e", "CC_e",
389  [](EventInfo_t const& info){ return (info.isWeakChargedCurrent() & info.isNu_e()); }
390  },
391 
392  PlotCategory{
393  "NuNC", "NC",
394  [](EventInfo_t const& info){ return info.isWeakNeutralCurrent(); }
395  },
396 
397  PlotCategory{
398  "NuNC_mu", "NC_mu",
399  [](EventInfo_t const& info){ return (info.isWeakNeutralCurrent() & info.isNu_mu()); }
400  },
401 
402  PlotCategory{
403  "NuNC_e", "NC_e",
404  [](EventInfo_t const& info){ return (info.isWeakNeutralCurrent() & info.isNu_e()); }
405  }
406 
407 }; // PlotCategories[]
408 
409 
410 
411 // --- BEGIN -- ROOT tree helpers ----------------------------------------------
412 struct TreeHolder {
413 
414  TreeHolder() = default;
415  TreeHolder(TTree& tree): fTree(&tree) {}
416 
417  TTree& tree() { return *fTree; }
418  TTree const& tree() const { return *fTree; }
419 
420  private:
421  TTree* fTree = nullptr;
422 
423 }; // struct TreeHolder
424 
425 
426 /**
427  * @brief Class managing the serialization of event ID in a simple ROOT tree.
428  *
429  * The tree is supplied by the caller.
430  * This object will create the proper branches into the tree and assign
431  * addresses to them. Then it will assume they will stay assigned.
432  *
433  * On `assignEvent()`, the branch addresses are assigned the values from the
434  * event ID. The tree is not `Fill()`-ed.
435  *
436  * The tree structure is: `Run/i:SubRun/i:Event/i`, with a single branch per
437  * element.
438  */
439 struct EventIDTree: public TreeHolder {
440 
441  /// Creates the required branches and assigns addresses to them.
442  EventIDTree(TTree& tree);
443 
444  /// Fills the information of the specified event.
445  void assignID(art::EventID const& id);
446  void assignEvent(art::Event const& event) { assignID(event.id()); }
447 
448  UInt_t fRunNo;
449  UInt_t fSubRunNo;
450  UInt_t fEventNo;
451 
452 }; // struct EventIDTree
453 
454 
455 /**
456  * @brief Class managing the serialization of plot information in a simple ROOT
457  * tree.
458  *
459  * The tree is supplied by the caller.
460  * This object will create the proper branches into the tree and assign
461  * addresses to them. Then it will assume they will stay assigned.
462  *
463  * On `assign()`, the branch addresses are assigned the values from the
464  * arguments. The tree is not `Fill()`-ed.
465  *
466  * The tree structure is:
467  * `InPlots/O`,
468  * with a single branch per element.
469  *
470  * Branches:
471  * * `InPlots` (bool): the event was _not_ filtered out before plotting
472  * (it may still belong to no category and eventually appear in no plot)
473  *
474  */
475 struct PlotInfoTree: public TreeHolder {
476 
477  /// Creates the required branches and assigns addresses to them.
478  PlotInfoTree(TTree& tree);
479 
480  /**
481  * @brief Fills the information of the specified event.
482  * @param inPlots whether this event is plotted (as opposed to filtered out)
483  */
484  void assign(bool inPlots);
485 
486  Bool_t fInPlots;
487 
488 }; // struct PlotInfoTree
489 
490 
491 /**
492  * @brief Class managing the serialization of event information in a simple ROOT
493  * tree.
494  *
495  * The tree is supplied by the caller.
496  * This object will create the proper branches into the tree and assign
497  * addresses to them. Then it will assume they will stay assigned.
498  *
499  * On `assignEvent()`, the branch addresses are assigned the values from the
500  * event information. The tree is not `Fill()`-ed.
501  *
502  * The tree structure is:
503  * `CC/i:NC/i:IntType:I/NuE:D/OutLeptE:D/TotE/D:SpillE/D:InActive/O`,
504  * with a single branch per element.
505  *
506  * Branches:
507  * * `CC` (unsigned integer): number of neutrino CC interactions in the event
508  * * `NC` (unsigned integer): number of neutrino NC interactions in the event
509  * * `IntType` (integer): code of interaction type (see `simb::int_type_`)
510  * * `NuE` (double): energy of the generated initial state neutrino [GeV]
511  * * `OutLeptE` (double): energy of the generated final state lepton [GeV]
512  * * `TotE` (double): total deposited energy in the event [GeV]
513  * * `SpillE` (double): total deposited energy during the beam gate [GeV]
514  * * `InActive` (bool): whether an interaction happened in active volume'
515  * this requires an interaction vertex (e.g. cosmic rays are out)
516  *
517  */
518 struct EventInfoTree: public TreeHolder {
519 
520  /// Creates the required branches and assigns addresses to them.
521  EventInfoTree(TTree& tree);
522 
523  /**
524  * @brief Fills the information of the specified event.
525  * @param info event information to fill the tree with
526  * @param inPlots whether this event is plotted (as opposed to filtered out)
527  */
528  void assignEvent(EventInfo_t const& info);
529 
530  UInt_t fCC;
531  UInt_t fNC;
532  Int_t fIntType;
533  Double_t fNuE;
534  Double_t fOutLeptE;
535  Double_t fTotE;
536  Double_t fSpillE;
537  Double_t fActiveE;
538  Double_t fSpillActiveE;
539 
540  Bool_t fInActive;
541 
542 }; // struct EventInfoTree
543 
544 
545 /**
546  * @brief Class managing the serialization of trigger responses in a simple ROOT
547  * tree.
548  *
549  * The tree is supplied by the caller.
550  * This object will create the proper branches into the tree and assign
551  * addresses to them. Then it will assume they will stay assigned.
552  *
553  * On `assignResponse()`, the proper branch address is assigned the specified
554  * trigger response (`true` or `false`).
555  *
556  * The branch structure is: a `RespTxxRxx/O` branch for each threshold (numeric)
557  * and requirement (also numeric).
558  * with a single branch per element.
559  *
560  */
561 struct ResponseTree: public TreeHolder {
562 
563  /// Constructor: accommodates that many thresholds and requirements.
564  template <typename Thresholds, typename Requirements>
566  (TTree& tree, Thresholds const& thresholds, Requirements const& minReqs);
567 
568  /// Assigns the response for the specified trigger.
569  void assignResponse(std::size_t iThr, std::size_t iReq, bool resp);
570 
571  // `std::vector<bool>` is too special for us. Let's pack it to go.
573  std::unique_ptr<bool[]> RespTxxRxx;
574 
575 }; // struct ResponseTree
576 
577 
578 // --- END -- ROOT tree helpers ------------------------------------------------
579 
580 
581 //------------------------------------------------------------------------------
582 namespace icarus::trigger { class TriggerEfficiencyPlots; }
583 /**
584  * @brief Produces plots about trigger simulation and trigger efficiency.
585  *
586  * @deprecated This module is deprecated for the ones derived from
587  * `icarus::trigger::TriggerEfficiencyPlotsBase`
588  * (e.g. `SlidingWindowTriggerEfficiencyPlots`).
589  *
590  * This module produces sets of plots based on trigger primitives given in
591  * input.
592  *
593  * The following documentation mainly deals with the most standard configuration
594  * and operations in ICARUS. This module and the ones upstream of it have quite
595  * some knobs that can be manipulated to test unorthodox configurations.
596  *
597  *
598  * Overview of trigger simulation steps
599  * =====================================
600  *
601  * This simulation only trigger primitives derived from the optical detector
602  * via hardware (V1730B boards).
603  * The trigger simulation branches from the standard simulation of optical
604  * detector waveforms (`icarus::SimPMTIcarus` module).
605  * From there, multiple steps are needed.
606  *
607  * 1. Produce single-PMT-channel discriminated waveforms: the discrimination
608  * threshold can be chosen ("ADC threshold" or just "threshold" in the
609  * following), and the result is one binary discriminated waveform that
610  * has value `0` when the waveform is under threshold and `1` when it's
611  * above threshold, with the same time discretization as the PMT waveform.
612  * Each discriminated waveform spans the whole event readout time and
613  * therefore it may merge information from multiple readout waveforms
614  * (`raw::OpDetWaveform`), but all from the same optical detector channel.
615  * This step can be performed with the module
616  * `icarus::trigger::DiscriminatePMTwaveforms`.
617  * 2. Combine the discriminated waveforms in pairs, in the same fashion as the
618  * V1730B readout board does to produce LVDS output. The output of this step
619  * is roughly half as many discriminated outputs as there were from the
620  * previous step (some channels are not paired). This output will be called
621  * _trigger primitives_ because it is what the trigger logics is based on.
622  * We say that a trigger primitive is "on" when its level is `1`.
623  * This step can be performed with the module `icarus::trigger::LVDSgates`.
624  * 3. Simulate the trigger logic based on the trigger primitives _(see below)_.
625  * This usually includes the requirement of coincidence with the beam gate.
626  *
627  * Trigger logic may be complex, being implemented in a FPGA.
628  * Many options are available, including:
629  *
630  * * coincidence of a minimum number of trigger primitives: the event is trigger
631  * if at least _N_ trigger primitives are on at the same time;
632  * * sliding window: primitives are grouped depending on their location, and
633  * there is a requirement on the minimum number of primitives in "on" level
634  * within one or more of these groups. For example, any sliding window of 30
635  * channels (corresponding to 16 trigger primitives in standard ICARUS PMT
636  * readout installation) has to contain at least 10 trigger primitives "on"
637  * at the same time; or there must be two sliding windows with that condition;
638  * etc. Sliding windows can be obtained from further processing of the LVDS
639  * trigger primitives, for example with the module
640  * `icarus::trigger::SlidingWindowTrigger`.
641  *
642  * This module (`icarus::trigger::TriggerEfficiencyPlots`) is designed for
643  * implementing a trigger logic based on coincidence of trigger primitives,
644  * and to plot trigger efficiency and related information based on a requirement
645  * of a minimum number of trigger primitives, in that enabling the simulation
646  * in the fashion of the first option above. More details on the steps performed
647  * by this module are documented @ref TriggerEfficiencyPlots_Algorithm "later".
648  *
649  * For the implementation of sliding window, different logic needs to be
650  * implemented, possibly keeping track of the location of each primitive, and
651  * different plots are needed as well. This module may provide a template for
652  * such implementation, but it hasn't been designed with enough flexibility to
653  * accommodate that directly.
654  *
655  *
656  * @anchor TriggerEfficiencyPlots_Data
657  * Data objects for discriminated waveforms
658  * -----------------------------------------
659  *
660  * See the documentation of
661  * @ref TriggerEfficiencyPlotsBase_Data "icarus::trigger::TriggerEfficiencyPlotsBase".
662  *
663  *
664  * On the terminology
665  * -------------------
666  *
667  * In this documentation, the "discriminated waveforms" are sometimes called
668  * "trigger gates" and sometimes "trigger primitives".
669  * Although these concepts are, strictly, different, usually the difference
670  * does not affect the meaning and they end up being exchanged carelessly.
671  *
672  *
673  * @anchor TriggerEfficiencyPlots_Algorithm
674  * Trigger logic algorithm
675  * ========================
676  *
677  * This section describes the trigger logic algorithm used in
678  * `icarus::trigger::TriggerEfficiencyPlots` and its assumptions.
679  *
680  * The algorithm treats all the trigger primitives equally, whether they
681  * originate from one or from two channels (or 10 or 30), and wherever their
682  * channels are in the detector.
683  * The trigger primitives are combined in a multi-level gate by adding them,
684  * so that the level of the resulting gate describes at any time matches how
685  * many trigger primitives are on at that time.
686  *
687  * This multi-level gate is set in coincidence with the beam gate by multiplying
688  * the multi-level and the beam gates.
689  * The beam gate opens at a time configured in `DetectorClocks` service provider
690  * (`detinfo::DetectorClocks::BeamGateTime()`) and has a duration configured
691  * in this module (`BeamGateDuration`).
692  *
693  * At this point, the trigger gate is a multi-level gate suppressed everywhere
694  * except than during the beam gate.
695  * The algorithm handles multiple trigger definitions, or requirements.
696  * Each requirement is simply how many trigger primitives must be open at the
697  * same time for the trigger to fire. The values of these requirements are
698  * set in the configuration (`MinimumPrimitives`).
699  * To determine whether a trigger with a given requirement, i.e. with a required
700  * minimum number of trigger primitives open at the same time, has fired, the
701  * gate is scanned to find _the first tick_ where the level of the gate reaches
702  * or passes this minimum required number. If such tick exists, the trigger is
703  * considered to have fired, and at that time.
704  *
705  * As a consequence, there are for each event as many different trigger
706  * responses as how many different requirements are configured in
707  * `MinimumPrimitives`, _times_ how many ADC thresholds are provided in input,
708  * configured in `Thresholds`.
709  *
710  * While there _is_ a parameter describing the time resolution of the trigger
711  * (`TriggerTimeResolution`), this is currently only used for aesthetic purposes
712  * to choose the binning of some plots: the resolution is _not_ superimposed
713  * to the gates (yet).
714  *
715  * This algorithm is currently implemented in
716  * `detinfo::trigger::TriggerEfficiencyPlots::plotResponses()`.
717  *
718  *
719  * Event categories
720  * =================
721  *
722  * Each event is assigned to a set of categories, and its information
723  * contributes to the plots of all those categories.
724  * The categories are defined in the `PlotCategories` list (hard-coded).
725  *
726  *
727  * Module usage
728  * =============
729  *
730  *
731  * Input data products
732  * --------------------
733  *
734  * This module uses the following data products as input:
735  *
736  * * trigger primitives:
737  * * `std::vector<icarus::trigger::OpticalTriggerGateData_t>` (labels out of
738  * `TriggerGatesTag` and `Thresholds`): full sets of discriminated
739  * waveforms, each waveform possibly covering multiple optical channels,
740  * and their associations to optical waveforms. One set per threshold;
741  * * associations with `raw::OpDetWaveform` (currently superfluous);
742  * * event characterization:
743  * * `std::vector<simb::MCTruth>`: generator information (from
744  * `GeneratorTags`; multiple generators are allowed at the same time);
745  * it is used mostly to categorize the type of event (background,
746  * weak charged current, electron neutrino, etc.);
747  * * `std::vector<simb::MCParticle>`: particles propagating in the detector
748  * (from `PropagatedParticles`); currently not used;
749  * * `std::vector<sim::SimEnergyDeposit>`: energy deposited in the active
750  * liquid argon volume (from `EnergyDepositTags`); it is used to
751  * quantify the energy available to be detected in the event.
752  *
753  *
754  * Output plots
755  * -------------
756  *
757  * The module produces a standard set of plots for each configured ADC threshold
758  * and for each event category.
759  * The plots are saved via _art_ service `TFileService` in a ROOT file and they
760  * are organized in nested ROOT directories under the module label directory
761  * which is assigned by _art_:
762  *
763  * * `Thr###` (outer level) describes the ADC threshold on the discriminated
764  * waveforms: the threshold is `###` from the baseline;
765  * * `<Category>` (inner level) describes the category of events included in
766  * the plots (e.g. `All`, `NuCC`; see `PlotCategories`).
767  *
768  * Each of the inner ROOT directories contains a full set of plots, whose name
769  * is the standard plot name followed by its event category and threshold
770  * (e.g. `Eff_NuCC_Thr15` for the trigger efficiency plot on neutrino charged
771  * current events with 15 ADC counts as PMT threshold).
772  *
773  * Each set of plots, defined in
774  * `icarus::trigger::TriggerEfficiencyPlots::initializePlotSet()`
775  * (and, within, `initializeEventPlots()`
776  * and `initializeEfficiencyPerTriggerPlots()`), is contributed only by the
777  * events in the set category.
778  *
779  * There are different "types" of plots. Some
780  * @ref TriggerEfficiencyPlots_SelectionPlots "do not depend on triggering at all",
781  * like the deposited energy distribution. Others
782  * @ref TriggerEfficiencyPlots_MultiTriggerPlots "cross different trigger definitions",
783  * like the trigger efficiency as function of trigger requirement. Others still
784  * @ref TriggerEfficiencyPlots_SingleTriggerPlots "assume a single trigger definition":
785  * this is the case of trigger efficiency plots versus energy. Finally, there are
786  * @ref TriggerEfficiencyPlots_SingleTriggerResponsePlots "plots that depend on a specific trigger definition and outcome":
787  * this is the case of all the plots including only triggering or non-triggering
788  * events.
789  *
790  * A list of plots follows for each plot type.
791  * All the plots are always relative to a specific optical detector channel
792  * threshold (ADC) and a broad event category.
793  *
794  *
795  * ### Plots independent of the triggers (selection plots)
796  *
797  * @anchor TriggerEfficiencyPlots_SelectionPlots
798  *
799  * These plots are stored directly in a threshold/category folder:
800  *
801  * * `EnergyInSpill`: total energy deposited in the detector during the time the
802  * beam gate is open. It is proportional to the amount of scintillation light
803  * in the event;
804  * * `EnergyInSpillActive`: energy deposited in the active volume of the
805  * detector during the time the beam gate is open; the active volume is
806  * defined as the union of all TPC drift voulmes;
807  * * plots specific to neutrino interactions (if the event is not a neutrino
808  * interaction, it will not contribute to them); if not otherwise specified,
809  * only the first neutrino interaction in the event is considered:
810  * * `InteractionType`: code of the interaction type, as in
811  * `sim::TruthInteractionTypeName`;
812  * * `NeutrinoEnergy`: generated energy of the interacting neutrino;
813  * * `LeptonEnergy`: generated energy of the lepton out of the _first_
814  * neutrino interaction;
815  * * `InteractionVertexYZ`: coordinates of the location of all interactions
816  * in the event, in world coordinates, projected on the anode plane.
817  *
818  * @note In fact, these plots usually do not even depend on the ADC threshold
819  * of the optical detector channels. Nevertheless, they are stored in the
820  * folders under specific thresholds, and they are duplicate.
821  *
822  *
823  * ### Plots including different trigger requirements
824  *
825  * @anchor TriggerEfficiencyPlots_MultiTriggerPlots
826  *
827  * These plots collect information from scenarios with different trigger
828  * requirements, but still with the same underlying optical detector channel
829  * ADC threshold.
830  * They are also stored in the threshold/category folder:
831  *
832  * * `Eff`: trigger efficiency defined as number of triggered events over the
833  * total number of events, as function of the minimum number of trigger
834  * primitives (`MinimumPrimitives`) to define a firing trigger; uncertainties
835  * are managed by `TEfficiency`.
836  * * `TriggerTick`: distribution of the time of the earliest trigger for the
837  * event, as function of the minimum number of trigger primitives (as in
838  * `Eff`). It may happen that the event is such that there is e.g. a
839  * 20-primitive flash, then subsiding, and then another 30-primitive flash.
840  * In such a case, in the trigger requirement "&geq; 15 primitives" such event
841  * will show at the time of the 20-primitive flash, while in the trigger
842  * requirement "&geq; 25 primitives" it will show at the time of the
843  * 30-primitive flash. Each event appears at most once for each trigger
844  * requirement, and it may not appear at all if does not fire a trigger.
845  * * `NPrimitives`: the maximum number of primitives "on" at any time.
846  *
847  *
848  * ### Plots depending on a specific trigger definition
849  *
850  * @anchor TriggerEfficiencyPlots_SingleTriggerPlots
851  *
852  * The following plots depend on the full definition of the trigger, including
853  * PMT thresholds _and_ other requirements.
854  * They are hosted each in a subfolder of the threshold/category folder, with
855  * a name encoding the requirement: `ReqXX` for trigger with minimum required
856  * primitives `XX`.
857  *
858  * * `EffVsEnergyInSpill`: trigger efficiency as function of the total energy
859  * deposited during the beam gate;
860  * * `EffVsEnergyInSpillActive`: trigger efficiency as function of the energy
861  * deposited in the TPC active volumes during the beam gate;
862  * * `EffVsNeutrinoEnergy`, `EffVsLeptonEnergy`: trigger efficiency as function
863  * of the true energy of the first interacting neutrino, and of the outgoing
864  * lepton in the final state of the interaction, respectively;
865  * * `TriggerTick`: time of the earliest trigger. Only triggering events
866  * contribute.
867  *
868  * The parameters are defined in the same way as in the
869  * @ref TriggerEfficiencyPlots_SelectionPlots "selection plots", unless stated
870  * otherwise.
871  *
872  *
873  * ### Plots depending on a specific trigger definition and response
874  *
875  * @anchor TriggerEfficiencyPlots_SingleTriggerResponsePlots
876  *
877  * These plots depend on the trigger definition, as the ones in the previous
878  * type, and on the actual trigger response.
879  * They are hosted each in a subfolder of the threshold/category/requirement
880  * folder, with a name encoding the response: `triggering` for triggering
881  * events, `nontriggering` for the others.
882  *
883  * Their event pool is filtered to include only the events in the current
884  * category which also pass, or do not pass, the trigger requirement.
885  *
886  * The plots are:
887  * * `EnergyInSpill`, `EnergyInSpillActive`, `InteractionType`,
888  * `NeutrinoEnergy`,`LeptonEnergy`, `InteractionVertexYZ`:
889  * these are defined in the same way as the
890  * @ref TriggerEfficiencyPlots_SelectionPlots "selection plots"
891  *
892  *
893  * Configuration parameters
894  * =========================
895  *
896  * A terse description of the parameters is printed by running
897  * `lar --print-description TriggerEfficiencyPlots`.
898  *
899  * * `TriggerGatesTag` (string, mandatory): name of the module
900  * instance which produced the trigger primitives to be used as input;
901  * it must not include any instance name, as the instance names will be
902  * automatically added from `Thresholds` parameter.
903  * The typical trigger primitives used as input may be LVDS discriminated
904  * output (e.g. from `icarus::trigger::LVDSgates` module) or combinations
905  * of them (e.g. from `icarus::trigger::SlidingWindowTrigger` module).
906  * * `Thresholds` (list of integers, mandatory): list of the discrimination
907  * thresholds to consider, in ADC counts. A data product containing a
908  * digital signal is read for each one of the thresholds, and the tag of the
909  * data product is expected to be the module label `TriggerGatesTag` with as
910  * instance name the value of the threshold (e.g. for a threshold of 60 ADC
911  * counts the data product tag might be `LVDSgates:60`).
912  * * `GeneratorTags` (list of input tags, default: `[ generator ]`): a list of
913  * data products containing the particles generated by event generators;
914  * * `DetectorParticleTag` (input tag, default: `largeant`): data product
915  * containing the list of particles going through the argon in the detector;
916  * * `EnergyDepositTags`
917  * (list of input tags, default: `[ "largeant:TPCActive" ]`): a list of
918  * data products with energy depositions;
919  * * `BeamGateDuration` (time, _mandatory_): the duration of the beam
920  * gate; _the time requires the unit to be explicitly specified_: use
921  * `"1.6 us"` for BNB, `9.5 us` for NuMI (also available as
922  * `BNB_settings.spill_duration` and `NuMI_settings.spill_duration` in
923  * `trigger_icarus.fcl`);
924  * * `MinimumPrimitives` (list of integers, _mandatory_): a list of alternative
925  * requirements for the definition of a trigger; each value is the number
926  * of trigger primitives needed to be "on" at the same time for the trigger
927  * to fire;
928  * * `TriggerTimeResolution` (time, default: `8 ns`): time resolution for the
929  * trigger primitives;
930  * * `EventTreeName` (optional string): if specified, a simple ROOT tree is
931  * created with the information from each event (see `EventInfoTree` for
932  * its structure);
933  * * `EventDetailsLogCategory` (optional string): if specified, information for
934  * each single event is output into the specified stream category; if
935  * the string is specified empty, the default module stream is used, as
936  * determined by `LogCategory` parameter;
937  * * `LogCategory` (string, default `TriggerEfficiencyPlots`): name of category
938  * used to stream messages from this module into message facility.
939  *
940  * An example job configuration is provided as `maketriggerplots_icarus.fcl`.
941  *
942  *
943  * Technical description of the module
944  * ====================================
945  *
946  * This module reads
947  * @ref TriggerEfficiencyPlots_Data "trigger gate data products" from different
948  * ADC thresholds, and for each threahold it combines the data into a trigger
949  * response depending on a ADC threshold.
950  * It then applies different requirement levels and for each one it fills plots.
951  *
952  * All the input sets (each with its own ADC treshold) are treated independently
953  * from each other.
954  *
955  * We can separate the plots in two categories: the ones which depend on the
956  * trigger response, and the ones which do not.
957  * The latter include event-wide information like the total energy deposited in
958  * the detector by one event. They also include plots that depend on the ADC
959  * threshold, e.g. the largest number of trigger primitives available at any
960  * time during the event. Therefore there are more precisely three plot
961  * categories:
962  *
963  * 1. plots which do not depend on the trigger primitives at all;
964  * 2. plots which depend on the ADC threshold of the trigger primitives,
965  * but not on the trigger response;
966  * 3. plots which depend on the trigger response (and therefore also on the
967  * ADC threshold of the trigger primitives).
968  *
969  * In addition, each event is assigned to event categories. These categories
970  * do not depend on trigger primitives. For example, a electron neutrino neutral
971  * current interaction event would be assigned to the neutral current neutrino
972  * interaction category, to the electron neutrino category, and so on.
973  * A set of plot is produced at once for each of the event categories.
974  *
975  * This module is designed as follows:
976  *
977  * * each _art_ event is treated independently from the others
978  * (this is the norm; method: `analyze()`);
979  * * information for the plots which do not depend on the trigger primitives
980  * are extracted once per event (method: `extractEventInfo()`);
981  * * the event is assigned its categories (method: `selectedPlotCategories()`,
982  * with the categories efined in `PlotCategories` global variable);
983  * * for each input primitive set (i.e. for each ADC threshold):
984  * * trigger logic is applied (method: `combineTriggerPrimitives()`):
985  * * all trigger primitives are combined in a single nulti-level gate
986  * * the level of the gate is the count of trigger primitives on which
987  * the trigger response is based
988  * * the beam gate is imposed in coincidence (method: `applyBeamGate()`)
989  * * control is passed to method `plotResponses()` for plots, together with
990  * the list of all the categories the event belongs:
991  * * for each trigger requirement, that is a minimum number of
992  * primitives, the response to that requirement is plotted (as
993  * efficiency) and the time of the resulting trigger is added into an
994  * histogram
995  * * response-independent plots are also filled
996  * * the event is plotted for all the event categories which apply
997  *
998  * Note that the plots depending on the response may require multiple fillings.
999  * For example, the trigger efficiency plot is a single plot in the plot set
1000  * which requires a filling for every requirement level.
1001  * In a similar fashion, the plot of trigger time requires one filling for
1002  * each requirement level that is met.
1003  *
1004  *
1005  * @anchor TriggerEfficiencyPlots_OrganizationOfPlots
1006  * Organization of the plots
1007  * --------------------------
1008  *
1009  * Plots are written on disk via the standard _art_ service `TFileService`,
1010  * which puts them in a ROOT subdirectory named as the instance of this module
1011  * being run.
1012  *
1013  * As described above, there are two "dimensions" for the plot sets: there is
1014  * one plot set for each ADC threshold and for each event category, the total
1015  * number of plot sets being the product of the options in the two dimensions.
1016  *
1017  * The code is structured to work by threshold by threshold, because the trigger
1018  * response depends on the threshold but not by the event category: the response
1019  * is computed once per threshold, then all the plots related to that response
1020  * (including all the event categories) are filled.
1021  *
1022  * The structure in the file reflects this logic, and there are two levels of
1023  * ROOT directories inside the one assigned by _art_ to this module:
1024  * * the outer level pins down the ADC threshold of the plots inside it;
1025  * the name of the directory follows the pattern `Thr###` (### being the ADC
1026  * threshold), which is the "threshold tag";
1027  * * the inner level is the category of events included in the plots, and the
1028  * name of the directories reflect the name of the category as defined in
1029  * the corresponding `PlotCategory` object (`PlotCategory::name()`); this
1030  * defines the "category tag".
1031  *
1032  * In each inner directory, a complete set of plots is contained.
1033  * The name of each plot is a base name for that plot (e.g. `Eff` for the
1034  * efficiency plot) with the event category tag and the threshold tag appended
1035  * (separated by an underscore character, `"_"`). The title of the plot is also
1036  * modified to include the description of the plot category (as defined in
1037  * `PlotCategory::description()`) and the ADC threshold.
1038  * Therefore, within the same module directory, all plot names are different.
1039  *
1040  *
1041  * Adding a plot
1042  * --------------
1043  *
1044  * When adding a plot, two actions are necessary:
1045  *
1046  * 1. initialize the new plot
1047  * 2. fill the new plot
1048  *
1049  * The initialization happens in
1050  * `icarus::trigger::TriggerEfficiencyPlots::initializePlotSet()` method.
1051  * A request must be issued to the
1052  * @ref TriggerEfficiencyPlots_PlotSandboxes "plot sandbox" to "make" the plot.
1053  * In general it can be expected that all the arguments `args` in a call
1054  * `plots.make<PlotType>(args)` are forwarded to the constructor of `PlotType`,
1055  * so that to make a new `PlotType` object one has to consult only the
1056  * constructor of that class. Be aware though that the library expects the first
1057  * argument to be the ROOT name of the new object and the second to be its ROOT
1058  * title.
1059  * It is possible to work around this requirement with additional coding.
1060  *
1061  * The method performing the plotting is `plotResponses()`.
1062  * The plots should be filled inside loops going through all plot sets.
1063  * The existing code already does that in two loops, one for the plots
1064  * depending on the trigger response requirement, and the other for the plots
1065  * _not_ depending on it.
1066  *
1067  *
1068  * @anchor TriggerEfficiencyPlots_PlotSandboxes
1069  * ### About plot sandboxes
1070  *
1071  * For the sake of this module, a plot sandbox is an object similar to a ROOT
1072  * `TDirectory`, which can mangle the objects it contains to change ("process")
1073  * their name and title, and that interacts properly with `art::TFileDirectory`
1074  * so make `TFileService` happy.
1075  * The processing of the name and title is used to add the category and
1076  * threshold tags to the plot ROOT name and the corresponding annotations to
1077  * the plot title, as described
1078  * @ref TriggerEfficiencyPlots_OrganizationOfPlots "above".
1079  *
1080  *
1081  * @anchor TriggerEfficiencyPlots_AddingCategory
1082  * Adding an event category
1083  * -------------------------
1084  *
1085  * Event categories are listed in `PlotCategories`: each one is described by
1086  * a simple object `PlotCategory` which contains a name (used in ROOT
1087  * directories, plot name tags and to univocally identify the category),
1088  * a description (used in plot titles) and a condition for the event to fulfill
1089  * in order to qualify for the category. The condition is a function object
1090  * that accepts an `EventInfo_t` object with the relevant event information,
1091  * and returns its decision (`true` if the event belongs to this category).
1092  *
1093  * To add a category, it is enough to append an entry at the end of the
1094  * `PlotCategories` list, taking the existing ones as example.
1095  *
1096  * The condition function adjudicates based on the information in `EventInfo_t`.
1097  * It is likely though that if a new category is needed, the information
1098  * currently available in `EventInfo_t` is not enough to decide the response.
1099  * In that case, `EventInfo_t` must be extended to hold the newly required
1100  * information. In addition, the method `extractEventInfo()` must be modified
1101  * to set the new information. The documentation of that method expands on this
1102  * topic.
1103  *
1104  *
1105  * Changing the trigger logic
1106  * ---------------------------
1107  *
1108  * The trigger logic is hard-coded in `analyze()`, but it effectively has a
1109  * follow up in the plotting code.
1110  * In fact, in `analyze()` the primitives are combined into a single object,
1111  * but it is in `plotResponses()` that this object is interpreted to decide
1112  * whether the trigger fired according to a requirement.
1113  *
1114  * To implement a radically different trigger logic, several components need
1115  * to be coordinated:
1116  *
1117  * 1. configuration of trigger logic parameters
1118  * 2. combination of trigger primitives;
1119  * 3. the data structure the combination is stored in;
1120  * 4. interpretation of that data structure in terms of trigger firing;
1121  * 5. configuration of the trigger logic from user parameters.
1122  *
1123  * Because of the design of this module, the first and the third elements are
1124  * in different places (causing the necessity of the shared data structure that
1125  * is the second element) and changing one typically imply changing the other.
1126  *
1127  * This module simulates a trigger counting the number of primitives that are
1128  * "on" and imposing a required minimum on that number for the trigger to fire.
1129  * This is how _this_ implementation includes those elements:
1130  *
1131  * 1. combination of trigger primitives: primitives are summed into a single
1132  * multilevel gate (with beam gate on top);
1133  * 2. the data structure the combination is stored in: a
1134  * @ref TriggerEfficiencyPlots_Data "trigger gate object";
1135  * 3. interpretation of that data structure in terms of trigger firing:
1136  * the logic requires a minimum number of primitives being on at the same
1137  * time; the interpretation uses the level of the combined trigger gate as
1138  * the number of primitives on at every time, and decides that the first
1139  * time this number meets the requirement, the trigger fires;
1140  * 4. configuration of the trigger logic from user parameters:
1141  * it's a parameter of the module itself (`MinimumPrimitives`).
1142  *
1143  * An example of how a different trigger logic _could_ be implemented following
1144  * a similar model. Let's assume we want a trigger based on two sliding windows.
1145  * A window is a group of LVDS signals from neighboring channels. We can split
1146  * the whole optical detector channels into many windows, and we can make the
1147  * windows overlap: for example, a window includes the first 30 channels behind
1148  * one anode plane, the second window includes 30 channels starting from the
1149  * fifteenth, the third window includes 30 channels starting from the thirtieth,
1150  * and so on. In ICARUS, 30 channels are served in 16 LVDS discriminated
1151  * waveforms, i.e. 16 trigger primitives. We may require as a loose trigger
1152  * that any window contains at least 10 primitives on at the same time (that is
1153  * about 20 PMT channels beyond ADC threshold), and as a tighter trigger that
1154  * there is a window with at least 8 trigger primitives on, _and_ the
1155  * corresponding window in the opposite TPC has at least 6 primitives on
1156  * at the same time. Note that a splitting 7/7 won't do: one of the two windows
1157  * must have 8 or more primitives on.
1158  * The input primitives from the point of view of the module now become the
1159  * "sliding window" combination of the LVDS trigger primitives, as produced e.g.
1160  * by `icarus::trigger::SlidingWindowTrigger` module.
1161  * How this can be implemented following the pattern of this module:
1162  *
1163  * 1. combination of trigger primitives: we prepare two primitives:
1164  * one describes the maximum level among all the windows, and the other
1165  * describes the level of the window opposite to the maximum (the way to
1166  * achieve this is quite complicate);
1167  * 2. the data structure the combination is stored in: two
1168  * @ref TriggerEfficiencyPlots_Data "trigger gate objects";
1169  * 3. interpretation of that data structure in terms of trigger firing:
1170  * for the loose trigger, we look for the first time the maximum window level
1171  * reaches the designated threshold; for the tighter trigger, some
1172  * additional logic is required, discriminating the maximum window level
1173  * primitive to the higher threshold, the opposite window level on the lower
1174  * threshold, and setting them in coincidence; then the time the resulting
1175  * gate opens, if any, is the trigger time;
1176  * 4. configuration of trigger logic parameters: the two requirements (single
1177  * window, double window with their minimum number of LVDS primitives)
1178  * are read from the module FHiCL configuration; for computing efficiency,
1179  * either in the constructor or in a `beginJob()` method we may also prepare
1180  * the associations (pairing) between opposite windows or just channels;
1181  *
1182  * @todo add ability to discriminate a trigger gate object?
1183  * discrimination on level _N_ (>=N -> 1, <N -> 0) can be obtained with
1184  * adding a _-N_ uniform level, flooring on level 0 and (if needed) maxing
1185  * on level 1.
1186  *
1187  *
1188  * Code style: quantities with units
1189  * ----------------------------------
1190  *
1191  * To avoid issues with missing or wrong conversions, this code often uses
1192  * LArSoft quantities library. A variable with a `Quantity` type is represented
1193  * with a specific unit (e.g. microseconds) and can undergo only limited
1194  * manipulation. The allowed manipulation should guarantee that the unit of
1195  * the quantity is correctly preserved. For example, it is not possible to
1196  * add to a `microseconds` interval a pure number (e.g. `9.0`), but rather
1197  * it is only possible to add time quantities (e.g. another `microseconds`
1198  * variable, or a `nanoseconds` variable, or a literal value with unit, like
1199  * `9.0_ns`), and those quantities are properly converted, with the caveat that
1200  * rounding errors may happen that a more careful explicit handling could avoid.
1201  * Also, there are two types of variables that can feature the same unit,
1202  * intervals and points. A point can't be scaled (e.g. you can't "double" the
1203  * time the beam gate opens, while you can double the beam gate _duration_)
1204  * and it can't be added to another point (the difference between two points
1205  * is an interval).
1206  *
1207  * To avoid mistakes in the conversion between different time scales, the
1208  * LArSoft utility library `detinfo::DetectorTimings` is used, which is a
1209  * wrapper of `DetectorClocks` service provider that makes the interface to
1210  * convert times easier and uniform. Here we use it to convert time points
1211  * from the simulation (which are expressed in nanoseconds and are starting
1212  * at trigger time) into optical detector readout ticks, and vice versa.
1213  * The values returned by this library have encoded in them which time scale
1214  * they belong to, and in which unit they are measured.
1215  *
1216  */
1217 class icarus::trigger::TriggerEfficiencyPlots: public art::EDAnalyzer {
1218 
1221 
1222  public:
1223 
1224  // --- BEGIN Configuration ---------------------------------------------------
1225  struct Config {
1226 
1228  using Comment = fhicl::Comment;
1229 
1230  fhicl::Sequence<art::InputTag> GeneratorTags {
1231  Name("GeneratorTags"),
1232  Comment("labels of the event generators"),
1233  std::vector<art::InputTag>{ "generator" }
1234  };
1235 
1236  fhicl::Atom<art::InputTag> DetectorParticleTag {
1237  Name("DetectorParticleTag"),
1238  Comment("label of simulated particles through the detector"),
1239  "largeant" // tradition demands
1240  };
1241 
1242  fhicl::Sequence<art::InputTag> EnergyDepositTags {
1243  Name("EnergyDeposits"),
1244  Comment("label of energy deposition data product(s) in the detector"),
1245  std::vector<art::InputTag>{ "largeant:TPCActive" }
1246  };
1247 
1248  fhicl::Atom<std::string> TriggerGatesTag {
1249  Name("TriggerGatesTag"),
1250  Comment("label of the input trigger gate data product (no instance name)")
1251  };
1252 
1253  fhicl::Sequence<raw::ADC_Count_t> Thresholds {
1254  Name("Thresholds"),
1255  Comment("thresholds to consider [ADC counts]")
1256  };
1257 
1258  fhicl::Atom<microseconds> BeamGateDuration {
1259  Name("BeamGateDuration"),
1260  Comment("length of time interval when optical triggers are accepted")
1261  };
1262 
1263  fhicl::Sequence<unsigned int> MinimumPrimitives {
1264  Name("MinimumPrimitives"),
1265  Comment("minimum required number of trigger primitives for a trigger")
1266  };
1267 
1268  fhicl::Atom<nanoseconds> TriggerTimeResolution {
1269  Name("TriggerTimeResolution"),
1270  Comment("resolution of trigger in time"),
1271  8_ns
1272  };
1273 
1274  fhicl::Atom<bool> PlotOnlyActiveVolume {
1275  Name("PlotOnlyActiveVolume"),
1276  Comment
1277  ("only events within TPC active volume are plot (if that makes sense)"),
1278  true
1279  };
1280 
1281  fhicl::OptionalAtom<std::string> EventTreeName {
1282  Name("EventTreeName"),
1283  Comment("name of a ROOT tree where to store event-by-event information")
1284  };
1285 
1286  fhicl::OptionalAtom<std::string> EventDetailsLogCategory {
1287  Name("EventDetailsLogCategory"),
1288  Comment("name of the category used for event information output")
1289  };
1290 
1291  fhicl::Atom<std::string> LogCategory {
1292  Name("LogCategory"),
1293  Comment("name of the category used for the output"),
1294  "SlidingWindowTrigger" // default
1295  };
1296 
1297  }; // struct Config
1298 
1299  using Parameters = art::EDAnalyzer::Table<Config>;
1300  // --- END Configuration -----------------------------------------------------
1301 
1302 
1303  // --- BEGIN Constructors ----------------------------------------------------
1304  explicit TriggerEfficiencyPlots(Parameters const& config);
1305 
1306  // Plugins should not be copied or assigned.
1309  TriggerEfficiencyPlots& operator=(TriggerEfficiencyPlots const&) = delete;
1310  TriggerEfficiencyPlots& operator=(TriggerEfficiencyPlots&&) = delete;
1311 
1312  // --- END Constructors ------------------------------------------------------
1313 
1314 
1315  // --- BEGIN Framework hooks -------------------------------------------------
1316 
1317  /// Fills the plots. Also extracts the information to fill them with.
1318  virtual void analyze(art::Event const& event) override;
1319 
1320  /// Prints end-of-job summaries.
1321  virtual void endJob() override;
1322 
1323  // --- END Framework hooks ---------------------------------------------------
1324 
1325 
1326  private:
1327 
1328  /// Reconstituted trigger gate type internally used.
1329  using TrackedTriggerGate_t
1331 
1332  /// Type of gate data without channel information.
1333  using TriggerGateData_t
1335 
1336  using PlotSandbox = icarus::trigger::PlotSandbox; ///< Import type.
1337 
1338  /// List of references to plot sandboxes.
1339  using PlotSandboxRefs_t
1340  = std::vector<std::reference_wrapper<PlotSandbox const>>;
1341 
1342 
1343  // --- BEGIN Configuration variables -----------------------------------------
1344 
1345  std::vector<art::InputTag> fGeneratorTags; ///< Generator data product tags.
1346  art::InputTag fDetectorParticleTag; ///< Input simulated particles.
1347  /// Energy deposition data product tags.
1348  std::vector<art::InputTag> fEnergyDepositTags;
1349 
1350  /// ADC thresholds to read, and the input tag connected to their data.
1351  std::map<icarus::trigger::ADCCounts_t, art::InputTag> fADCthresholds;
1352 
1353  /// Duration of the gate during with global optical triggers are accepted.
1355 
1356  /// Minimum number of trigger primitives for a trigger to happen.
1357  std::vector<unsigned int> fMinimumPrimitives;
1358 
1359  nanoseconds fTriggerTimeResolution; ///< Trigger resolution in time.
1360 
1361  bool fPlotOnlyActiveVolume; ///< Plot only events in active volume.
1362 
1363  /// Message facility stream category for output.
1364  std::string const fLogCategory;
1365 
1366  std::string fLogEventDetails; ///< Steam where to print event info.
1367 
1368  // --- END Configuration variables -------------------------------------------
1369 
1370 
1371  // --- BEGIN Service variables -----------------------------------------------
1372 
1374 
1375  /// ROOT directory where all the plots are written.
1376  art::TFileDirectory fOutputDir;
1377 
1378  // --- END Service variables -------------------------------------------------
1379 
1380 
1381  // --- BEGIN Internal variables ----------------------------------------------
1382 
1383  /// Beam gate start and stop tick in optical detector scale.
1384  std::pair
1387 
1388  /// Beam gate start and stop time in simulation scale.
1389  std::pair
1392 
1393  /// All plots, one set per ADC threshold.
1394  std::vector<PlotSandbox> fThresholdPlots;
1395 
1396  std::unique_ptr<EventIDTree> fIDTree; ///< Handler of ROOT tree output.
1397  std::unique_ptr<PlotInfoTree> fPlotTree; ///< Handler of ROOT tree output.
1398  std::unique_ptr<EventInfoTree> fEventTree; ///< Handler of ROOT tree output.
1399  std::unique_ptr<ResponseTree> fResponseTree; ///< Handler of ROOT tree output.
1400 
1401  std::atomic<unsigned int> nEvents { 0U }; ///< Count of seen events.
1402  std::atomic<unsigned int> nPlottedEvents { 0U }; ///< Count of plotted events.
1403 
1404  // --- END Internal variables ------------------------------------------------
1405 
1406 
1407  /// Initializes all the plot sets, one per threshold.
1408  void initializePlots(detinfo::DetectorClocksData const& clockData, PlotCategories_t const& categories);
1409 
1410  /// Initializes full set of plots for (ADC threshold + category) into `plots`.
1411  void initializePlotSet(detinfo::DetectorClocksData const& clockData, PlotSandbox& plots) const;
1412 
1413  /// Initializes set of plots per complete trigger definition into `plots`.
1414  void initializeEfficiencyPerTriggerPlots(detinfo::DetectorClocksData const& clockData, PlotSandbox& plots) const;
1415 
1416  /// Initializes a single, trigger-independent plot set into `plots`.
1417  void initializeEventPlots(PlotSandbox& plots) const;
1418 
1419 
1420  /// Returns whether an event with the specified information should be included
1421  /// in the plots at all (it's a filter).
1422  bool shouldPlotEvent(EventInfo_t const& eventInfo) const;
1423 
1424 
1425  /// Returns the names of the plot categories event qualifies for.
1426  std::vector<std::string> selectPlotCategories
1427  (EventInfo_t const& info, PlotCategories_t const& categories) const;
1428 
1429  /**
1430  * @brief Extracts from `event` the relevant information on the physics event.
1431  *
1432  * This returns a `EventInfo_t` object filled as completely as possible.
1433  * The result is used in the context of
1434  * @ref TriggerEfficiencyPlots_AddingCategory "assigning the `event` to categories".
1435  *
1436  * This method can read any data product in the event, and has access to the
1437  * module configuration. If information is needed from a data product that
1438  * is not read yet, the following actions are needed:
1439  *
1440  * 1. decide the input tag of the data product(s) to be read; this is usually
1441  * delegated to the user via module configuration (see `Config` structure);
1442  * 2. the required input tags need to be stored into `TriggerEfficiencyPlots`
1443  * object;
1444  * 3. _art_ needs to be informed of the intention of reading the data products
1445  * via `consumes()` or equivalent calls, in `TriggerEfficiencyPlots`
1446  * constructor;
1447  * 4. the data product can be read with the usual means in this method.
1448  *
1449  * For an example of this procedure, see how the configuration parameter
1450  * `DetectorParticleTag` is treated: read by `Config::DetectorParticleTag`,
1451  * stored in `fDetectorParticleTag`, declared as "consumed" in
1452  * `TriggerEfficiencyPlots` constructor. Likewise a known list of data
1453  * products can be used: see `GeneratorTags` parameter, read by the sequence
1454  * `Config::GeneratorTags`, stored in the class field `fGeneratorTags`,
1455  * declared as "consumed" in a loop in `TriggerEfficiencyPlots` constructor,
1456  * and read in `extractEventInfo()` to be used within it.
1457  *
1458  */
1459  EventInfo_t extractEventInfo(art::Event const& event, detinfo::DetectorClocksData const& clockData) const;
1460 
1461  /// Returns the TPC `point` is within, `nullptr` if none.
1462  geo::TPCGeo const* pointInTPC(geo::Point_t const& point) const;
1463 
1464  /// Returns in which TPC `point` is within the _active volume_ of;
1465  /// `nullptr` if none.
1466  geo::TPCGeo const* pointInActiveTPC(geo::Point_t const& point) const;
1467 
1468  /// Computes the trigger response from primitives with the given `threshold`.
1469  std::vector<TriggerGateData_t> combineTriggerPrimitives(
1470  art::Event const& event,
1471  icarus::trigger::ADCCounts_t const threshold,
1472  art::InputTag const& dataTag
1473  ) const;
1474 
1475  /// Adds all the `responses` (one per threshold) to the plots.
1476  void plotResponses(
1477  std::size_t iThr, icarus::trigger::ADCCounts_t const threshold,
1479  PlotSandboxRefs_t const& plots, EventInfo_t const& info,
1480  std::vector<TriggerGateData_t> const& primitiveCounts
1481  ) const;
1482 
1483  /// Reads a set of input gates from the `event`.
1484  std::vector<std::vector<TrackedTriggerGate_t>> ReadTriggerGates
1485  (art::Event const& event, art::InputTag const& dataTag) const;
1486 
1487  static std::string thrAndCatName
1488  (std::string const& boxName, std::string const& category)
1489  { return boxName + "_" + category; }
1490  static std::string thrAndCatName
1491  (PlotSandbox const& box, std::string const& category)
1492  { return thrAndCatName(box.name(), category); }
1493 
1494  /// Returns a gate that is `Max()` of all the specified `gates`.
1495  template <typename TrigGateColl>
1496  static auto computeMaxGate(TrigGateColl const& gates);
1497 
1498 
1499 }; // icarus::trigger::TriggerEfficiencyPlots
1500 
1501 
1502 //------------------------------------------------------------------------------
1503 //--- Implementation
1504 //------------------------------------------------------------------------------
1505 //--- icarus::trigger::TriggerEfficiencyPlots
1506 //------------------------------------------------------------------------------
1508  (Parameters const& config)
1509  : art::EDAnalyzer(config)
1510  // configuration
1511  , fGeneratorTags (config().GeneratorTags())
1512  , fDetectorParticleTag (config().DetectorParticleTag())
1513  , fEnergyDepositTags (config().EnergyDepositTags())
1514  , fBeamGateDuration (config().BeamGateDuration())
1515  , fMinimumPrimitives (config().MinimumPrimitives())
1516  , fTriggerTimeResolution(config().TriggerTimeResolution())
1517  , fPlotOnlyActiveVolume (config().PlotOnlyActiveVolume())
1518  , fLogCategory (config().LogCategory())
1519  // services
1520  , fGeom (*lar::providerFrom<geo::Geometry>())
1521  , fOutputDir (*art::ServiceHandle<art::TFileService>())
1522  // cached
1523 {
1524  //
1525  // more complex parameter parsing
1526  //
1527  if (config().EventDetailsLogCategory(fLogEventDetails)) {
1528  // the parameter is somehow set, so fLogEventDetails won't be empty;
1529  // but if the parameter value is specified empty, we set it to fLogCategory
1530  if (fLogEventDetails.empty()) fLogEventDetails = fLogCategory;
1531  } // if EventDetailsLogCategory is specified
1532 
1533  std::string const discrModuleLabel = config().TriggerGatesTag();
1534  for (raw::ADC_Count_t threshold: config().Thresholds()) {
1535  fADCthresholds[icarus::trigger::ADCCounts_t{threshold}]
1536  = art::InputTag{ discrModuleLabel, util::to_string(threshold) };
1537  }
1538 
1539  if (config().EventTreeName.hasValue()) {
1540  std::string treeName;
1541  config().EventTreeName(treeName);
1542 
1543  fIDTree = std::make_unique<EventIDTree>
1544  (*(fOutputDir.make<TTree>(treeName.c_str(), "Event information")));
1545  fEventTree = std::make_unique<EventInfoTree>(fIDTree->tree());
1546  fPlotTree = std::make_unique<PlotInfoTree>(fIDTree->tree());
1547  fResponseTree = std::make_unique<ResponseTree>(
1548  fIDTree->tree(),
1549  util::get_elements<0U>(fADCthresholds), fMinimumPrimitives
1550  );
1551 
1552  } // if make tree
1553 
1554  //
1555  // input data declaration
1556  //
1557  using icarus::trigger::OpticalTriggerGateData_t; // for convenience
1558 
1559  // event information
1560  for (art::InputTag const& inputTag: fGeneratorTags)
1561  consumes<std::vector<simb::MCTruth>>(inputTag);
1562  for (art::InputTag const& inputTag: fEnergyDepositTags)
1563  consumes<std::vector<sim::SimEnergyDeposit>>(inputTag);
1564 // consumes<std::vector<simb::MCParticle>>(fDetectorParticleTag);
1565 
1566  // trigger primitives
1567  for (art::InputTag const& inputDataTag: util::const_values(fADCthresholds)) {
1568  icarus::trigger::TriggerGateReader{ inputDataTag }
1570  } // for
1571 
1572  //
1573  // initialization of plots and plot categories
1574  //
1575  auto const clockData
1576  = art::ServiceHandle<detinfo::DetectorClocksService const>()->DataForJob();
1577 
1578  initializePlots(clockData, ::PlotCategories);
1579  {
1580  mf::LogInfo log(fLogCategory);
1581  log << "\nConfigured " << fADCthresholds.size() << " thresholds:";
1582  for (auto const& [ threshold, dataTag ]: fADCthresholds)
1583  log << "\n * " << threshold << " ADC (from '" << dataTag.encode() << "')";
1584 
1585  auto const beamGate = icarus::trigger::makeBeamGateStruct
1586  (detinfo::DetectorTimings{clockData}, fBeamGateDuration);
1587  log << "\nBeam gate used for plot ranges: " << beamGate.asSimulationRange();
1588  } // local block
1589 
1590 } // icarus::trigger::TriggerEfficiencyPlots::TriggerEfficiencyPlots()
1591 
1592 
1593 //------------------------------------------------------------------------------
1595 
1596  /*
1597  * 1. find out the features of the event and the categories it belongs to
1598  * 2. for each threshold:
1599  * 1. read the trigger primitives
1600  * 2. apply the beam gate on the primitives
1601  * 3. (optional) combine the trigger primitives
1602  * 4. generate the trigger response
1603  * 5. pick the plots to be filled
1604  * 6. add the response to all the plots
1605  *
1606  */
1607 
1608  ++nEvents;
1609 
1610  //
1611  // 1. find out the features of the event and the categories it belongs to
1612  //
1613  auto const clockData
1614  = art::ServiceHandle<detinfo::DetectorClocksService const>()->DataFor(event);
1615  detinfo::DetectorTimings const detTimings{clockData};
1616  EventInfo_t const eventInfo = extractEventInfo(event, clockData);
1617 
1618  bool const bPlot = shouldPlotEvent(eventInfo);
1619  if (bPlot) ++nPlottedEvents;
1620 
1621  if (fIDTree) fIDTree->assignEvent(event);
1622  if (fPlotTree) fPlotTree->assign(bPlot);
1623  if (fEventTree) fEventTree->assignEvent(eventInfo);
1624 
1625  std::vector<std::string> selectedPlotCategories
1626  = selectPlotCategories(eventInfo, ::PlotCategories);
1627  {
1628  mf::LogTrace log(fLogCategory);
1629  log
1630  << "Event " << event.id() << " falls in " << selectedPlotCategories.size()
1631  << " categories:"
1632  ;
1633  for (std::string const& name: selectedPlotCategories)
1634  log << " \"" << name << "\"";
1635  // print the information on the event
1636  } // local block
1637  if (!fLogEventDetails.empty()) {
1638  mf::LogTrace(fLogEventDetails)
1639  << "Event " << event.id() << ": " << eventInfo;
1640  }
1641 
1642  //
1643  // 2. for each threshold:
1644  //
1645  /// Gate representing the time we expect light from beam interactions.
1646  auto const beam_gate = icarus::trigger::BeamGateMaker{clockData}(fBeamGateDuration);
1647 
1648  for (auto&& [ iThr, thrPair, thrPlots ]
1649  : util::enumerate(fADCthresholds, fThresholdPlots)
1650  ) {
1651 
1652  auto const& [ threshold, dataTag ] = thrPair;
1653 
1654  //
1655  // 1.1. read the trigger primitives
1656  // 1.2. (optional) combine the trigger primitives
1657  // 1.3. apply the beam gate on the primitives
1658  // 1.4. generate the trigger response
1659  //
1660 
1661  std::vector<TriggerGateData_t> primitiveCounts;
1662  std::vector<TriggerGateData_t> combinedTriggerPrimitives
1663  = combineTriggerPrimitives(event, threshold, dataTag);
1664 
1665  for (TriggerGateData_t combinedPrimitive: combinedTriggerPrimitives) {
1666  TriggerGateData_t const primitiveCount
1667  = combinedPrimitive.Mul(beam_gate);
1668 
1669  primitiveCounts.push_back(primitiveCount);
1670  }
1671 
1672  //
1673  // 1.5. pick the plots to be filled
1674  //
1675  PlotSandboxRefs_t selectedPlots;
1676 
1677  if (bPlot) {
1678  for (std::string const& name: selectedPlotCategories)
1679  selectedPlots.emplace_back(*(thrPlots.findSandbox(name)));
1680  }
1681 
1682  // 1.6. add the response to the appropriate plots
1683  plotResponses(iThr, threshold, detTimings, selectedPlots, eventInfo, primitiveCounts);
1684 
1685  } // for thresholds
1686 
1687 
1688  //
1689  // store information in output tree if any
1690  //
1691  if (fIDTree) fIDTree->tree().Fill();
1692 
1693 } // icarus::trigger::TriggerEfficiencyPlots::analyze()
1694 
1695 
1696 //------------------------------------------------------------------------------
1698 
1699  mf::LogInfo(fLogCategory)
1700  << nPlottedEvents << "/" << nEvents << " events plotted."
1701  ;
1702 
1703 } // icarus::trigger::TriggerEfficiencyPlots::endJob()
1704 
1705 
1706 //------------------------------------------------------------------------------
1708  (detinfo::DetectorClocksData const& clockData, PlotCategories_t const& categories)
1709 {
1710  using namespace std::string_literals;
1711 
1712  for (icarus::trigger::ADCCounts_t const threshold
1713  : util::get_elements<0U>(fADCthresholds))
1714  {
1715  // create a plot sandbox inside `fOutputDir` with a name/prefix `Thr###`
1716  auto const thr = threshold.value();
1717  icarus::trigger::PlotSandbox thrPlots { fOutputDir,
1718  "Thr"s + util::to_string(thr), "(thr: "s + util::to_string(thr) + ")"s };
1719 
1720  // create a subbox for each plot category
1721  for (PlotCategory const& category: categories) {
1722  PlotSandbox& plots = thrPlots.addSubSandbox(
1723  category.name(),
1724  category.description()
1725  );
1726 
1727  initializePlotSet(clockData, plots);
1728  }
1729  fThresholdPlots.push_back(std::move(thrPlots));
1730  } // for thresholds
1731 
1732  mf::LogTrace log(fLogCategory);
1733  log << "Created " << fThresholdPlots.size() << " plot boxes:\n";
1734  for (auto const& box: fThresholdPlots) {
1735  box.dump(log, " ");
1736  } // for
1737 
1738 } // icarus::trigger::TriggerEfficiencyPlots::initializePlots()
1739 
1740 
1741 //------------------------------------------------------------------------------
1743  (detinfo::DetectorClocksData const& clockData, PlotSandbox& plots) const
1744 {
1745 
1746  // a variable binning for the required number of trigger primitives
1747  auto [ minimumPrimBinning, minimumPrimBinningLabels ]
1748  = util::ROOT::makeVariableBinningAndLabels(fMinimumPrimitives);
1749  assert(minimumPrimBinning.size() == minimumPrimBinningLabels.size() + 1U);
1750 
1751  {
1752  mf::LogTrace log(fLogCategory);
1753  log << "TriggerEfficiencyPlots (plots '"
1754  << plots.name() << "') variable binning including the "
1755  << fMinimumPrimitives.size() << " points {";
1756  for (auto value: fMinimumPrimitives) log << " " << value;
1757  log << " } => " << minimumPrimBinningLabels.size() << " bins: ";
1758  for (auto const& [ value, label ]
1759  : util::zip<1U>(minimumPrimBinning, minimumPrimBinningLabels))
1760  {
1761  log << " " << value << " (\"" << label << "\") =>";
1762  } // for
1763  log << " " << minimumPrimBinning.back();
1764  } // debug output block
1765 
1766  //
1767  // Triggering efficiency vs. threshold.
1768  //
1769  detinfo::DetectorTimings const detTimings{clockData};
1770  detinfo::timescales::optical_time_ticks const triggerResolutionTicks
1771  { detTimings.toOpticalTicks(fTriggerTimeResolution) };
1772  auto const beam_gate_opt = std::make_pair(detTimings.toOpticalTick(detTimings.BeamGateTime()),
1773  detTimings.toOpticalTick(detTimings.BeamGateTime() + fBeamGateDuration));
1774  auto* TrigTime = plots.make<TH2F>(
1775  "TriggerTick",
1776  "Trigger time tick"
1777  ";minimum requested number of trigger primitives"
1778  ";optical time tick [ /" + util::to_string(triggerResolutionTicks) + " ]",
1779  minimumPrimBinning.size() - 1U, minimumPrimBinning.data(),
1780 // fMinimumPrimitives.back(), 0, fMinimumPrimitives.back() + 1
1781  (beam_gate_opt.second - beam_gate_opt.first) / triggerResolutionTicks,
1782  beam_gate_opt.first.value(), beam_gate_opt.second.value()
1783  );
1784 
1785  util::ROOT::applyAxisLabels(TrigTime->GetXaxis(), minimumPrimBinningLabels);
1786 
1787  auto* Eff = plots.make<TEfficiency>(
1788  "Eff",
1789  "Efficiency of triggering"
1790  ";minimum requested number of trigger primitives"
1791  ";trigger efficiency",
1792  minimumPrimBinning.size() - 1U, minimumPrimBinning.data()
1793 // fMinimumPrimitives.back(), 0, fMinimumPrimitives.back() + 1
1794  );
1795 
1796  // people are said to have earned hell for things like this;
1797  // but TEfficiency really does not expose the interface to assign labels to
1798  // its axes, which supposedly could be done had we chosen to create it by
1799  // histograms instead of directly as recommended.
1801  const_cast<TH1*>(Eff->GetTotalHistogram())->GetXaxis(),
1802  minimumPrimBinningLabels
1803  );
1804 
1805  //
1806  // plots independent of the trigger primitive requirements
1807  //
1808  plots.make<TH1F>(
1809  "NPrimitives",
1810  "Number of trigger primitives (\"channels firing at once\")"
1811  ";maximum trigger primitives at the same time"
1812  ";events",
1813  192, 0.0, 192.0 // large number, zoom in presentations!
1814  );
1815 
1816  //
1817  // Selection-related plots
1818  //
1819  initializeEventPlots(plots);
1820 
1821 
1822  //
1823  // Plots per trigger setting, split in triggering and not triggering events;
1824  // the plot set is the same as the "global" one.
1825  //
1826  using SS_t = std::pair<std::string, std::string>;
1827  std::array<SS_t, 2U> const classes {
1828  SS_t{ "triggering", "triggering events" },
1829  SS_t{ "nontriggering", "non-triggering events" }
1830  };
1831  for (auto minCount: fMinimumPrimitives) {
1832 
1833  std::string const minCountStr = std::to_string(minCount);
1834 
1835  // this defines a specific trigger, with its thresholds and settings
1836  PlotSandbox& reqBox = plots.addSubSandbox
1837  ("Req" + minCountStr, minCountStr + " channels required");
1838 
1839  initializeEfficiencyPerTriggerPlots(clockData, reqBox);
1840 
1841  for (auto const& [ name, desc ]: classes) {
1842 
1843  PlotSandbox& box = reqBox.addSubSandbox(name, desc);
1844 
1845  initializeEventPlots(box);
1846 
1847  } // for triggering requirement
1848  } // for triggering classes
1849 
1850  //
1851  // No trigger related plots
1852  //
1853  plots.make<TH1F>(
1854  "NeutrinoEnergy_NoTrig",
1855  "True Neutrino Energy of Non-Triggered Event"
1856  ";neutrino energy [GeV]"
1857  ";events",
1858  120,0.0,6.0 // GeV
1859  );
1860  plots.make<TH1F>(
1861  "EnergyInSpill_NoTrig",
1862  "Energy eposited during the beam gate opening of Non-Triggered Event"
1863  ";deposited energy [ GeV ]"
1864  ";events [ / 50 MeV ]",
1865  120, 0.0, 6.0 // 6 GeV should be enough for a MIP crossing 20 m of detector
1866  );
1867  plots.make<TH1I>(
1868  "InteractionType_NoTrig",
1869  "Interaction type of Non-Triggered Event"
1870  ";Interaction Type"
1871  ";events [ / 50 MeV ]",
1872  200, 999.5, 1199.5
1873  );
1874  plots.make<TH1F>(
1875  "LeptonEnergy_NoTrig",
1876  "Energy of outgoing lepton of Non-Triggered Event"
1877  ";lepton generated energy [ GeV ]"
1878  ";events [ / 50 MeV ]",
1879  120, 0.0, 6.0
1880  );
1881 
1882 } // icarus::trigger::TriggerEfficiencyPlots::initializePlotSet()
1883 
1884 
1885 //------------------------------------------------------------------------------
1887  (detinfo::DetectorClocksData const& clockData, PlotSandbox& plots) const
1888 {
1889  detinfo::DetectorTimings const detTimings{clockData};
1890  detinfo::timescales::optical_time_ticks const triggerResolutionTicks
1891  { detTimings.toOpticalTicks(fTriggerTimeResolution) };
1892 
1893  auto const beam_gate_opt = std::make_pair(detTimings.toOpticalTick(detTimings.BeamGateTime()),
1894  detTimings.toOpticalTick(detTimings.BeamGateTime() + fBeamGateDuration));
1895  //
1896  // Triggering efficiency vs. something else
1897  //
1898  plots.make<TEfficiency>(
1899  "EffVsEnergyInSpill",
1900  "Efficiency of triggering vs. energy deposited in spill"
1901  ";energy deposited in spill [ GeV ]"
1902  ";trigger efficiency [ / 50 GeV ]",
1903  120, 0.0, 6.0 // 6 GeV should be enough for a MIP crossing 20 m of detector
1904  );
1905 
1906  plots.make<TEfficiency>(
1907  "EffVsEnergyInSpillActive",
1908  "Efficiency of triggering vs. energy deposited in active volume"
1909  ";energy deposited in active volume in spill [ GeV ]"
1910  ";trigger efficiency [ / 50 GeV ]",
1911  120, 0.0, 6.0 // 6 GeV should be enough for a MIP crossing 20 m of detector
1912  );
1913 
1914  plots.make<TEfficiency>(
1915  "EffVsNeutrinoEnergy",
1916  "Efficiency of triggering vs. neutrino energy"
1917  ";neutrino true energy [ GeV ]"
1918  ";trigger efficiency [ / 50 GeV ]",
1919  120, 0.0, 6.0 // 6 GeV is not that much for NuMI, but we should be ok
1920  );
1921 
1922  plots.make<TEfficiency>(
1923  "EffVsLeptonEnergy",
1924  "Efficiency of triggering vs. outgoing lepton energy"
1925  ";final state lepton true energy [ GeV ]"
1926  ";trigger efficiency [ / 50 GeV ]",
1927  120, 0.0, 6.0
1928  );
1929 
1930  plots.make<TH1F>(
1931  "TriggerTick",
1932  "Trigger time tick"
1933  ";optical time tick [ /" + util::to_string(triggerResolutionTicks) + " ]",
1934  (beam_gate_opt.second - beam_gate_opt.first) / triggerResolutionTicks,
1935  beam_gate_opt.first.value(), beam_gate_opt.second.value()
1936  );
1937 
1938 } // initializeEfficiencyPerTriggerPlots()
1939 
1940 
1941 //------------------------------------------------------------------------------
1943  (PlotSandbox& plots) const
1944 {
1945 
1946  // a variable binning for the required number of trigger primitives
1947  auto [ minimumPrimBinning, minimumPrimBinningLabels ]
1948  = util::ROOT::makeVariableBinningAndLabels(fMinimumPrimitives);
1949  assert(minimumPrimBinning.size() == minimumPrimBinningLabels.size() + 1U);
1950 
1951  {
1952  mf::LogTrace log(fLogCategory);
1953  log << "TriggerEfficiencyPlots (plots '"
1954  << plots.name() << "') variable binning including the "
1955  << fMinimumPrimitives.size() << " points {";
1956  for (auto value: fMinimumPrimitives) log << " " << value;
1957  log << " } => " << minimumPrimBinningLabels.size() << " bins: ";
1958  for (auto const& [ value, label ]
1959  : util::zip<1U>(minimumPrimBinning, minimumPrimBinningLabels))
1960  {
1961  log << " " << value << " (\"" << label << "\") =>";
1962  } // for
1963  log << " " << minimumPrimBinning.back();
1964  } // debug output block
1965 
1966  //
1967  // Selection-related plots
1968  //
1969  plots.make<TH1F>(
1970  "NeutrinoEnergy",
1971  "True Neutrino Energy"
1972  ";neutrino energy [GeV]"
1973  ";events",
1974  120, 0.0, 6.0 // GeV
1975  );
1976  plots.make<TH1F>(
1977  "EnergyInSpill",
1978  "Energy deposited during the beam gate opening"
1979  ";energy deposited in spill [ GeV ]"
1980  ";events [ / 50 MeV ]",
1981  120, 0.0, 6.0 // 6 GeV should be enough for a MIP crossing 20 m of detector
1982  );
1983  plots.make<TH1F>(
1984  "EnergyInSpillActive",
1985  "Energy deposited during the beam gate opening in active volume"
1986  ";energy deposited in active volume in spill [ GeV ]"
1987  ";events [ / 50 MeV ]",
1988  120, 0.0, 6.0 // 6 GeV should be enough for a MIP crossing 20 m of detector
1989  );
1990  plots.make<TH1I>(
1991  "InteractionType",
1992  "Interaction type"
1993  ";Interaction Type"
1994  ";events",
1995  200, 999.5, 1199.5
1996  );
1997  plots.make<TH1F>(
1998  "LeptonEnergy",
1999  "Energy of outgoing lepton"
2000  ";deposited energy [ GeV ]"
2001  ";events [ / 50 MeV ]",
2002  120, 0.0, 6.0
2003  );
2004  plots.make<TH2F>(
2005  "InteractionVertexYZ",
2006  "Vertex of triggered interaction"
2007  ";Z [ / 20 cm ]"
2008  ";Y [ / 5 cm ]",
2009  120, -1200., +1200.,
2010  100, -250., +250.
2011  );
2012 
2013 } // icarus::trigger::TriggerEfficiencyPlots::initializeEventPlots()
2014 
2015 
2016 //------------------------------------------------------------------------------
2018  (EventInfo_t const& eventInfo) const
2019 {
2020  if (fPlotOnlyActiveVolume
2021  && eventInfo.hasVertex() && !eventInfo.isInActiveVolume())
2022  {
2023  return false;
2024  }
2025 
2026  return true;
2027 } // icarus::trigger::TriggerEfficiencyPlots::shouldPlotEvent()
2028 
2029 
2030 //------------------------------------------------------------------------------
2031 std::vector<std::string>
2033  (EventInfo_t const& info, PlotCategories_t const& categories) const
2034 {
2035  std::vector<std::string> selected;
2036 
2037  for (auto const& category: categories)
2038  if (category(info)) selected.push_back(category);
2039 
2040  return selected;
2041 
2042 } // icarus::trigger::TriggerEfficiencyPlots::selectPlotCategories()
2043 
2044 
2045 //------------------------------------------------------------------------------
2047  (art::Event const& event, detinfo::DetectorClocksData const& clockData) const -> EventInfo_t
2048 {
2049 
2050  EventInfo_t info;
2051 
2052  //
2053  // generator information
2054  //
2055  for (art::InputTag const& inputTag: fGeneratorTags) {
2056 
2057  auto const& truthRecords
2058  = *(event.getValidHandle<std::vector<simb::MCTruth>>(inputTag));
2059 
2060  for (simb::MCTruth const& truth: truthRecords) {
2061 
2062  if (truth.NeutrinoSet()) {
2063  //
2064  // interaction flavor (nu_mu, nu_e)
2065  // interaction type (CC, NC)
2066  //
2067 
2068  simb::MCParticle const& nu = truth.GetNeutrino().Nu();
2069  info.SetNeutrinoPDG(nu.PdgCode());
2070  info.SetInteractionType(truth.GetNeutrino().InteractionType());
2071 
2072  info.SetNeutrinoEnergy(GeV{ nu.E() });
2073  info.SetLeptonEnergy(GeV{ truth.GetNeutrino().Lepton().E() });
2074  //info.SetNucleonEnergy(truth.GetNeutrino().HitNuc().E());
2075 
2076  switch (nu.PdgCode()) {
2077  case 14:
2078  case -14:
2079  info.SetNu_mu(true);
2080  break;
2081  case 12:
2082  case -12:
2083  info.SetNu_e(true);
2084  break;
2085  }
2086 
2087  switch (truth.GetNeutrino().CCNC()) {
2088  case simb::kCC: info.AddWeakChargedCurrentInteractions(); break;
2089  case simb::kNC: info.AddWeakNeutralCurrentInteractions(); break;
2090  default:
2091  mf::LogWarning(fLogCategory)
2092  << "Event " << event.id() << " has unexpected NC/CC flag ("
2093  << truth.GetNeutrino().CCNC() << ")";
2094  } // switch
2095 
2096  // we do not trust the vertex (`GvX()`) of the neutrino particle,
2097  // since GenieHelper does not translate the vertex
2098  // of some of the particles from GENIE to detector frame;
2099  // trajectory is always translated:
2100  geo::Point_t const vertex { nu.EndX(), nu.EndY(), nu.EndZ() };
2101  info.AddVertex(vertex);
2102 
2103  geo::TPCGeo const* tpc = pointInActiveTPC(vertex);
2104  if (tpc) info.SetInActiveVolume();
2105 
2106  } // if neutrino event
2107 
2108  } // for truth records
2109 
2110  } // for generators
2111 
2112  //
2113  // propagation in the detector
2114  //
2115  GeV totalEnergy { 0.0 }, inSpillEnergy { 0.0 };
2116  GeV activeEnergy { 0.0 }, inSpillActiveEnergy { 0.0 };
2117 
2118  detinfo::DetectorTimings const detTimings{clockData};
2119  auto const beam_gate_opt = std::make_pair(detTimings.toOpticalTick(detTimings.BeamGateTime()),
2120  detTimings.toOpticalTick(detTimings.BeamGateTime() + fBeamGateDuration));
2121  auto const beam_gate_sim = std::make_pair(detTimings.toSimulationTime(beam_gate_opt.first),
2122  detTimings.toSimulationTime(beam_gate_opt.second));
2123  for (art::InputTag const& edepTag: fEnergyDepositTags) {
2124 
2125  auto const& energyDeposits
2126  = *(event.getValidHandle<std::vector<sim::SimEnergyDeposit>>(edepTag));
2127  mf::LogTrace(fLogCategory)
2128  << "Event " << event.id() << " has " << energyDeposits.size()
2129  << " energy deposits recorded in '" << edepTag.encode() << "'";
2130 
2131  for (sim::SimEnergyDeposit const& edep: energyDeposits) {
2132 
2133  MeV const e { edep.Energy() }; // assuming it's stored in MeV
2134 
2135  detinfo::timescales::simulation_time const t { edep.Time() };
2136  bool const inSpill
2137  = (t >= beam_gate_sim.first) && (t <= beam_gate_sim.second);
2138 
2139  totalEnergy += e;
2140  if (inSpill) inSpillEnergy += e;
2141 
2142  if (pointInActiveTPC(edep.MidPoint())) {
2143  activeEnergy += e;
2144  if (inSpill) inSpillActiveEnergy += e;
2145  }
2146 
2147  } // for all energy deposits in the data product
2148 
2149  } // for all energy deposit tags
2150 
2151  info.SetDepositedEnergy(totalEnergy);
2152  info.SetDepositedEnergyInSpill(inSpillEnergy);
2153  info.SetDepositedEnergyInActiveVolume(activeEnergy);
2154  info.SetDepositedEnergyInActiveVolumeInSpill(inSpillActiveEnergy);
2155 
2156  mf::LogTrace(fLogCategory) << "Event " << event.id() << ": " << info;
2157 
2158  return info;
2159 } // icarus::trigger::TriggerEfficiencyPlots::extractEventInfo()
2160 
2161 
2162 //------------------------------------------------------------------------------
2164  (geo::Point_t const& point) const
2165 {
2166  return fGeom.PositionToTPCptr(point);
2167 } // icarus::trigger::TriggerEfficiencyPlots::pointInTPC()
2168 
2169 
2170 //------------------------------------------------------------------------------
2172  (geo::Point_t const& point) const
2173 {
2174  geo::TPCGeo const* tpc = pointInTPC(point);
2175  return
2176  (tpc && tpc->ActiveBoundingBox().ContainsPosition(point))? tpc: nullptr;
2177 } // icarus::trigger::TriggerEfficiencyPlots::pointInActiveTPC()
2178 
2179 
2180 //------------------------------------------------------------------------------
2182  art::Event const& event,
2183  icarus::trigger::ADCCounts_t const threshold,
2184  art::InputTag const& dataTag
2185 ) const -> std::vector<TriggerGateData_t>{
2186 
2187  //
2188  // simple count
2189  //
2190 
2191  std::vector<std::vector<TrackedTriggerGate_t>> const& cryoGates
2192  = ReadTriggerGates(event, dataTag);
2193 
2194  std::vector<TriggerGateData_t> cryoCombinedGate;
2195 
2196  for (auto const& gates: (cryoGates)) {
2197  mf::LogTrace(fLogCategory)
2198  << "Simulating trigger response with ADC threshold " << threshold
2199  << " from '" << dataTag.encode() << "' (" << gates.size() << " primitives)";
2200 
2201  if (gates.empty()) { // this is unexpected...
2202  mf::LogWarning(fLogCategory)
2203  << " No trigger primitive found for threshold " << threshold
2204  << " ('" << dataTag.encode() << "')";
2205  return {};
2206  } // if no gates
2207 
2208  TrackedTriggerGate_t combinedGate = icarus::trigger::sumGates(gates);
2209 
2210  cryoCombinedGate.push_back(std::move(combinedGate).gate()) ;
2211  }
2212 
2213  return cryoCombinedGate;
2214 } // icarus::trigger::TriggerEfficiencyPlots::combineTriggerPrimitives()
2215 
2216 
2217 //------------------------------------------------------------------------------
2218 // out-of-order definition, needs to be before `plotResponses()`
2219 template <typename TrigGateColl>
2221  (TrigGateColl const& gates)
2222 {
2223 
2224  // if `gates` is empty return a default-constructed gate of the contained type
2225  if (empty(gates)) return decltype(*begin(gates)){};
2226 
2227  auto iGate = cbegin(gates);
2228  auto const gend = cend(gates);
2229  auto maxGate = *iGate;
2230  while (++iGate != gend) maxGate.Max(*iGate);
2231 
2232  return maxGate;
2233 } // icarus::trigger::TriggerEfficiencyPlots::computeMaxGate()
2234 
2235 
2236 //------------------------------------------------------------------------------
2238  std::size_t iThr,
2239  icarus::trigger::ADCCounts_t const threshold,
2241  PlotSandboxRefs_t const& plotSets,
2242  EventInfo_t const& eventInfo,
2243  std::vector<TriggerGateData_t> const& primitiveCounts
2244 ) const {
2245 
2246  /*
2247  * This function plots according to the configured minimum number of trigger
2248  * primitives: for each requirement of minimum number of primitives, the
2249  * earliest time where that requirement is met is found, and that is
2250  * considered as the trigger time.
2251  *
2252  * The following quantities are drawn per ADC threshold and per plot category:
2253  *
2254  * * per minimum number of primitives:
2255  * * trigger efficiency (i.e. whether there was _any time_ a number of
2256  * primitives fulfilling the requirement)
2257  * * trigger time in ticks (distribution as 2D histogram)
2258  * * maximum number of trigger primitives present at any time
2259  * * deposited energy during beam spill
2260  *
2261  */
2262  using namespace std::string_literals;
2263 
2264  using ClockTick_t = TriggerGateData_t::ClockTick_t;
2265  using OpeningCount_t = TriggerGateData_t::OpeningCount_t;
2266 
2267  using PrimitiveCount_t = std::pair<ClockTick_t, OpeningCount_t>;
2268 
2269  // largest number of trigger primitives at any time
2270  assert(!primitiveCounts.empty());
2271  auto const primitiveCount = computeMaxGate(primitiveCounts);
2272 
2273  auto const maxPrimitiveTime { primitiveCount.findMaxOpen() };
2274  PrimitiveCount_t const maxPrimitives
2275  { maxPrimitiveTime, primitiveCount.openingCount(maxPrimitiveTime) };
2276 
2277  mf::LogTrace(fLogCategory)
2278  << "Max primitive count in " << threshold << ": "
2279  << maxPrimitives.second << " at tick " << maxPrimitives.first << " ("
2280  << detTimings.toElectronicsTime
2281  (detinfo::DetectorTimings::optical_tick{ maxPrimitives.first })
2282  << ")"
2283  ;
2284 
2285  /*
2286  * Fill all the histograms for all the minimum primitive requirements
2287  * (filling the information whether or not the trigger fired),
2288  * for all the qualifying categories.
2289  * Note that in this type of plots each event appears in all bins
2290  * (may be with "fired" or "not fired" on each bin)
2291  */
2292  PrimitiveCount_t lastMinCount { TriggerGateData_t::MinTick, 0 };
2293  bool fired = true; // the final trigger response (changes with requirement)
2294 
2295  class HistGetter { // helper, since this seems "popular"
2296  PlotSandbox const& plots;
2297 
2298  public:
2299  HistGetter(PlotSandbox const& plots): plots(plots) {}
2300 
2301  PlotSandbox const& box() const { return plots; }
2302 
2303  TH1& Hist(std::string const& name) const { return plots.demand<TH1>(name); }
2304  TH2& Hist2D(std::string const& name) const { return plots.demand<TH2>(name); }
2305  TEfficiency& Eff(std::string const& name) const
2306  { return plots.demand<TEfficiency>(name); }
2307 
2308  }; // class HistGetter
2309 
2310 
2311  for (auto [ iReq, minCount ]: util::enumerate(fMinimumPrimitives)) {
2312 
2313  if (fired && (lastMinCount.second < minCount)) {
2314  // if we haven't passed this minimum yet
2315  ClockTick_t const time = primitiveCount.findOpen(minCount);
2316  if (time == TriggerGateData_t::MaxTick) {
2317  mf::LogTrace(fLogCategory)
2318  << "Never got at " << minCount << " primitives or above.";
2319  fired = false;
2320  }
2321  else {
2322  lastMinCount = { time, primitiveCount.openingCount(time) };
2323  mf::LogTrace(fLogCategory)
2324  << "Reached " << minCount << " primitives or above ("
2325  << lastMinCount.second << ") at " << lastMinCount.first << ".";
2326  }
2327  } // if
2328 
2329  // at this point we know we have minCount or more trigger primitives,
2330  // and the time of this one is in lastMinCount.first (just in case)
2331 
2332  if (fResponseTree) fResponseTree->assignResponse(iThr, iReq, fired);
2333 
2334  std::string const minCountStr { "Req" + std::to_string(minCount) };
2335 
2336  // go through all the plot categories this event qualifies for
2337  for (icarus::trigger::PlotSandbox const& plotSet: plotSets) {
2338 
2339  HistGetter const get { plotSet };
2340 
2341  // simple efficiency
2342  get.Eff("Eff"s).Fill(fired, minCount);
2343 
2344  // trigger time (if any)
2345  if (fired) {
2346  get.Hist2D("TriggerTick"s).Fill(minCount, lastMinCount.first);
2347  if (minCount == 1) {
2348  for (auto point : eventInfo.fVertices) {
2349  get.Hist2D("InteractionVertexYZ"s).Fill(point.Z(), point.Y());
2350  }
2351  }
2352  }
2353 
2354  //
2355  // plotting specific to this trigger definition
2356  //
2357 
2358  HistGetter const getTrigEff { plotSet.demandSandbox(minCountStr) };
2359 
2360  // efficiency plots
2361  getTrigEff.Eff("EffVsEnergyInSpill").Fill
2362  (fired, double(eventInfo.DepositedEnergyInSpill()));
2363  getTrigEff.Eff("EffVsEnergyInSpillActive").Fill
2364  (fired, double(eventInfo.DepositedEnergyInSpillInActiveVolume()));
2365  getTrigEff.Eff("EffVsNeutrinoEnergy").Fill
2366  (fired, double(eventInfo.NeutrinoEnergy()));
2367  getTrigEff.Eff("EffVsLeptonEnergy").Fill
2368  (fired, double(eventInfo.LeptonEnergy()));
2369  if (fired) {
2370  getTrigEff.Hist("TriggerTick"s).Fill(lastMinCount.first);
2371  }
2372 
2373 
2374  //
2375  // plotting split for triggering/not triggering events
2376  //
2377 
2378  HistGetter const getTrig
2379  { getTrigEff.box().demandSandbox(fired? "triggering": "nontriggering") };
2380 
2381  getTrig.Hist("EnergyInSpill"s).Fill(double(eventInfo.DepositedEnergyInSpill()));
2382  getTrig.Hist("EnergyInSpillActive"s).Fill(double(eventInfo.DepositedEnergyInSpillInActiveVolume()));
2383  if (eventInfo.isNeutrino()) {
2384  getTrig.Hist("NeutrinoEnergy"s).Fill(double(eventInfo.NeutrinoEnergy()));
2385  getTrig.Hist("InteractionType"s).Fill(eventInfo.InteractionType());
2386  getTrig.Hist("LeptonEnergy"s).Fill(double(eventInfo.LeptonEnergy()));
2387  } // if neutrino event
2388  TH2& vertexHist = getTrig.Hist2D("InteractionVertexYZ"s);
2389  for (auto const& point: eventInfo.fVertices)
2390  vertexHist.Fill(point.Z(), point.Y());
2391 
2392 
2393  //
2394  // non triggered events
2395  //
2396  if (!fired && minCount == 1 ) { // I only am interested in events that aren't triggered when there is a low multiplicity requirement
2397  get.Hist("EnergyInSpill_NoTrig"s).Fill(double(eventInfo.DepositedEnergyInSpill()));
2398  get.Hist("NeutrinoEnergy_NoTrig"s).Fill(double(eventInfo.NeutrinoEnergy()));
2399  get.Hist("InteractionType_NoTrig"s).Fill(eventInfo.InteractionType());
2400  get.Hist("LeptonEnergy_NoTrig"s).Fill(double(eventInfo.LeptonEnergy()));
2401  //getHist("NucleonEnergy_NoTrig"s)->Fill(double(eventInfo.NucleonEnergy()));
2402  }
2403 
2404  } // for all qualifying plot categories
2405 
2406  } // for all thresholds
2407 
2408  /*
2409  * Now fill the plots independent of the trigger response:
2410  * the same value is plotted in all plot sets.
2411  *
2412  */
2413  for (PlotSandbox const& plotSet: plotSets) {
2414 
2415  HistGetter const get(plotSet);
2416 
2417  // number of primitives
2418  get.Hist("NPrimitives"s).Fill(maxPrimitives.second);
2419 
2420  // selection-related plots:
2421  get.Hist("EnergyInSpill"s).Fill(double(eventInfo.DepositedEnergyInSpill()));
2422  get.Hist("EnergyInSpillActive"s).Fill(double(eventInfo.DepositedEnergyInSpillInActiveVolume()));
2423 
2424  if (eventInfo.isNeutrino()) {
2425  get.Hist("NeutrinoEnergy"s).Fill(double(eventInfo.NeutrinoEnergy()));
2426  get.Hist("InteractionType"s).Fill(eventInfo.InteractionType());
2427  get.Hist("LeptonEnergy"s).Fill(double(eventInfo.LeptonEnergy()));
2428  } // if neutrino event
2429  TH2& vertexHist = get.Hist2D("InteractionVertexYZ"s);
2430  for (auto const& point: eventInfo.fVertices)
2431  vertexHist.Fill(point.Z(), point.Y());
2432 
2433  } // for
2434 
2435 } // icarus::trigger::TriggerEfficiencyPlots::plotResponses()
2436 
2437 
2438 //------------------------------------------------------------------------------
2440  (art::Event const& event, art::InputTag const& dataTag) const
2441  -> std::vector<std::vector<TrackedTriggerGate_t>>
2442 {
2443 
2444  std::vector<TrackedTriggerGate_t> allGates
2445  = icarus::trigger::ReadTriggerGates(event, dataTag);
2446 
2447  std::vector<geo::CryostatID> fChannelCryostat;
2448 
2449  fChannelCryostat.reserve(fGeom.NOpChannels());
2450  for (auto const opChannel: util::counter(fGeom.NOpChannels())) {
2451  if (!fGeom.IsValidOpChannel(opChannel)) continue;
2452  fChannelCryostat[opChannel] = fGeom.OpDetGeoFromOpChannel(opChannel).ID();
2453  } // for all channels
2454 
2455  std::vector<std::vector<TrackedTriggerGate_t>> gatesPerCryostat
2456  { fGeom.Ncryostats() };
2457  for (auto& gate: allGates) {
2458  assert(gate.gate().hasChannels());
2459  gatesPerCryostat[fChannelCryostat[gate.channels().front()].Cryostat]
2460  .push_back(std::move(gate));
2461  }
2462  return gatesPerCryostat;
2463 
2464 } // icarus::trigger::TriggerEfficiencyPlots::ReadTriggerGates()
2465 
2466 
2467 //------------------------------------------------------------------------------
2468 //--- EventIDTree
2469 //------------------------------------------------------------------------------
2471 
2472  this->tree().Branch("RunNo", &fRunNo);
2473  this->tree().Branch("SubRunNo", &fSubRunNo);
2474  this->tree().Branch("EventNo", &fEventNo);
2475 
2476 } // EventIDTree::EventIDTree()
2477 
2478 
2479 //------------------------------------------------------------------------------
2480 void EventIDTree::assignID(art::EventID const& id) {
2481 
2482  fRunNo = id.run();
2483  fSubRunNo = id.subRun();
2484  fEventNo = id.event();
2485 
2486 } // EventIDTree::assignID()
2487 
2488 
2489 //------------------------------------------------------------------------------
2490 //--- PlotInfoTree
2491 //------------------------------------------------------------------------------
2493 
2494  this->tree().Branch("InPlots", &fInPlots);
2495 
2496 } // PlotInfoTree::PlotInfoTree()
2497 
2498 
2499 //------------------------------------------------------------------------------
2500 void PlotInfoTree::assign(bool inPlots) {
2501 
2502  fInPlots = static_cast<Bool_t>(inPlots);
2503 
2504 } // PlotInfoTree::assignEvent()
2505 
2506 
2507 //------------------------------------------------------------------------------
2508 //--- EventInfoTree
2509 //------------------------------------------------------------------------------
2511 
2512  this->tree().Branch("CC", &fCC);
2513  this->tree().Branch("NC", &fNC);
2514  this->tree().Branch("IntType", &fIntType);
2515  this->tree().Branch("NuE", &fNuE);
2516  this->tree().Branch("OutLeptE", &fOutLeptE);
2517 
2518  this->tree().Branch("TotE", &fTotE);
2519  this->tree().Branch("SpillE", &fSpillE);
2520  this->tree().Branch("ActiveE", &fActiveE);
2521  this->tree().Branch("SpillActiveE", &fSpillActiveE);
2522  this->tree().Branch("InActive", &fInActive);
2523 
2524 } // EventInfoTree::EventInfoTree()
2525 
2526 
2527 //------------------------------------------------------------------------------
2529 
2532  fIntType = info.InteractionType();
2533  fNuE = static_cast<Double_t>(info.NeutrinoEnergy());
2534  fOutLeptE = static_cast<Double_t>(info.LeptonEnergy());
2535 
2536  fTotE = static_cast<Double_t>(info.DepositedEnergy());
2537  fSpillE = static_cast<Double_t>(info.DepositedEnergyInSpill());
2538  fActiveE = static_cast<Double_t>(info.DepositedEnergyInActiveVolume());
2539  fSpillActiveE = static_cast<Double_t>(info.DepositedEnergyInSpillInActiveVolume());
2540  fInActive = static_cast<Bool_t>(info.isInActiveVolume());
2541 
2542 } // EventInfoTree::assignEvent()
2543 
2544 
2545 //------------------------------------------------------------------------------
2546 //--- ResponseTree
2547 //------------------------------------------------------------------------------
2548 template <typename Thresholds, typename Requirements>
2550  (TTree& tree, Thresholds const& thresholds, Requirements const& minReqs)
2551  : TreeHolder(tree)
2552  , indices(std::size(thresholds), std::size(minReqs))
2553  , RespTxxRxx{ std::make_unique<bool[]>(indices.size()) }
2554 {
2555 
2556  for (auto [ iThr, threshold]: util::enumerate(thresholds)) {
2557  std::string const thrStr = util::to_string(raw::ADC_Count_t(threshold));
2558 
2559  for (auto [ iReq, req ]: util::enumerate(minReqs)) {
2560 
2561  std::string const branchName
2562  = "RespT" + thrStr + "R" + util::to_string(req);
2563 
2564  this->tree().Branch
2565  (branchName.c_str(), &(RespTxxRxx[indices(iThr, iReq)]));
2566 
2567  } // for all requirements
2568 
2569  } // for all thresholds
2570 
2571 } // ResponseTree::ResponseTree()
2572 
2573 
2574 //------------------------------------------------------------------------------
2576  (std::size_t iThr, std::size_t iReq, bool resp)
2577 {
2578  RespTxxRxx[indices(iThr, iReq)] = resp;
2579 } // ResponseTree::assignResponse()
2580 
2581 
2582 //------------------------------------------------------------------------------
2583 DEFINE_ART_MODULE(icarus::trigger::TriggerEfficiencyPlots)
2584 
2585 
2586 //------------------------------------------------------------------------------
process_name vertex
Definition: cheaterreco.fcl:51
BEGIN_PROLOG BeamGateDuration pmtthr physics producers trigtilewindowORS Thresholds
process_name opflashCryo1 flashfilter analyze
GeV DepositedEnergyInSpill() const
Returns the total energy deposited in the detector during beam [GeV].
Obj * make(std::string const &name, std::string const &title, Args &&...args)
Creates a new ROOT object with the specified name and title.
Definition: PlotSandbox.h:701
Base_t GateData_t
Type for gate data access.
void initializeEventPlots(PlotSandbox &plots) const
Initializes a single, trigger-independent plot set into plots.
double std(const std::vector< short > &wf, const double ped_mean, size_t start, size_t nsample)
Definition: UtilFunc.cxx:42
Produces plots about trigger simulation and trigger efficiency.
Definition of util::zip().
GeV DepositedEnergyInSpillInActiveVolume() const
Returns the energy deposited in the active volume during the beam [GeV].
Utilities related to art service access.
EventIDTree(TTree &tree)
Creates the required branches and assigns addresses to them.
std::string const & description() const
Returns the description of the category.
std::string fLogEventDetails
Steam where to print event info.
Utilities for the conversion of trigger gate data formats.
void SetInteractionType(int type)
nanoseconds fTriggerTimeResolution
Trigger resolution in time.
std::string TruthInteractionTypeName(int type)
decltype(auto) constexpr cend(T &&obj)
ADL-aware version of std::cend.
Definition: StdUtils.h:87
void AddWeakNeutralCurrentInteractions(unsigned int n=1U)
Marks this event as including n more weak neutral current interactions.
void assignEvent(EventInfo_t const &info)
Fills the information of the specified event.
std::pair< std::vector< double >, std::vector< std::string > > makeVariableBinningAndLabels(Coll const &centralPoints)
Returns a variable size binning for the points.
std::vector< unsigned int > fMinimumPrimitives
Minimum number of trigger primitives for a trigger to happen.
void SetNu_mu(bool numu)
Marks the neutrino flavor.
An object representing a time gate, with a start and and end.
Definition of util::get_elements() and util::get_const_elements().
Definition of util::enumerate().
A wrapper to trigger gate objects tracking the input of operations.
microseconds_as<> microseconds
Type of time interval stored in microseconds, in double precision.
Definition: spacetime.h:259
std::vector< std::vector< TrackedTriggerGate_t > > ReadTriggerGates(art::Event const &event, art::InputTag const &dataTag) const
Reads a set of input gates from the event.
GeV DepositedEnergyInActiveVolume() const
Returns the energy deposited in the active volume during the event [GeV].
void dump(std::ostream &out) const
Prints the content of the object into a stream.
std::vector< std::string > selectPlotCategories(EventInfo_t const &info, PlotCategories_t const &categories) const
Returns the names of the plot categories event qualifies for.
Class managing the serialization of event information in a simple ROOT tree.
Class managing the serialization of trigger responses in a simple ROOT tree.
ResponseTree(TTree &tree, Thresholds const &thresholds, Requirements const &minReqs)
Constructor: accommodates that many thresholds and requirements.
Geometry information for a single TPC.
Definition: TPCGeo.h:38
void assignResponse(std::size_t iThr, std::size_t iReq, bool resp)
Assigns the response for the specified trigger.
int NeutrinoPDG() const
Returns the neutrino PDG code.
bool isInActiveVolume() const
Returns whether there is an interaction within the active volume.
bool isWeakNeutralCurrent() const
Returns whether the event is generated as a neutrino NC interaction.
geo::BoxBoundedGeo const & ActiveBoundingBox() const
Returns the box of the active volume of this TPC.
Definition: TPCGeo.h:320
decltype(auto) const_values(Coll &&coll)
Range-for loop helper iterating across the constant values of the specified collection.
std::size_t size(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:561
void applyAxisLabels(TAxis *pAxis, std::vector< std::string > const &labels, int first=1)
Sets all the labels starting with the bin first (1 by default).
Definition: ROOTutils.h:175
void declareConsumes(ConsumesCollector &collector) const
Declares to the collector the data products that are going to be read.
TensorIndices class to flatten multi-dimension indices into linear.
std::string const & name() const
Returns the sandbox name.
Definition: PlotSandbox.h:196
art::TFileDirectory fOutputDir
ROOT directory where all the plots are written.
static auto computeMaxGate(TrigGateColl const &gates)
Returns a gate that is Max() of all the specified gates.
pure virtual base interface for detector clocks
EventInfoTree(TTree &tree)
Creates the required branches and assigns addresses to them.
unsigned int nWeakCurrentInteractions() const
Returns the number of weak current interactions in the event.
Assembles and returns trigger gates from serialized data.
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
std::unique_ptr< ResponseTree > fResponseTree
Handler of ROOT tree output.
timescale_traits< OpticalTimeCategory >::tick_interval_t optical_time_ticks
Interface to detinfo::DetectorClocks.
fDetProps &fDetProps fDetProps &fDetProps fLogCategory
PlotCategories_t const PlotCategories
List of event categories.
Access the description of detector geometry.
Information about the event.
void SetNeutrinoPDG(int NU)
Marks this event&#39;s neutrino type.
Charged-current interactions.
Definition: IPrediction.h:38
void initializeEfficiencyPerTriggerPlots(detinfo::DetectorClocksData const &clockData, PlotSandbox &plots) const
Initializes set of plots per complete trigger definition into plots.
triggergatedata_t & Mul(triggergatedata_t const &other)
Combines with a gate, keeping the product of openings of the two.
std::vector< art::InputTag > fGeneratorTags
Generator data product tags.
EventInfo_t extractEventInfo(art::Event const &event, detinfo::DetectorClocksData const &clockData) const
Extracts from event the relevant information on the physics event.
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:285
std::pair< detinfo::timescales::optical_tick, detinfo::timescales::optical_tick > const fBeamGateOpt
Beam gate start and stop tick in optical detector scale.
void initializePlots(detinfo::DetectorClocksData const &clockData, PlotCategories_t const &categories)
Initializes all the plot sets, one per threshold.
microseconds fBeamGateDuration
Duration of the gate during with global optical triggers are accepted.
std::vector< PlotSandbox > fThresholdPlots
All plots, one set per ADC threshold.
Converts a tensor element specification into a linear index.
Definition: TensorIndices.h:46
electronics_time toElectronicsTime(FromTime time) const
Converts a time point into electronics time scale.
geo::TPCGeo const * pointInActiveTPC(geo::Point_t const &point) const
TimeRange< simulation_time > const & asSimulationRange() const
Returns the gate as start/stop pair in simulation time scale.
std::vector< PlotCategory > PlotCategories_t
PlotCategory(std::string name, std::string descr={}, QualifyFunc_t &&test=[](EventInfo_t const &){return true;})
Constructor from category name and test function.
Definitions of geometry vector data types.
decltype(auto) constexpr to_string(T &&obj)
ADL-aware version of std::to_string.
timescale_traits< SimulationTimeCategory >::time_point_t simulation_time
A point in time on the simulation time scale.
A value measured in the specified unit.
Definition: quantities.h:566
megaelectronvolt_as<> megaelectronvolt
Type of energy stored in megaelectronvolt, in double precision.
Definition: energy.h:119
Simple type definitions for trigger algorithms.
bool shouldPlotEvent(EventInfo_t const &eventInfo) const
EventInfo_t()
Constructor. As if nobody noticed.
std::array< unsigned int, NInteractionTypes > fInteractions
A trigger gate data object for optical detector electronics.
constexpr std::array< std::size_t, geo::vect::dimension< Vector >)> indices()
Returns a sequence of indices valid for a vector of the specified type.
icarus::trigger::ReadoutTriggerGate< TriggerGateTick_t, TriggerGateTicks_t, raw::Channel_t > OpticalTriggerGateData_t
Type of trigger gate data serialized into art data products.
void initializePlotSet(detinfo::DetectorClocksData const &clockData, PlotSandbox &plots) const
Initializes full set of plots for (ADC threshold + category) into plots.
A bunch of diverse utilities and futilities related to ROOT.
void SetInActiveVolume(bool active=true)
Set whether the event has relevant activity in the active volume.
bool isNeutrino() const
Returns whether the event is generated as a neutrino interaction.
TTree const & tree() const
bool isWeakChargedCurrent() const
Returns whether the event is generated as a neutrino CC interaction.
std::unique_ptr< PlotInfoTree > fPlotTree
Handler of ROOT tree output.
bool hasVertex() const
Returns whether this type of event has a known vertex.
std::vector< PlotDef > plots
Definition: demo.h:54
unsigned int nWeakNeutralCurrentInteractions() const
Returns the number of weak neutral current interactions in the event.
Class managing the serialization of plot information in a simple ROOT tree.
void plotResponses(std::size_t iThr, icarus::trigger::ADCCounts_t const threshold, detinfo::DetectorTimings const &detTimings, PlotSandboxRefs_t const &plots, EventInfo_t const &info, std::vector< TriggerGateData_t > const &primitiveCounts) const
Adds all the responses (one per threshold) to the plots.
Utilities for the conversion of trigger gate data formats.
BEGIN_PROLOG vertical distance to the surface Name
std::string const fLogCategory
Message facility stream category for output.
void assignID(art::EventID const &id)
Fills the information of the specified event.
Description of geometry of one entire detector.
An interval (duration, length, distance) between two quantity points.
Definition: intervals.h:114
gigaelectronvolt_as<> gigaelectronvolt
Type of energy stored in gigaelectronvolt, in double precision.
Definition: energy.h:129
std::unique_ptr< EventInfoTree > fEventTree
Handler of ROOT tree output.
std::string const & name() const
Returns the name of the category.
std::map< icarus::trigger::ADCCounts_t, art::InputTag > fADCthresholds
ADC thresholds to read, and the input tag connected to their data.
void AddWeakChargedCurrentInteractions(unsigned int n=1U)
Marks this event as including n more weak charged current interactions.
auto sumGates(GateColl const &gates)
Sums all the gates in a collection.
unsigned int OpeningCount_t
Type of count of number of open channels.
nanoseconds_as<> nanoseconds
Type of time interval stored in nanoseconds, in double precision.
Definition: spacetime.h:270
geo::TPCGeo const * pointInTPC(geo::Point_t const &point) const
Returns the TPC point is within, nullptr if none.
Utilities to read interval and point quantity FHiCL configuration.
std::vector< art::InputTag > fEnergyDepositTags
Energy deposition data product tags.
timescale_traits< OpticalTimeCategory >::tick_t optical_tick
unsigned int nWeakChargedCurrentInteractions() const
Returns the number of weak charged current interactions in the event.
Dimensioned variables representing energy.
void AddVertex(geo::Point_t const &vertex)
Adds a point to the list of interaction vertices in the event.
bool fPlotOnlyActiveVolume
Plot only events in active volume.
Contains all timing reference information for the detector.
BeamGateStruct makeBeamGateStruct(detinfo::DetectorTimings const &detTimings, util::quantities::intervals::microseconds duration, util::quantities::intervals::microseconds delay=util::quantities::intervals::microseconds{0.0})
Creates a BeamGateStruct object of specified duration and start.
std::string to_string(WindowPattern const &pattern)
then echo File list $list not found else cat $list while read file do echo $file sed s
Definition: file_to_url.sh:60
Utility functions to print MC truth information.
std::ostream & operator<<(std::ostream &, Binner< T > const &)
Definition: Binner.h:218
void assignEvent(art::Event const &event)
Dimensioned variables representing space or time quantities.
bool test(EventInfo_t const &info) const
Returns whether the event belong to this category.
contains information for a single step in the detector simulation
virtual void endJob() override
Prints end-of-job summaries.
std::pair< detinfo::timescales::simulation_time, detinfo::timescales::simulation_time > const fBeamGateSim
Beam gate start and stop time in simulation scale.
A class exposing an upgraded interface of detinfo::DetectorClocksData.
Data types for detinfo::DetectorTimings.
Energy deposition in the active material.
Functions pulling in STL customization if available.
Class to create an object representing a beam gate.
virtual void analyze(art::Event const &event) override
Fills the plots. Also extracts the information to fill them with.
SandboxType & addSubSandbox(std::string const &name, std::string const &desc, Args &&...args)
Creates a new sandbox contained in this one.
Definition: PlotSandbox.h:723
do i e
Neutral-current interactions.
Definition: IPrediction.h:39
void SetDepositedEnergyInActiveVolumeInSpill(GeV e)
decltype(auto) constexpr cbegin(T &&obj)
ADL-aware version of std::cbegin.
Definition: StdUtils.h:82
void SetDepositedEnergy(GeV e)
Sets the total deposited energy of the event [GeV].
void SetDepositedEnergyInActiveVolume(GeV e)
Sets the total deposited energy of the event in active volume [GeV].
Logical multi-level gate.
std::function< bool(EventInfo_t const &)> QualifyFunc_t
Type of test function.
Simple utility to generate gates around beam time.
Definition: BeamGateMaker.h:49
std::vector< TriggerGateData_t > combineTriggerPrimitives(art::Event const &event, icarus::trigger::ADCCounts_t const threshold, art::InputTag const &dataTag) const
Computes the trigger response from primitives with the given threshold.
then echo fcl name
Class managing the serialization of event ID in a simple ROOT tree.
Obj & demand(std::string const &name) const
Fetches an object with the specified name to be modified.
Definition: PlotSandbox.h:667
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:72
temporary value
fDetProps &fDetProps fDetProps &fDetProps detTimings
GeV DepositedEnergy() const
Returns the total energy deposited in the detector during the event [GeV].
void assign(bool inPlots)
Fills the information of the specified event.
Definition of util::values() and util::const_values().
BEGIN_PROLOG hitmakerfive clustermakerfour pfparticlemakerthree showermakertwo END_PROLOG hitmakerfive clustermakerfour pfparticlemakerthree sequence::inline_paths sequence::inline_paths sequence::inline_paths showermakers test
std::vector< std::reference_wrapper< PlotSandbox const >> PlotSandboxRefs_t
List of references to plot sandboxes.
bool isNu_mu() const
Returns which neutrino flavor is present in an event.
PlotInfoTree(TTree &tree)
Creates the required branches and assigns addresses to them.
Tick ClockTick_t
Type of a point in time, measured in ticks.
void SetDepositedEnergyInSpill(GeV e)
Sets the energy of the event deposited during beam gate [GeV].
std::unique_ptr< EventIDTree > fIDTree
Handler of ROOT tree output.
std::vector< icarus::trigger::TrackedOpticalTriggerGate< OpDetInfo > > ReadTriggerGates(Event const &event, art::InputTag const &dataTag)
Assembles and returns trigger gates from serialized data.
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:184
std::vector< geo::Point_t > fVertices
Position of all vertices.
BEGIN_PROLOG SN nu
detinfo::timescales::optical_tick optical_tick
bool ContainsPosition(geo::Point_t const &point, double wiggle=1.0) const
Returns whether this volume contains the specified point.
A helper to manage ROOT objects in a art::TFileDirectory.
art framework interface to geometry description
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:97
fDetProps &fDetProps fDetProps &fDetProps consumesCollector())
A helper to manage ROOT objects with consistent naming.
Definition: PlotSandbox.h:95