ATLAS Offline Software
Loading...
Searching...
No Matches
ItkBlueprintNodeBuilder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5// This absolutely needs to go first to ensure Eigen plugin is loaded
7//
8
9#include "Acts/Definitions/Units.hpp"
10#include "Acts/Geometry/Blueprint.hpp"
11#include "Acts/Geometry/BlueprintNode.hpp"
12#include "Acts/Geometry/ContainerBlueprintNode.hpp"
13#include "Acts/Geometry/CylinderVolumeBounds.hpp"
14#include "Acts/Geometry/Extent.hpp"
15#include "Acts/Geometry/GeometryIdentifierBlueprintNode.hpp"
16#include "Acts/Geometry/LayerBlueprintNode.hpp"
17#include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp"
18#include "Acts/Geometry/ProtoLayer.hpp"
19#include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
20#include "Acts/Geometry/PadBlueprintNode.hpp"
21#include "Acts/Navigation/SurfaceArrayNavigationPolicy.hpp"
22#include "Acts/Navigation/CylinderNavigationPolicy.hpp"
23#include "Acts/Navigation/TryAllNavigationPolicy.hpp"
24#include "Acts/Surfaces/SurfaceArray.hpp"
25#include "Acts/Utilities/AxisDefinitions.hpp"
26#include "Acts/Utilities/AxisSpec.hpp"
27#include <cstddef>
28#include <format>
29#include <ranges>
30
31#include "Acts/Geometry/VolumeResizeStrategy.hpp"
37
38using namespace Acts;
39using namespace Acts::Experimental;
40using namespace Acts::UnitLiterals;
41
42namespace {
43
44using enum Acts::CylinderVolumeBounds::Face;
45using enum Acts::AxisDirection;
46using enum Acts::SurfaceArrayNavigationPolicy::LayerType;
47using AttachmentStrategy = Acts::VolumeAttachmentStrategy;
48using ResizeStrategy = Acts::VolumeResizeStrategy;
49using namespace ActsTrk::detail::GeoVolIds;
50
51
52// Helper struct to keep ProtoLayer and its associated surfaces together
53struct LayerData {
54 Acts::ProtoLayer protoLayer;
55 std::vector<std::shared_ptr<Acts::Surface>> surfaces;
56
57 LayerData(const Acts::GeometryContext& gctx,
58 std::vector<std::shared_ptr<Acts::Surface>> surfs)
59 : protoLayer(gctx, Acts::unpackConstSmartPointers(surfs)),
60 surfaces(std::move(surfs)) {}
61};
62
63// Helper function to merge layers that overlap in z
64std::vector<LayerData> mergeLayers(const Acts::GeometryContext& gctx,
65 std::vector<LayerData> layers) {
66 using enum Acts::AxisDirection;
67
68 std::vector<LayerData> mergedLayers;
69 if (layers.empty()) {
70 return mergedLayers;
71 }
72
73 mergedLayers.push_back(std::move(layers.front()));
74
75 for (size_t i = 1; i < layers.size(); i++) {
76 auto& current = layers[i];
77 auto& prev = mergedLayers.back();
78
79 // Check if they overlap in z
80 bool overlap =
81 (current.protoLayer.min(AxisZ) <= prev.protoLayer.max(AxisZ) &&
82 current.protoLayer.max(AxisZ) >= prev.protoLayer.min(AxisZ));
83
84 if (overlap) {
85 // Merge surfaces
86 std::vector<std::shared_ptr<Acts::Surface>> mergedSurfaces;
87 mergedSurfaces.reserve(current.surfaces.size() + prev.surfaces.size());
88 mergedSurfaces.insert(mergedSurfaces.end(), current.surfaces.begin(),
89 current.surfaces.end());
90 mergedSurfaces.insert(mergedSurfaces.end(), prev.surfaces.begin(),
91 prev.surfaces.end());
92
93 mergedLayers.pop_back();
94 mergedLayers.emplace_back(gctx, std::move(mergedSurfaces));
95 auto& merged = mergedLayers.back();
96 merged.protoLayer.envelope[AxisR] = current.protoLayer.envelope[AxisR];
97 merged.protoLayer.envelope[AxisZ] = current.protoLayer.envelope[AxisZ];
98 } else {
99 mergedLayers.push_back(std::move(current));
100 }
101 }
102
103 return mergedLayers;
104}
105
106void addStripBarrelLayer(
107 Acts::BlueprintNode& parent, std::size_t ilayer,
108 const std::vector<std::shared_ptr<Acts::Surface>>& surfaces) {
109 using enum Acts::SurfaceArrayNavigationPolicy::LayerType;
110 using enum Acts::CylinderVolumeBounds::Face;
111 using enum Acts::AxisDirection;
112
113 auto addLayer = [ilayer, &surfaces](auto& node) {
114 node.addLayer(std::format("Strip_Brl_{:}", ilayer), [&](auto& layer) {
115 layer.setNavigationPolicyFactory(
116 Acts::NavigationPolicyFactory{}
117 .add<Acts::SurfaceArrayNavigationPolicy>(
118 Acts::SurfaceArrayNavigationPolicy::Config{
119 .layerType = Cylinder, .bins = {0, 0}, .numberOfBinsFactor = 5.0})
120 .add<Acts::CylinderNavigationPolicy>()
121 .asUniquePtr());
122
123 layer.setSurfaces(surfaces);
124 layer.setEnvelope(Acts::ExtentEnvelope{{
125 .z = {5_mm, 5_mm},
126 .r = {2_mm, 2_mm},
127 }});
128 });
129 };
130
131 // Keep material on each layer's OuterCylinder (the lower-r side of the gap
132 // above it); drop the InnerCylinder so it can fuse with the inner
133 // neighbour's kept OuterCylinder when the inter-layer gap is collapsed. The
134 // innermost layer also drops its InnerCylinder: it is expanded inward onto
135 // the StripMaterial inner shell (382.5), whose full-z InnerCylinder material
136 // (3mm below) is the surviving surface, so keeping the layer's own
137 // InnerCylinder would double-fuse. Surviving material sits at the
138 // lower-radius side of each collapsed gap.
139 // Layer 3 carries no material; skip the MaterialDesignator wrapper to avoid
140 // empty-designator warnings and call addLayer directly on the parent.
141 if (ilayer < 3) {
142 const std::string materialName =
143 "Strip_Brl_" + std::to_string(ilayer) + "_Material";
144 parent.addMaterial(materialName,
145 [&addLayer, &materialName](auto& lmat) {
146 lmat.configureFace(
147 OuterCylinder,
148 AxisSpec::DeferredEquidistant(50, AxisRPhi),
149 AxisSpec::DeferredEquidistant(100, AxisZ),
150 materialName);
151 addLayer(lmat);
152 });
153 } else {
154 addLayer(parent);
155 }
156}
157
158void addStripEndcapLayer(
159 Acts::BlueprintNode& parent, const std::string& name,
160 const std::vector<std::shared_ptr<Acts::Surface>>& surfaces, int bec) {
161 using enum Acts::SurfaceArrayNavigationPolicy::LayerType;
162 using enum Acts::CylinderVolumeBounds::Face;
163 using enum Acts::AxisDirection;
164
165 // Keep material only on the disk's outward disc; the inward disc is dropped
166 // so it can fuse with the neighbour's outward disc when the inter-disk gap is
167 // collapsed. The innermost disk also drops its inward disc: it is expanded
168 // toward the barrel and fuses with the barrel end disc (the kept, smaller-|z|
169 // surface). Surviving material sits at the smaller-|z| side of each gap.
170 const auto outwardDisc = (bec > 0) ? PositiveDisc : NegativeDisc;
171 const std::string materialName = name + "_Material";
172 parent.addMaterial(materialName, [&](auto& mat) {
173 mat.configureFace(outwardDisc, AxisSpec::DeferredEquidistant(50, AxisR),
174 AxisSpec::DeferredEquidistant(50, AxisPhi),
175 materialName);
176
177 mat.addLayer(name, [&surfaces](auto& layer) {
178 layer.setNavigationPolicyFactory(
179 Acts::NavigationPolicyFactory{}
180 .add<Acts::SurfaceArrayNavigationPolicy>(
181 Acts::SurfaceArrayNavigationPolicy::Config{.layerType = Disc,
182 .bins = {0, 0}, .numberOfBinsFactor = 5.0})
183 .add<Acts::CylinderNavigationPolicy>()
184 .asUniquePtr());
185
186 layer.setSurfaces(surfaces);
187 layer.setEnvelope(Acts::ExtentEnvelope{{
188 .z = {0.1_mm, 0.1_mm},
189 .r = {2_mm, 2_mm},
190 }});
191 });
192 });
193}
194} // namespace
195
196namespace ActsTrk {
197
199 // Retrieve the detector managers from the detector store for enabled systems
200 if (m_buildPixel) {
201 ATH_CHECK(detStore()->retrieve(m_itkPixelMgr, "ITkPixel"));
202 }
203 if (m_buildStrip) {
204 ATH_CHECK(detStore()->retrieve(m_itkStripMgr, "ITkStrip"));
205 }
206 m_elementStore = std::make_shared<ActsElementVector>();
207 return StatusCode::SUCCESS;
208}
209
210std::shared_ptr<Acts::BlueprintNode>
212 const Acts::GeometryContext& gctx,
213 std::shared_ptr<Acts::BlueprintNode>&& childNode) {
214
215 auto itkNode =
216 std::make_shared<Acts::CylinderContainerBlueprintNode>(
217 "itkNode", AxisZ);
218 itkNode->setAttachmentStrategy(AttachmentStrategy::Gap);
219 itkNode->setResizeStrategy(ResizeStrategy::Gap);
220
221 auto& itk = itkNode->addCylinderContainer("ItkNodeMain", AxisR);
222 itk.setAttachmentStrategy(AttachmentStrategy::Gap);
223 itk.setResizeStrategy(ResizeStrategy::Gap);
224
225 itk.addMaterial("ItkNodeMain_Material", [&](auto& mat) {
226 if (m_buildStrip) {
227 mat.configureFace(NegativeDisc,
228 AxisSpec::DeferredEquidistant(50, AxisR),
229 AxisSpec::DeferredEquidistant(50, AxisPhi),
230 "ItkNodeMain_Material_NegativeDisc");
231 mat.configureFace(PositiveDisc,
232 AxisSpec::DeferredEquidistant(50, AxisR),
233 AxisSpec::DeferredEquidistant(50, AxisPhi),
234 "ItkNodeMain_Material_PositiveDisc");
235 }
236
237 auto& innerContainer = mat.addCylinderContainer("ITkInnerContainer", AxisR);
238 // The radial dead bands between the R-stacked sub-detectors (beampipe /
239 // InnerPixel / OuterPixel / Strip) are genuine empty space, but the default
240 // Midpoint attachment splits each band at its midpoint and lets BOTH
241 // neighbours fill their half with a ResizeStrategy::Gap volume -> two thin
242 // gap volumes per interface. Second makes a single neighbour own the whole
243 // band -> one gap per interface. Material is unaffected: each sub-detector
244 // keeps ResizeStrategy::Gap, so its material-bearing shell stays frozen at
245 // its true edge and only the empty band is merged.
246 innerContainer.setAttachmentStrategy(AttachmentStrategy::Second);
247 innerContainer.setResizeStrategy(ResizeStrategy::Gap);
248 // Beam pipe is passed in as an optional child from BeamPipeBlueprintNodeBuilder
249 if (childNode) {
250 innerContainer.addChild(std::move(childNode));
251 }
252
253 if (m_buildPixel) {
254 buildItkPixelBlueprintNode(gctx, innerContainer);
255 }
256 if (m_buildStrip) {
257 buildItkStripBlueprintNode(gctx, innerContainer);
258 }
259 });
261 Acts::ExtentEnvelope envelope{};
262 envelope[AxisDirection::AxisZ] = {20_mm, 20_mm};
263 envelope[AxisDirection::AxisR] = {1_mm, 2_mm};
264 auto padNode = std::make_shared<Acts::PadBlueprintNode>("ITkEnvelope", envelope);
265 padNode->addChild(itkNode);
267 auto idNode = std::make_shared<Acts::GeometryIdentifierBlueprintNode>();
268 idNode->setDirectChildVolumeIdTo(s_ITkEnvelopeId);
269 idNode->addChild(std::move(padNode));
270 return idNode;
271}
272
274 const Acts::GeometryContext& gctx,
275 Acts::BlueprintNode& node) {
276
277 // Get ITkPixel parameters from detector manager
278 if (!m_itkPixelMgr) {
279 ATH_MSG_ERROR("ITkPixel manager not available");
280 throw std::runtime_error("ITkPixel manager not available");
281 }
282
283 ATH_MSG_DEBUG("Detector manager has "
284 << m_itkPixelMgr->getDetectorElementCollection()->size()
285 << " elements");
286
287 std::vector<std::shared_ptr<ActsDetectorElement>> elements;
288
290 for (const auto* element : *m_itkPixelMgr->getDetectorElementCollection()) {
291 const InDetDD::SiDetectorElement* siDetElement =
292 dynamic_cast<const InDetDD::SiDetectorElement*>(element);
293 if (siDetElement == nullptr) {
294 ATH_MSG_ERROR("Detector element was nullptr");
295 throw std::runtime_error{"Corrupt detector element collection"};
296 }
297 elements.push_back(std::make_shared<ActsDetectorElement>(*siDetElement));
298 }
299 ATH_MSG_VERBOSE("Retrieved " << elements.size() << " elements");
300
301 // Copy to service level store to extend lifetime
302 m_elementStore->vector().insert(m_elementStore->vector().end(),
303 elements.begin(), elements.end());
304
305 // Inner pixel: 2 innermost barrel layers + inner endcap disks
306 auto& innerPixel = node.addCylinderContainer("InnerPixel", AxisR);
307 innerPixel.setAttachmentStrategy(AttachmentStrategy::Gap);
308 // Inner edge Expand: extend all InnerPixel cylinders (endcaps + barrel_0)
309 // inward onto the beam pipe, removing InnerPixel::Gap1. The InnerCylinder
310 // material (30.9) is dropped below so the expanded face fuses cleanly with
311 // the beam pipe outer material (23.9, the kept lower-r surface). Outer edge
312 // stays Gap.
313 innerPixel.setResizeStrategies(ResizeStrategy::Expand, ResizeStrategy::Gap);
314
315 innerPixel.addMaterial("InnerPixelMaterial", [&](auto& mat) {
316 mat.configureFace(OuterCylinder,
317 AxisSpec::DeferredEquidistant(50, AxisRPhi),
318 AxisSpec::DeferredEquidistant(100, AxisZ),
319 "InnerPixelMaterial");
320
321 auto& innerPixelContainer = mat.addCylinderContainer("InnerPixel", AxisZ);
322
323 // Add barrel container
324 auto& barrelGeoId = innerPixelContainer.withGeometryIdentifier();
325 barrelGeoId.setAllVolumeIdsTo(s_innerPixelVolumeId)
326 .incrementLayerIds(1)
327 .sortBy([](auto& a, auto& b) {
328 auto& boundsA =
329 dynamic_cast<const Acts::CylinderVolumeBounds&>(a.volumeBounds());
330 auto& boundsB =
331 dynamic_cast<const Acts::CylinderVolumeBounds&>(b.volumeBounds());
332
333 using enum Acts::CylinderVolumeBounds::BoundValues;
334 double aMidR = (boundsA.get(eMinR) + boundsA.get(eMaxR)) / 2.0;
335 double bMidR = (boundsB.get(eMinR) + boundsB.get(eMaxR)) / 2.0;
336
337 return aMidR < bMidR;
338 });
339
340 auto& brl_mat =
341 barrelGeoId.addMaterial("InnerPixel_Material", [&](auto& material) {
342 material.configureFace(NegativeDisc,
343 AxisSpec::DeferredEquidistant(50, AxisR),
344 AxisSpec::DeferredEquidistant(50, AxisPhi),
345 "InnerPixel_Material_NegativeDisc");
346 material.configureFace(PositiveDisc,
347 AxisSpec::DeferredEquidistant(50, AxisR),
348 AxisSpec::DeferredEquidistant(50, AxisPhi),
349 "InnerPixel_Material_PositiveDisc");
350 });
351 auto& barrel = brl_mat.addCylinderContainer("InnerPixel_Brl", AxisR);
352
353 // Barrel layers carry material only on their OuterCylinder face. Attaching
354 // with Second extends the outer (higher-R) neighbour's material-free inner
355 // face inward to meet each layer's outer face, collapsing the inter-layer
356 // gap volumes while leaving every material surface fixed.
357 barrel.setAttachmentStrategy(AttachmentStrategy::Second);
358 // Inner edge Expand: Brl_0's inner cylinder is material-free, so it extends
359 // inward onto the beam pipe (the InnerPixel container also expands inward,
360 // so the endcaps reach the beam pipe too) removing the barrel's share of the
361 // InnerPixel beam-pipe gap.
362 // Outer edge Expand: the outermost layer grows out onto the InnerPixel
363 // OuterCylinder shell (123.7), removing InnerPixel_Brl::Gap1 (the gap after
364 // the last barrel layer). Its own OuterCylinder material is dropped below
365 // (kept only up to the second-outermost layer) so the expanded face fuses
366 // cleanly with the container shell (the kept surface).
367 barrel.setResizeStrategies(ResizeStrategy::Expand, ResizeStrategy::Expand);
368
369 std::map<int, std::vector<std::shared_ptr<Acts::Surface>>> layers{};
370
371 for (auto& element : elements) {
372 IdentityHelper id = element->identityHelper();
373 if (id.bec() != 0) {
374 continue;
375 }
376
377 if (id.layer_disk() >= 2) {
378 continue;
379 }
380
381 int elementLayer = id.layer_disk();
382 layers[elementLayer].push_back(element->surface().getSharedPtr());
383 }
384
385 ATH_MSG_DEBUG("Adding " << layers.size() << " layers to InnerPixel barrel");
386
387 const int outermostBrlLayer = layers.rbegin()->first;
388 for (const auto& [ilayer, surfaces] : layers) {
389 ATH_MSG_DEBUG("- Layer " << ilayer << " has " << surfaces.size()
390 << " surfaces");
391
392 auto configureLayer = [&](auto& node) {
393 auto& layer = node.addLayer(std::format("InnerPixel_Brl_{}", ilayer));
394 layer.setNavigationPolicyFactory(
395 Acts::NavigationPolicyFactory{}
396 .add<Acts::SurfaceArrayNavigationPolicy>(
397 Acts::SurfaceArrayNavigationPolicy::Config{
398 .layerType = Cylinder,
399 .bins = {0, 0}, .numberOfBinsFactor = 5.0})
400 .add<Acts::CylinderNavigationPolicy>()
401 .asUniquePtr());
402 layer.setSurfaces(surfaces);
403 layer.setEnvelope(Acts::ExtentEnvelope{{
404 .z = {5_mm, 5_mm},
405 .r = {2_mm, 2_mm},
406 }});
407 };
408
409 // Outermost layer expands onto the container OuterCylinder shell, so
410 // drop its own OuterCylinder; skip the MaterialDesignator wrapper to
411 // avoid empty-designator warnings.
412 if (ilayer != outermostBrlLayer) {
413 const std::string materialName =
414 std::format("InnerPixel_Brl_{}_Material", ilayer);
415 barrel.addMaterial(
416 materialName,
417 [&](auto& lmat) {
418 lmat.configureFace(OuterCylinder,
419 AxisSpec::DeferredEquidistant(50, AxisRPhi),
420 AxisSpec::DeferredEquidistant(100, AxisZ),
421 materialName);
422 configureLayer(lmat);
423 });
424 } else {
425 configureLayer(barrel);
426 }
427 }
428
429 // Add endcap containers
430 for (int bec : {-2, 2}) {
431 std::string s = bec > 0 ? "p" : "n";
432 auto& ecGeoId = innerPixelContainer.withGeometryIdentifier();
433 ecGeoId.setAllVolumeIdsTo(s_innerPixelVolumeId + std::floor(bec / 2))
434 .incrementLayerIds(1);
435 auto& ec = ecGeoId.addCylinderContainer(std::format("InnerPixel_{:}EC", s), AxisZ);
436 // Collapse the inter-disk gaps by extending the outer disk inward onto
437 // its neighbour (Second for +z, First for -z), keeping the material
438 // surface at the smaller-|z| side of each gap (see per-disk material
439 // handling below).
440 ec.setAttachmentStrategy(bec > 0 ? AttachmentStrategy::Second
441 : AttachmentStrategy::First);
442 // Barrel-facing edge Expand: extend the innermost disk toward the barrel
443 // (smaller |z|), removing the barrel<->endcap transition gap. That edge is
444 // the low-z (inner) side for +z endcaps and the high-z (outer) side for -z
445 // endcaps. The far (z-end) edge stays Gap. The innermost disk's inward disc
446 // material is dropped below so the expanded face fuses with the barrel end
447 // disc (the kept, smaller-|z| surface).
448 ec.setResizeStrategies(
449 bec > 0 ? ResizeStrategy::Expand : ResizeStrategy::Gap,
450 bec > 0 ? ResizeStrategy::Gap : ResizeStrategy::Expand);
451
452 std::map<std::tuple<int, int, int>,
453 std::vector<std::shared_ptr<Acts::Surface>>>
454 initialLayers{};
455
456 for (auto& element : elements) {
457 IdentityHelper id = element->identityHelper();
458 if (id.bec() * bec <= 0) {
459 continue;
460 }
461
462 if (id.layer_disk() >= 3) {
463 continue;
464 }
465
466 std::tuple<int, int, int> key{id.bec(), id.layer_disk(),
467 id.eta_module()};
468 initialLayers[key].push_back(element->surface().getSharedPtr());
469 }
470
471 ATH_MSG_DEBUG("Found " << initialLayers.size()
472 << " initial layers to InnerPixel " << s << "EC");
473
474 std::vector<LayerData> protoLayers;
475 protoLayers.reserve(initialLayers.size());
476
477 for (const auto& [key, surfaces] : initialLayers) {
478 auto& layer = protoLayers.emplace_back(gctx, surfaces);
479 layer.protoLayer.envelope[AxisR] = {2_mm, 2_mm};
480 layer.protoLayer.envelope[AxisZ] = {1_mm, 1_mm};
481 }
482
483 std::ranges::sort(protoLayers,
484 [](const LayerData& a, const LayerData& b) {
485 return std::abs(a.protoLayer.medium(AxisZ)) <
486 std::abs(b.protoLayer.medium(AxisZ));
487 });
488
489 ATH_MSG_DEBUG("Found " << protoLayers.size() << " initial layers");
490
491 std::vector<LayerData> mergedLayers;
493 mergedLayers = mergeLayers(gctx, std::move(protoLayers));
494 } else {
495 mergedLayers = std::move(protoLayers);
496 }
497
498 ATH_MSG_DEBUG("After merging: " << mergedLayers.size() << " layers");
499
500 for (const auto [key, pl] : Acts::enumerate(mergedLayers)) {
501 ATH_MSG_DEBUG("- Layer " << key << " has " << pl.surfaces.size()
502 << " surfaces");
503
504 pl.protoLayer.medium(AxisZ);
505 auto layerName = std::format("InnerPixel_{}EC_{}", key, s);
506
507 auto addLayer = [&layerName, &pl](auto& parent) {
508 auto& layer = parent.addLayer(layerName);
509
510 layer.setNavigationPolicyFactory(
511 Acts::NavigationPolicyFactory{}
512 .add<Acts::SurfaceArrayNavigationPolicy>(
513 Acts::SurfaceArrayNavigationPolicy::Config{
514 .layerType = Disc,
515 .bins = {0, 0}, .numberOfBinsFactor = 5.0})
516 .add<Acts::CylinderNavigationPolicy>()
517 .asUniquePtr());
518
519 layer.setSurfaces(pl.surfaces);
520 layer.setEnvelope(Acts::ExtentEnvelope{{
521 .z = {1_mm, 1_mm},
522 .r = {2_mm, 2_mm},
523 }});
524 };
525
526 ATH_MSG_VERBOSE("Add inner pixel layer"
527 << key << " / " << mergedLayers.size()
528 << " at z = " << pl.protoLayer.medium(AxisZ));
529 ATH_MSG_VERBOSE("Adding material for layer " << layerName);
530 // Keep material only on each disk's outward disc (away from the IP);
531 // the inward disc is dropped so it can fuse with the adjacent disk's
532 // kept outward disc without the "both portals carry material" fuse
533 // error. The innermost disk drops its inward disc too: it is expanded
534 // toward the barrel and fuses with the barrel end disc (the kept,
535 // smaller-|z| surface).
536 const auto outwardDisc = (bec > 0) ? PositiveDisc : NegativeDisc;
537 const std::string materialName = layerName + "_Material";
538 ec.addMaterial(materialName, [&](auto& lmat) {
539 lmat.configureFace(outwardDisc,
540 AxisSpec::DeferredEquidistant(50, AxisR),
541 AxisSpec::DeferredEquidistant(50, AxisPhi),
542 materialName);
543 addLayer(lmat);
544 });
545 }
546 }
547 });
548
549 // Outer pixel: 3 outer barrel layers + outer endcaps
550 auto& outerPixel = node.addCylinderContainer("OuterPixel", AxisR);
551 outerPixel.setAttachmentStrategy(AttachmentStrategy::Gap);
552 // Inner edge Expand: extend all OuterPixel cylinders inward onto the
553 // InnerPixel outer shell (123.7), removing OuterPixel::Gap1. The InnerCylinder
554 // material (147.7) is dropped below so the expanded face fuses cleanly with
555 // the InnerPixel OuterCylinder (123.7, the kept lower-r surface). Outer edge
556 // stays Gap (the OuterPixel/Strip interface is handled from the Strip side).
557 outerPixel.setResizeStrategies(ResizeStrategy::Expand, ResizeStrategy::Gap);
558
559 outerPixel.addMaterial("OuterPixelMaterial", [&](auto& opmat) {
560 opmat.configureFace(OuterCylinder,
561 AxisSpec::DeferredEquidistant(50, AxisRPhi),
562 AxisSpec::DeferredEquidistant(100, AxisZ),
563 "OuterPixelMaterial");
564
565 auto& outerPixelContainer = opmat.addCylinderContainer("OuterPixel", AxisZ);
566
567 auto& barrelGeoId = outerPixelContainer.withGeometryIdentifier();
568 barrelGeoId.setAllVolumeIdsTo(s_outerPixelVolumeId).incrementLayerIds(1);
569
570 auto& brl_mat =
571 barrelGeoId.addMaterial("OuterPixel_Material", [&](auto& bmat) {
572 bmat.configureFace(NegativeDisc,
573 AxisSpec::DeferredEquidistant(50, AxisR),
574 AxisSpec::DeferredEquidistant(50, AxisPhi),
575 "OuterPixel_Material_NegativeDisc");
576 bmat.configureFace(PositiveDisc,
577 AxisSpec::DeferredEquidistant(50, AxisR),
578 AxisSpec::DeferredEquidistant(50, AxisPhi),
579 "OuterPixel_Material_PositiveDisc");
580 });
581 auto& barrel = brl_mat.addCylinderContainer("OuterPixel_Brl", AxisR);
582
583 // See InnerPixel_Brl: layers carry material only on OuterCylinder, so
584 // Second attachment removes the inter-layer gaps without moving any
585 // material surface.
586 barrel.setAttachmentStrategy(AttachmentStrategy::Second);
587 // Inner edge Expand: the innermost layer's inner cylinder is material-free,
588 // so it extends inward onto the container inner shell and fuses with the
589 // OuterPixelMaterial InnerCylinder (one material side, nothing moves).
590 // Outer edge Expand: the outermost layer grows out onto the OuterPixel
591 // OuterCylinder shell (316.7), removing the gap after the last barrel layer.
592 // Its own OuterCylinder material is dropped below so the expanded face fuses
593 // cleanly with the container shell (the kept surface).
594 barrel.setResizeStrategies(ResizeStrategy::Expand, ResizeStrategy::Expand);
595
596 std::map<int, std::vector<std::shared_ptr<Acts::Surface>>> layers{};
597
598 for (auto& element : elements) {
599 IdentityHelper id = element->identityHelper();
600 if (id.bec() != 0) {
601 continue;
602 }
603
604 if (id.layer_disk() <= 1) {
605 continue;
606 }
607
608 int elementLayer = id.layer_disk();
609 layers[elementLayer].push_back(element->surface().getSharedPtr());
610 }
611
612 ATH_MSG_DEBUG("Adding " << layers.size() << " layers to OuterPixel barrel");
613
614 const int outermostBrlLayer = layers.rbegin()->first;
615 for (const auto& [ilayer, surfaces] : layers) {
616 ATH_MSG_DEBUG("- Layer " << ilayer << " has " << surfaces.size()
617 << " surfaces");
618
619 auto configureLayer = [&](auto& node) {
620 auto& layer = node.addLayer(std::format("OuterPixel_Brl_{:}", ilayer));
621 layer.setNavigationPolicyFactory(
622 Acts::NavigationPolicyFactory{}
623 .add<Acts::SurfaceArrayNavigationPolicy>(
624 Acts::SurfaceArrayNavigationPolicy::Config{
625 .layerType = Cylinder,
626 .bins = {0, 0}, .numberOfBinsFactor = 5.0})
627 .add<Acts::CylinderNavigationPolicy>()
628 .asUniquePtr());
629 layer.setSurfaces(surfaces);
630 layer.setEnvelope(Acts::ExtentEnvelope{{
631 .z = {5_mm, 5_mm},
632 .r = {2_mm, 2_mm},
633 }});
634 };
635
636 // Outermost layer expands onto the container OuterCylinder shell, so
637 // drop its own OuterCylinder; skip the MaterialDesignator wrapper to
638 // avoid empty-designator warnings.
639 if (ilayer != outermostBrlLayer) {
640 const std::string materialName =
641 std::format("OuterPixel_Brl_{}_Material", ilayer);
642 barrel.addMaterial(
643 materialName,
644 [&](auto& lmat) {
645 lmat.configureFace(OuterCylinder,
646 AxisSpec::DeferredEquidistant(50, AxisRPhi),
647 AxisSpec::DeferredEquidistant(100, AxisZ),
648 materialName);
649 configureLayer(lmat);
650 });
651 } else {
652 configureLayer(barrel);
653 }
654 }
655
656 constexpr static auto addEndcapLayer = [](auto& parent, const auto& name,
657 const auto& surfaces) {
658 parent.addLayer(name, [&surfaces](auto& layer) {
659 layer.setNavigationPolicyFactory(
660 Acts::NavigationPolicyFactory{}
661 .add<Acts::SurfaceArrayNavigationPolicy>(
662 Acts::SurfaceArrayNavigationPolicy::Config{
663 .layerType = Disc, .bins = {0, 0}, .numberOfBinsFactor = 5.0})
664 .add<Acts::CylinderNavigationPolicy>()
665 .asUniquePtr());
666
667 layer.setSurfaces(surfaces);
668 });
669 };
670
671 for (int bec : {-2, 2}) {
672 const std::string s = bec > 0 ? "p" : "n";
673
674 auto& ec_outer_geoId = outerPixelContainer.withGeometryIdentifier();
675 ec_outer_geoId
676 .setAllVolumeIdsTo(s_outerPixelVolumeId + std::floor(bec / 2))
677 .incrementLayerIds(1);
678
679 auto& ec_outer =
680 ec_outer_geoId.addCylinderContainer(std::format("OuterPixel_{:}EC", s), AxisR);
681
682 // Three groups of disks stacked in R
683 std::array diskGroups{std::pair{3, 4}, std::pair{6, 5}, std::pair{7, 8}};
684
685 for (size_t idx = 0; idx < diskGroups.size(); ++idx) {
686 auto [disk1, disk2] = diskGroups[idx];
687
688 Acts::MaterialDesignatorBlueprintNode* material = nullptr;
689 if (idx < (diskGroups.size() - 1)) {
690 const std::string materialName =
691 "OuterPixel_" + s + "EC_" + std::to_string(idx) + "_Material";
692 material = &ec_outer.addMaterial(
693 materialName,
694 [&](auto& mat) {
695 mat.configureFace(OuterCylinder,
696 AxisSpec::DeferredEquidistant(50, AxisRPhi),
697 AxisSpec::DeferredEquidistant(100, AxisZ),
698 materialName);
699 });
700 }
701
702 auto& ec_stack =
703 material
704 ? material->addCylinderContainer(
705 std::format("OuterPixel_{:}EC_{:}", s, idx), AxisZ)
706 : ec_outer.addCylinderContainer(
707 std::format("OuterPixel_{:}EC_{:}", s, idx), AxisZ);
708
709 // Collapse inter-disk gaps by extending the outer disk inward (Second
710 // for +z, First for -z); material kept only on outward discs (see
711 // below), so surviving material sits at the smaller-|z| side of each gap.
712 ec_stack.setAttachmentStrategy(bec > 0 ? AttachmentStrategy::Second
713 : AttachmentStrategy::First);
714 // Barrel-facing edge Expand: extend the innermost disk of each R-group
715 // toward the barrel (smaller |z|), removing the barrel<->endcap gap; far
716 // (z-end) edge stays Gap. Innermost inward disc dropped below so the
717 // expanded face fuses with the barrel end disc (kept, smaller |z|).
718 ec_stack.setResizeStrategies(
719 bec > 0 ? ResizeStrategy::Expand : ResizeStrategy::Gap,
720 bec > 0 ? ResizeStrategy::Gap : ResizeStrategy::Expand);
721
722 std::map<std::tuple<int, int>,
723 std::vector<std::shared_ptr<Acts::Surface>>>
724 eta_rings;
725
726 for (auto& element : elements) {
727 IdentityHelper id = element->identityHelper();
728 if (id.bec() != bec ||
729 (id.layer_disk() != disk1 && id.layer_disk() != disk2)) {
730 continue;
731 }
732 std::tuple<int, int> key{id.layer_disk(), id.eta_module()};
733 eta_rings[key].push_back(element->surface().getSharedPtr());
734 }
735
736 ATH_MSG_DEBUG("Found " << eta_rings.size() << " eta rings in group "
737 << idx);
738
739 std::vector<std::vector<std::shared_ptr<Acts::Surface>>> sorted_rings;
740 sorted_rings.reserve(eta_rings.size());
741 for (const auto& [key, surfaces] : eta_rings) {
742 sorted_rings.push_back(surfaces);
743 }
744
745 std::ranges::sort(sorted_rings, [&gctx](const auto& a, const auto& b) {
746 Acts::ProtoLayer pl_a(gctx, Acts::unpackConstSmartPointers(a));
747 Acts::ProtoLayer pl_b(gctx, Acts::unpackConstSmartPointers(b));
748 return std::abs(pl_a.min(AxisZ)) < std::abs(pl_b.min(AxisZ));
749 });
750
751 for (size_t i = 0; i < sorted_rings.size(); ++i) {
752 const auto& surfaces = sorted_rings[i];
753 const std::string layerName = std::format("OuterPixel_{:}EC_{:}_{:}", s,idx, i);
754
755 const auto outwardDisc = (bec > 0) ? PositiveDisc : NegativeDisc;
756 const std::string materialName = layerName + "_Material";
757 ec_stack.addMaterial(materialName, [&](auto& mat) {
758 mat.configureFace(outwardDisc,
759 AxisSpec::DeferredEquidistant(50, AxisR),
760 AxisSpec::DeferredEquidistant(50, AxisPhi),
761 materialName);
762 addEndcapLayer(mat, layerName, surfaces);
763 });
764 }
765 }
766 }
767 });
768}
769
771 const Acts::GeometryContext& gctx,
772 Acts::BlueprintNode& node) {
773
774 // Get ITkStrip parameters from detector manager
775 if (!m_itkStripMgr) {
776 ATH_MSG_ERROR("ITkStrip manager not available");
777 throw std::runtime_error("ITkStrip manager not available");
778 }
779
780 ATH_MSG_DEBUG("Detector manager has "
781 << m_itkStripMgr->getDetectorElementCollection()->size()
782 << " elements");
783
784 std::vector<std::shared_ptr<ActsDetectorElement>> elements;
785
787 for (const auto* element : *m_itkStripMgr->getDetectorElementCollection()) {
788 const InDetDD::SiDetectorElement* siDetElement =
789 dynamic_cast<const InDetDD::SiDetectorElement*>(element);
790 if (siDetElement == nullptr) {
791 ATH_MSG_ERROR("Detector element was nullptr");
792 throw std::runtime_error{"Corrupt detector element collection"};
793 }
794 elements.push_back(std::make_shared<ActsDetectorElement>(*siDetElement));
795 }
796 ATH_MSG_VERBOSE("Retrieved " << elements.size() << " elements");
797
798 // Copy to service level store to extend lifetime
799 m_elementStore->vector().insert(m_elementStore->vector().end(),
800 elements.begin(), elements.end());
801
802 auto& strip = node.addCylinderContainer("Strip", AxisR);
803 strip.setAttachmentStrategy(AttachmentStrategy::Gap);
804 // Inner edge Expand: extend all Strip cylinders inward onto the OuterPixel
805 // outer shell (316.7), removing Strip::Gap1. The InnerCylinder material
806 // (382.5) is dropped below so the expanded face fuses cleanly with the
807 // OuterPixel OuterCylinder (316.7, the kept lower-r surface). Outer edge stays
808 // Gap (Strip::Gap2, the resize gap up to the ItkNodeMain envelope, carries
809 // the Strip outer material and must not move).
810 strip.setResizeStrategies(ResizeStrategy::Expand, ResizeStrategy::Gap);
811
812 strip.addMaterial("StripMaterial", [&](auto& mat) {
813 mat.configureFace(OuterCylinder,
814 AxisSpec::DeferredEquidistant(50, AxisRPhi),
815 AxisSpec::DeferredEquidistant(100, AxisZ),
816 "StripMaterial");
817
818 auto& stripContainer = mat.addCylinderContainer("Strip", AxisZ);
819
820 // Add barrel container
821 stripContainer.withGeometryIdentifier([this, &elements](auto& geoId) {
822 geoId.setAllVolumeIdsTo(s_stripVolumeId).incrementLayerIds(1);
823
824 auto& brl_mat =
825 geoId.addMaterial("Strip_Brl_Material", [&](auto& material) {
826 material.configureFace(NegativeDisc,
827 AxisSpec::DeferredEquidistant(50, AxisR),
828 AxisSpec::DeferredEquidistant(50, AxisPhi),
829 "Strip_Brl_Material_NegativeDisc");
830 material.configureFace(PositiveDisc,
831 AxisSpec::DeferredEquidistant(50, AxisR),
832 AxisSpec::DeferredEquidistant(50, AxisPhi),
833 "Strip_Brl_Material_PositiveDisc");
834 });
835 brl_mat.addCylinderContainer(
836 "Strip_Brl", AxisR, [this, &elements](auto& barrel) {
837 // Collapse the inter-layer gaps by extending the outer layer inward
838 // (Second); material kept only on OuterCylinder faces (see
839 // addStripBarrelLayer) so it stays at the lower-radius side.
840 barrel.setAttachmentStrategy(AttachmentStrategy::Second);
841 // Inner edge: Expand the innermost layer onto the StripMaterial
842 // inner shell (382.5), removing Strip_Brl::Gap1. The layer's own
843 // InnerCylinder material (385.5, redundant with the full-z shell
844 // 3mm below it) is dropped in addStripBarrelLayer so the expanded
845 // face fuses cleanly (one material side). Outer edge stays Gap
846 // (invariant, see OuterPixel_Brl).
847 barrel.setResizeStrategies(ResizeStrategy::Expand,
848 ResizeStrategy::Gap);
849
850 std::map<int, std::vector<std::shared_ptr<Acts::Surface>>> layers{};
851
852 for (auto& element : elements) {
853 IdentityHelper id = element->identityHelper();
854 if (id.bec() != 0) {
855 continue;
856 }
857
858 int elementLayer = id.layer_disk();
859 layers[elementLayer].push_back(element->surface().getSharedPtr());
860 }
861
862 ATH_MSG_DEBUG("Adding " << layers.size()
863 << " layers to Strip barrel");
864
865 for (const auto& [ilayer, surfaces] : layers) {
866 ATH_MSG_DEBUG("- Layer " << ilayer << " has " << surfaces.size()
867 << " surfaces");
868 addStripBarrelLayer(barrel, ilayer, surfaces);
869 }
870 });
871 });
872
873 // Add endcap containers
874 for (int bec : {-2, 2}) {
875 const std::string s = bec > 0 ? "p" : "n";
876
877 std::map<int, std::vector<std::shared_ptr<Acts::Surface>>> layers{};
878
879 for (auto& element : elements) {
880 IdentityHelper id = element->identityHelper();
881 if (id.bec() * bec <= 0) {
882 continue;
883 }
884
885 layers[id.layer_disk()].push_back(element->surface().getSharedPtr());
886 }
887
888 ATH_MSG_DEBUG("Found " << layers.size() << " layers in Strip " << s
889 << "EC");
890
891 std::vector<std::vector<std::shared_ptr<Acts::Surface>>> sorted_layers;
892 sorted_layers.reserve(layers.size());
893 for (const auto& [key, surfaces] : layers) {
894 sorted_layers.push_back(surfaces);
895 }
896
897 std::sort(sorted_layers.begin(), sorted_layers.end(),
898 [&gctx](const auto& a, const auto& b) {
899 Acts::ProtoLayer pl_a(gctx, Acts::unpackConstSmartPointers(a));
900 Acts::ProtoLayer pl_b(gctx, Acts::unpackConstSmartPointers(b));
901 return std::abs(pl_a.min(AxisZ)) < std::abs(pl_b.min(AxisZ));
902 });
903
904 stripContainer.withGeometryIdentifier([&sorted_layers, bec,
905 &s](auto& geoId) {
906 geoId.setAllVolumeIdsTo(s_stripVolumeId + std::floor(bec / 2))
907 .incrementLayerIds(1);
908
909 geoId.addCylinderContainer(
910 std::format("Strip_{:}_EC", s), AxisZ, [&sorted_layers, &s, bec](auto& ec) {
911 // Collapse inter-disk gaps; material kept on outward discs only
912 // (see addStripEndcapLayer), so it stays at the smaller-|z| side.
913 ec.setAttachmentStrategy(bec > 0 ? AttachmentStrategy::Second
914 : AttachmentStrategy::First);
915 // Barrel-facing edge Expand: extend the innermost disk toward the
916 // barrel (smaller |z|), removing the barrel<->endcap gap; far edge
917 // stays Gap. Innermost inward disc dropped (addStripEndcapLayer) so
918 // the expanded face fuses with the barrel end disc.
919 ec.setResizeStrategies(
920 bec > 0 ? ResizeStrategy::Expand : ResizeStrategy::Gap,
921 bec > 0 ? ResizeStrategy::Gap : ResizeStrategy::Expand);
922
923 for (size_t i = 0; i < sorted_layers.size(); ++i) {
924 const auto& surfaces = sorted_layers[i];
925 auto layerName = std::format("Strip_{:}EC_{:}", s, i);
926 addStripEndcapLayer(ec, layerName, surfaces, bec);
927 }
928 });
929 });
930 };
931 });
932
933 // Explicit spacer volume beyond the strip detector's natural outer radius.
934 // Without it, the outer cylinder carrying the strip container material
935 // coincides with the ITk node boundary, causing volume merging issues.
936 std::vector<std::shared_ptr<Acts::Surface>> allStripSurfaces;
937 allStripSurfaces.reserve(elements.size());
938 for (auto& element : elements) {
939 allStripSurfaces.push_back(element->surface().getSharedPtr());
940 }
941 Acts::ProtoLayer stripExtent(gctx, Acts::unpackConstSmartPointers(allStripSurfaces));
942
943 constexpr double spacerClearance = 5_mm;
944 constexpr double spacerThickness = 5_mm;
945 double spacerRmin = stripExtent.max(AxisR) + spacerClearance;
946 double spacerHalfZ = std::max(std::abs(stripExtent.min(AxisZ)),
947 std::abs(stripExtent.max(AxisZ))) +
948 spacerClearance;
949
950 strip.addStaticVolume(
951 Acts::Transform3::Identity(),
952 std::make_shared<Acts::CylinderVolumeBounds>(
953 spacerRmin, spacerRmin + spacerThickness, spacerHalfZ),
954 "Strip_OuterSpacer");
955}
956
957} // namespace ActsTrk
Helper to hold elements for deletion.
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_VERBOSE(x,...)
static Double_t a
std::shared_ptr< Acts::BlueprintNode > buildBlueprintNode(const Acts::GeometryContext &gctx, std::shared_ptr< Acts::BlueprintNode > &&child) override
Build the Itk Blueprint Node.
void buildItkStripBlueprintNode(const Acts::GeometryContext &gctx, Acts::BlueprintNode &node)
const InDetDD::SiDetectorManager * m_itkStripMgr
const InDetDD::SiDetectorManager * m_itkPixelMgr
std::shared_ptr< ActsElementVector > m_elementStore
Gaudi::Property< bool > m_doEndcapLayerMerging
void buildItkPixelBlueprintNode(const Acts::GeometryContext &gctx, Acts::BlueprintNode &node)
DataModel_detail::const_iterator< DataVector > const_iterator
Definition DataVector.h:861
int layer_disk() const
Class to hold geometrical description of a silicon detector element.
Definition node.h:24
Define the volume parts of the GeometryIdentifier for each ATLAS subsystem centrally.
constexpr std::size_t s_outerPixelVolumeId
constexpr std::size_t s_stripVolumeId
Volume Ids used within the ITk.
constexpr std::size_t s_ITkEnvelopeId
constexpr std::size_t s_innerPixelVolumeId
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
@ layer
Definition HitInfo.h:79
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.