ATLAS Offline Software
Loading...
Searching...
No Matches
eFexTowerBuilder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3*/
4
5//***************************************************************************
6// eFexTowerBuilder - description:
7// Builds an eFexTowerContainer from a CaloCellContainer (for supercells) and TriggerTowerContainer (for ppm tile towers)
8// -------------------
9// begin : 06 12 2022
10// email : will@cern.ch
11//***************************************************************************/
12
13
14// MyPackage includes
15#include "eFexTowerBuilder.h"
16
18
21
22#include "TFile.h"
23#include "TTree.h"
25
26namespace LVL1 {
27
28eFexTowerBuilder::eFexTowerBuilder( const std::string& name, ISvcLocator* pSvcLocator ) : AthReentrantAlgorithm( name, pSvcLocator ){
29
30
31}
32
34 ATH_MSG_INFO ("Initializing " << name() << "...");
35
36 CHECK( m_ddmKey.initialize(true) );
37 CHECK( m_ttKey.initialize(true) );
38 CHECK( m_scellKey.initialize(true) );
39 CHECK( m_outKey.initialize(true) );
40 CHECK( m_eiKey.initialize(true) );
42
43 // initialise any tower decorations
45
46 if(!m_mappingFile.empty()) {
47 if (auto fileName = PathResolverFindCalibFile(m_mappingFile); !fileName.empty()) {
48 std::unique_ptr <TFile> f(TFile::Open(fileName.c_str()));
49 if (f) {
50 TTree *t = f->Get<TTree>("mapping");
51 if (t) {
52 unsigned long long scid = 0;
53 std::pair<int, int> coord = {0, 0};
54 std::pair<int, int> slot;
55 t->SetBranchAddress("scid", &scid);
56 t->SetBranchAddress("etaIndex", &coord.first);
57 t->SetBranchAddress("phiIndex", &coord.second);
58 t->SetBranchAddress("slot1", &slot.first);
59 t->SetBranchAddress("slot2", &slot.second);
60 for (Long64_t i = 0; i < t->GetEntries(); i++) {
61 t->GetEntry(i);
62 m_scMap[scid] = std::make_pair(coord, slot);
63 }
64 }
65 }
66 if (m_scMap.empty()) {
67 ATH_MSG_WARNING("Failed to load sc -> eFexTower map from " << fileName);
68 } else {
69 ATH_MSG_INFO("Loaded sc -> eFexTower map from " << fileName);
70 }
71 }
72 }
73
74 return StatusCode::SUCCESS;
75}
76
77StatusCode eFexTowerBuilder::fillTowers(const EventContext& ctx) const {
78
79
82 if(!tTowers.isValid()){
83 ATH_MSG_FATAL("Could not retrieve collection " << m_ttKey.key() );
84 return StatusCode::FAILURE;
85 }
86 SG::ReadHandle<CaloCellContainer> scells(m_scellKey,ctx); // n.b. 34048 is a full complement of scells
87 if(!scells.isValid()){
88 ATH_MSG_FATAL("Could not retrieve collection " << m_scellKey.key() );
89 return StatusCode::FAILURE;
90 }
91
93 if(!ei.isValid()) {
94 ATH_MSG_FATAL("Cannot retrieve eventinfo");
95 return StatusCode::FAILURE;
96 }
97 bool isMC = ei->eventType(xAOD::EventInfo::IS_SIMULATION); // currently only used to decide if should set a saturation code or not
98
99 std::map<std::pair<int,int>, std::pair<std::array<int,11>, std::vector<float>>> towers;
100
101 constexpr int INVALID_VALUE = -99999; // use this value to indicate invalid
102 constexpr int MASKED_VALUE = std::numeric_limits<int>::max(); // use this value to indicate masked
103 constexpr int SATURATED_VALUE = std::numeric_limits<int>::max()-1; // use this value to indicate saturation
104 constexpr int MISSING_VALUE = -99998; // use this value to indicate missing supercell
105
106
107
108 for (auto digi: *scells) {
109 const auto itr = m_scMap.find(digi->ID().get_compact());
110 if (itr == m_scMap.end()) { continue; } // not in map so not mapping to a tower
111 int val = std::round(digi->energy()/(12.5*std::cosh(digi->eta()))); // 12.5 is b.c. energy is in units of 12.5MeV per count
112 // note: a val of < -99998 is what is produced if efex was sent an invalid code of 1022 (see LArRawtoSuperCell)
113 auto elem = ddm->get_element(digi->ID());
114
115 // timing cut except HEC stuff
116 bool isHEC = false;
117 if (elem) {
118 int sampling = elem->getSampling();
119 isHEC = (sampling >= 8 && sampling <= 11);
120 }
121
122 // decide whether to apply timing cut
123 bool applyTiming = false;
124
126 applyTiming = true; // apply everywhere
127 }
128 else if (m_applyTimingCut) {
129 applyTiming = !isHEC; // apply everywhere except HEC
130 }
131
132 // apply cut
133 if (isMC && applyTiming && !((digi)->provenance() & 0x200)) {
134 val = 0;
135 }
136
137 bool isSaturated = (!isMC) ? (digi->quality()) : false; // not applying saturation codes in MC until the changes to trigger counts has been investigated
138 bool isMasked = ((digi)->provenance()&0x80);
139 bool isInvalid = m_applyMasking ? ((digi)->provenance()&0x40) : false;
140 // note: if debugging, the SCIDs have value: digi->ID().get_compact()>>32
141 if(isInvalid) {
142 val = INVALID_VALUE;
143 }
144 if(isSaturated) {
145 val = SATURATED_VALUE;
146 }
147
148 // fill the Et
149 auto [towerItr, inserted] = towers.try_emplace(itr->second.first);
150
151 if (inserted) {
152 towerItr->second.first.fill(MISSING_VALUE);
153 towerItr->second.second.assign(11, -6000.0f);
154 }
155
156 auto& counts = towerItr->second.first;
157 auto& timing = towerItr->second.second;
158
159 if (itr->second.second.second == 11) {
160 timing.at(itr->second.second.first) = digi->time();
161 }
162 if (itr->second.second.second<11) {
163 // doing an energy split between slots ... don't include a masked channel (or invalid channel)
164 if (!isMasked && val!=INVALID_VALUE) {
165 if(isSaturated) {
166 // mark both as saturated
167 counts.at(itr->second.second.first) = SATURATED_VALUE;
168 counts.at(itr->second.second.second) = SATURATED_VALUE;
169 }
170 if(counts.at(itr->second.second.first)!=(SATURATED_VALUE)) { // don't override saturation
171 // if the other contribution was masked or invalid or missing, revert to 0 before adding this contribution
172 if (counts.at(itr->second.second.first)==MASKED_VALUE || counts.at(itr->second.second.first)==INVALID_VALUE || counts.at(itr->second.second.first)==MISSING_VALUE) {
173 counts.at(itr->second.second.first)=0;
174 }
175 counts.at(itr->second.second.first) += val >> 1;
176 }
177 if(counts.at(itr->second.second.second)!=(SATURATED_VALUE)) { // don't override saturation
178 // if the other contribution was masked or invalid or missing, revert to 0 before adding this contribution
179 if (counts.at(itr->second.second.second)==MASKED_VALUE || counts.at(itr->second.second.second)==INVALID_VALUE || counts.at(itr->second.second.second)==MISSING_VALUE) {
180 counts.at(itr->second.second.second)=0;
181 }
182 counts.at(itr->second.second.second) += (val - (val >> 1)); // HW seems fixed now!
183 }
184 }
185 // hw is incorrectly ignoring masking on the second part
186 // so always add the 2nd bit
187 } else {
188 auto& v = counts.at(itr->second.second.first);
189 if (isMasked) {
190 // dont mark it masked if it already has a contribution
191 if(v==MISSING_VALUE) v = MASKED_VALUE;
192 } else if(isSaturated) {
193 v = val;
194 } else {
195 if(v==INVALID_VALUE || v==MISSING_VALUE) v = 0;
196 v += val;
197 }
198 }
199
200 } // Scells
201
202 // add tile energies from TriggerTowers
203 static const auto etaIndex = [](float eta) { return int( eta*10 ) + ((eta<0) ? -1 : 1); };
204 static const auto phiIndex = [](float phi) { return int( phi*32./M_PI ) + (phi<0 ? -1 : 1); };
205 for(const xAOD::TriggerTower_v2* tTower : *tTowers) {
206 if (std::abs(tTower->eta()) > 1.5) continue;
207 if (tTower->sampling() != 1) continue;
208 double phi = tTower->phi(); if(phi > M_PI) phi -= 2.*M_PI;
209 auto coord = std::pair(etaIndex(tTower->eta()), phiIndex(phi));
210
211 auto [towerItr, inserted] = towers.try_emplace(coord);
212
213 if (inserted) {
214 towerItr->second.first.fill(MISSING_VALUE);
215 towerItr->second.second.assign(11, -6000.0f);
216 }
217 towerItr->second.first.at(10) = tTower->cpET();
218 }
219
220
222 ATH_CHECK( eTowers.record(std::make_unique<xAOD::eFexTowerContainer>(),std::make_unique<xAOD::eFexTowerAuxContainer>()) );
223
224 static const auto calToFex = [](int calEt) {
225 if(calEt == MASKED_VALUE) return 0; // indicates masked channel
226 if(calEt == SATURATED_VALUE) return 1023; // saturated channel
227 if( calEt == INVALID_VALUE ) return 1022; // invalid channel value
228 if( calEt == MISSING_VALUE ) return 1025; // missing channel value
229 if(calEt<448) return std::max((calEt&~1)/2+32,1); // 25 MeV per eFexTower count
230 if(calEt<1472) return (calEt-448)/4+256; // 50 MeV per eFexTower count
231 if(calEt<3520) return (calEt-1472)/8+512; // 100 MeV ...
232 if(calEt<11584) return (calEt-3520)/32+768; // 400 MeV ...
233 return 1020;
234 };
235
236 // now create the towers. Note that we need a code for "missing" input (1025), because a tower can contain some but not all
237 // inputs, depending on which sources are present in the run (e.g. if tile is present but not LAr).
238 // This is different to e.g. jFex, which creates a separate tower for each source at each location
239 // so jFex doesn't need a "missing" input code.
240 for (auto& [coord, towerData] : towers) {
241 auto& counts = towerData.first;
242 auto& timingvec = towerData.second;
243
244 size_t ni = (std::abs(coord.first) <= 15) ? 10 : 11;
245
246 for (size_t i = 0; i < ni; ++i) {
247 counts[i] = scells->empty() ? 1025 : calToFex(counts[i]);
248 }
249
250 eTowers->push_back(std::make_unique<xAOD::eFexTower>());
251 eTowers->back()->initialize(
252 ((coord.first < 0 ? 0.5 : -0.5) + coord.first) * 0.1,
253 ((coord.second < 0 ? 0.5 : -0.5) + coord.second) * M_PI / 32,
254 std::vector<uint16_t>(counts.begin(), counts.end()),
255 -1, /* module number */
256 -1, /* fpga number */
257 0,0 /* status flags ... could use to indicate which cells were actually present?? */
258 );
259 // fill timing decorator
260 if (!m_tauTimingDecorKey.empty()) {
263 );
264
265 eTowerTiming(*eTowers->back()) = timingvec;
266 }
267 }
268 return StatusCode::SUCCESS;
269
270}
271
272StatusCode eFexTowerBuilder::fillMap(const EventContext& ctx) const {
273
274 ATH_MSG_INFO("Filling sc -> eFexTower map");
275
277 SG::ReadHandle<CaloCellContainer> scells(m_scellKey,ctx); // 34048 is a full complement of scells
278 if(!scells.isValid()){
279 ATH_MSG_FATAL("Could not retrieve collection " << m_scellKey.key() );
280 return StatusCode::FAILURE;
281 }
282 if (scells->size() != 34048 && !m_mappingFile.empty()) {
283 ATH_MSG_FATAL("Cannot fill sc -> eFexTower mapping with an incomplete sc collection");
284 return StatusCode::FAILURE;
285 }
286
287 // read the LATOME header if a key is given, so that we can determine LATOME version and get mapping right
288 bool doV6Mapping = m_v6Mapping;
289
290 if(!m_LArLatomeHeaderContainerKey.empty()) {
292 if(hdrCont.isValid()) {
293 for (const LArLATOMEHeader* hit : *hdrCont) {
294 doV6Mapping = (hit->FWversion()>1600);
295 }
296 if (doV6Mapping != m_v6Mapping) {
297 ATH_MSG_WARNING("Used LATOME Hardware to determine mapping different to python configuration (use V6 Mapping = " << doV6Mapping << " )");
298 }
299 }
300
301 }
302 struct TowerSCells {
303 std::vector<unsigned long long> ps;
304 std::vector<std::pair<float,unsigned long long>> l1;
305 std::vector<std::pair<float,unsigned long long>> l2;
306 std::vector<unsigned long long> l3;
307 std::vector<unsigned long long> had;
308 std::vector<unsigned long long> other;
309 };
310 static const auto etaIndex = [](float eta) { return int( eta*10 ) + ((eta<0) ? -1 : 1); }; // runs from -25 to 25, skipping over 0 (so gives outer edge eta)
311 static const auto phiIndex = [](float phi) { return int( phi*32./ROOT::Math::Pi() ) + (phi<0 ? -1 : 1); }; // runs from -pi to pi, skipping over 0 (gives out edge phi)
312 std::map<std::pair<int,int>,TowerSCells> towers;
313 std::map<unsigned long long,int> eTowerSlots; // not used by this alg, but we produce the map for benefit of eFexTower->eTower alg
314
315 for (auto digi: *scells) {
316 Identifier id = digi->ID(); // this is if using supercells
317
318 if (auto elem = ddm->get_element(id); elem && std::abs(elem->eta_raw())<2.5) {
319 float eta = elem->eta_raw(); // this seems more symmetric
320 int sampling = elem->getSampling();
321 if(sampling==6 && ddm->getCaloCell_ID()->region(id)==0 && eta<0) eta-=0.01; // nudge this L2 endcap supercell into correct tower (right on boundary)
322
323 unsigned long long val = id.get_compact();
324
325 int towerid = -1;int slot = -1;bool issplit = false;
326 CHECK(m_eFEXSuperCellTowerIdProviderTool->geteTowerIDandslot(id.get_compact(), towerid, slot, issplit));
327 eTowerSlots[id.get_compact()] = slot;
328
329 auto& sc = towers[std::pair(etaIndex(eta),phiIndex(elem->phi_raw()))];
330 switch(sampling) {
331 case 0: case 4: //lar barrel/endcap presampler
332 sc.ps.push_back(val);
333 break;
334 case 1: case 5: //lar barrel/endcap l1
335 sc.l1.push_back({elem->eta(),val}); break;
336 case 2: case 6: //lar barrel/endcap l2
337 sc.l2.push_back({elem->eta(),val}); break;
338 case 3: case 7: //lar barrel/endcap l3
339 sc.l3.push_back(val); break;
340 case 8: case 9: case 10: case 11: //lar hec
341 sc.had.push_back(val); break;
342 default:
343 sc.other.push_back(val); break;
344 }
345 }
346 }
347
348
349 // sort (by increasing eta) l1/l2 sc and handle special cases
350 // finally also output the eTower slot vector
351 std::vector<size_t> slotVector(11);
352 for(auto& [coord,sc] : towers) {
353 std::sort(sc.l1.begin(),sc.l1.end());
354 std::sort(sc.l2.begin(),sc.l2.end());
355 // we have 5 l2 cells @ |eta|=1.45 ... put lowest |eta| one in l3 slot
356 if (sc.l2.size()==5) {
357 if (coord.first >= 0) {
358 sc.l3.push_back(sc.l2.front().second);
359 sc.l2.erase(sc.l2.begin()); // remove first
360 } else {
361 sc.l3.push_back(sc.l2.back().second);
362 sc.l2.resize(sc.l2.size()-1); // remove last
363 }
364 }
365 if (std::abs(coord.first)==15) { //|eta| = 1.45
366 // in the overlap region it seems like the latome id with highest |eta| is swapped with next highest
367 // so to compare we swap the first and second (3rd and 4th are fine) if eta < 0, or 3rd and 4th if eta > 0
368 if (coord.first<0) {std::swap(sc.l1.at(0),sc.l1.at(1)); }
369 else {std::swap(sc.l1.at(2),sc.l1.at(3));}
370 }
371 // handle case @ |eta|~1.8-2 with 6 L1 cells
372 if (sc.l1.size()==6) {
373 m_scMap[sc.l1.at(0).second] = std::pair(coord,std::pair(1,11));
374 m_scMap[sc.l1.at(1).second] = std::pair(coord,(doV6Mapping && coord.first < 0) ? std::pair(2,1) : std::pair(1,2)); // in LATOME v5 FW this was (1,2) for both sides
375 m_scMap[sc.l1.at(2).second] = std::pair(coord,std::pair(2,11));
376 m_scMap[sc.l1.at(3).second] = std::pair(coord,std::pair(3,11));
377 m_scMap[sc.l1.at(4).second] = std::pair(coord,(doV6Mapping && coord.first < 0) ? std::pair(4,3) : std::pair(3,4)); // in LATOME v5 FW this was (3,4) for both sides
378 m_scMap[sc.l1.at(5).second] = std::pair(coord,std::pair(4,11));
379 slotVector[1] = eTowerSlots[sc.l1.at(0).second];
380 slotVector[2] = eTowerSlots[sc.l1.at(2).second];
381 slotVector[3] = eTowerSlots[sc.l1.at(3).second];
382 slotVector[4] = eTowerSlots[sc.l1.at(5).second];
383 }
384
385 // for |eta|>2.4 there's only 1 l1 sc, to match hardware this should be compared placed in the 'last' l1 input
386 if (sc.l1.size()==1) {
387 m_scMap[sc.l1.at(0).second] = std::pair(coord,std::pair(4,11));
388 slotVector[1] = 1; slotVector[2] = 2; slotVector[3] = 3; slotVector[4] = eTowerSlots[sc.l1.at(0).second];
389 }
390
391 // fill the map with sc ids -> tower coord + slot
392 if (!sc.ps.empty()) {m_scMap[sc.ps.at(0)] = std::pair(coord,std::pair(0,11)); slotVector[0] = eTowerSlots[sc.ps.at(0)]; }
393 if(sc.l1.size()==4) for(size_t i=0;i<4;i++) if(sc.l1.size() > i) {m_scMap[sc.l1.at(i).second] = std::pair(coord,std::pair(i+1,11)); slotVector[i+1] = eTowerSlots[sc.l1.at(i).second]; }
394 for(size_t i=0;i<4;i++) if(sc.l2.size() > i) { m_scMap[sc.l2.at(i).second] = std::pair(coord,std::pair(i+5,11)); slotVector[i+5] = eTowerSlots[sc.l2.at(i).second]; }
395 if (!sc.l3.empty()) {m_scMap[sc.l3.at(0)] = std::pair(coord,std::pair(9,11)); slotVector[9] = eTowerSlots[sc.l3.at(0)]; }
396 if (!sc.had.empty()) {m_scMap[sc.had.at(0)] = std::pair(coord,std::pair(10,11));slotVector[10] = eTowerSlots[sc.had.at(0)]; }
397
398 // finally output the slotVector for this tower
399 // do only for the slots that don't match
400 // note to self: seems like everything is fine apart from the l1->ps remap for |eta|>2.4
401 // so leaving this bit commented out for now ... useful to leave it here in case need to recheck in future
402// for(size_t i=0;i<slotVector.size();i++) {
403// if(slotVector[i] != i) {
404// std::cout << coord.first << "," << coord.second << "," << i << "," << slotVector[i] << std::endl;
405// }
406// }
407 }
408
409 // save the map to disk, if required
410 if(!m_mappingFile.empty()) {
411 TFile f(m_mappingFile.value().c_str(), "RECREATE");
412 TTree *t = new TTree("mapping", "mapping");
413 unsigned long long scid = 0;
414 std::pair<int, int> coord = {0, 0};
415 std::pair<int, int> slot = {-1, -1};
416 t->Branch("scid", &scid);
417 t->Branch("etaIndex", &coord.first);
418 t->Branch("phiIndex", &coord.second);
419 t->Branch("slot1", &slot.first);
420 t->Branch("slot2", &slot.second);
421 for (auto &[id, val]: m_scMap) {
422 scid = id;
423 coord = val.first;
424 slot = val.second;
425 t->Fill();
426 }
427 t->Write();
428 f.Close();
429 }
430 return StatusCode::SUCCESS;
431
432}
433
434
435StatusCode eFexTowerBuilder::execute(const EventContext& ctx) const {
436 ATH_MSG_DEBUG("Executing " << name() << "...");
437 setFilterPassed(true, ctx);
438
439
440 {
441 std::lock_guard lock(m_fillMapMutex);
442 if (m_scMap.empty()) CHECK( fillMap(ctx) );
443 }
444
445 return fillTowers(ctx);
446
447}
448
449} // LVL1 Namespace
#define M_PI
Scalar eta() const
pseudorapidity method
Scalar phi() const
phi method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_FATAL(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
Helper class for offline supercell identifiers.
#define CHECK(...)
Evaluate an expression and check for errors.
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
double coord
Type of coordination system.
static Double_t sc
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
Handle class for adding a decoration to an object.
virtual void setFilterPassed(bool state, const EventContext &ctx) const
An algorithm that can be simultaneously executed in multiple threads.
Holds information from the LATOME Header.
SG::ReadHandleKey< xAOD::TriggerTowerContainer > m_ttKey
SG::ReadHandleKey< xAOD::EventInfo > m_eiKey
virtual StatusCode execute(const EventContext &ctx) const
StatusCode fillMap(const EventContext &ctx) const
SG::ReadCondHandleKey< CaloSuperCellDetDescrManager > m_ddmKey
Gaudi::Property< bool > m_applyTimingCut
eFexTowerBuilder(const std::string &name, ISvcLocator *pSvcLocator)
SG::ReadHandleKey< LArLATOMEHeaderContainer > m_LArLatomeHeaderContainerKey
Gaudi::Property< bool > m_v6Mapping
ToolHandle< eFEXSuperCellTowerIdProvider > m_eFEXSuperCellTowerIdProviderTool
virtual StatusCode initialize()
Gaudi::Property< std::string > m_mappingFile
SG::WriteDecorHandleKey< xAOD::eFexTowerContainer > m_tauTimingDecorKey
SG::ReadHandleKey< CaloCellContainer > m_scellKey
StatusCode fillTowers(const EventContext &ctx) const
Gaudi::Property< bool > m_applyTimingCutAll
SG::WriteHandleKey< xAOD::eFexTowerContainer > m_outKey
Gaudi::Property< bool > m_applyMasking
virtual bool isValid() override final
Can the handle be successfully dereferenced?
Handle class for adding a decoration to an object.
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
@ IS_SIMULATION
true: simulation, false: data
Description of TriggerTower_v2.
eFexTowerBuilder creates xAOD::eFexTowerContainer from supercells (LATOME) and triggerTowers (TREX) i...
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)