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/Configure/Python.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
158
166
168 double max_time_;
169
171 double min_time_;
172
178 double time_step_;
179
182
185
186 public:
187 EcalFakeSimHits(const std::string& name, framework::Process& p)
188 : framework::Producer(name, p) {}
190
191 void configure(framework::config::Parameters& ps) final override {
192 min_energy_ = ps.get<double>("min_energy", MIP_SI_ENERGY);
193 max_energy_ = ps.get<double>("max_energy", 10000. * MEV_PER_FC);
194 // 299mm is about 1ns from target and in middle of ECal,
195 // so the default arrival time of 1ns is an in-time hit
196 min_time_ = ps.get<double>("min_time", 1.);
197 max_time_ = ps.get<double>("max_time", 1.);
198 // should match the number of events the process runs
199 int n_steps = ps.get<int>("n_steps", NUM_TEST_SIM_HITS);
200
201 energy_step_ = (max_energy_ - min_energy_) / n_steps;
202 time_step_ = (max_time_ - min_time_) / n_steps;
203
206 }
207
208 void beforeNewRun(ldmx::RunHeader& header) final override {
209 header.setDetectorName("ldmx-det-v15-8gev");
210 }
211
212 void produce(framework::Event& event) final override {
213 // put in a single sim hit
214 std::vector<ldmx::SimCalorimeterHit> pretend_sim_hits(1);
215
216 ldmx::EcalID id(0, 0, 0);
217 pretend_sim_hits[0].setID(id.raw());
218 // incidentID, trackID, pdg ID, edep, time
219 pretend_sim_hits[0].addContrib(-1, -1, 0, curr_energy_, curr_time_);
220 // sim position in middle of ECal
221 pretend_sim_hits[0].setPosition(0., 0., 299.);
222
223 // needs to be correct collection name
224 REQUIRE_NOTHROW(event.add("EcalSimHits", pretend_sim_hits));
225
227 curr_time_ += time_step_;
228
229 return;
230 }
231}; // EcalFakeSimHits
232
245 private:
246 std::string ecal_simhits_passname_;
247 std::string ecal_digis_passname_;
248 std::string ecal_rechits_passname_;
249 std::string ecal_trig_digis_passname_;
250 bool check_trig_prim_;
251
252 public:
253 EcalCheckEnergyReconstruction(const std::string& name, framework::Process& p)
254 : framework::Analyzer(name, p) {}
256
257 void configure(framework::config::Parameters& parameters) final override {
258 // the trigger primitives are an in-time, single-sample estimate,
259 // so they are not expected to see out-of-time hits
260 check_trig_prim_ = parameters.get<bool>("check_trig_prim", true);
261 ecal_simhits_passname_ =
262 parameters.get<std::string>("ecal_simhits_passname", "");
263 ecal_digis_passname_ =
264 parameters.get<std::string>("ecal_digis_passname", "");
265 ecal_rechits_passname_ =
266 parameters.get<std::string>("ecal_rechits_passname", "");
267 ecal_trig_digis_passname_ =
268 parameters.get<std::string>("ecal_trig_digis_passname", "");
269 }
270
271 void onProcessStart() final override {
273 ntuple_.create("EcalDigiTest");
274 ntuple_.addVar<float>("EcalDigiTest", "SimEnergy");
275 ntuple_.addVar<float>("EcalDigiTest", "RecEnergy");
276 ntuple_.addVar<float>("EcalDigiTest", "TrigPrimEnergy");
277
278 ntuple_.addVar<int>("EcalDigiTest", "DaqDigi");
279 ntuple_.addVar<int>("EcalDigiTest", "DaqDigiIsADC");
280 ntuple_.addVar<int>("EcalDigiTest", "DaqDigiADC");
281 ntuple_.addVar<int>("EcalDigiTest", "DaqDigiTOT");
282 ntuple_.addVar<int>("EcalDigiTest", "TrigPrimDigiEncoded");
283 ntuple_.addVar<int>("EcalDigiTest", "TrigPrimDigiLinear");
284 }
285
286 void analyze(const framework::Event& event) final override {
287 const auto sim_hits = event.getCollection<ldmx::SimCalorimeterHit>(
288 "EcalSimHits", ecal_simhits_passname_);
289
290 REQUIRE(sim_hits.size() == 1);
291
292 float truth_energy = sim_hits.at(0).getEdep();
293 ntuple_.setVar<float>("SimEnergy", truth_energy);
294
295 const auto daq_digis{event.getObject<ldmx::HgcrocDigiCollection>(
296 "EcalDigis", ecal_digis_passname_)};
297
298 if (daq_digis.getNumDigis() == 1) {
299 auto daq_digi = daq_digis.getDigi(0);
300 ntuple_.setVar<int>("DaqDigi", daq_digi.soi().raw());
301 bool is_in_adc_mode = daq_digi.isADC();
302 ntuple_.setVar<int>("DaqDigiIsADC", is_in_adc_mode);
303 ntuple_.setVar<int>("DaqDigiADC", daq_digi.soi().adcT());
304 ntuple_.setVar<int>("DaqDigiTOT", daq_digi.tot());
305
306 // arrival time of the hit at the chip, helpful when the failure
307 // depends on where in the readout window the pulse lands
308 INFO("sim hit arrival time = " << sim_hits.at(0).getContrib(0).time_
309 << " ns");
310 INFO("digi is " << (is_in_adc_mode ? "ADC" : "TOT") << " mode");
311
312 const auto rec_hits = event.getCollection<ldmx::EcalHit>(
313 "EcalRecHits", ecal_rechits_passname_);
314 CHECK(rec_hits.size() == 1);
315 // a hit that was read out but not reconstructed has nothing left
316 // for us to check, keep going so that all events are checked
317 if (rec_hits.size() != 1) return;
318
319 auto hit = rec_hits.at(0);
320 ldmx::EcalID id(hit.getID());
321 CHECK_FALSE(hit.isNoise());
322 CHECK(id.raw() == sim_hits.at(0).getID());
323
324 double daq_energy{hit.getAmplitude()};
325 CHECK_THAT(daq_energy, IsCloseEnough(truth_energy, MAX_ENERGY_ERROR_DAQ,
326 MAX_ENERGY_PERCENT_ERROR_DAQ));
327 ntuple_.setVar<float>("RecEnergy", hit.getAmplitude());
328
329 if (not check_trig_prim_) return;
330
331 const auto trig_digis{event.getObject<ldmx::HgcrocTrigDigiCollection>(
332 "ecalTrigDigis", ecal_trig_digis_passname_)};
333 CHECK(trig_digis.size() == 1);
334
335 auto trig_digi = trig_digis.at(0);
336 float tp_energy =
337 8 * trig_digi.linearPrimitive() * 320. / 1024 * MEV_PER_FC;
338
339 CHECK_THAT(tp_energy, IsCloseEnough(truth_energy, MAX_ENERGY_ERROR_TP,
340 MAX_ENERGY_PERCENT_ERROR_TP));
341 ntuple_.setVar<float>("TrigPrimEnergy", tp_energy);
342 ntuple_.setVar<int>("TrigPrimDigiEncoded", trig_digi.getPrimitive());
343 ntuple_.setVar<int>("TrigPrimDigiLinear", trig_digi.linearPrimitive());
344 }
345
346 return;
347 }
348}; // EcalCheckEnergyReconstruction
349
350} // namespace test
351} // namespace ecal
352
355
356
369TEST_CASE("Ecal Digi Pipeline test", "[Ecal][functionality]") {
370 const std::string config_file{"ecal_digi_pipeline_test_config.py"};
371 char** args{nullptr};
372
373 auto cfg{framework::config::run("ldmxcfg.Process.last_process", config_file,
374 args, 0)};
375 auto p{std::make_unique<framework::Process>(cfg)};
376 p->run();
377}
378
390TEST_CASE("Ecal Digi Pipeline out-of-time test", "[Ecal][functionality]") {
391 const std::string config_file{"ecal_digi_out_of_time_test_config.py"};
392 char** args{nullptr};
393
394 auto cfg{framework::config::run("ldmxcfg.Process.last_process", config_file,
395 args, 0)};
396 auto p{std::make_unique<framework::Process>(cfg)};
397 p->run();
398}
Class that defines an ECal detector ID with a cell number.
Base classes for all user event processing components to extend.
#define DECLARE_ANALYZER(CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
#define DECLARE_PRODUCER(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 configure(framework::config::Parameters &parameters) final override
Callback for the EventProcessor to configure itself from the given set of parameters.
void analyze(const framework::Event &event) final override
Process the event and make histograms or summaries.
double curr_time_
current arrival time of the sim hit we are on
double max_time_
last arrival time of the sim hit to make [ns]
void beforeNewRun(ldmx::RunHeader &header) final override
Callback for Producers to add parameters to the run header before conditions are initialized.
void produce(framework::Event &event) final override
Process the event and put new data products into it.
double curr_energy_
current energy of the sim hit we are on
void configure(framework::config::Parameters &ps) final override
Callback for the EventProcessor to configure itself from the given set of parameters.
double max_energy_
Maximum energy to make a simulated hit for [MeV].
double min_time_
first arrival time of the sim hit to make [ns]
double min_energy_
Minimum energy to make a sim hit for [MeV] Needs to be above readout threshold (after internal EcalDi...
double energy_step_
The step between energies (times) is calculated depending on the min, max energy (time) and the numbe...
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.
virtual std::string describe() const override
Describes matcher for printing to terminal.
bool match(const double &daq_energy) const override
Performs the test for this matcher.
const double MAX_ABSOLUTE_DIFF
maximum absolute energy difference [MeV]
const double MAX_RELATIVE_DIFF
maximum relative energy difference
double truth_
correct (sim-level) energy [MeV]
Base class for a module which does not produce a data product.
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:42
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:37
Base class for a module which produces a data product.
Producer(const std::string &name, Process &process)
Class constructor.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
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:67
Stores simulated calorimeter hit information.
Parameters run(const std::string &root_object, const std::string &pythonScript, char *args[], int nargs)
run the python script and extract the parameters
Definition Python.cxx:302
All classes in the ldmx-sw project use this namespace.