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