ATLAS Offline Software
Loading...
Searching...
No Matches
CaloBlueprintNodeBuilder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
9
10#include "Acts/Geometry/Blueprint.hpp"
11#include "Acts/Geometry/StaticBlueprintNode.hpp"
12
13#include "Acts/Surfaces/RadialBounds.hpp"
14#include "Acts/Surfaces/CylinderBounds.hpp"
15#include "Acts/Geometry/ContainerBlueprintNode.hpp"
16#include "Acts/Geometry/Extent.hpp"
17#include "Acts/Definitions/Units.hpp"
18#include "Acts/Geometry/LayerBlueprintNode.hpp"
19#include "Acts/Geometry/VolumeResizeStrategy.hpp"
20#include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
21#include "Acts/Geometry/TrackingVolume.hpp"
22#include "Acts/Geometry/CylinderVolumeBounds.hpp"
23#include "Acts/Geometry/PadBlueprintNode.hpp"
24#include "Acts/Geometry/GeometryIdentifierBlueprintNode.hpp"
25
26#include "Acts/Utilities/Helpers.hpp"
27#include "Acts/Utilities/AxisDefinitions.hpp"
28
31#include "CaloGeoHelpers/CaloSampling.h"
32
33
34#include <cmath>
35
36using namespace Acts::UnitLiterals;
37using namespace ActsTrk::detail::GeoVolIds;
38
39using SampleSet_t = std::unordered_set<CaloCell_ID::CaloSample>;
40
41namespace {
42 //create lists from all possible calo samplings that tracks could hit
43 //The first list is for disc shaped samples and the second for cylindrical shaped samples
44 //Note that TileGap3 is the barrel, but is disk shaped.
45
46 static const SampleSet_t s_caloDiscSampleList{
47 CaloCell_ID::PreSamplerE,
48 CaloCell_ID::EME1, CaloCell_ID::EME2, CaloCell_ID::EME3,
49 CaloCell_ID::HEC0, CaloCell_ID::HEC1, CaloCell_ID::HEC2, CaloCell_ID::HEC3,
50 CaloCell_ID::TileGap3,
51 CaloCell_ID::FCAL0, CaloCell_ID::FCAL1, CaloCell_ID::FCAL2};
52
53 static const SampleSet_t s_caloCylinderSymmetricSampleList{
54 CaloCell_ID::PreSamplerB,
55 CaloCell_ID::EMB1, CaloCell_ID::EMB2, CaloCell_ID::EMB3,
56 CaloCell_ID::TileBar0, CaloCell_ID::TileBar1, CaloCell_ID::TileBar2};
57
58 static const SampleSet_t s_caloCylinderAsymmetricSampleList{
59 CaloCell_ID::TileGap1, CaloCell_ID::TileGap2,
60 CaloCell_ID::TileExt0, CaloCell_ID::TileExt1, CaloCell_ID::TileExt2};
61
62 std::string print(std::span<const CaloDetDescrElement* const> elements) {
63 std::stringstream sstr{};
64 unsigned counter{};
65 for (const CaloDetDescrElement* theDDE : elements) {
66 sstr<<" - "<<(counter++)<<") "
67 <<CaloSampling::getSamplingName(theDDE->getSampling())
68 <<", radius: ["<< (theDDE->r() - 0.5*theDDE->dr())<<";"
69 <<(theDDE->r() + 0.5*theDDE->dr())
70 <<"], z: ["<<(theDDE->z() -0.5* theDDE->dz())<<";"
71 <<(theDDE->z() +0.5* theDDE->dz())<<"]"<<std::endl;
72 }
73 return sstr.str();
74 }
75}
76
77namespace ActsTrk{
80
83 ATH_MSG_ERROR("Invalid size of "<<m_surfaceDuplicates<<". Expected: "
84 <<CaloSampling::getNumberOfSamplings()<<" got "<<m_surfaceDuplicates.value().size());
85 return StatusCode::FAILURE;
86 }
87 return StatusCode::SUCCESS;
88}
89
91 DetElementMap_t sortedElements{};
92 for (const CaloDetDescrElement* theDDE : detDecrMgr.element_range()){
93 const CaloCell_ID::CaloSample sample =theDDE->getSampling();
94 sortedElements[Acts::toUnderlying(sample)].push_back(theDDE);
95 }
96 for (DetElVec_t& detElVec : sortedElements) {
97 if (detElVec.empty()) {
98 continue;
99 }
100 const CaloCell_ID::CaloSample sample = detElVec.front()->getSampling();
101 std::ranges::sort(detElVec, [&](const CaloDetDescrElement* a, const CaloDetDescrElement* b){
103 if (!s_caloDiscSampleList.count(sample)) {
104 if (std::abs(a->r() - b->r()) > Acts::s_epsilon) {
105 return a->r() < b->r();
106 }
107 return a->z() < b->z();
108 }
110 if (std::abs(a->z() - b->z()) > Acts::s_epsilon) {
111 return a->z() < b->z();
112 }
113 return a->r() < b->r();
114 });
116 auto [begin, end] = std::ranges::unique(detElVec,
117 [&](const CaloDetDescrElement* a, const CaloDetDescrElement* b) {
118 return std::abs(a->r() - b->r()) < Acts::s_onSurfaceTolerance &&
119 std::abs(a->z() - b->z()) < Acts::s_onSurfaceTolerance;
120 });
121 const std::size_t nBefore = detElVec.size();
122 detElVec.erase(begin, end);
123 ATH_MSG_DEBUG(__func__<<"() "<<__LINE__<<" - Removed "<<(nBefore - detElVec.size())
124 <<" detector elements for layer "<<CaloSampling::getSamplingName(sample)
125 <<", current size: "<<detElVec.size()<<"\n"<<print(detElVec));
126 }
131 DetElVec_t& gapVec = sortedElements[CaloSampling::TileGap2];
132 DetElVec_t& barVec = sortedElements[CaloSampling::TileBar2];
133 barVec.insert(barVec.end(), gapVec.begin(), gapVec.end());
134 gapVec.clear();
135 return sortedElements;
136}
137
138std::shared_ptr<Acts::Surface> CaloBlueprintNodeBuilder::createCylinderSurface(std::span<const CaloDetDescrElement* const> detElVec) const {
139
140 float zMin{std::numeric_limits<float>::max()},
141 zMax{-std::numeric_limits<float>::max()},
142 rMin{std::numeric_limits<float>::max()}, rMax{0.f};
143 for (const CaloDetDescrElement* detEl : detElVec){
144 zMin = std::min(zMin, detEl->z() - 0.5f*detEl->dz());
145 zMax = std::max(zMax, detEl->z() + 0.5f*detEl->dz());
146 rMin = std::min(rMin, detEl->r() - 0.5f*detEl->dr());
147 rMax = std::max(rMax, detEl->r() + 0.5f*detEl->dr());
148 }
149 const float cylinderR = 0.5*(rMin + rMax);
150 const float cylinderZ = 0.5*(zMax - zMin);
151 const float surfaceZ = 0.5*(zMin + zMax);
152 auto cylinder = Acts::Surface::makeShared<Acts::CylinderSurface>(Amg::getTranslateZ3D(surfaceZ), cylinderR, cylinderZ);
153 cylinder->assignThickness(0.5*(rMax - rMin));
154 cylinder->assignGeometryId(Acts::GeometryIdentifier{}.withLayer(detElVec.front()->getSampling()));
155 ATH_MSG_DEBUG(__func__<<"() - Create surface "<<CaloSampling::getSamplingName(detElVec.front()->getSampling())
156 <<" r["<<rMin<<";"<<rMax<<"] z["<<zMin<<";"<<zMax<<"]"
157 <<" -> surface parameters R: "<<cylinder->bounds()<<", Z position: "<<surfaceZ
158 <<", "<<cylinder->geometryId());
159 return cylinder;
160}
161
162std::shared_ptr<Acts::Surface> CaloBlueprintNodeBuilder::createDiscSurface(std::span<const CaloDetDescrElement* const> detElVec) const {
163 float zMin{std::numeric_limits<float>::max()},
164 zMax{-std::numeric_limits<float>::max()},
165 rMin{std::numeric_limits<float>::max()}, rMax{0.f};
166 for (const CaloDetDescrElement* detEl : detElVec){
167 zMin = std::min(zMin, detEl->z() - 0.5f*detEl->dz());
168 zMax = std::max(zMax, detEl->z() + 0.5f*detEl->dz());
169 rMin = std::min(rMin, detEl->r() - 0.5f*detEl->dr());
170 rMax = std::max(rMax, detEl->r() + 0.5f*detEl->dr());
171 }
172 const float surfaceZ = 0.5*(zMin + zMax);
173 const auto sampling = detElVec.front()->getSampling();
174 ATH_MSG_DEBUG(__func__<<"() - Create surface "<<CaloSampling::getSamplingName(sampling)
175 <<" r["<<rMin<<";"<<rMax<<"] z["<<zMin<<";"<<zMax<<"]"
176 <<" -> surface position: "<<surfaceZ);
177 auto disc = Acts::Surface::makeShared<Acts::DiscSurface>(Amg::getTranslateZ3D(surfaceZ), rMin, rMax);
178 disc->assignThickness(0.5*(zMax - zMin));
179 disc->assignGeometryId(Acts::GeometryIdentifier{}.withLayer(sampling));
180 return disc;
181}
182
184 SurfaceMap_t surfaces{};
185 for (const DetElVec_t& detElVec : sortedElements) {
186 if (detElVec.empty()) {
187 continue;
188 }
189 const CaloCell_ID::CaloSample sample = detElVec.front()->getSampling();
190 using enum caloRegion;
191 if (s_caloCylinderSymmetricSampleList.count(sample)) {
192 surfaces[Acts::toUnderlying(BarrelCylinder)].push_back(createCylinderSurface(detElVec));
193 } else if (s_caloCylinderAsymmetricSampleList.count(sample)) {
194 for (DetElVec_t::const_iterator itr = detElVec.begin();
195 itr != detElVec.end(); ) {
196 const CaloDetDescrElement* detEl{*itr};
197 auto next = std::ranges::find_if(itr, detElVec.end(), [&](const CaloDetDescrElement* testMe){
198 return detEl->z() * testMe->z() < 0.;
199 });
200 surfaces[Acts::toUnderlying(BarrelCylinder)].push_back(createCylinderSurface({itr, next}));
201 itr = next;
202 }
203 } else {
204 for (DetElVec_t::const_iterator itr = detElVec.begin();
205 itr != detElVec.end(); ) {
206 const CaloDetDescrElement* detEl{*itr};
207 auto next = std::ranges::find_if(itr, detElVec.end(), [&](const CaloDetDescrElement* testMe){
208 return detEl->z() * testMe->z() < 0.;
209 });
210 const auto idx = detEl->z() > 0 ? Acts::toUnderlying(DiscPositiveZ)
211 : Acts::toUnderlying(DiscNegativeZ);
212 surfaces[idx].push_back(createDiscSurface({itr, next}));
213 itr = next;
214 }
215 }
216 }
217 return surfaces;
218}
219std::shared_ptr<Acts::BlueprintNode>
220 CaloBlueprintNodeBuilder::buildBlueprintNode(const Acts::GeometryContext& tgContext,
221 std::shared_ptr<Acts::BlueprintNode>&& childNode) {
222 std::unique_ptr<CaloDetDescrManager> caloDetDescrMgr = buildCaloDetDescrNoAlign(serviceLocator(), Athena::getMessageSvc());
224 DetElementMap_t detElements = fillDetectorElements(*caloDetDescrMgr);
226 SurfaceMap_t sensitives = translateToSurfaces(detElements);
227
228 std::shared_ptr<Acts::BlueprintNode> caloNode{};
230 Acts::TrackingVolume* caloVolume{};
231 {
232 auto caloEnvelope = envelopeVolume(tgContext, sensitives);
233 caloVolume = caloEnvelope.get();
234 caloNode = std::make_unique<Acts::StaticBlueprintNode>(std::move(caloEnvelope));
235 }
236 auto barrelNode = std::make_shared<Acts::CylinderContainerBlueprintNode>(
237 "CaloBarrelCylinder", Acts::AxisDirection::AxisR);
238 barrelNode->setAttachmentStrategy(Acts::VolumeAttachmentStrategy::Second);
239 barrelNode->setResizeStrategy(Acts::VolumeResizeStrategy::Expand);
240 if (childNode) {
241 barrelNode->addChild(std::move(childNode));
242 }
243 std::unordered_map<CaloSampling::CaloSample, unsigned> barrelExtraCounts{};
244 for (std::shared_ptr<Acts::Surface>& surface : sensitives[Acts::toUnderlying(caloRegion::BarrelCylinder)]) {
245 const auto layer = static_cast<CaloSampling::CaloSample>(surface->geometryId().layer());
246 if (s_caloCylinderAsymmetricSampleList.count(layer) ||
247 layer == CaloSampling::TileBar2) {
248 std::ranges::for_each(fillLayer(tgContext, std::move(surface)),
249 [&](std::shared_ptr<Acts::Surface>& surfToAdd){
250 surfToAdd->assignGeometryId(Acts::GeometryIdentifier{}.withVolume(s_caloEnvelopeID)
251 .withLayer(layer)
252 .withSensitive(barrelExtraCounts[layer]++));
253 caloVolume->addSurface(std::move(surfToAdd));
254 });
255 continue;
256 }
257 auto layerNode = std::make_shared<Acts::LayerBlueprintNode>(std::format("{:}_Layer", CaloSampling::getSamplingName(layer)));
258 layerNode->setSurfaces(fillLayer(tgContext, std::move(surface)));
259 layerNode->setEnvelope(Acts::ExtentEnvelope{{
260 .z = {0.1_mm, 0.1_mm},
261 .r = {1._cm, 1._cm}}});
262 auto idNode = std::make_shared<Acts::GeometryIdentifierBlueprintNode>();
263 idNode->addChild(std::move(layerNode));
264 idNode->setAllVolumeIdsTo(s_caloBarrelId);
265 idNode->setLayerIdTo(layer);
266 barrelNode->addChild(std::move(idNode));
267 }
268 caloNode->addChild(std::move(barrelNode));
269 for (const auto& disc: {caloRegion::DiscNegativeZ, caloRegion::DiscPositiveZ}){
270 auto layerNode = std::make_shared<Acts::LayerBlueprintNode>(std::format("CaloLayer{:}", disc));
271 layerNode->setEnvelope(Acts::ExtentEnvelope{{
272 .z = {1_mm, 1_mm},
273 .r = {2_mm, 2_mm}, }});
274 using namespace ActsTrk::detail::GeoVolIds;
275 const std::size_t volId = disc == caloRegion::DiscNegativeZ ? s_caloEndcapCId : s_caloEndcapAId;
276 std::vector<std::shared_ptr<Acts::Surface>> layerSurfaces{};
277
278 for (auto& surface : sensitives[Acts::toUnderlying(disc)]) {
279
280 const auto layer = static_cast<CaloSampling::CaloSample>(surface->geometryId().layer());
281 if (layer != CaloSampling::TileGap3) {
282 std::ranges::for_each(fillLayer(tgContext, std::move(surface)),
283 [&](std::shared_ptr<Acts::Surface>& surf) {
284 surf->assignGeometryId(Acts::GeometryIdentifier{}.withVolume(volId).
285 withLayer(layer).withSensitive(layerSurfaces.size()+1));
286 layerSurfaces.push_back(std::move(surf));
287 });
288 } else {
289 surface->assignGeometryId(surface->geometryId().withVolume(s_caloEnvelopeID)
290 .withSensitive(barrelExtraCounts[layer]++));
291 caloVolume->addSurface(std::move(surface));
292 }
293 }
294 layerNode->setSurfaces(std::move(layerSurfaces));
295 auto idNode = std::make_shared<Acts::GeometryIdentifierBlueprintNode>();
296 idNode->addChild(std::move(layerNode));
297 idNode->setAllVolumeIdsTo(volId);
298 caloNode->addChild(std::move(idNode));
299 }
300 return caloNode;
301}
302std::unique_ptr<Acts::TrackingVolume> CaloBlueprintNodeBuilder::envelopeVolume(const Acts::GeometryContext& tgContext,
303 const SurfaceMap_t& surfacePerRegion) const {
304 double maxR{0.}, minZ{std::numeric_limits<double>::max()}, maxZ{-std::numeric_limits<double>::max()};
305 for (auto& surfaces : surfacePerRegion) {
306 for (auto& surface : surfaces) {
307 const Amg::Vector3D center = surface->center(tgContext);
308 if (surface->type() == Acts::Surface::SurfaceType::Disc) {
309 minZ = std::min(minZ, center.z() - 0.5*surface->thickness());
310 maxZ = std::max(maxZ, center.z() + 0.5*surface->thickness());
311 const auto& bounds = static_cast<const Acts::RadialBounds&>(surface->bounds());
312 maxR = std::max(maxR, bounds.rMax());
313 } else {
314 const auto& bounds = static_cast<const Acts::CylinderBounds&>(surface->bounds());
315 using enum Acts::CylinderBounds::BoundValues;
316 minZ = std::min(minZ, center.z() - bounds.get(eHalfLengthZ));
317 maxZ = std::max(maxZ, center.z() + bounds.get(eHalfLengthZ));
318 maxR = std::max(maxR, bounds.get(eR) + 0.5*surface->thickness());
319 }
320 }
321 }
322 auto volBounds = std::make_shared<Acts::CylinderVolumeBounds>(0, maxR + 1_cm, 0.5*(maxZ - minZ) + 1._cm);
323 ATH_MSG_DEBUG(__func__<<"() Create envelope volume with bounds "<<(*volBounds));
324 auto volume = std::make_unique<Acts::TrackingVolume>(Amg::getTranslateZ3D(0.5*(minZ + maxZ)),
325 volBounds, "ITkCalo");
326 volume->assignGeometryId(Acts::GeometryIdentifier{}.withVolume(s_caloEnvelopeID));
327 return volume;
328 }
329 std::vector<std::shared_ptr<Acts::Surface>>
330 CaloBlueprintNodeBuilder::fillLayer(const Acts::GeometryContext& tgContext,
331 std::shared_ptr<Acts::Surface>&& centralSurf) const{
332 unsigned extraSurf = m_surfaceDuplicates.value().at(centralSurf->geometryId().layer());
333 centralSurf->assignGeometryId(Acts::GeometryIdentifier{});
334 if (extraSurf == 0) {
335 return {std::move(centralSurf)};
336 }
337 const double stepLength = centralSurf->thickness() / (extraSurf +1u);
338 const Acts::Transform3& trf = centralSurf->localToGlobalTransform(tgContext);
339 std::vector<std::shared_ptr<Acts::Surface>> surfaces{};
340 if (centralSurf->type() == Acts::Surface::SurfaceType::Cylinder) {
341 const auto& bounds = static_cast<const Acts::CylinderBounds&>(centralSurf->bounds());
342 const double radius = bounds.get(Acts::CylinderBounds::BoundValues::eR);
343 const double halfZ = bounds.get(Acts::CylinderBounds::BoundValues::eHalfLengthZ);
344 for (unsigned extra = 1ul; extra <= extraSurf; ++extra) {
345 surfaces.emplace_back(Acts::Surface::makeShared<Acts::CylinderSurface>(trf, radius - stepLength* extra, halfZ));
346 surfaces.emplace_back(Acts::Surface::makeShared<Acts::CylinderSurface>(trf, radius + stepLength* extra, halfZ));
347 }
348 } else if (centralSurf->type() == Acts::Surface::SurfaceType::Disc) {
349 using DiscB = Acts::RadialBounds;
350 auto bounds = std::make_shared<DiscB>(static_cast<const DiscB&>(centralSurf->bounds()));
351 for (unsigned extra = 1ul; extra <= extraSurf; ++extra) {
352 const double shiftZ = stepLength *extra;
353 surfaces.emplace_back(Acts::Surface::makeShared<Acts::DiscSurface>(trf * Amg::getTranslateZ3D(shiftZ),
354 bounds));
355 surfaces.emplace_back(Acts::Surface::makeShared<Acts::DiscSurface>(trf * Amg::getTranslateZ3D(-shiftZ),
356 bounds));
357 }
358 }
359 surfaces.push_back(std::move(centralSurf));
360 return surfaces;
361 }
362}
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
std::unordered_set< CaloCell_ID::CaloSample > SampleSet_t
std::unique_ptr< CaloDetDescrManager > buildCaloDetDescrNoAlign(ISvcLocator *svcLocator, IMessageSvc *msgSvc)
static Double_t a
void print(char *figname, TCanvas *c1)
DetElementMap_t fillDetectorElements(const CaloDetDescrManager &detDecrMgr) const
Fill the detector elements per calorimeter layer and sort them in ascending r(z) for the barrel (endc...
Gaudi::Property< std::vector< std::uint32_t > > m_surfaceDuplicates
Configure the insert of extra surfaces per each calorimeter layer.
std::vector< const CaloDetDescrElement * > DetElVec_t
std::vector< std::shared_ptr< Acts::Surface > > fillLayer(const Acts::GeometryContext &tgContext, std::shared_ptr< Acts::Surface > &&centralSurf) const
Fill the layer with extra surfaces.
SurfaceMap_t translateToSurfaces(const DetElementMap_t &detElements) const
Translate all detector elements to a Cylinder and Disc surfaces.
std::shared_ptr< Acts::Surface > createDiscSurface(std::span< const CaloDetDescrElement *const > detElements) const
Create a disc surface from the passed calo description elements.
std::unique_ptr< Acts::TrackingVolume > envelopeVolume(const Acts::GeometryContext &tgContext, const SurfaceMap_t &surfacesPerRegion) const
Create the envelope volume of the Calo Tracking Geometry.
std::array< std::vector< std::shared_ptr< Acts::Surface > >, Acts::toUnderlying(caloRegion::nRegions)> SurfaceMap_t
Vector of surfaces per calorimeter region.
std::shared_ptr< Acts::Surface > createCylinderSurface(std::span< const CaloDetDescrElement *const > detElements) const
Create a cylinder surface from a list of Calo detector description elements.
std::array< DetElVec_t, CaloSampling::getNumberOfSamplings()> DetElementMap_t
Vector of Detector Elements per calorimeter layer.
virtual StatusCode initialize() override final
std::shared_ptr< Acts::BlueprintNode > buildBlueprintNode(const Acts::GeometryContext &gctx, std::shared_ptr< Acts::BlueprintNode > &&childNode) override
CaloSampling::CaloSample CaloSample
Definition CaloCell_ID.h:53
This class groups all DetDescr information related to a CaloCell.
calo_element_range element_range() const
Range over element vector.
This class provides the client interface for accessing the detector description information common to...
static std::string getSamplingName(CaloSample theSample)
Returns a string (name) for each CaloSampling.
static constexpr unsigned int getNumberOfSamplings()
Get number of available samplings.
Define the volume parts of the GeometryIdentifier for each ATLAS subsystem centrally.
constexpr std::size_t s_caloEndcapAId
constexpr std::size_t s_caloEnvelopeID
Volume Ids ofthe Calorimeter.
constexpr std::size_t s_caloBarrelId
constexpr std::size_t s_caloEndcapCId
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
CaloBlueprintNodeBuilder::DetElementMap_t DetElementMap_t
CaloBlueprintNodeBuilder::SurfaceMap_t SurfaceMap_t
Eigen::Matrix< double, 3, 1 > Vector3D
Amg::Isometry3D getTranslateZ3D(const double Z)
: Returns a shift transformation along the z-axis
IMessageSvc * getMessageSvc(bool quiet=false)