LDMX Software
EcalDigiPipelineTest.cxx
1#include <catch2/catch_approx.hpp>
2#include <catch2/catch_test_macros.hpp>
3#include <catch2/matchers/catch_matchers.hpp>
4
5#include "DetDescr/EcalID.h" //creating unique cell IDs
6#include "Ecal/Event/EcalHit.h"
7#include "Framework/ConfigurePython.h"
9#include "Framework/Process.h"
11#include "Recon/Event/HgcrocTrigDigi.h"
13
14using Catch::Approx;
15
16namespace ecal {
17namespace test {
18
23static const double MIP_SI_ENERGY = 0.130;
24
32static const double MeV_per_fC = MIP_SI_ENERGY / (37 * 0.1602);
33
43static const double MAX_ENERGY_PERCENT_ERROR_DAQ = 0.025;
44
54static const double MAX_ENERGY_PERCENT_ERROR_TP = 0.15;
55
65static const double MAX_ENERGY_ERROR_DAQ = MIP_SI_ENERGY / 2;
66
76static const double MAX_ENERGY_ERROR_TP = 2 * MIP_SI_ENERGY;
77
88static const int NUM_TEST_SIM_HITS = 2000;
89
95class isCloseEnough : public Catch::Matchers::MatcherBase<double> {
96 private:
98 double truth_;
99
101 const double max_absolute_diff_;
102
104 const double max_relative_diff_;
105
106 public:
112 isCloseEnough(double const &truth, double const &abs_diff,
113 double const &rel_diff)
114 : truth_{truth},
115 max_absolute_diff_{abs_diff},
116 max_relative_diff_{rel_diff} {}
117
125 bool match(const double &daq_energy) const override {
126 return (daq_energy == Approx(truth_).epsilon(max_relative_diff_) or
127 daq_energy == Approx(truth_).margin(max_absolute_diff_));
128 }
129
133 virtual std::string describe() const override {
134 std::ostringstream ss;
135 ss << "is within an absolute difference of " << max_absolute_diff_
136 << "MeV OR a relative difference of " << max_relative_diff_ << " with "
137 << truth_ << " MeV.";
138 return ss.str();
139 }
140};
141
157 const double maxEnergy_ = 10000. * MeV_per_fC;
158
165 const double minEnergy_ = MIP_SI_ENERGY;
166
172 const double energyStep_ = (maxEnergy_ - minEnergy_) / NUM_TEST_SIM_HITS;
173
176
177 public:
178 EcalFakeSimHits(const std::string &name, framework::Process &p)
179 : framework::Producer(name, p) {}
181
182 void beforeNewRun(ldmx::RunHeader &header) final override {
183 header.setDetectorName("ldmx-det-v14-8gev");
184 }
185
186 void produce(framework::Event &event) final override {
187 // put in a single sim hit
188 std::vector<ldmx::SimCalorimeterHit> pretendSimHits(1);
189
190 ldmx::EcalID id(0, 0, 0);
191 pretendSimHits[0].setID(id.raw());
192 // incidentID, trackID, pdg ID, edep, time - 299mm is about 1ns from target
193 // and in middle of ECal
194 pretendSimHits[0].addContrib(-1, -1, 0, currEnergy_, 1.);
195 // sim position in middle of ECal
196 pretendSimHits[0].setPosition(0., 0., 299.);
197
198 // needs to be correct collection name
199 REQUIRE_NOTHROW(event.add("EcalSimHits", pretendSimHits));
200
202
203 return;
204 }
205}; // EcalFakeSimHits
206
219 public:
220 EcalCheckEnergyReconstruction(const std::string &name, framework::Process &p)
221 : framework::Analyzer(name, p) {}
223
224 void onProcessStart() final override {
226 ntuple_.create("EcalDigiTest");
227 ntuple_.addVar<float>("EcalDigiTest", "SimEnergy");
228 ntuple_.addVar<float>("EcalDigiTest", "RecEnergy");
229 ntuple_.addVar<float>("EcalDigiTest", "TrigPrimEnergy");
230
231 ntuple_.addVar<int>("EcalDigiTest", "DaqDigi");
232 ntuple_.addVar<int>("EcalDigiTest", "DaqDigiIsADC");
233 ntuple_.addVar<int>("EcalDigiTest", "DaqDigiADC");
234 ntuple_.addVar<int>("EcalDigiTest", "DaqDigiTOT");
235 ntuple_.addVar<int>("EcalDigiTest", "TrigPrimDigiEncoded");
236 ntuple_.addVar<int>("EcalDigiTest", "TrigPrimDigiLinear");
237 }
238
239 void analyze(const framework::Event &event) final override {
240 const auto simHits =
241 event.getCollection<ldmx::SimCalorimeterHit>("EcalSimHits");
242
243 REQUIRE(simHits.size() == 1);
244
245 float truth_energy = simHits.at(0).getEdep();
246 ntuple_.setVar<float>("SimEnergy", truth_energy);
247
248 const auto daqDigis{
249 event.getObject<ldmx::HgcrocDigiCollection>("EcalDigis")};
250
251 CHECK(daqDigis.getNumDigis() == 1);
252 auto daqDigi = daqDigis.getDigi(0);
253 ntuple_.setVar<int>("DaqDigi", daqDigi.soi().raw());
254 bool is_in_adc_mode = daqDigi.isADC();
255 ntuple_.setVar<int>("DaqDigiIsADC", is_in_adc_mode);
256 ntuple_.setVar<int>("DaqDigiADC", daqDigi.soi().adc_t());
257 ntuple_.setVar<int>("DaqDigiTOT", daqDigi.tot());
258
259 const auto recHits = event.getCollection<ldmx::EcalHit>("EcalRecHits");
260 CHECK(recHits.size() == 1);
261
262 auto hit = recHits.at(0);
263 ldmx::EcalID id(hit.getID());
264 CHECK_FALSE(hit.isNoise());
265 CHECK(id.raw() == simHits.at(0).getID());
266
267 double daq_energy{hit.getAmplitude()};
268 CHECK_THAT(daq_energy, isCloseEnough(truth_energy, MAX_ENERGY_ERROR_DAQ,
269 MAX_ENERGY_PERCENT_ERROR_DAQ));
270 ntuple_.setVar<float>("RecEnergy", hit.getAmplitude());
271
272 const auto trigDigis{
273 event.getObject<ldmx::HgcrocTrigDigiCollection>("ecalTrigDigis")};
274 CHECK(trigDigis.size() == 1);
275
276 auto trigDigi = trigDigis.at(0);
277 float tp_energy = 8 * trigDigi.linearPrimitive() * 320. / 1024 * MeV_per_fC;
278
279 CHECK_THAT(tp_energy, isCloseEnough(truth_energy, MAX_ENERGY_ERROR_TP,
280 MAX_ENERGY_PERCENT_ERROR_TP));
281 ntuple_.setVar<float>("TrigPrimEnergy", tp_energy);
282 ntuple_.setVar<int>("TrigPrimDigiEncoded", trigDigi.getPrimitive());
283 ntuple_.setVar<int>("TrigPrimDigiLinear", trigDigi.linearPrimitive());
284
285 return;
286 }
287}; // EcalCheckEnergyReconstruction
288
289} // namespace test
290} // namespace ecal
291
292DECLARE_PRODUCER_NS(ecal::test, EcalFakeSimHits)
293DECLARE_ANALYZER_NS(ecal::test, EcalCheckEnergyReconstruction)
294
295
308TEST_CASE("Ecal Digi Pipeline test", "[Ecal][functionality]") {
309 const std::string config_file{"ecal_digi_pipeline_test_config.py"};
310
311 char **args{nullptr};
313
314 framework::ConfigurePython cfg(config_file, args, 0);
315 REQUIRE_NOTHROW(p = cfg.makeProcess());
316 p->run();
317}
Class that defines an ECal detector ID with a cell number.
Base classes for all user event processing components to extend.
#define DECLARE_ANALYZER_NS(NS, CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
#define DECLARE_PRODUCER_NS(NS, CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Class that represents a digitized hit in a calorimeter cell readout by an HGCROC.
Class which represents the process under execution.
Class which stores simulated calorimeter hit information.
void onProcessStart() final override
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
void analyze(const framework::Event &event) final override
Process the event and make histograms or summaries.
const double energyStep_
The step between energies is calculated depending on the min, max energy and the total number of sim ...
const double minEnergy_
Minimum energy to make a sim hit for [MeV] Needs to be above readout threshold (after internal EcalDi...
const double maxEnergy_
Maximum energy to make a simulated hit for [MeV].
void beforeNewRun(ldmx::RunHeader &header) final override
Handle allowing producers to modify run headers before the run begins.
void produce(framework::Event &event) final override
Process the event and put new data products into it.
double currEnergy_
current energy of the sim hit we are on
Our custom energy checker which makes sure that the input energy is "close enough" to the truth energ...
isCloseEnough(double const &truth, double const &abs_diff, double const &rel_diff)
Constructor.
const double max_relative_diff_
maximum relative energy difference
double truth_
correct (sim-level) energy [MeV]
const double max_absolute_diff_
maximum absolute energy difference [MeV]
bool match(const double &daq_energy) const override
Performs the test for this matcher.
virtual std::string describe() const override
Describes matcher for printing to terminal.
Base class for a module which does not produce a data product.
Utility class which reads/executes a python script and creates a Process object based on the input.
NtupleManager & ntuple_
Manager for any ntuples.
TDirectory * getHistoDirectory()
Access/create a directory in the histogram file for this event processor to create histograms and ana...
Implements an event buffer system for storing event data.
Definition Event.h:41
void addVar(const std::string &tname, const std::string &vname)
Add a variable of type VarType to the ROOT tree with name 'tname'.
void create(const std::string &tname)
Create a ROOT tree to hold the ntuple variables (ROOT leaves).
void setVar(const std::string &vname, const T &value)
Set the value of the variable named 'vname'.
Class which represents the process under execution.
Definition Process.h:36
Base class for a module which produces a data product.
Producer(const std::string &name, Process &process)
Class constructor.
Stores reconstructed hit information from the ECAL.
Definition EcalHit.h:19
Extension of DetectorID providing access to ECal layers and cell numbers in a hex grid.
Definition EcalID.h:20
Represents a collection of the digi hits readout by an HGCROC.
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:54
Stores simulated calorimeter hit information.
All classes in the ldmx-sw project use this namespace.
Definition PerfDict.cxx:45
std::unique_ptr< Process > ProcessHandle
A handle to the current process Used to pass a process from ConfigurePython to fire....
Definition Process.h:248