ATLAS Offline Software
Loading...
Searching...
No Matches
MuonBlueprintNodeBuilder.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
7#include <Acts/Geometry/BlueprintNode.hpp>
8#include <Acts/Geometry/StaticBlueprintNode.hpp>
9#include <Acts/Geometry/CylinderVolumeBounds.hpp>
10#include <Acts/Geometry/ContainerBlueprintNode.hpp>
11#include <Acts/Geometry/MultiWireVolumeBuilder.hpp>
12#include <Acts/Surfaces/TrapezoidBounds.hpp>
13#include <Acts/Geometry/MaterialDesignatorBlueprintNode.hpp>
14#include <Acts/Geometry/GeometryIdentifierBlueprintNode.hpp>
15#include <Acts/Geometry/VolumeAttachmentStrategy.hpp>
16#include <Acts/Geometry/VolumeResizeStrategy.hpp>
17#include <Acts/Geometry/TrackingVolume.hpp>
18#include <Acts/Geometry/TrapezoidVolumeBounds.hpp>
19#include <Acts/Geometry/CuboidVolumeBounds.hpp>
20#include <Acts/Geometry/DiamondVolumeBounds.hpp>
21#include <Acts/Surfaces/PlaneSurface.hpp>
22#include <Acts/Surfaces/CylinderSurface.hpp>
23#include <Acts/Surfaces/DiscSurface.hpp>
24#include <Acts/Surfaces/RadialBounds.hpp>
25#include <ActsPlugins/GeoModel/GeoModelMaterialConverter.hpp>
26#include <Acts/Visualization/ObjVisualization3D.hpp>
27#include <Acts/Visualization/GeometryView3D.hpp>
28#include <Acts/Surfaces/LineBounds.hpp>
29#include <Acts/Material/HomogeneousSurfaceMaterial.hpp>
30#include <Acts/Material/ProtoSurfaceMaterial.hpp>
31#include <Acts/Utilities/AxisSpec.hpp>
32#include <Acts/Utilities/MultiAxisSpec.hpp>
33#include <Acts/Surfaces/RectangleBounds.hpp>
34
39
40#include "GeoModelValidation/GeoMaterialHelper.h"
41
42using namespace Acts::UnitLiterals;
43using namespace Muon::MuonStationIndex;
44namespace {
45
46 //Helper function to configure a material node with the correct Faces of the chambers' tracking volumes
47 void configureMaterialFaces(
48 Acts::MaterialDesignatorBlueprintNode& node,
49 const Acts::VolumeBounds& bounds,
50 std::shared_ptr<const Acts::ISurfaceMaterial> material){
51
52 switch (bounds.type()) {
53 case Acts::VolumeBounds::BoundsType::eCuboid: {
54 node.configureFace(
55 Acts::CuboidVolumeBounds::Face::NegativeZFace, material);
56 node.configureFace(
57 Acts::CuboidVolumeBounds::Face::PositiveZFace, material);
58 break;
59 }
60 case Acts::VolumeBounds::BoundsType::eTrapezoid: {
61 node.configureFace(
62 Acts::TrapezoidVolumeBounds::Face::NegativeZFaceXY, material);
63 node.configureFace(
64 Acts::TrapezoidVolumeBounds::Face::PositiveZFaceXY, material);
65 break;
66 }
67 case Acts::VolumeBounds::BoundsType::eDiamond: {
68 node.configureFace(
69 Acts::DiamondVolumeBounds::Face::NegativeZFaceXY, material);
70 node.configureFace(
71 Acts::DiamondVolumeBounds::Face::PositiveZFaceXY, material);
72 break;
73 }
74 default:
76 "Unsupported volume bounds for material configuration");
77 }
78
79 }
80 // null check for variant of shared ptrs
81 template<typename... T>
82 bool isNullVariant(std::variant<T...> variant) {
83 return std::visit([](auto&& ptr) {
84 return !ptr;
85 }, variant);
86 }
87
88}
89
90namespace ActsTrk {
91
93 ATH_CHECK(detStore()->retrieve(m_detMgr));
94 return StatusCode::SUCCESS;
95 }
96
97
98std::shared_ptr<Acts::BlueprintNode> MuonBlueprintNodeBuilder::buildBlueprintNode(const Acts::GeometryContext& gctx, std::shared_ptr<Acts::BlueprintNode>&& childNode) {
99
100EnvelopeSet_t elements;
101EnvelopeSet_t barrelStations, endcapOuterAStations, endcapOuterCStations,
102 endcapMiddleAStations, endcapMiddleCStations;
103
104if (m_useSectors) {
105 elements = m_detMgr->getAllSectors();
106} else {
107 elements = m_detMgr->getAllChambers();
108}
109
110std::visit([&](auto& elems) {
111 using SetType = std::decay_t<decltype(elems)>;
112
113 // Initialize station containers of the same type
114 SetType barrel, endcapA, endcapC, endcapMiddleA, endcapMiddleC;
115
116 for (const auto& element : elems) {
117 if (isElementInTheStation(*element,
118 {StIdx::BI, StIdx::BM, StIdx::BO, StIdx::BE, StIdx::EE, StIdx::EI},
120 barrel.push_back(element);
121 } else if (isElementInTheStation(*element, {StIdx::EO}, EndcapSide::A)) {
122 endcapA.push_back(element);
123 } else if (isElementInTheStation(*element, {StIdx::EO}, EndcapSide::C)) {
124 endcapC.push_back(element);
125 } else if (isElementInTheStation(*element, {StIdx::EM}, EndcapSide::A)) {
126 endcapMiddleA.push_back(element);
127 } else if (isElementInTheStation(*element, {StIdx::EM}, EndcapSide::C)) {
128 endcapMiddleC.push_back(element);
129 } else {
130 ATH_MSG_WARNING("Element " << element->identString()
131 << " not assigned to any station!");
132 }
133 }
134
135 // Assign back into the outer variants
136 barrelStations = std::move(barrel);
137 endcapOuterAStations = std::move(endcapA);
138 endcapOuterCStations = std::move(endcapC);
139 endcapMiddleAStations = std::move(endcapMiddleA);
140 endcapMiddleCStations = std::move(endcapMiddleC);
141}, elements);
142
143 // Top level node for the Muon system
144auto muonNode = std::make_shared<Acts::CylinderContainerBlueprintNode>("MuonNode", Acts::AxisDirection::AxisZ);
145
146Acts::VolumeBoundFactory boundsFactory{};
147using namespace ActsTrk::detail::GeoVolIds;
148auto barrelNode = buildMuonNode(gctx, barrelStations, "BI_BM_BO_EE_EI", Acts::GeometryIdentifier().withVolume(s_muonBarrelId), boundsFactory, {ChIdx::BIS, ChIdx::BML, ChIdx::BOL,
149 ChIdx::EIS, ChIdx::EIL});
150auto endcapANode = buildMuonNode(gctx, endcapOuterAStations, "EO_A", Acts::GeometryIdentifier().withVolume(s_muonEndcapAId), boundsFactory);
151auto endcapCNode = buildMuonNode(gctx, endcapOuterCStations, "EO_C", Acts::GeometryIdentifier().withVolume(s_muonEndcapCId), boundsFactory);
152auto endcapMiddleANode = buildMuonNode(gctx, endcapMiddleAStations, "EM_A", Acts::GeometryIdentifier().withVolume(s_muonEndcapMiddleAId), boundsFactory, {ChIdx::EML, ChIdx::EMS});
153auto endcapMiddleCNode = buildMuonNode(gctx, endcapMiddleCStations, "EM_C", Acts::GeometryIdentifier().withVolume(s_muonEndcapMiddleCId), boundsFactory, {ChIdx::EML, ChIdx::EMS});
154
155//Add to the muon barrel child node (e.g calo or Itk) - if existed
156if(childNode){
157 barrelNode->addChild(std::move(childNode));
158}
159muonNode->addChild(std::move(barrelNode));
160muonNode->addChild(std::move(endcapANode));
161muonNode->addChild(std::move(endcapCNode));
162muonNode->addChild(std::move(endcapMiddleANode));
163muonNode->addChild(std::move(endcapMiddleCNode));
164
165return muonNode;
166
167}
168
169std::shared_ptr<Acts::StaticBlueprintNode>
170MuonBlueprintNodeBuilder::buildMuonNode(const Acts::GeometryContext& gctx,
171 const EnvelopeSet_t& elements,
172 const std::string& name,
173 const Acts::GeometryIdentifier& id,
174 Acts::VolumeBoundFactory& boundsFactory,
175 const std::vector<ChIdx>& passiveStationIds) const {
176
177 const ActsTrk::GeometryContext* context = gctx.get<const ActsTrk::GeometryContext* >();
178 std::vector<std::string> stationNames;
179
180 //build the material nodes that will have as children the static nodes bult from the tracking volumes of the chambers
181 std::vector<std::variant<staticNodePtr, materialNodePtr>> nodes;
182
183 double innerRadius{0.0};
184 double outerRadius{std::numeric_limits<double>::lowest()};
185 double maxZ{std::numeric_limits<double>::lowest()};
186 double minZ{std::numeric_limits<double>::max()};
187 int chamberId = 1;
188 std::vector<std::shared_ptr<Acts::Surface>> passiveSurfaces;
189
190 std::visit([&](const auto& elems){
191
192 using SetType = std::decay_t<decltype(elems)>;
193 std::unordered_map<unsigned int, SetType> elementsPerStation;
194
195 for(const auto& element : elems){
196 std::unique_ptr<Acts::TrackingVolume> vol{};
197 if (m_alignableVolumes) {
198 vol = std::make_unique<Acts::TrackingVolume>(*element->boundingVolume(),
199 element->identString());
200 } else {
201 vol = std::make_unique<Acts::TrackingVolume>(element->localToGlobalTransform(*context),
202 element->bounds(),
203 element->identString());
204 }
205 // //the chamber geometry id
206 Acts::GeometryIdentifier chId = id.withLayer(chamberId++);
207 vol->assignGeometryId(chId);
208 //build the inner structure of the chamber this will return inner sensitive surfaces
209 //or volumes that have already constructed as blueptint nodes and will nbe assigned as children to the element node
210 std::pair<std::vector<blueprintNodePtr>,std::vector<surfacePtr>> innerStructure = getSensitiveElements(*context, *element, chId, boundsFactory);
211 for(auto& surface: innerStructure.second){
212 vol->addSurface(surface);
213 }
214
215 //calculate the bounds of the cylinder container
216 for(const auto& surface: vol->volumeBounds().orientedSurfaces(vol->localToGlobalTransform(gctx))) {
217 const auto& surfaceRepr = (*surface.surface);
218 const Acts::Polyhedron& polyhedron = surfaceRepr.polyhedronRepresentation(gctx);
219 const Amg::Vector3D& center = surfaceRepr.center(gctx);
220
221 maxZ = std::max(maxZ, center.z());
222 minZ = std::min(minZ, center.z());
223
224 // Outer radius needs to be treated differently due to curvature of cylindrical surface
225 for(const Amg::Vector3D& vertex: polyhedron.vertices){
226 outerRadius = std::max(outerRadius, vertex.perp());
227 }
228 }
229
230 std::variant<staticNodePtr, materialNodePtr> chamberNode;
231 const bool isSingleMdt =
232 (element->readoutEles().size() == 1 &&
233 element->readoutEles().front()->detectorType() == DetectorType::Mdt);
234 //for the single MDT elements we build the material node during the volume construction
235 //and the node returned is the material node already
236 if (isSingleMdt) {
237 // Take ownership of the single existing node where we have already included the static node as child
238 // if we allow active material assignment the node is the material node, otherwise it is the static node
239 chamberNode = buildChamberNode(innerStructure.first.front());
240 } else {
241 //for the non single MDT elements we build the material node that has as child the static node representing the chamber volume if we build with material
242 // or it is a static node with the other static nodes as children if not active material is assigned
243 chamberNode = buildChamberNode(element, vol, innerStructure.first);
244 innerStructure.first.clear();
245 }
246 if (isNullVariant(chamberNode)) {
247 THROW_EXCEPTION("No blueprint node constructed");
248 }
249 nodes.push_back(std::move(chamberNode));
250
251 //keep the elements of the stations we want to assign passive material surfaces
252 if(!Acts::rangeContainsValue(passiveStationIds, element->chamberIndex())){
253 continue;
254 }
255
256 DetIdx detIdx = toDetectorRegionIndex(element->chamberIndex(), element->side());
257 elementsPerStation[regionChamberHash(detIdx, element->chamberIndex())].push_back(element);
258 }
259 //construct the surfaces we want to map passive material on using the elements' geometrical parameters
260 passiveSurfaces = getPassiveMaterialSurfaces(gctx, std::move(elementsPerStation));
261
262 }, elements);
263
264 double halfLengthZ = 0.5 * std::abs(maxZ - minZ);
265 ATH_MSG_DEBUG("Inner radius: " << innerRadius<<", outer radius: " << outerRadius
266 <<", max Z: " << maxZ<<", min Z: " << minZ<<", half length Z: " << halfLengthZ);
267
268 Amg::Isometry3D trf = Amg::getTranslateZ3D(halfLengthZ + minZ);
269
270 auto bounds = boundsFactory.makeBounds<Acts::CylinderVolumeBounds>(innerRadius, outerRadius, halfLengthZ);
271 auto volume = std::make_unique<Acts::TrackingVolume>(trf, bounds, name);
272 volume->assignGeometryId(id);
273
274 //put the passive material surfaces into the volume
275 std::ranges::for_each(passiveSurfaces, [&volume](auto& surf){
276 volume->addSurface(surf);
277 });
278
279 auto muonNode = std::make_shared<Acts::StaticBlueprintNode>(std::move(volume));
280 ATH_MSG_DEBUG("There are " << nodes.size() << " nodes");
281 //loop through the nodes-material pairs to add the nodes to the muon node and assign the material to the faces
282 std::ranges::for_each(nodes, [&muonNode](auto& nodeVariant){
283 std::visit([&](auto&& ptr) {
284 muonNode->addChild(ptr);
285 }, nodeVariant);
286 });
287 return muonNode;
288 }
289
290
291std::variant<MuonBlueprintNodeBuilder::staticNodePtr, MuonBlueprintNodeBuilder::materialNodePtr>
294 auto materialNode = std::dynamic_pointer_cast<Acts::MaterialDesignatorBlueprintNode>(chamberVolumeNode);
295 return materialNode;
296 }
297 auto staticNode = std::dynamic_pointer_cast<Acts::StaticBlueprintNode>(chamberVolumeNode);
298 return staticNode;
299}
300
301template<typename T>
302std::variant<MuonBlueprintNodeBuilder::staticNodePtr, MuonBlueprintNodeBuilder::materialNodePtr>
304 std::unique_ptr<Acts::TrackingVolume>& vol,
305 const std::vector<blueprintNodePtr>& innerStructure) const{
306 //copy of the volume bounds
307 const Acts::VolumeBounds& bounds = vol->volumeBounds();
308 staticNodePtr staticNode = std::make_shared<Acts::StaticBlueprintNode>(std::move(vol));
309 for (auto& childNode : innerStructure) {
310 auto node = std::dynamic_pointer_cast<Acts::StaticBlueprintNode>(childNode);
311 if (node) {
312 staticNode->addChild(std::move(node));
313 }
314 }
316 return staticNode;
317 }
318 auto materialNode = std::make_shared<Acts::MaterialDesignatorBlueprintNode>(element->identString() + "_MaterialNode");
319 configureMaterialFaces(*materialNode, bounds, getActiveMaterial(*element));
320 materialNode->addChild(staticNode);
321 return materialNode;
322}
323
324
325template<typename T>
328 const T& element,
329 const Acts::GeometryIdentifier& chId,
330 Acts::VolumeBoundFactory& boundsFactory) const
331 requires(std::is_same_v<T, MuonGMR4::Chamber> || std::is_same_v<T, MuonGMR4::SpectrometerSector>){
332
333 std::vector<blueprintNodePtr> readoutVolumes;
334 std::vector<surfacePtr> readoutSurfaces;
335 Acts::GeometryIdentifier::Value mdtId{1};
336
337 for (const MuonGMR4::MuonReadoutElement* readoutEle : element.readoutEles()) {
338
339 std::vector<surfacePtr> detSurfaces = readoutEle->getSurfaces();
340 switch(readoutEle->detectorType()){
341 case DetectorType::Mdt: {
342 const auto* mdtReadoutEle = static_cast<const MuonGMR4::MdtReadoutElement*>(readoutEle);
343 const MuonGMR4::MdtReadoutElement::parameterBook& parameters{mdtReadoutEle->getParameters()};
344
345 std::unique_ptr<ActsTrk::VolumePlacement> placement{};
346
347 // create the MDT multilayer volume with the dedicated builder
348 Acts::MultiWireVolumeBuilder::Config mwCfg;
349 mwCfg.name = m_detMgr->idHelperSvc()->toStringDetEl(mdtReadoutEle->identify());
350 mwCfg.mlSurfaces = detSurfaces;
351 mwCfg.transform = readoutEle->localToGlobalTransform(gctx);
352
353 //initialize a nullptr material node which will be filled in the case of single MDT readout elements
354 //and used to assign the material to the volume and add the static node as child of the material node
355 std::shared_ptr<Acts::MaterialDesignatorBlueprintNode> mdtMaterialNode;
356
357 //special treatment of BIS78 MDT multilayer
358 //use different shape because of clashes with EIL chambers
359 if(isBIS78(readoutEle) && mdtReadoutEle->multilayer() == 2){
360
361
362 //find the minimum and the maximum tube length (x dimension of the diamond bounds)
363 std::vector<double> tubeLengths;
364 tubeLengths.reserve(mdtReadoutEle->numTubesInLay());
365 for(std::size_t tube = 1; tube < mdtReadoutEle->numTubesInLay(); ++tube){
367 const auto& surface = mdtReadoutEle->surface(tubeHash);
368 const auto& lBounds = static_cast<const Acts::LineBounds&>(surface.bounds());
369 using BoundEnum = Acts::LineBounds::BoundValues;
370 const double tubeLength = 2.*lBounds.get(BoundEnum::eHalfLengthZ);
371 tubeLengths.push_back(tubeLength);
372 }
373 auto [minX,maxX] = std::ranges::minmax_element(tubeLengths);
374 int nSmallTubes = std::count_if(tubeLengths.begin(), tubeLengths.end(), [minX](double length){
375 return std::abs(*minX-length) < Acts::s_epsilon;
376 });
377
378 //create the diamond bounds for the volume
379 constexpr double extraMargin = 1._cm;
380 double y2 = (nSmallTubes+1.)*parameters.tubePitch;
381 double y1 = 2.*parameters.halfY + extraMargin - y2;
382 if (m_alignableVolumes) {
383 placement = std::make_unique<ActsTrk::VolumePlacement>(*readoutEle,
384 Amg::getTranslateY3D(parameters.halfY + extraMargin -y2));
385 }
386 mwCfg.transform = mwCfg.transform * Amg::getTranslateY3D(parameters.halfY + extraMargin - y2);
387 mwCfg.bounds = boundsFactory.makeBounds<Acts::DiamondVolumeBounds>(0.5*(*maxX), 0.5*(*maxX), 0.5*(*minX),
388 y1, y2, parameters.halfHeight);
389
390 } else {
392 placement = std::make_unique<ActsTrk::VolumePlacement>(*readoutEle);
393 }
394 //check for rectangular or trapezoidal shape bounds
395 if(std::abs(parameters.shortHalfX - parameters.longHalfX) < Acts::s_epsilon){
396 mwCfg.bounds = boundsFactory.makeBounds<Acts::CuboidVolumeBounds>(parameters.shortHalfX,
397 parameters.halfY,
398 parameters.halfHeight);
399 } else {
400 mwCfg.bounds = boundsFactory.makeBounds<Acts::TrapezoidVolumeBounds>(parameters.shortHalfX,
401 parameters.longHalfX,
402 parameters.halfY,
403 parameters.halfHeight);
404 }
405 }
406 mwCfg.alignablePlacement = placement.get();
408 element.addPlacement(std::move(placement));
409 }
410 mwCfg.binning = {{Acts::AxisDirection::AxisY, 2u},
411 {Acts::AxisDirection::AxisZ, 1u}};
412 mwCfg.shiftDirection = Acts::AxisDirection::AxisY;
413 Acts::MultiWireVolumeBuilder mdtBuilder{mwCfg};
414 std::unique_ptr<Acts::TrackingVolume> mdtVolume = mdtBuilder.buildVolume();
415
416 mdtVolume->assignGeometryId(chId.withExtra(mdtId++));
417 //create the blueprint node for the mdt multilayers
418 // check if this is a single mdt (single multilayer) chamber so we assign the material directly to the multilayer
419 if(element.readoutEles().size() == 1 && m_assignActiveMaterial){
420
421 mdtMaterialNode = std::make_shared<Acts::MaterialDesignatorBlueprintNode>(element.identString() + "_MaterialNode");
422 configureMaterialFaces(*mdtMaterialNode, mdtVolume->volumeBounds(), getActiveMaterial(element));
423 auto staticNode = std::make_shared<Acts::StaticBlueprintNode>(std::move(mdtVolume));
424 mdtMaterialNode->addChild(std::move(staticNode));
425 readoutVolumes.push_back(std::move(mdtMaterialNode));
426 break;
427 }
428 auto mdtNode = std::make_shared<Acts::StaticBlueprintNode>(std::move(mdtVolume));
429 mdtNode->setNavigationPolicyFactory(mdtBuilder.createNavigationPolicyFactory(gctx.context()));
430 readoutVolumes.push_back(std::move(mdtNode));
431
432 break;
433
434 } case DetectorType::Rpc:
437 case DetectorType::Mm: {
438
439 readoutSurfaces.insert(readoutSurfaces.end(), std::make_move_iterator(detSurfaces.begin()),
440 std::make_move_iterator(detSurfaces.end()));
441
442 break;
443
444 } default:
445 THROW_EXCEPTION("Unknown detector type for readout element: " << readoutEle->detectorType());
446 break;
447
448 }
449 }
450
451 return std::make_pair(std::move(readoutVolumes), std::move(readoutSurfaces));
452}
453
454
456 int stEta = element->stationEta();
457 if(m_isRun4){
458 stEta = std::abs(element->stationEta());
459 }
460 return element->detectorType() == ActsTrk::DetectorType::Mdt &&
461 element->chamberIndex() == ChIndex::BIS &&
462 stEta >= 7;
463 }
464
465template<typename ElementSet_t>
466std::vector<std::shared_ptr<Acts::Surface>>
468 const Acts::GeometryContext& gctx,
469 const std::unordered_map<unsigned int, ElementSet_t>& elementsPerStation) const {
470
472 return {};
473 }
474 //this is a margin to put the surfaces along Z
475 //(a margin distance from the corresponding chamber's boundary surface)
476 constexpr double margin{4._mm};
477
478 std::vector<std::shared_ptr<Acts::Surface>> surfaces;
479 surfaces.reserve(elementsPerStation.size());
480 LayIdx layIdx = LayIdx::LayerIndexMax;
481 DetIdx detIdx = DetIdx::DetectorRegionIndexMax;
482
483 const ActsTrk::GeometryContext* context = gctx.get<const ActsTrk::GeometryContext* >();
484
485 //lamda function to reject BIS78 chambers from the extension of the passive surface
486 //otherwise they create overlap with the NSW sectors - stop a little bit before the cylinder of the passive surface
487 const auto rejectBIS78 = [&](const MuonGMR4::MuonReadoutElement* readoutEle) {
488 bool reject{false};
489 if(readoutEle->chamberIndex() != ChIdx::BIS){
490 return reject;
491 }
492 int stEta = readoutEle->stationEta();
493 if(m_isRun4){
494 stEta = std::abs(readoutEle->stationEta());
495 }
496 switch (readoutEle->detectorType()) {
497 case DetectorType::Mdt: {
498 const auto* techEle =
499 static_cast<const MuonGMR4::MdtReadoutElement*>(readoutEle);
500 if (techEle->multilayer() == 2 && stEta >= 7) {
501 reject = true;
502 }
503 break;
504 }
505 case DetectorType::Rpc: {
506 const auto* techEle =
507 static_cast<const MuonGMR4::RpcReadoutElement*>(readoutEle);
508 if (techEle->doubletZ() == 2 && stEta >= 7) {
509 reject = true;
510 }
511 break;
512 }
513 default:
514 break;
515 }
516 return reject;
517 };
518
519 for(const auto& [hash, elements] : elementsPerStation){
520
521 //decompose the layer hash to the detector region idx and layer index
522 const auto& [detIdxVal, chIdx] = decomposeRegionChamberHash(hash);
523 layIdx = toLayerIndex(chIdx);
524 detIdx = detIdxVal;
525
526 double maxZ{std::numeric_limits<double>::lowest()};
527 double minZ{std::numeric_limits<double>::max()};
528 double rMin{std::numeric_limits<double>::max()};
529 double rMax{std::numeric_limits<double>::lowest()};
530 //loop through the elements of every station to construct the cylinder/disc surfaces
531 for(const auto& el : elements){
532
533 if(rejectBIS78(el->readoutEles().front())){
534 continue;
535 }
536 const auto& locToGlobal = el->localToGlobalTransform(*context);
537 const auto& bounds = el->bounds();
538 for(const auto& surface : bounds->orientedSurfaces(locToGlobal)){
539 const auto& surfaceRepr = (*surface.surface);
540 const Amg::Vector3D& center = surfaceRepr.center(gctx);
541 rMin = std::min(rMin, center.perp());
542 minZ = std::min(minZ, center.z());
543 maxZ = std::max(maxZ, center.z());
544 rMax = std::max(rMax, center.perp());
545 }
546
547 }
548 double halfZ = 0.5*std::abs(maxZ-minZ);
549 Amg::Isometry3D trf = Amg::Isometry3D::Identity();
550 double zShift{0.};
551 // the chambers are groupd per chamber index and detector region(side) -
552 // we can use the first one for the distinction
553 const auto& testCh = elements.front();
554 int8_t side = testCh->side();
555 switch (testCh->chamberIndex()) {
556 //small NSW sectors (disc passive surface in front of NSW and one in front of EMS)
557 case ChIdx::EIS :
558 case ChIdx::EMS :{
559 side > 0 ? zShift = minZ - margin : zShift = maxZ + margin;
560 trf = Amg::getTranslateZ3D(zShift);
561 auto surface = Acts::Surface::makeShared<Acts::DiscSurface>(trf, std::make_shared<Acts::RadialBounds>(rMin, rMax));
562 const auto [nBins1, nBins2] = getMaterialBins(testCh->chamberIndex());
563 surface->assignSurfaceMaterial(preparePassiveMaterial(surface->bounds(), nBins1, nBins2));
564 surfaces.push_back(surface);
565 break;
566 //large sectors (disc passive surface after NSW/EIL and after EML)
567 } case ChIdx::EIL :
568 case ChIdx::EML : {
569 // HARDCODED!! (maybe think a better solution in the future)
570 // But for the EIL that we put after the EIS/EIL chambers we extend the radius of the disc surface
571 // in order to have a better coverage for the projections from EE
572 if(testCh->chamberIndex() == ChIdx::EIL){
573 rMax += 60*margin;
574 }
575 side > 0 ? zShift = maxZ + margin : zShift = minZ - margin;
576 trf = Amg::getTranslateZ3D(zShift);
577 auto surface = Acts::Surface::makeShared<Acts::DiscSurface>(trf,
578 std::make_shared<Acts::RadialBounds>(rMin, rMax));
579 const auto [nBins1, nBins2] = getMaterialBins(testCh->chamberIndex());
580 surface->assignSurfaceMaterial(preparePassiveMaterial(surface->bounds(), nBins1, nBins2));
581 surfaces.push_back(surface);
582 break;
583 } case ChIdx::BIS :
584 case ChIdx::BML :
585 case ChIdx::BOL : {
586 //hack for run3 because of overlaps with eta = -7 BIS chambers
587
588 if(!m_isRun4 && testCh->chamberIndex() == ChIdx::BIS){
589 halfZ -= 130.;
590
591 }
592 auto surface = Acts::Surface::makeShared<Acts::CylinderSurface>(trf,
593 std::make_shared<Acts::CylinderBounds>(rMin - margin, halfZ));
594
596 const auto [nBins1, nBins2] = getMaterialBins(testCh->chamberIndex());
597 surface->assignSurfaceMaterial(preparePassiveMaterial(surface->bounds(), nBins1, nBins2));
598 }
599 surfaces.push_back(surface);
600 break;
601 } default :
602 THROW_EXCEPTION("No implementation of passive material surface for this station!!!! - sorry :) ");
603 }
604 ATH_MSG_VERBOSE("Putting passive material surface for station " << layerName(layIdx) << "/ "<< regionName(detIdx) << ": minZ = " << minZ << ", maxZ = " << maxZ<< "and radius "<< rMax);
605 }
606
607 if(msgLvl(MSG::VERBOSE)){
608 std::stringstream stream{};
609 for(const auto& surf : surfaces){
610 stream<< " at position : "<< Amg::toString(surf->center(gctx))
611 << "with bounds "<< surf->bounds()<<std::endl;
612 }
613 ATH_MSG_VERBOSE("Constructed "<< surfaces.size()
614 << " surfaces for passive material description : "<<std::endl<<stream.str());
615 }
616
617 return surfaces;
618}
619
620template<typename T>
621std::shared_ptr<const Acts::ISurfaceMaterial>
623 requires(std::is_same_v<T, MuonGMR4::Chamber> ||
624 std::is_same_v<T, MuonGMR4::SpectrometerSector>) {
625
626 const float thickness = element.halfZ();
627 PVConstLink parentVolume = element.readoutEles().front()->getMaterialGeom()->getParent();
628 GeoModelTools::GeoMaterialHelper geoMaterialHelper;
629 std::pair<GeoModelTools::GeoMaterialPtr, double> geoMaterials = geoMaterialHelper.collectMaterial(parentVolume);
630
631 const Acts::Material aMat = ActsPlugins::GeoModel::geoMaterialConverter(*geoMaterials.first);
632 Acts::MaterialSlab slab{aMat, thickness};
633 std::shared_ptr<Acts::HomogeneousSurfaceMaterial> material = std::make_shared<Acts::HomogeneousSurfaceMaterial>(slab);
634 material->scale(0.5); // we want to split the active material in two and put it on the two faces of the chamber bounds
635
636 return material;
637
638}
639
640template<typename T>
642 const std::vector<StIdx>& stationIndex,
643 const EndcapSide side) const
644 requires(std::is_same_v<T, MuonGMR4::Chamber> ||
645 std::is_same_v<T, MuonGMR4::SpectrometerSector>) {
646 bool etaSignCorrect = (side == EndcapSide::Both) ||
647 (side == EndcapSide::A && element.side() > 0) ||
648 (side == EndcapSide::C && element.side() < 0);
649 return etaSignCorrect &&
650 Acts::rangeContainsValue(stationIndex, toStationIndex(element.chamberIndex()));
651}
652
653
654std::shared_ptr<Acts::ISurfaceMaterial>
655 MuonBlueprintNodeBuilder::preparePassiveMaterial(const Acts::SurfaceBounds& bounds,
656 const std::size_t nBins1,
657 const std::size_t nBins2) const {
658 if (nBins1 == 0 || nBins2 == 0) {
659 ATH_MSG_ERROR("Cannot create material for "<<bounds
660 <<" as one of the bin dimensions is zero. nBins1: "<<nBins1<<", nBins2: "<<nBins2);
661 return nullptr;
662 }
663 if (nBins1 == 1 && nBins1 == nBins2) {
664 return std::make_shared<Acts::HomogeneousSurfaceMaterial>();
665 }
666
667 // the ranges and boundary types are left to the surface the material ends
668 // up on, they are resolved from its bounds during material mapping
669 auto protoMaterial = [](Acts::AxisDirection dir1, std::size_t bins1,
670 Acts::AxisDirection dir2, std::size_t bins2) {
671 return std::make_shared<Acts::ProtoGridSurfaceMaterial>(
672 Acts::MultiAxisSpec2D({Acts::AxisSpec::DeferredEquidistant(bins1, dir1),
673 Acts::AxisSpec::DeferredEquidistant(bins2, dir2)}));
674 };
675
676 switch (bounds.type()) {
677 using enum Acts::SurfaceBounds::BoundsType;
678 case eCylinder: {
679 return protoMaterial(Acts::AxisDirection::AxisZ, nBins1,
680 Acts::AxisDirection::AxisRPhi, nBins2);
681 } case eDisc: {
682 return protoMaterial(Acts::AxisDirection::AxisR, nBins1,
683 Acts::AxisDirection::AxisPhi, nBins2);
684 } default:
685 ATH_MSG_ERROR("Unsupoorted type "<<bounds<<".");
686 return nullptr;
687 }
688}
689std::pair<std::size_t, std::size_t>
691 switch(chIdx) {
692 using enum ChIndex;
693 case BIS:
694 case BIL:
695 return std::make_pair(1ul * m_nZBinsBI, 1ul * m_nPhiBinsBI);
696 case BML:
697 case BMS:
698 return std::make_pair(1ul * m_nZBinsBM, 1ul * m_nPhiBinsBM);
699 case BOL:
700 case BOS:
701 return std::make_pair(1ul * m_nZBinsBO, 1ul * m_nPhiBinsBO);
702 case EIS:
703 return std::make_pair(1ul* m_nRBinsEI1, 1ul* m_nPhiBinsEI1);
704 case EIL:
705 return std::make_pair(1ul* m_nRBinsEI2, 1ul* m_nPhiBinsEI2);
706 case EMS:
707 return std::make_pair(1ul* m_nRBinsEM1, 1ul* m_nPhiBinsEM1);
708 case EML:
709 return std::make_pair(1ul* m_nRBinsEM2, 1ul* m_nPhiBinsEM2);
710 default:
711 THROW_EXCEPTION("getMaterialBins() - "<<chName(chIdx)<<" is not yet implemented");
712 }
713 return std::make_pair(0ul, 0ul);
714}
715} //namespace ActsTrk
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_VERBOSE(x,...)
double length(const pvec &v)
double tubeLength
@ BIL
Definition RegSelEnums.h:10
@ BOL
Definition RegSelEnums.h:14
@ BIS
Definition RegSelEnums.h:11
@ BOS
Definition RegSelEnums.h:15
@ BML
Definition RegSelEnums.h:12
@ BMS
Definition RegSelEnums.h:13
size_t size() const
Number of registered mappings.
virtual DetectorType detectorType() const =0
Returns the detector element type.
Gaudi::Property< std::size_t > m_nPhiBinsEM1
Number of bins in phi direction on the disc before the middle big wheel.
BluePrintSurfPairs_t getSensitiveElements(const ActsTrk::GeometryContext &gctx, const T &element, const Acts::GeometryIdentifier &chId, Acts::VolumeBoundFactory &boundsFactory) const
Get the chamber's sensitive elements.
std::shared_ptr< Acts::BlueprintNode > buildBlueprintNode(const Acts::GeometryContext &gctx, std::shared_ptr< Acts::BlueprintNode > &&childNode) override
Build the Muon Blueprint Node.
Gaudi::Property< std::size_t > m_nZBinsBI
Number of bins in Z direction on the BI cylinder surface.
Gaudi::Property< bool > m_assignEmptyMaterial
Assign empty material slabs for the passive surfaces.
Gaudi::Property< std::size_t > m_nZBinsBM
Number of bins in Z direction on the BM cylinder surface.
std::shared_ptr< Acts::BlueprintNode > blueprintNodePtr
Abrivation of the blueprint node ptr base class.
Gaudi::Property< std::size_t > m_nPhiBinsBI
Number of bins in phi direction on the BI cylinder surface.
std::shared_ptr< Acts::ISurfaceMaterial > preparePassiveMaterial(const Acts::SurfaceBounds &bounds, const std::size_t nBins1, const std::size_t nBins2) const
Prepare a binned material which is associated to the surface.
Gaudi::Property< std::size_t > m_nPhiBinsEI1
Number of bins in phi direction on the disc before the NSW.
bool isElementInTheStation(const T &element, const std::vector< StIdx > &stationNames, const EndcapSide side) const
Check if the chamber is in this node.
Gaudi::Property< bool > m_buildPassiveVolumes
Flag to construct the passive material surfaces.
std::vector< surfacePtr > getPassiveMaterialSurfaces(const Acts::GeometryContext &gctx, const std::unordered_map< unsigned int, ElementSet_t > &elementsPerStation) const
Construct and return the surfaces for the passive material description (e.g cylinders for barrel/ dis...
Muon::MuonStationIndex::DetectorRegionIndex DetIdx
Abrivatin for the detector region index.
Gaudi::Property< std::size_t > m_nPhiBinsEM2
Number of bins in phi direction on the disc after the NSW.
Gaudi::Property< bool > m_alignableVolumes
Flag to control if the volumes should be alignable or not.
Gaudi::Property< bool > m_isRun4
Flag to control if we use run4 geometry or not.
Gaudi::Property< bool > m_useSectors
Flag to control if we want to build the muon node from sectors or chambers.
std::pair< std::size_t, std::size_t > getMaterialBins(const Muon::MuonStationIndex::ChIndex chIdx) const
std::pair< std::vector< blueprintNodePtr >, std::vector< surfacePtr > > BluePrintSurfPairs_t
Abrivate the vector pair of blue print nodes and associated active surfaces.
std::variant< MuonChamberSet, MuonSectorSet > EnvelopeSet_t
Hide the flexibility to build the tracking geometry from sectors or chambers behind a variant.
std::variant< staticNodePtr, materialNodePtr > buildChamberNode(const blueprintNodePtr &chamberNode) const
Build a static or a material node for a chamber that corresponds to a single blueprint node (e....
Gaudi::Property< std::size_t > m_nPhiBinsEI2
Number of bins in phi direction on the disc after the NSW.
const MuonGMR4::MuonDetectorManager * m_detMgr
the Detector manager
Gaudi::Property< std::size_t > m_nRBinsEI1
Number of bins in R direction on the disc before the NSW.
Gaudi::Property< std::size_t > m_nRBinsEM1
Number of bins in R direction on the disc before the middle big wheel.
Gaudi::Property< std::size_t > m_nRBinsEM2
Number of bins in R direction on the disc after the middle big wheel.
Muon::MuonStationIndex::LayerIndex LayIdx
Abrivation for the layer index.
bool isBIS78(const MuonGMR4::MuonReadoutElement *element) const
Helper function determining whether a readout element is BIS78.
std::shared_ptr< Acts::StaticBlueprintNode > staticNodePtr
Abrivation of the blue print node pointer.
Gaudi::Property< bool > m_assignActiveMaterial
Flag to assign active material on the chambers.
Gaudi::Property< std::size_t > m_nPhiBinsBM
Number of bins in phi direction on the BM cylinder surface.
staticNodePtr buildMuonNode(const Acts::GeometryContext &gctx, const EnvelopeSet_t &elements, const std::string &name, const Acts::GeometryIdentifier &id, Acts::VolumeBoundFactory &boundsFactory, const std::vector< ChIdx > &passiveStationIds={}) const
Build subnodes for the muon system node.
std::shared_ptr< const Acts::ISurfaceMaterial > getActiveMaterial(const T &element) const
Get the active material for a given element representing the chamber/sector.
Gaudi::Property< std::size_t > m_nZBinsBO
Number of bins in Z direction on the BM cylinder surface.
Gaudi::Property< std::size_t > m_nPhiBinsBO
Number of bins in phi direction on the BM cylinder surface.
Gaudi::Property< std::size_t > m_nRBinsEI2
Number of bins in R direction on the disc after the NSW.
This is a "hash" representation of an Identifier.
Readout element to describe the Monitored Drift Tube (Mdt) chambers Mdt chambers usually comrpise out...
static IdentifierHash measurementHash(unsigned layerNumber, unsigned tubeNumber)
Constructs a Measurement hash from layer && tube number.
MuonReadoutElement is an abstract class representing the geometry of a muon detector.
int stationEta() const
Returns the stationEta (positive A site, negative C site).
Muon::MuonStationIndex::ChIndex chamberIndex() const
Returns the chamber index of the Identifier (MMS & STS) have the same chamber Index (EIS).
Definition node.h:24
Define the volume parts of the GeometryIdentifier for each ATLAS subsystem centrally.
constexpr std::size_t s_muonEndcapAId
constexpr std::size_t s_muonEndcapCId
constexpr std::size_t s_muonEndcapMiddleCId
constexpr std::size_t s_muonBarrelId
constexpr std::size_t s_muonEndcapMiddleAId
The AlignStoreProviderAlg loads the rigid alignment corrections and pipes them through the readout ge...
@ Mm
Maybe not needed in the migration.
@ Tgc
Resitive Plate Chambers.
@ sTgc
Micromegas (NSW).
@ Rpc
Monitored Drift Tubes.
@ Mdt
MuonSpectrometer.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Amg::Isometry3D getTranslateY3D(const double Y)
: Returns a shift transformation along the y-axis
Eigen::Isometry3d Isometry3D
Eigen::Matrix< double, 3, 1 > Vector3D
Amg::Isometry3D getTranslateZ3D(const double Z)
: Returns a shift transformation along the z-axis
const std::string & layerName(LayerIndex index)
convert LayerIndex into a string
std::pair< DetectorRegionIndex, ChIndex > decomposeRegionChamberHash(unsigned int hash)
decompose the hash into Region and Chamber
StIndex toStationIndex(ChIndex index)
convert ChIndex into StIndex
const std::string & chName(ChIndex index)
convert ChIndex into a string
const std::string & regionName(DetectorRegionIndex index)
convert DetectorRegionIndex into a string
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
DetectorRegionIndex toDetectorRegionIndex(ChIndex index, int8_t etaSign)
convert ChamberIndex + etaSign into DetectorRegionIndex
ChIndex
enum to classify the different chamber layers in the muon spectrometer
void * ptr(T *p)
Definition SGImplSvc.cxx:76
#define THROW_EXCEPTION(MESSAGE)
Definition throwExcept.h:10