File indexing completed on 2026-10-24 08:25:21
0001
0002
0003
0004 #include <DD4hep/Detector.h>
0005 #include <DD4hep/IDDescriptor.h>
0006 #include <DD4hep/Readout.h>
0007 #include <algorithms/geo.h>
0008 #include <catch2/catch_test_macros.hpp>
0009 #include <catch2/matchers/catch_matchers.hpp>
0010 #include <catch2/matchers/catch_matchers_floating_point.hpp>
0011 #include <edm4eic/CALOROC1BSample.h>
0012 #include <edm4eic/CalorimeterHitCollection.h>
0013 #include <edm4hep/MCParticleCollection.h>
0014 #include <edm4eic/MCRecoCalorimeterHitAssociationCollection.h>
0015 #include <edm4eic/MCRecoCalorimeterHitLinkCollection.h>
0016 #include <edm4eic/RawCALOROCHitCollection.h>
0017 #include <edm4hep/CaloHitContributionCollection.h>
0018 #include <edm4hep/RawCalorimeterHitCollection.h>
0019 #include <edm4hep/SimCalorimeterHitCollection.h>
0020 #include <edm4hep/Vector3f.h>
0021 #include <podio/detail/LinkCollectionIterator.h>
0022 #include <stdint.h>
0023 #include <algorithm>
0024 #include <deque>
0025 #include <filesystem>
0026 #include <fstream>
0027 #include <gsl/pointers>
0028 #include <initializer_list>
0029 #include <iostream>
0030 #include <memory>
0031 #include <string>
0032 #include <utility>
0033 #include <vector>
0034
0035 #include "algorithms/calorimetry/CalorimeterCALOROCReco.h"
0036 #include "algorithms/calorimetry/CalorimeterCALOROCRecoConfig.h"
0037
0038
0039
0040 static edm4eic::MutableRawCALOROCHit make_raw_hit(edm4eic::RawCALOROCHitCollection& coll,
0041 uint64_t cellID, int32_t timeStamp,
0042 int32_t samplePhase, uint16_t highGainADC,
0043 uint16_t lowGainADC, uint16_t timeOfArrival) {
0044 auto hit = coll.create();
0045 hit.setCellID(cellID);
0046 hit.setTimeStamp(timeStamp);
0047 hit.setSamplePhase(samplePhase);
0048 edm4eic::CALOROC1BSample sample;
0049 sample.highGainADC = highGainADC;
0050 sample.lowGainADC = lowGainADC;
0051 sample.timeOfArrival = timeOfArrival;
0052 hit.addToBSamples(sample);
0053 return hit;
0054 }
0055
0056
0057 static edm4hep::MutableSimCalorimeterHit
0058 make_npe_hit(edm4hep::SimCalorimeterHitCollection& hitColl,
0059 edm4hep::CaloHitContributionCollection& contribColl, uint64_t cellID,
0060 edm4hep::Vector3f position, std::initializer_list<float> contrib_energies,
0061 std::initializer_list<edm4hep::MCParticle> particles = {}) {
0062 REQUIRE((particles.size() == 0 || particles.size() == contrib_energies.size()));
0063
0064 float total = 0.f;
0065 for (auto e : contrib_energies)
0066 total += e;
0067 auto hit = hitColl.create();
0068 hit.setCellID(cellID);
0069 hit.setEnergy(total);
0070 hit.setPosition(position);
0071 auto particle = particles.begin();
0072 for (auto e : contrib_energies) {
0073 auto c = contribColl.create();
0074 c.setEnergy(e);
0075 c.setTime(0.f);
0076 c.setStepPosition({0.f, 0.f, 0.f});
0077 if (particle != particles.end())
0078 c.setParticle(*particle++);
0079 hit.addToContributions(c);
0080 }
0081 return hit;
0082 }
0083
0084
0085
0086 static std::string write_lut_file(const std::string& path) {
0087 std::ofstream f(path);
0088 f << "0 0 1.0\n";
0089 return path;
0090 }
0091
0092
0093 static eicrecon::CalorimeterCALOROCRecoConfig make_cfg(const std::string& lut_path) {
0094 eicrecon::CalorimeterCALOROCRecoConfig cfg;
0095 cfg.readout = "MockCALOROCHits";
0096 cfg.layerField = "layer";
0097 cfg.sectorField = "sector";
0098 cfg.localDetFields = {"system"};
0099 cfg.edep_to_npe_filename = lut_path;
0100 cfg.edep_to_npe_fields = {"layer", "sector"};
0101
0102
0103 cfg.attenuationParameters = {1.0, 1.0e9, 1.0e9};
0104
0105 cfg.attenuationReferencePositionNamePos = "MockCALOROC_RefPosP";
0106 cfg.attenuationReferencePositionNameNeg = "MockCALOROC_RefPosN";
0107
0108 cfg.slope = 1.0;
0109 cfg.intercept = 0.0;
0110
0111 cfg.highGainDR = 1000;
0112 cfg.gainRatio = 1.0;
0113
0114
0115 cfg.lightSpeedParameters = {1.0, 0.0};
0116
0117 cfg.timeWalkCor = false;
0118 cfg.timeWalkCorrectionParameters = {0.0, 1.0, 0.0, 1.0};
0119
0120
0121 cfg.useNpeHitPos = false;
0122 cfg.proxy_type = eicrecon::CalorimeterCALOROCRecoConfig::ProxyType::sum;
0123
0124 return cfg;
0125 }
0126
0127
0128
0129 static double expected_toa(int samplePhase, int timeStamp, int idx_toa, uint16_t toa_val) {
0130 return samplePhase * (25.0 / 1042.0) + (timeStamp + idx_toa) * 25.0 - toa_val * (25.0 / 1024.0);
0131 }
0132
0133
0134
0135
0136 TEST_CASE("CalorimeterCALOROCReco: ADC amplitude in low-signal regime",
0137 "[CalorimeterCALOROCReco][Amplitude]") {
0138
0139
0140 const std::string lut_path =
0141 std::filesystem::temp_directory_path() / "caloroc_test_lut_amplitude.txt";
0142 write_lut_file(lut_path);
0143
0144 auto cfg = make_cfg(lut_path);
0145
0146 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoAmplitude");
0147 algo.applyConfig(cfg);
0148 algo.init();
0149
0150 auto detector = algorithms::GeoSvc::instance().detector();
0151 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0152
0153
0154 uint64_t cellID =
0155 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0156
0157
0158
0159 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0160
0161
0162 make_raw_hit(adcP_coll, cellID, 0, 0,
0163 100, 50, 512);
0164 make_raw_hit(adcN_coll, cellID, 0, 0,
0165 200, 100, 512);
0166
0167 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0168 edm4hep::CaloHitContributionCollection contribs_coll;
0169 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {60.f});
0170 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {40.f});
0171
0172 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0173 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0174 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0175 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0176
0177 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0178 {recohits.get(), rawhits.get(), &links, &assocs});
0179
0180 const int64_t actual_amplitude = (*rawhits)[0].getAmplitude();
0181 std::cout << "--- ADC amplitude (no saturation) ---" << "\n";
0182 std::cout << "P-side highGainADC = 100, gainRatio = 1.0" << "\n";
0183 std::cout << "Expected amplitude (npeP = highGainADC / gainRatio) = 100" << "\n";
0184 std::cout << "Number of rawhits produced = " << rawhits->size() << "\n";
0185 std::cout << "Actual rawhit amplitude = " << actual_amplitude << "\n";
0186 REQUIRE(rawhits->size() == 1);
0187 REQUIRE(actual_amplitude == 100);
0188 }
0189
0190
0191
0192
0193 TEST_CASE("CalorimeterCALOROCReco: ADC amplitude switches to low-gain on saturation",
0194 "[CalorimeterCALOROCReco][Amplitude]") {
0195
0196 const std::string lut_path =
0197 std::filesystem::temp_directory_path() / "caloroc_test_lut_saturation.txt";
0198 write_lut_file(lut_path);
0199
0200 auto cfg = make_cfg(lut_path);
0201
0202 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoSaturation");
0203 algo.applyConfig(cfg);
0204 algo.init();
0205
0206 auto detector = algorithms::GeoSvc::instance().detector();
0207 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0208 uint64_t cellID =
0209 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0210
0211 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0212
0213
0214 make_raw_hit(adcP_coll, cellID, 0, 0, 1000, 75, 512);
0215
0216 make_raw_hit(adcN_coll, cellID, 0, 0, 1000, 150, 512);
0217
0218 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0219 edm4hep::CaloHitContributionCollection contribs_coll;
0220 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {60.f});
0221 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {40.f});
0222
0223 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0224 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0225 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0226 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0227
0228 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0229 {recohits.get(), rawhits.get(), &links, &assocs});
0230
0231 const int64_t actual_amplitude = (*rawhits)[0].getAmplitude();
0232 std::cout << "--- ADC amplitude (high-gain saturation) ---" << "\n";
0233 std::cout << "P-side highGainADC = 1000 >= highGainDR = 1000 -> switch to lowGainADC = 75"
0234 << "\n";
0235 std::cout << "Expected amplitude = 75" << "\n";
0236 std::cout << "Number of rawhits produced = " << rawhits->size() << "\n";
0237 std::cout << "Actual rawhit amplitude = " << actual_amplitude << "\n";
0238 REQUIRE(rawhits->size() == 1);
0239 REQUIRE(actual_amplitude == 75);
0240 }
0241
0242
0243
0244
0245 TEST_CASE("CalorimeterCALOROCReco: z-position from timing difference",
0246 "[CalorimeterCALOROCReco][ZPosition]") {
0247
0248 const std::string lut_path = std::filesystem::temp_directory_path() / "caloroc_test_lut_zpos.txt";
0249 write_lut_file(lut_path);
0250
0251 auto cfg = make_cfg(lut_path);
0252 cfg.timeWalkCor = false;
0253 cfg.useNpeHitPos = false;
0254
0255 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoZPos");
0256 algo.applyConfig(cfg);
0257 algo.init();
0258
0259 auto detector = algorithms::GeoSvc::instance().detector();
0260 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0261 uint64_t cellID =
0262 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0263
0264
0265
0266
0267
0268 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0269 make_raw_hit(adcP_coll, cellID, 0, 0, 100, 50, 512);
0270 make_raw_hit(adcN_coll, cellID, 1, 0, 200, 100, 512);
0271
0272 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0273 edm4hep::CaloHitContributionCollection contribs_coll;
0274 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {60.f});
0275 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {40.f});
0276
0277 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0278 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0279 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0280 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0281
0282 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0283 {recohits.get(), rawhits.get(), &links, &assocs});
0284
0285 const double tP_expected = expected_toa(0, 0, 0, 512);
0286 const double tN_expected = expected_toa(0, 1, 0, 512);
0287 const double dt_expected = tN_expected - tP_expected;
0288 const double zpos_expected = dt_expected * 1.0 + 0.0;
0289 const double actual_z = (*recohits)[0].getPosition().z;
0290 std::cout << "--- z-position from timing difference ---" << "\n";
0291 std::cout << "P-side: timeStamp=0, samplePhase=0, timeOfArrival=512" << "\n";
0292 std::cout << "N-side: timeStamp=1, samplePhase=0, timeOfArrival=512" << "\n";
0293 std::cout << "tP (expected) = " << tP_expected << " ns" << "\n";
0294 std::cout << "tN (expected) = " << tN_expected << " ns" << "\n";
0295 std::cout << "dt = tN - tP = " << dt_expected << " ns" << "\n";
0296 std::cout << "zpos = dt * lightSpeed[0] + lightSpeed[1] = " << zpos_expected << " mm" << "\n";
0297 std::cout << "Number of recohits = " << recohits->size() << "\n";
0298 std::cout << "Actual recohit z = " << actual_z << " mm" << "\n";
0299 REQUIRE(recohits->size() == 1);
0300 REQUIRE_THAT(actual_z, Catch::Matchers::WithinAbs(zpos_expected, 1e-4));
0301 }
0302
0303
0304
0305
0306 TEST_CASE("CalorimeterCALOROCReco: z-position read from NpeHit position",
0307 "[CalorimeterCALOROCReco][ZPosition]") {
0308
0309 const std::string lut_path =
0310 std::filesystem::temp_directory_path() / "caloroc_test_lut_npepos.txt";
0311 write_lut_file(lut_path);
0312
0313 auto cfg = make_cfg(lut_path);
0314 cfg.useNpeHitPos = true;
0315
0316 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoNpePos");
0317 algo.applyConfig(cfg);
0318 algo.init();
0319
0320 auto detector = algorithms::GeoSvc::instance().detector();
0321 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0322 uint64_t cellID =
0323 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0324
0325 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0326 make_raw_hit(adcP_coll, cellID, 0, 0, 100, 50, 512);
0327 make_raw_hit(adcN_coll, cellID, 1, 0, 200, 100, 512);
0328
0329 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0330 edm4hep::CaloHitContributionCollection contribs_coll;
0331
0332 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 42.f}, {60.f});
0333 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {40.f});
0334
0335 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0336 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0337 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0338 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0339
0340 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0341 {recohits.get(), rawhits.get(), &links, &assocs});
0342
0343 const double npe_z = 42.0;
0344 const double actual_z = (*recohits)[0].getPosition().z;
0345 std::cout << "--- z-position from NpeHit position (useNpeHitPos=true) ---" << "\n";
0346 std::cout << "NpeHitP position.z = " << npe_z << " mm (set explicitly)" << "\n";
0347 std::cout << "Expected recohit z = " << npe_z << " mm" << "\n";
0348 std::cout << "Number of recohits = " << recohits->size() << "\n";
0349 std::cout << "Actual recohit z = " << actual_z << " mm" << "\n";
0350 REQUIRE(recohits->size() == 1);
0351 REQUIRE_THAT(actual_z, Catch::Matchers::WithinAbs(npe_z, 1e-4));
0352 }
0353
0354
0355
0356
0357 TEST_CASE("CalorimeterCALOROCReco: time walk correction changes z from delta-t",
0358 "[CalorimeterCALOROCReco][TimeWalk]") {
0359
0360 const std::string lut_path =
0361 std::filesystem::temp_directory_path() / "caloroc_test_lut_timewalk.txt";
0362 write_lut_file(lut_path);
0363
0364 auto detector = algorithms::GeoSvc::instance().detector();
0365 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0366 uint64_t cellID =
0367 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0368
0369 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0370
0371
0372 make_raw_hit(adcP_coll, cellID, 0, 0, 100, 50, 512);
0373 make_raw_hit(adcN_coll, cellID, 1, 0, 200, 100, 512);
0374
0375 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0376 edm4hep::CaloHitContributionCollection contribs_coll;
0377 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {60.f});
0378 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {40.f});
0379
0380
0381
0382 auto cfg_nocor = make_cfg(lut_path);
0383 cfg_nocor.timeWalkCor = false;
0384 {
0385 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoNoTWC");
0386 algo.applyConfig(cfg_nocor);
0387 algo.init();
0388
0389 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0390 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0391 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0392 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0393 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0394 {recohits.get(), rawhits.get(), &links, &assocs});
0395
0396 const double tP_raw = expected_toa(0, 0, 0, 512);
0397 const double tN_raw = expected_toa(0, 1, 0, 512);
0398 const double dt_raw = tN_raw - tP_raw;
0399 const double zpos_raw = dt_raw * 1.0 + 0.0;
0400 const double actual_z = (*recohits)[0].getPosition().z;
0401 std::cout << "--- Time walk correction OFF ---" << "\n";
0402 std::cout << "tP (raw) = " << tP_raw << " ns" << "\n";
0403 std::cout << "tN (raw) = " << tN_raw << " ns" << "\n";
0404 std::cout << "dt (raw) = " << dt_raw << " ns" << "\n";
0405 std::cout << "Expected zpos = " << zpos_raw << " mm" << "\n";
0406 std::cout << "Actual recohit z = " << actual_z << " mm" << "\n";
0407 REQUIRE(recohits->size() == 1);
0408 REQUIRE_THAT(actual_z, Catch::Matchers::WithinAbs(25.0, 1e-4));
0409 }
0410
0411
0412
0413
0414
0415
0416 auto cfg_cor = make_cfg(lut_path);
0417 cfg_cor.timeWalkCor = true;
0418 cfg_cor.timeWalkCorrectionParameters = {0.0, 1.0, 0.0, 1.0};
0419 {
0420 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoWithTWC");
0421 algo.applyConfig(cfg_cor);
0422 algo.init();
0423
0424 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0425 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0426 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0427 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0428 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0429 {recohits.get(), rawhits.get(), &links, &assocs});
0430
0431 const double tP_raw = expected_toa(0, 0, 0, 512);
0432 const double tN_raw = expected_toa(0, 1, 0, 512);
0433 const double adcSumP = 100.0;
0434 const double adcSumN = 200.0;
0435
0436
0437
0438 const double tP_corrected = tP_raw - adcSumP;
0439 const double tN_corrected = tN_raw - adcSumN;
0440 const double dt_corrected = tN_corrected - tP_corrected;
0441 const double zpos_corrected = dt_corrected * 1.0 + 0.0;
0442 const double actual_z = (*recohits)[0].getPosition().z;
0443 std::cout << "--- Time walk correction ON ---" << "\n";
0444 std::cout << "timeWalkCorrectionParameters = {0, 1, 0, 1}" << "\n";
0445 std::cout << "tP (raw) = " << tP_raw << " ns" << "\n";
0446 std::cout << "tN (raw) = " << tN_raw << " ns" << "\n";
0447 std::cout << "ADC sum P-side = " << adcSumP << "\n";
0448 std::cout << "ADC sum N-side = " << adcSumN << "\n";
0449 std::cout << "tP (corrected) = " << tP_corrected << " ns" << "\n";
0450 std::cout << "tN (corrected) = " << tN_corrected << " ns" << "\n";
0451 std::cout << "dt (corrected) = " << dt_corrected << " ns" << "\n";
0452 std::cout << "Expected zpos = " << zpos_corrected << " mm" << "\n";
0453 std::cout << "Actual recohit z = " << actual_z << " mm" << "\n";
0454 REQUIRE(recohits->size() == 1);
0455 REQUIRE_THAT(actual_z, Catch::Matchers::WithinAbs(-75.0, 1e-4));
0456 }
0457 }
0458
0459
0460
0461
0462 TEST_CASE("CalorimeterCALOROCReco: MC truth link weights are energy-normalized",
0463 "[CalorimeterCALOROCReco][MCLinks]") {
0464
0465 const std::string lut_path =
0466 std::filesystem::temp_directory_path() / "caloroc_test_lut_links.txt";
0467 write_lut_file(lut_path);
0468
0469 auto cfg = make_cfg(lut_path);
0470
0471 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoLinks");
0472 algo.applyConfig(cfg);
0473 algo.init();
0474
0475 auto detector = algorithms::GeoSvc::instance().detector();
0476 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0477 uint64_t cellID =
0478 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0479
0480 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0481
0482 make_raw_hit(adcP_coll, cellID, 0, 0, 100, 50, 512);
0483 make_raw_hit(adcN_coll, cellID, 0, 0, 200, 100, 512);
0484
0485
0486
0487
0488 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0489 edm4hep::CaloHitContributionCollection contribs_coll;
0490 edm4hep::MCParticleCollection particles;
0491 auto particleP1 = particles.create();
0492 auto particleP2 = particles.create();
0493 auto particleN1 = particles.create();
0494 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {30.f, 70.f},
0495 {particleP1, particleP2});
0496 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {50.f}, {particleN1});
0497
0498 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0499 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0500 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0501 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0502
0503 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0504 {recohits.get(), rawhits.get(), &links, &assocs});
0505
0506 const double edepP1 = 30.0, edepP2 = 70.0, edepN1 = 50.0;
0507 const double total_edep = edepP1 + edepP2 + edepN1;
0508 std::vector<double> weights;
0509 for (const auto& lnk : links)
0510 weights.push_back(lnk.getWeight());
0511 std::sort(weights.begin(), weights.end());
0512 double total_weight = 0.;
0513 for (auto w : weights)
0514 total_weight += w;
0515
0516 std::cout << "--- MC truth link weights ---" << "\n";
0517 std::cout << "NpeHitP contributions: " << edepP1 << ", " << edepP2 << "\n";
0518 std::cout << "NpeHitN contributions: " << edepN1 << "\n";
0519 std::cout << "Total edep = " << total_edep << "\n";
0520 std::cout << "Expected weights (sorted): " << edepP1 / total_edep << ", " << edepN1 / total_edep
0521 << ", " << edepP2 / total_edep << "\n";
0522 std::cout << "Number of links = " << links.size() << "\n";
0523 std::cout << "Number of assocs = " << assocs.size() << "\n";
0524 std::cout << "Sum of weights = " << total_weight << "\n";
0525 if (weights.size() == 3) {
0526 std::cout << "Actual weights (sorted): " << weights[0] << ", " << weights[1] << ", "
0527 << weights[2] << "\n";
0528 }
0529
0530 REQUIRE(links.size() == 3);
0531 REQUIRE(assocs.size() == 3);
0532 REQUIRE_THAT(total_weight, Catch::Matchers::WithinAbs(1.0, 1e-5));
0533 REQUIRE_THAT(weights[0], Catch::Matchers::WithinAbs(30.0 / 150.0, 1e-5));
0534 REQUIRE_THAT(weights[1], Catch::Matchers::WithinAbs(50.0 / 150.0, 1e-5));
0535 REQUIRE_THAT(weights[2], Catch::Matchers::WithinAbs(70.0 / 150.0, 1e-5));
0536 }
0537
0538
0539
0540
0541 TEST_CASE("CalorimeterCALOROCReco: rawhit timestamp is average of P and N TOA",
0542 "[CalorimeterCALOROCReco][Timing]") {
0543
0544 const std::string lut_path = std::filesystem::temp_directory_path() / "caloroc_test_lut_time.txt";
0545 write_lut_file(lut_path);
0546
0547 auto cfg = make_cfg(lut_path);
0548 cfg.timeWalkCor = false;
0549
0550 eicrecon::CalorimeterCALOROCReco algo("CALOROCRecoTime");
0551 algo.applyConfig(cfg);
0552 algo.init();
0553
0554 auto detector = algorithms::GeoSvc::instance().detector();
0555 auto id_desc = detector->readout("MockCALOROCHits").idSpec();
0556 uint64_t cellID =
0557 id_desc.encode({{"system", 4}, {"layer", 0}, {"sector", 0}, {"x", 0}, {"y", 0}});
0558
0559
0560
0561
0562 edm4eic::RawCALOROCHitCollection adcP_coll, adcN_coll;
0563 make_raw_hit(adcP_coll, cellID, 0, 0, 100, 50, 512);
0564 make_raw_hit(adcN_coll, cellID, 1, 0, 200, 100, 512);
0565
0566 edm4hep::SimCalorimeterHitCollection npeP_coll, npeN_coll;
0567 edm4hep::CaloHitContributionCollection contribs_coll;
0568 make_npe_hit(npeP_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {60.f});
0569 make_npe_hit(npeN_coll, contribs_coll, cellID, {0.f, 0.f, 0.f}, {40.f});
0570
0571 auto recohits = std::make_unique<edm4eic::CalorimeterHitCollection>();
0572 auto rawhits = std::make_unique<edm4hep::RawCalorimeterHitCollection>();
0573 edm4eic::MCRecoCalorimeterHitLinkCollection links;
0574 edm4eic::MCRecoCalorimeterHitAssociationCollection assocs;
0575
0576 algo.process({&npeP_coll, &adcP_coll, &npeN_coll, &adcN_coll},
0577 {recohits.get(), rawhits.get(), &links, &assocs});
0578
0579 const double tP = expected_toa(0, 0, 0, 512);
0580 const double tN = expected_toa(0, 1, 0, 512);
0581 const double expected = 0.5 * (tP + tN);
0582 const double actual_time = static_cast<double>((*rawhits)[0].getTimeStamp());
0583 std::cout << "--- Rawhit timestamp = average of P and N TOA ---" << "\n";
0584 std::cout << "P-side: timeStamp=0, samplePhase=0, timeOfArrival=512" << "\n";
0585 std::cout << "N-side: timeStamp=1, samplePhase=0, timeOfArrival=512" << "\n";
0586 std::cout << "tP (formula) = " << tP << " ns" << "\n";
0587 std::cout << "tN (formula) = " << tN << " ns" << "\n";
0588 std::cout << "Expected timestamp = 0.5*(tP+tN) = " << expected << " ns" << "\n";
0589 std::cout << "Number of rawhits = " << rawhits->size() << "\n";
0590 std::cout << "Actual timestamp = " << actual_time << " ns" << "\n";
0591 REQUIRE(rawhits->size() == 1);
0592 REQUIRE_THAT(actual_time, Catch::Matchers::WithinAbs(expected, 1e-4));
0593 }