From dffe687492d3b625d0b3d0cd1cc5931bf60c5a77 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 1/7] Share one volume for the MCM cooling pipe stubs in the TRD Co-Authored-By: Claude Opus 5 --- Detectors/TRD/base/src/Geometry.cxx | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Detectors/TRD/base/src/Geometry.cxx b/Detectors/TRD/base/src/Geometry.cxx index c8be5d03455fb..fe6a3c30f56c2 100644 --- a/Detectors/TRD/base/src/Geometry.cxx +++ b/Detectors/TRD/base/src/Geometry.cxx @@ -2030,6 +2030,19 @@ void Geometry::createServices(std::vector const& idtmed) parMCM[2] = kMCMcoTh / 2.0; createVolume("UMC4", "BOX", idtmed[24], parMCM, kNparMCM); + // The two short cooling pipe stubs that sit on top of every MCM. Their dimensions do not + // depend on layer or stack, so one volume is built here and placed ~7300 times per + // supermodule. Gsposp would instead create a new TGeoVolume on every single call. + parTube[0] = 0.0; + parTube[1] = 0.3 / 2.0; // Thickness of the cooling pipes + parTube[2] = kMCMx / 2.0; + createVolume("UTCQ", "TUBE", idtmed[24], parTube, kNparTube); + parTube[0] = 0.0; + parTube[1] = 0.2 / 2.0; // The cooling water inside them + parTube[2] = kMCMx / 2.0; + createVolume("UTCR", "TUBE", idtmed[14], parTube, kNparTube); + TVirtualMC::GetMC()->Gspos("UTCR", 1, "UTCQ", 0.0, 0.0, 0.0, 0, "ONLY"); + // Put the MCM material inside the MCM mother volume xpos = 0.0; ypos = 0.0; @@ -2064,13 +2077,10 @@ void Geometry::createServices(std::vector const& idtmed) xpos = (0.5 + iMCM[iMCMcol]) * xSize + 1.0 - CWIDTH[ilayer] / 2.0; ypos = (0.5 + iMCMrow) * ySize - CLENGTH[ilayer][istack] / 2.0 + HSPACE / 2.0; zpos = 0.0 + 0.742 / 2.0; - parTube[0] = 0.0; - parTube[1] = 0.3 / 2.0; // Thickness of the cooling pipes - parTube[2] = kMCMx / 2.0; - TVirtualMC::GetMC()->Gsposp("UTCP", iCopy + iMCMrow * 10 + iMCMcol + 50, cTagV, xpos, ypos + 1.0, zpos, - matrix[2], "ONLY", parTube, kNparTube); - TVirtualMC::GetMC()->Gsposp("UTCP", iCopy + iMCMrow * 10 + iMCMcol + 500, cTagV, xpos, ypos + 2.0, zpos, - matrix[2], "ONLY", parTube, kNparTube); + TVirtualMC::GetMC()->Gspos("UTCQ", iCopy + iMCMrow * 10 + iMCMcol + 50, cTagV, xpos, ypos + 1.0, zpos, + matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos("UTCQ", iCopy + iMCMrow * 10 + iMCMcol + 500, cTagV, xpos, ypos + 2.0, zpos, + matrix[2], "ONLY"); } } } From e7b38017f17033d570eb027147df1e67694cd17e Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 2/7] Build the TRD cooling pipes and power lines once per layer Co-Authored-By: Claude Opus 5 --- Detectors/TRD/base/src/Geometry.cxx | 60 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/Detectors/TRD/base/src/Geometry.cxx b/Detectors/TRD/base/src/Geometry.cxx index fe6a3c30f56c2..4652619e2921a 100644 --- a/Detectors/TRD/base/src/Geometry.cxx +++ b/Detectors/TRD/base/src/Geometry.cxx @@ -1534,6 +1534,7 @@ void Geometry::createServices(std::vector const& idtmed) const int kTag = 100; char cTagV[kTag]; + char cTagM[kTag]; const int kNparBox = 3; float parBox[kNparBox]; @@ -1919,21 +1920,22 @@ void Geometry::createServices(std::vector const& idtmed) // The cooling pipes inside the service volumes // - // The cooling pipes - parTube[0] = 0.0; - parTube[1] = 0.0; - parTube[2] = 0.0; - createVolume("UTCP", "TUBE", idtmed[24], parTube, 0); - // The cooling water - parTube[0] = 0.0; - parTube[1] = 0.2 / 2.0; - parTube[2] = -1.0; - createVolume("UTCH", "TUBE", idtmed[14], parTube, kNparTube); - // Water inside the cooling pipe - xpos = 0.0; - ypos = 0.0; - zpos = 0.0; - TVirtualMC::GetMC()->Gspos("UTCH", 1, "UTCP", xpos, ypos, zpos, 0, "ONLY"); + // The cooling pipes and the water inside them. Their only free parameter is the chamber + // width, which depends on the layer alone, so six volumes cover all 456 rows of a + // supermodule. + for (ilayer = 0; ilayer < NLAYER; ilayer++) { + snprintf(cTagV, kTag, "UCP%01d", ilayer); + parTube[0] = 0.0; + parTube[1] = 0.3 / 2.0; // Thickness of the cooling pipes + parTube[2] = CWIDTH[ilayer] / 2.0; + createVolume(cTagV, "TUBE", idtmed[24], parTube, kNparTube); + snprintf(cTagM, kTag, "UCW%01d", ilayer); + parTube[0] = 0.0; + parTube[1] = 0.2 / 2.0; // The cooling water + parTube[2] = CWIDTH[ilayer] / 2.0; + createVolume(cTagM, "TUBE", idtmed[14], parTube, kNparTube); + TVirtualMC::GetMC()->Gspos(cTagM, 1, cTagV, 0.0, 0.0, 0.0, 0, "ONLY"); + } // Position the cooling pipes in the mother volume for (istack = 0; istack < NSTACK; istack++) { @@ -1943,16 +1945,12 @@ void Geometry::createServices(std::vector const& idtmed) int nMCMrow = getRowMax(ilayer, istack, 0); float ySize = (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)nMCMrow); snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagM, kTag, "UCP%01d", ilayer); for (int iMCMrow = 0; iMCMrow < nMCMrow; iMCMrow++) { xpos = 0.0; ypos = (0.5 + iMCMrow) * ySize - CLENGTH[ilayer][istack] / 2.0 + HSPACE / 2.0; zpos = 0.0 + 0.742 / 2.0; - // The cooling pipes - parTube[0] = 0.0; - parTube[1] = 0.3 / 2.0; // Thickness of the cooling pipes - parTube[2] = CWIDTH[ilayer] / 2.0; - TVirtualMC::GetMC()->Gsposp("UTCP", iCopy + iMCMrow, cTagV, xpos, ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); + TVirtualMC::GetMC()->Gspos(cTagM, iCopy + iMCMrow, cTagV, xpos, ypos, zpos, matrix[2], "ONLY"); } } } @@ -1961,11 +1959,14 @@ void Geometry::createServices(std::vector const& idtmed) // The power lines // - // The copper power lines - parTube[0] = 0.0; - parTube[1] = 0.0; - parTube[2] = 0.0; - createVolume("UTPL", "TUBE", idtmed[5], parTube, 0); + // The copper power lines, again one per layer rather than one per row + for (ilayer = 0; ilayer < NLAYER; ilayer++) { + snprintf(cTagV, kTag, "UPL%01d", ilayer); + parTube[0] = 0.0; + parTube[1] = 0.2 / 2.0; // Thickness of the power lines + parTube[2] = CWIDTH[ilayer] / 2.0; + createVolume(cTagV, "TUBE", idtmed[5], parTube, kNparTube); + } // Position the power lines in the mother volume for (istack = 0; istack < NSTACK; istack++) { @@ -1975,15 +1976,12 @@ void Geometry::createServices(std::vector const& idtmed) int nMCMrow = getRowMax(ilayer, istack, 0); float ySize = (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)nMCMrow); snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagM, kTag, "UPL%01d", ilayer); for (int iMCMrow = 0; iMCMrow < nMCMrow; iMCMrow++) { xpos = 0.0; ypos = (0.5 + iMCMrow) * ySize - 1.0 - CLENGTH[ilayer][istack] / 2.0 + HSPACE / 2.0; zpos = -0.4 + 0.742 / 2.0; - parTube[0] = 0.0; - parTube[1] = 0.2 / 2.0; // Thickness of the power lines - parTube[2] = CWIDTH[ilayer] / 2.0; - TVirtualMC::GetMC()->Gsposp("UTPL", iCopy + iMCMrow, cTagV, xpos, ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); + TVirtualMC::GetMC()->Gspos(cTagM, iCopy + iMCMrow, cTagV, xpos, ypos, zpos, matrix[2], "ONLY"); } } } From 4804f0cf3864a07d0c514fd43cd98a4251e32123 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 3/7] Write out the TRD chamber dimensions instead of resolving them at run time Co-Authored-By: Claude Opus 5 --- Detectors/TRD/base/src/Geometry.cxx | 86 +++++++++++++++++------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/Detectors/TRD/base/src/Geometry.cxx b/Detectors/TRD/base/src/Geometry.cxx index 4652619e2921a..90abcfaf63ba5 100644 --- a/Detectors/TRD/base/src/Geometry.cxx +++ b/Detectors/TRD/base/src/Geometry.cxx @@ -377,6 +377,22 @@ void Geometry::createVolumes(std::vector const& idtmed) for (int ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); + // Half-sizes of this chamber and of the three air volumes the material layers sit in. + // The Geant3 convention of passing -1 and letting TGeo copy the dimension from the + // mother at CheckGeometry() time is not used here: every such placement makes TGeo + // clone a fresh TGeoVolume, so the dimensions are written out instead. + // double, not float: the originals compute the whole expression in double and only the + // store into parCha[] rounds, so a float intermediate here would shift a dimension by + // one ulp and, through Geant4's stepping, move a handful of hits. + const double halfWidth = CWIDTH[ilayer] / 2.0; + const double halfLength = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; + const double radX = halfWidth - CALT - CCLST - CGLT; // inside of the radiator (UC) + const double radY = halfLength - CCLFT - CGLT; + const double ampX = halfWidth + CROW - CCUTB; // inside of the amplification frame (UE) + const double ampY = halfLength - CCUTA; + const double robX = halfWidth + CROW - CAUT; // inside of the back-panel frame (UG) + const double robY = halfLength - CAUT; + // The lower part of the readout chambers (drift volume + radiator) // The aluminum frames snprintf(cTagV, kTag, "UA%02d", iDet); @@ -402,8 +418,8 @@ void Geometry::createVolumes(std::vector const& idtmed) // The Wacosit frames snprintf(cTagV, kTag, "UB%02d", iDet); parCha[0] = CWIDTH[ilayer] / 2.0 - CALT; - parCha[1] = -1.0; - parCha[2] = -1.0; + parCha[1] = halfLength; + parCha[2] = CRAH / 2.0 + CDRH / 2.0; createVolume(cTagV, "BOX ", idtmed[7], parCha, kNparCha); // The glue around the radiator snprintf(cTagV, kTag, "UX%02d", iDet); @@ -415,7 +431,7 @@ void Geometry::createVolumes(std::vector const& idtmed) snprintf(cTagV, kTag, "UC%02d", iDet); parCha[0] = CWIDTH[ilayer] / 2.0 - CALT - CCLST - CGLT; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CCLFT - CGLT; - parCha[2] = -1.0; + parCha[2] = CRAH / 2.0; createVolume(cTagV, "BOX ", idtmed[2], parCha, kNparCha); // The upper part of the readout chambers (amplification volume) @@ -429,7 +445,7 @@ void Geometry::createVolumes(std::vector const& idtmed) snprintf(cTagV, kTag, "UE%02d", iDet); parCha[0] = CWIDTH[ilayer] / 2.0 + CROW - CCUTB; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CCUTA; - parCha[2] = -1.; + parCha[2] = CAMH / 2.0; createVolume(cTagV, "BOX ", idtmed[2], parCha, kNparCha); // The back panel, including pad plane and readout boards @@ -443,7 +459,7 @@ void Geometry::createVolumes(std::vector const& idtmed) snprintf(cTagV, kTag, "UG%02d", iDet); parCha[0] = CWIDTH[ilayer] / 2.0 + CROW - CAUT; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CAUT; - parCha[2] = -1.0; + parCha[2] = CROH / 2.0; createVolume(cTagV, "BOX ", idtmed[2], parCha, kNparCha); // @@ -451,32 +467,32 @@ void Geometry::createVolumes(std::vector const& idtmed) // // Mylar layer (radiator) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = radX; + parCha[1] = radY; parCha[2] = RMYTHICK / 2.0; snprintf(cTagV, kTag, "URMY%02d", iDet); createVolume(cTagV, "BOX ", idtmed[27], parCha, kNparCha); // Carbon layer (radiator) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = radX; + parCha[1] = radY; parCha[2] = RCBTHICK / 2.0; snprintf(cTagV, kTag, "URCB%02d", iDet); createVolume(cTagV, "BOX ", idtmed[26], parCha, kNparCha); // Araldite layer (radiator) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = radX; + parCha[1] = radY; parCha[2] = RGLTHICK / 2.0; snprintf(cTagV, kTag, "URGL%02d", iDet); createVolume(cTagV, "BOX ", idtmed[11], parCha, kNparCha); // Rohacell layer (radiator) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = radX; + parCha[1] = radY; parCha[2] = RRHTHICK / 2.0; snprintf(cTagV, kTag, "URRH%02d", iDet); createVolume(cTagV, "BOX ", idtmed[15], parCha, kNparCha); // Fiber layer (radiator) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = radX; + parCha[1] = radY; parCha[2] = RFBTHICK / 2.0; snprintf(cTagV, kTag, "URFB%02d", iDet); createVolume(cTagV, "BOX ", idtmed[28], parCha, kNparCha); @@ -489,63 +505,63 @@ void Geometry::createVolumes(std::vector const& idtmed) createVolume(cTagV, "BOX ", idtmed[9], parCha, kNparCha); // Xe/Isobutane layer (amplification volume) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = ampX; + parCha[1] = ampY; parCha[2] = AMTHICK / 2.0; snprintf(cTagV, kTag, "UK%02d", iDet); createVolume(cTagV, "BOX ", idtmed[9], parCha, kNparCha); // Cu layer (wire plane) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = ampX; + parCha[1] = ampY; parCha[2] = WRTHICK / 2.0; snprintf(cTagV, kTag, "UW%02d", iDet); createVolume(cTagV, "BOX ", idtmed[3], parCha, kNparCha); // Cu layer (pad plane) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PPDTHICK / 2.0; snprintf(cTagV, kTag, "UPPD%02d", iDet); createVolume(cTagV, "BOX ", idtmed[5], parCha, kNparCha); // G10 layer (pad plane) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PPPTHICK / 2.0; snprintf(cTagV, kTag, "UPPP%02d", iDet); createVolume(cTagV, "BOX ", idtmed[13], parCha, kNparCha); // Araldite layer (glue) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PGLTHICK / 2.0; snprintf(cTagV, kTag, "UPGL%02d", iDet); createVolume(cTagV, "BOX ", idtmed[11], parCha, kNparCha); // Carbon layer (carbon fiber mats) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PCBTHICK / 2.0; snprintf(cTagV, kTag, "UPCB%02d", iDet); createVolume(cTagV, "BOX ", idtmed[26], parCha, kNparCha); // Aramide layer (honeycomb) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PHCTHICK / 2.0; snprintf(cTagV, kTag, "UPHC%02d", iDet); createVolume(cTagV, "BOX ", idtmed[10], parCha, kNparCha); // G10 layer (PCB readout board) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PPCTHICK / 2; snprintf(cTagV, kTag, "UPPC%02d", iDet); createVolume(cTagV, "BOX ", idtmed[13], parCha, kNparCha); // Cu layer (traces in readout board) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PRBTHICK / 2.0; snprintf(cTagV, kTag, "UPRB%02d", iDet); createVolume(cTagV, "BOX ", idtmed[6], parCha, kNparCha); // Cu layer (other material on in readout board, incl. screws) - parCha[0] = -1.0; - parCha[1] = -1.0; + parCha[0] = robX; + parCha[1] = robY; parCha[2] = PELTHICK / 2.0; snprintf(cTagV, kTag, "UPEL%02d", iDet); createVolume(cTagV, "BOX ", idtmed[4], parCha, kNparCha); From 40c1df90db96bbfcf4938f581b0af6d461b9d804 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 4/7] Resolve the TRD hit volume lookup to integers at initialisation Co-Authored-By: Claude Opus 5 --- .../include/TRDSimulation/Detector.h | 22 +++++ Detectors/TRD/simulation/src/Detector.cxx | 93 ++++++++++++++----- 2 files changed, 91 insertions(+), 24 deletions(-) diff --git a/Detectors/TRD/simulation/include/TRDSimulation/Detector.h b/Detectors/TRD/simulation/include/TRDSimulation/Detector.h index 0341e0a96fce6..60dae83d940a5 100644 --- a/Detectors/TRD/simulation/include/TRDSimulation/Detector.h +++ b/Detectors/TRD/simulation/include/TRDSimulation/Detector.h @@ -61,6 +61,14 @@ class Detector : public o2::base::DetImpl // defines/sets-up the sensitive volumes void defineSensitiveVolumes(); + // Fills the volume-id lookup tables below; called once from InitializeO2Detector(). + void buildVolumeIdTables(); + + // What a sensitive volume is, in mRegionByVolId + enum Region : int8_t { kNotSensitive = 0, + kDrift = 1, + kAmplification = 2 }; + // addHit template void addHit(T x, T y, T z, T locC, T locR, T locT, T tof, int charge, int trackId, int detId, bool drift = false); @@ -83,6 +91,20 @@ class Detector : public o2::base::DetImpl Geometry* mGeom = nullptr; + // Volume-id lookup tables, resolved once at initialisation so that ProcessHits does + // integer indexing instead of an sscanf on a volume name at every step. Volume ids are + // small and dense, so a flat vector beats a map here. + std::vector mRegionByVolId; //!< drift / amplification / not sensitive, by volume id + std::vector mChamberByVolId; //!< chamber within the supermodule (0..29), -1 elsewhere + std::vector mSectorByVolId; //!< supermodule (0..17), -1 elsewhere + + // How far above a sensitive volume the chamber and the supermodule sit. This depends on + // how the transport engine represents the hierarchy -- a native-Geant4 conversion flattens + // the chamber assembly away -- so it is resolved from the tables on the first hit rather + // than hard-coded. + int mChamberOffset = -1; //! + int mSectorOffset = -1; //! + template friend class o2::base::DetImpl; ClassDefOverride(Detector, 1); diff --git a/Detectors/TRD/simulation/src/Detector.cxx b/Detectors/TRD/simulation/src/Detector.cxx index 8429f249aaecd..87f46f892f3e4 100644 --- a/Detectors/TRD/simulation/src/Detector.cxx +++ b/Detectors/TRD/simulation/src/Detector.cxx @@ -59,6 +59,40 @@ void Detector::InitializeO2Detector() { // register the sensitive volumes with FairRoot defineSensitiveVolumes(); + buildVolumeIdTables(); +} + +void Detector::buildVolumeIdTables() +{ + auto* vmc = TVirtualMC::GetMC(); + const int nVols = vmc->NofVolumes() + 1; + mRegionByVolId.assign(nVols, kNotSensitive); + mChamberByVolId.assign(nVols, -1); + mSectorByVolId.assign(nVols, -1); + + auto record = [nVols](std::vector& table, int vid, int8_t value, const char* what) { + if (vid <= 0 || vid >= nVols) { + LOG(fatal) << "TRD volume " << what << " has no usable volume id (" << vid << ")"; + } + table[vid] = value; + }; + + // The drift and amplification gas volumes, distinguished by the second character of + // their name exactly as Geometry::createVolume selects them as sensitive. + for (const auto& name : mGeom->getSensitiveTRDVolumes()) { + record(mRegionByVolId, vmc->VolId(name.c_str()), name[1] == 'J' ? kDrift : kAmplification, name.c_str()); + } + + // The readout-chamber assemblies and the supermodule mother volumes + char volName[16]; + for (int idet = 0; idet < NLAYER * NSTACK; ++idet) { + snprintf(volName, sizeof(volName), "UT%02d", idet); + record(mChamberByVolId, vmc->VolId(volName), idet, volName); + } + for (int sector = 0; sector < NSECTOR; ++sector) { + snprintf(volName, sizeof(volName), "BTRD%d", sector); + record(mSectorByVolId, vmc->VolId(volName), sector, volName); + } } void Detector::InitializeParams() @@ -89,35 +123,46 @@ bool Detector::ProcessHits(FairVolume* v) fMC->SetMaxStep(mMaxMCStepDef); // Should we optimize this value? // Inside sensitive volume ? - bool drRegion = false; - bool amRegion = false; - char idRegion; - int cIdChamber; - int r1 = std::sscanf(fMC->CurrentVolName(), "U%c%d", &idRegion, &cIdChamber); - if (r1 != 2) { - LOG(fatal) << "Something went wrong with the geometry volume name " << fMC->CurrentVolName(); - } - if (idRegion == 'J') { - drRegion = true; - } else if (idRegion == 'K') { - amRegion = true; - } else { + int copy = 0; + const int vid = fMC->CurrentVolID(copy); + const int8_t region = (vid > 0 && vid < (int)mRegionByVolId.size()) ? mRegionByVolId[vid] : kNotSensitive; + if (region == kNotSensitive) { return false; } - - const int idChamber = mGeom->getDetectorSec(cIdChamber); - if (idChamber < 0 || idChamber > 29) { - LOG(fatal) << "Chamber ID out of bounds"; + const bool drRegion = (region == kDrift); + const bool amRegion = (region == kAmplification); + + // Find how far up the chamber and the supermodule sit, once, by walking up until the + // ancestor's volume id is one we know. Hard-coding the depth breaks silently whenever a + // level is added, removed, or flattened away by the transport engine's own conversion. + if (mSectorOffset < 0) { + for (int off = 0; off < 16; ++off) { + const int oid = fMC->CurrentVolOffID(off, copy); + if (oid <= 0 || oid >= (int)mChamberByVolId.size()) { + continue; + } + if (mChamberOffset < 0 && mChamberByVolId[oid] >= 0) { + mChamberOffset = off; + } + if (mSectorByVolId[oid] >= 0) { + mSectorOffset = off; + break; + } + } + if (mChamberOffset < 0 || mSectorOffset < 0) { + LOG(fatal) << "No TRD chamber/supermodule ancestor above sensitive volume " << fMC->CurrentVolName(); + } + LOG(info) << "TRD: chamber at mother offset " << mChamberOffset << ", supermodule at " << mSectorOffset; } - int sector; - int r2 = std::sscanf(fMC->CurrentVolOffName(7), "BTRD%d", §or); - if (r2 != 1) { - LOG(fatal) << "Something went wrong with the geometry volume name " << fMC->CurrentVolOffName(7); - } - if (sector < 0 || sector >= NSECTOR) { - LOG(fatal) << "Sector out of bounds"; + const int chamberVol = fMC->CurrentVolOffID(mChamberOffset, copy); + const int sectorVol = fMC->CurrentVolOffID(mSectorOffset, copy); + const int idChamber = (chamberVol > 0 && chamberVol < (int)mChamberByVolId.size()) ? mChamberByVolId[chamberVol] : -1; + const int sector = (sectorVol > 0 && sectorVol < (int)mSectorByVolId.size()) ? mSectorByVolId[sectorVol] : -1; + if (idChamber < 0 || sector < 0) { + LOG(fatal) << "Cannot resolve TRD chamber/supermodule from volume " << fMC->CurrentVolName(); } + // The detector number (0 - 539) int det = mGeom->getDetector(mGeom->getLayer(idChamber), mGeom->getStack(idChamber), sector); if (det < 0 || det >= MAXCHAMBER) { From 190f3a77cfb1005cc1c8eaa0c26e4520887b6fe2 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 5/7] Build twelve TRD chamber shapes instead of thirty Co-Authored-By: Claude Opus 5 --- Detectors/TRD/base/include/TRDBase/Geometry.h | 5 + Detectors/TRD/base/src/Geometry.cxx | 210 +++++++++--------- 2 files changed, 114 insertions(+), 101 deletions(-) diff --git a/Detectors/TRD/base/include/TRDBase/Geometry.h b/Detectors/TRD/base/include/TRDBase/Geometry.h index 7a313a220830f..33d5a80ecdfe4 100644 --- a/Detectors/TRD/base/include/TRDBase/Geometry.h +++ b/Detectors/TRD/base/include/TRDBase/Geometry.h @@ -49,6 +49,11 @@ class Geometry : public GeometryBase, public o2::detectors::DetMatrixCacheIndire void fillMatrixCache(int mask) override; private: + /// Index of the chamber shape a (layer, stack) uses, 0..11. The chamber length is the + /// same for every stack except the middle one, so the thirty chambers of a supermodule + /// are built from twelve distinct sets of volumes. + static int shapeClass(int layer, int stack) { return layer + constants::NLAYER * (stack == 2 ? 1 : 0); } + void createVolumes(std::vector const& idtmed); void assembleChamber(int ilayer, int istack); void createFrame(std::vector const& idtmed); diff --git a/Detectors/TRD/base/src/Geometry.cxx b/Detectors/TRD/base/src/Geometry.cxx index 90abcfaf63ba5..f76fce3045a8f 100644 --- a/Detectors/TRD/base/src/Geometry.cxx +++ b/Detectors/TRD/base/src/Geometry.cxx @@ -373,9 +373,14 @@ void Geometry::createVolumes(std::vector const& idtmed) createVolume("UTF1", "TRD1", idtmed[2], parTrd, kNparTrd); createVolume("UTF2", "TRD1", idtmed[2], parTrd, kNparTrd); - for (int istack = 0; istack < NSTACK; istack++) { + // The chamber shape is fixed by the layer (its width) and by the chamber length, and the + // length is the same for every stack but the middle one -- so twelve chambers describe all + // thirty. Stacks 0 and 2 are built as the representatives of the two length classes; the + // per-chamber identity lives in the UTxx assembly of assembleChamber(), which is what the + // alignable volume and the hit lookup are keyed on. + for (int istack : {0, 2}) { for (int ilayer = 0; ilayer < NLAYER; ilayer++) { - int iDet = getDetectorSec(ilayer, istack); + int iShape = shapeClass(ilayer, istack); // Half-sizes of this chamber and of the three air volumes the material layers sit in. // The Geant3 convention of passing -1 and letting TGeo copy the dimension from the @@ -395,7 +400,7 @@ void Geometry::createVolumes(std::vector const& idtmed) // The lower part of the readout chambers (drift volume + radiator) // The aluminum frames - snprintf(cTagV, kTag, "UA%02d", iDet); + snprintf(cTagV, kTag, "UA%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; parCha[2] = CRAH / 2.0 + CDRH / 2.0; @@ -404,31 +409,31 @@ void Geometry::createVolumes(std::vector const& idtmed) // This part has not the correct shape but is just supposed to // represent the missing material. The correct form of the L-shaped // profile would not fit into the alignable volume. - snprintf(cTagV, kTag, "UZ%02d", iDet); + snprintf(cTagV, kTag, "UZ%02d", iShape); parCha[0] = CALWMOD / 2.0; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; parCha[2] = CALHMOD / 2.0; createVolume(cTagV, "BOX ", idtmed[1], parCha, kNparCha); // The additional Wacosit on the frames - snprintf(cTagV, kTag, "UP%02d", iDet); + snprintf(cTagV, kTag, "UP%02d", iShape); parCha[0] = CWSW / 2.0; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; parCha[2] = CWSH / 2.0; createVolume(cTagV, "BOX ", idtmed[7], parCha, kNparCha); // The Wacosit frames - snprintf(cTagV, kTag, "UB%02d", iDet); + snprintf(cTagV, kTag, "UB%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 - CALT; parCha[1] = halfLength; parCha[2] = CRAH / 2.0 + CDRH / 2.0; createVolume(cTagV, "BOX ", idtmed[7], parCha, kNparCha); // The glue around the radiator - snprintf(cTagV, kTag, "UX%02d", iDet); + snprintf(cTagV, kTag, "UX%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 - CALT - CCLST; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CCLFT; parCha[2] = CRAH / 2.0; createVolume(cTagV, "BOX ", idtmed[11], parCha, kNparCha); // The inner part of radiator (air) - snprintf(cTagV, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "UC%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 - CALT - CCLST - CGLT; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CCLFT - CGLT; parCha[2] = CRAH / 2.0; @@ -436,13 +441,13 @@ void Geometry::createVolumes(std::vector const& idtmed) // The upper part of the readout chambers (amplification volume) // The Wacosit frames - snprintf(cTagV, kTag, "UD%02d", iDet); + snprintf(cTagV, kTag, "UD%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 + CROW; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; parCha[2] = CAMH / 2.0; createVolume(cTagV, "BOX ", idtmed[7], parCha, kNparCha); // The inner part of the Wacosit frame (air) - snprintf(cTagV, kTag, "UE%02d", iDet); + snprintf(cTagV, kTag, "UE%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 + CROW - CCUTB; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CCUTA; parCha[2] = CAMH / 2.0; @@ -450,13 +455,13 @@ void Geometry::createVolumes(std::vector const& idtmed) // The back panel, including pad plane and readout boards // The aluminum frames - snprintf(cTagV, kTag, "UF%02d", iDet); + snprintf(cTagV, kTag, "UF%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 + CROW; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; parCha[2] = CROH / 2.0; createVolume(cTagV, "BOX ", idtmed[1], parCha, kNparCha); // The inner part of the aluminum frames - snprintf(cTagV, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UG%02d", iShape); parCha[0] = CWIDTH[ilayer] / 2.0 + CROW - CAUT; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CAUT; parCha[2] = CROH / 2.0; @@ -470,100 +475,100 @@ void Geometry::createVolumes(std::vector const& idtmed) parCha[0] = radX; parCha[1] = radY; parCha[2] = RMYTHICK / 2.0; - snprintf(cTagV, kTag, "URMY%02d", iDet); + snprintf(cTagV, kTag, "URMY%02d", iShape); createVolume(cTagV, "BOX ", idtmed[27], parCha, kNparCha); // Carbon layer (radiator) parCha[0] = radX; parCha[1] = radY; parCha[2] = RCBTHICK / 2.0; - snprintf(cTagV, kTag, "URCB%02d", iDet); + snprintf(cTagV, kTag, "URCB%02d", iShape); createVolume(cTagV, "BOX ", idtmed[26], parCha, kNparCha); // Araldite layer (radiator) parCha[0] = radX; parCha[1] = radY; parCha[2] = RGLTHICK / 2.0; - snprintf(cTagV, kTag, "URGL%02d", iDet); + snprintf(cTagV, kTag, "URGL%02d", iShape); createVolume(cTagV, "BOX ", idtmed[11], parCha, kNparCha); // Rohacell layer (radiator) parCha[0] = radX; parCha[1] = radY; parCha[2] = RRHTHICK / 2.0; - snprintf(cTagV, kTag, "URRH%02d", iDet); + snprintf(cTagV, kTag, "URRH%02d", iShape); createVolume(cTagV, "BOX ", idtmed[15], parCha, kNparCha); // Fiber layer (radiator) parCha[0] = radX; parCha[1] = radY; parCha[2] = RFBTHICK / 2.0; - snprintf(cTagV, kTag, "URFB%02d", iDet); + snprintf(cTagV, kTag, "URFB%02d", iShape); createVolume(cTagV, "BOX ", idtmed[28], parCha, kNparCha); // Xe/Isobutane layer (drift volume) parCha[0] = CWIDTH[ilayer] / 2.0 - CALT - CCLST; parCha[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0 - CCLFT; parCha[2] = DRTHICK / 2.0; - snprintf(cTagV, kTag, "UJ%02d", iDet); + snprintf(cTagV, kTag, "UJ%02d", iShape); createVolume(cTagV, "BOX ", idtmed[9], parCha, kNparCha); // Xe/Isobutane layer (amplification volume) parCha[0] = ampX; parCha[1] = ampY; parCha[2] = AMTHICK / 2.0; - snprintf(cTagV, kTag, "UK%02d", iDet); + snprintf(cTagV, kTag, "UK%02d", iShape); createVolume(cTagV, "BOX ", idtmed[9], parCha, kNparCha); // Cu layer (wire plane) parCha[0] = ampX; parCha[1] = ampY; parCha[2] = WRTHICK / 2.0; - snprintf(cTagV, kTag, "UW%02d", iDet); + snprintf(cTagV, kTag, "UW%02d", iShape); createVolume(cTagV, "BOX ", idtmed[3], parCha, kNparCha); // Cu layer (pad plane) parCha[0] = robX; parCha[1] = robY; parCha[2] = PPDTHICK / 2.0; - snprintf(cTagV, kTag, "UPPD%02d", iDet); + snprintf(cTagV, kTag, "UPPD%02d", iShape); createVolume(cTagV, "BOX ", idtmed[5], parCha, kNparCha); // G10 layer (pad plane) parCha[0] = robX; parCha[1] = robY; parCha[2] = PPPTHICK / 2.0; - snprintf(cTagV, kTag, "UPPP%02d", iDet); + snprintf(cTagV, kTag, "UPPP%02d", iShape); createVolume(cTagV, "BOX ", idtmed[13], parCha, kNparCha); // Araldite layer (glue) parCha[0] = robX; parCha[1] = robY; parCha[2] = PGLTHICK / 2.0; - snprintf(cTagV, kTag, "UPGL%02d", iDet); + snprintf(cTagV, kTag, "UPGL%02d", iShape); createVolume(cTagV, "BOX ", idtmed[11], parCha, kNparCha); // Carbon layer (carbon fiber mats) parCha[0] = robX; parCha[1] = robY; parCha[2] = PCBTHICK / 2.0; - snprintf(cTagV, kTag, "UPCB%02d", iDet); + snprintf(cTagV, kTag, "UPCB%02d", iShape); createVolume(cTagV, "BOX ", idtmed[26], parCha, kNparCha); // Aramide layer (honeycomb) parCha[0] = robX; parCha[1] = robY; parCha[2] = PHCTHICK / 2.0; - snprintf(cTagV, kTag, "UPHC%02d", iDet); + snprintf(cTagV, kTag, "UPHC%02d", iShape); createVolume(cTagV, "BOX ", idtmed[10], parCha, kNparCha); // G10 layer (PCB readout board) parCha[0] = robX; parCha[1] = robY; parCha[2] = PPCTHICK / 2; - snprintf(cTagV, kTag, "UPPC%02d", iDet); + snprintf(cTagV, kTag, "UPPC%02d", iShape); createVolume(cTagV, "BOX ", idtmed[13], parCha, kNparCha); // Cu layer (traces in readout board) parCha[0] = robX; parCha[1] = robY; parCha[2] = PRBTHICK / 2.0; - snprintf(cTagV, kTag, "UPRB%02d", iDet); + snprintf(cTagV, kTag, "UPRB%02d", iShape); createVolume(cTagV, "BOX ", idtmed[6], parCha, kNparCha); // Cu layer (other material on in readout board, incl. screws) parCha[0] = robX; parCha[1] = robY; parCha[2] = PELTHICK / 2.0; - snprintf(cTagV, kTag, "UPEL%02d", iDet); + snprintf(cTagV, kTag, "UPEL%02d", iShape); createVolume(cTagV, "BOX ", idtmed[4], parCha, kNparCha); // @@ -575,112 +580,112 @@ void Geometry::createVolumes(std::vector const& idtmed) // Lower part // Mylar layers (radiator) zpos = RMYTHICK / 2.0 - CRAH / 2.0; - snprintf(cTagV, kTag, "URMY%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URMY%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); zpos = -RMYTHICK / 2.0 + CRAH / 2.0; - snprintf(cTagV, kTag, "URMY%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URMY%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 2, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Carbon layers (radiator) zpos = RCBTHICK / 2.0 + RMYTHICK - CRAH / 2.0; - snprintf(cTagV, kTag, "URCB%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URCB%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); zpos = -RCBTHICK / 2.0 - RMYTHICK + CRAH / 2.0; - snprintf(cTagV, kTag, "URCB%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URCB%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 2, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Carbon layers (radiator) zpos = RGLTHICK / 2.0 + RCBTHICK + RMYTHICK - CRAH / 2.0; - snprintf(cTagV, kTag, "URGL%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URGL%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); zpos = -RGLTHICK / 2.0 - RCBTHICK - RMYTHICK + CRAH / 2.0; - snprintf(cTagV, kTag, "URGL%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URGL%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 2, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Rohacell layers (radiator) zpos = RRHTHICK / 2.0 + RGLTHICK + RCBTHICK + RMYTHICK - CRAH / 2.0; - snprintf(cTagV, kTag, "URRH%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URRH%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); zpos = -RRHTHICK / 2.0 - RGLTHICK - RCBTHICK - RMYTHICK + CRAH / 2.0; - snprintf(cTagV, kTag, "URRH%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URRH%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 2, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Fiber layers (radiator) zpos = 0.0; - snprintf(cTagV, kTag, "URFB%02d", iDet); - snprintf(cTagM, kTag, "UC%02d", iDet); + snprintf(cTagV, kTag, "URFB%02d", iShape); + snprintf(cTagM, kTag, "UC%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Xe/Isobutane layer (drift volume) zpos = DRZPOS; - snprintf(cTagV, kTag, "UJ%02d", iDet); - snprintf(cTagM, kTag, "UB%02d", iDet); + snprintf(cTagV, kTag, "UJ%02d", iShape); + snprintf(cTagM, kTag, "UB%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Upper part // Xe/Isobutane layer (amplification volume) zpos = AMZPOS; - snprintf(cTagV, kTag, "UK%02d", iDet); - snprintf(cTagM, kTag, "UE%02d", iDet); + snprintf(cTagV, kTag, "UK%02d", iShape); + snprintf(cTagM, kTag, "UE%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Cu layer (wire planes inside amplification volume) zpos = WRZPOSA; - snprintf(cTagV, kTag, "UW%02d", iDet); - snprintf(cTagM, kTag, "UK%02d", iDet); + snprintf(cTagV, kTag, "UW%02d", iShape); + snprintf(cTagM, kTag, "UK%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); zpos = WRZPOSB; - snprintf(cTagV, kTag, "UW%02d", iDet); - snprintf(cTagM, kTag, "UK%02d", iDet); + snprintf(cTagV, kTag, "UW%02d", iShape); + snprintf(cTagM, kTag, "UK%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 2, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Back panel + pad plane + readout part // Cu layer (pad plane) zpos = PPDTHICK / 2.0 - CROH / 2.0; - snprintf(cTagV, kTag, "UPPD%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPPD%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // G10 layer (pad plane) zpos = PPPTHICK / 2.0 + PPDTHICK - CROH / 2.0; - snprintf(cTagV, kTag, "UPPP%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPPP%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Araldite layer (glue) zpos = PGLTHICK / 2.0 + PPPTHICK + PPDTHICK - CROH / 2.0; - snprintf(cTagV, kTag, "UPGL%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPGL%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Carbon layers (carbon fiber mats) zpos = PCBTHICK / 2.0 + PGLTHICK + PPPTHICK + PPDTHICK - CROH / 2.0; - snprintf(cTagV, kTag, "UPCB%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPCB%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); zpos = -PCBTHICK / 2.0 - PPCTHICK - PRBTHICK - PELTHICK + CROH / 2.0; - snprintf(cTagV, kTag, "UPCB%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPCB%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 2, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Aramide layer (honeycomb) zpos = PHCTHICK / 2.0 + PCBTHICK + PGLTHICK + PPPTHICK + PPDTHICK - CROH / 2.0; - snprintf(cTagV, kTag, "UPHC%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPHC%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // G10 layer (PCB readout board) zpos = -PPCTHICK / 2.0 - PRBTHICK - PELTHICK + CROH / 2.0; - snprintf(cTagV, kTag, "UPPC%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPPC%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Cu layer (traces in readout board) zpos = -PRBTHICK / 2.0 - PELTHICK + CROH / 2.0; - snprintf(cTagV, kTag, "UPRB%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPRB%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Cu layer (other materials on readout board, incl. screws) zpos = -PELTHICK / 2.0 + CROH / 2.0; - snprintf(cTagV, kTag, "UPEL%02d", iDet); - snprintf(cTagM, kTag, "UG%02d", iDet); + snprintf(cTagV, kTag, "UPEL%02d", iShape); + snprintf(cTagM, kTag, "UG%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // Position the inner volumes of the chambers in the frames @@ -689,30 +694,30 @@ void Geometry::createVolumes(std::vector const& idtmed) // The inner part of the radiator (air) zpos = 0.0; - snprintf(cTagV, kTag, "UC%02d", iDet); - snprintf(cTagM, kTag, "UX%02d", iDet); + snprintf(cTagV, kTag, "UC%02d", iShape); + snprintf(cTagM, kTag, "UX%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // The glue around the radiator zpos = CRAH / 2.0 - CDRH / 2.0 - CRAH / 2.0; - snprintf(cTagV, kTag, "UX%02d", iDet); - snprintf(cTagM, kTag, "UB%02d", iDet); + snprintf(cTagV, kTag, "UX%02d", iShape); + snprintf(cTagM, kTag, "UB%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // The lower Wacosit frame inside the aluminum frame zpos = 0.0; - snprintf(cTagV, kTag, "UB%02d", iDet); - snprintf(cTagM, kTag, "UA%02d", iDet); + snprintf(cTagV, kTag, "UB%02d", iShape); + snprintf(cTagM, kTag, "UA%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // The inside of the upper Wacosit frame zpos = 0.0; - snprintf(cTagV, kTag, "UE%02d", iDet); - snprintf(cTagM, kTag, "UD%02d", iDet); + snprintf(cTagV, kTag, "UE%02d", iShape); + snprintf(cTagM, kTag, "UD%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); // The inside of the upper aluminum frame zpos = 0.0; - snprintf(cTagV, kTag, "UG%02d", iDet); - snprintf(cTagM, kTag, "UF%02d", iDet); + snprintf(cTagV, kTag, "UG%02d", iShape); + snprintf(cTagM, kTag, "UF%02d", iShape); TVirtualMC::GetMC()->Gspos(cTagV, 1, cTagM, xpos, ypos, zpos, 0, "ONLY"); } } @@ -1920,11 +1925,11 @@ void Geometry::createServices(std::vector const& idtmed) const int kNparServ = 3; float parServ[kNparServ]; - for (istack = 0; istack < NSTACK; istack++) { + for (int istack : {0, 2}) { for (ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); parServ[0] = CWIDTH[ilayer] / 2.0; parServ[1] = CLENGTH[ilayer][istack] / 2.0 - HSPACE / 2.0; parServ[2] = CSVH / 2.0; @@ -1954,13 +1959,13 @@ void Geometry::createServices(std::vector const& idtmed) } // Position the cooling pipes in the mother volume - for (istack = 0; istack < NSTACK; istack++) { + for (int istack : {0, 2}) { for (ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); int iCopy = getDetector(ilayer, istack, 0) * 100; int nMCMrow = getRowMax(ilayer, istack, 0); float ySize = (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)nMCMrow); - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); snprintf(cTagM, kTag, "UCP%01d", ilayer); for (int iMCMrow = 0; iMCMrow < nMCMrow; iMCMrow++) { xpos = 0.0; @@ -1985,13 +1990,13 @@ void Geometry::createServices(std::vector const& idtmed) } // Position the power lines in the mother volume - for (istack = 0; istack < NSTACK; istack++) { + for (int istack : {0, 2}) { for (ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); int iCopy = getDetector(ilayer, istack, 0) * 100; int nMCMrow = getRowMax(ilayer, istack, 0); float ySize = (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)nMCMrow); - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); snprintf(cTagM, kTag, "UPL%01d", ilayer); for (int iMCMrow = 0; iMCMrow < nMCMrow; iMCMrow++) { xpos = 0.0; @@ -2070,7 +2075,7 @@ void Geometry::createServices(std::vector const& idtmed) TVirtualMC::GetMC()->Gspos("UMC4", 1, "UMCM", xpos, ypos, zpos, 0, "ONLY"); // Position the MCMs in the mother volume - for (istack = 0; istack < NSTACK; istack++) { + for (int istack : {0, 2}) { for (ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); int iCopy = getDetector(ilayer, istack, 0) * 1000; @@ -2079,7 +2084,7 @@ void Geometry::createServices(std::vector const& idtmed) int nMCMcol = 8; float xSize = (getChamberWidth(ilayer) - 2.0 * CPADW) / ((float)nMCMcol + 6); // Introduce 6 gaps int iMCM[8] = {1, 2, 3, 5, 8, 9, 10, 12}; // 0..7 MCM + 6 gap structure - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); for (int iMCMrow = 0; iMCMrow < nMCMrow; iMCMrow++) { for (int iMCMcol = 0; iMCMcol < nMCMcol; iMCMcol++) { xpos = (0.5 + iMCM[iMCMcol]) * xSize + 1.0 - CWIDTH[ilayer] / 2.0; @@ -2147,7 +2152,7 @@ void Geometry::createServices(std::vector const& idtmed) TVirtualMC::GetMC()->Gspos("UDC3", 1, "UDCS", xpos, ypos, zpos, 0, "ONLY"); // Put the DCS board in the chamber services mother volume - for (istack = 0; istack < NSTACK; istack++) { + for (int istack : {0, 2}) { for (ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); int iCopy = iDet + 1; @@ -2155,7 +2160,7 @@ void Geometry::createServices(std::vector const& idtmed) 1.9 * (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)getRowMax(ilayer, istack, 0)); ypos = 0.05 * CLENGTH[ilayer][istack]; zpos = kDCSz / 2.0 - CSVH / 2.0; - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); TVirtualMC::GetMC()->Gspos("UDCS", iCopy, cTagV, xpos, ypos, zpos, 0, "ONLY"); } } @@ -2207,7 +2212,7 @@ void Geometry::createServices(std::vector const& idtmed) TVirtualMC::GetMC()->Gspos("UOR3", 1, "UORI", xpos, ypos, zpos, 0, "ONLY"); // Put the ORI board in the chamber services mother volume - for (istack = 0; istack < NSTACK; istack++) { + for (int istack : {0, 2}) { for (ilayer = 0; ilayer < NLAYER; ilayer++) { int iDet = getDetectorSec(ilayer, istack); int iCopy = iDet + 1; @@ -2215,13 +2220,13 @@ void Geometry::createServices(std::vector const& idtmed) 1.92 * (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)getRowMax(ilayer, istack, 0)); ypos = -16.0; zpos = kORIz / 2.0 - CSVH / 2.0; - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); TVirtualMC::GetMC()->Gspos("UORI", iCopy, cTagV, xpos, ypos, zpos, 0, "ONLY"); xpos = -CWIDTH[ilayer] / 2.0 + 3.8 * (getChamberLength(ilayer, istack) - 2.0 * RPADW) / ((float)getRowMax(ilayer, istack, 0)); ypos = -16.0; zpos = kORIz / 2.0 - CSVH / 2.0; - snprintf(cTagV, kTag, "UU%02d", iDet); + snprintf(cTagV, kTag, "UU%02d", shapeClass(ilayer, istack)); TVirtualMC::GetMC()->Gspos("UORI", iCopy + MAXCHAMBER, cTagV, xpos, ypos, zpos, 0, "ONLY"); } } @@ -2488,6 +2493,9 @@ void Geometry::assembleChamber(int ilayer, int istack) double zpos = 0.0; int idet = getDetectorSec(ilayer, istack); + // The parts below are shared between the stacks of equal chamber length; only the + // assembly keeps the per-chamber name, because it is the alignable volume. + int ishape = shapeClass(ilayer, istack); // Create the assembly for a given ROC snprintf(cTagM, kTag, "UT%02d", idet); @@ -2498,7 +2506,7 @@ void Geometry::assembleChamber(int ilayer, int istack) xpos = 0.0; ypos = 0.0; zpos = CRAH / 2.0 + CDRH / 2.0 - CHSV / 2.0; - snprintf(cTagV, kTag, "UA%02d", idet); + snprintf(cTagV, kTag, "UA%02d", ishape); TGeoVolume* rocA = gGeoManager->GetVolume(cTagV); roc->AddNode(rocA, 1, new TGeoTranslation(xpos, ypos, zpos)); @@ -2506,7 +2514,7 @@ void Geometry::assembleChamber(int ilayer, int istack) xpos = CWIDTH[ilayer] / 2.0 + CALWMOD / 2.0; ypos = 0.0; zpos = CRAH + CDRH - CALZPOS - CALHMOD / 2.0 - CHSV / 2.0; - snprintf(cTagV, kTag, "UZ%02d", idet); + snprintf(cTagV, kTag, "UZ%02d", ishape); TGeoVolume* rocZ = gGeoManager->GetVolume(cTagV); roc->AddNode(rocZ, 1, new TGeoTranslation(xpos, ypos, zpos)); roc->AddNode(rocZ, 2, new TGeoTranslation(-xpos, ypos, zpos)); @@ -2515,7 +2523,7 @@ void Geometry::assembleChamber(int ilayer, int istack) xpos = CWIDTH[ilayer] / 2.0 + CWSW / 2.0; ypos = 0.0; zpos = CRAH + CDRH - CWSH / 2.0 - CHSV / 2.0; - snprintf(cTagV, kTag, "UP%02d", idet); + snprintf(cTagV, kTag, "UP%02d", ishape); TGeoVolume* rocP = gGeoManager->GetVolume(cTagV); roc->AddNode(rocP, 1, new TGeoTranslation(xpos, ypos, zpos)); roc->AddNode(rocP, 2, new TGeoTranslation(-xpos, ypos, zpos)); @@ -2525,7 +2533,7 @@ void Geometry::assembleChamber(int ilayer, int istack) xpos = 0.0; ypos = 0.0; zpos = CAMH / 2.0 + CRAH + CDRH - CHSV / 2.0; - snprintf(cTagV, kTag, "UD%02d", idet); + snprintf(cTagV, kTag, "UD%02d", ishape); TGeoVolume* rocD = gGeoManager->GetVolume(cTagV); roc->AddNode(rocD, 1, new TGeoTranslation(xpos, ypos, zpos)); @@ -2534,7 +2542,7 @@ void Geometry::assembleChamber(int ilayer, int istack) xpos = 0.0; ypos = 0.0; zpos = CROH / 2.0 + CAMH + CRAH + CDRH - CHSV / 2.0; - snprintf(cTagV, kTag, "UF%02d", idet); + snprintf(cTagV, kTag, "UF%02d", ishape); TGeoVolume* rocF = gGeoManager->GetVolume(cTagV); roc->AddNode(rocF, 1, new TGeoTranslation(xpos, ypos, zpos)); @@ -2542,7 +2550,7 @@ void Geometry::assembleChamber(int ilayer, int istack) xpos = 0.0; ypos = 0.0; zpos = CSVH / 2.0 + CROH + CAMH + CRAH + CDRH - CHSV / 2.0; - snprintf(cTagV, kTag, "UU%02d", idet); + snprintf(cTagV, kTag, "UU%02d", ishape); TGeoVolume* rocU = gGeoManager->GetVolume(cTagV); roc->AddNode(rocU, 1, new TGeoTranslation(xpos, ypos, zpos)); From 04775e8918e7188f90816d5d9dc3d93ffecaf942 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 6/7] Give the front-of-supermodule TRD gas pipes their own names Co-Authored-By: Claude Opus 5 --- Detectors/TRD/base/src/Geometry.cxx | 57 +++++++++++++---------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/Detectors/TRD/base/src/Geometry.cxx b/Detectors/TRD/base/src/Geometry.cxx index f76fce3045a8f..de84fb97a3140 100644 --- a/Detectors/TRD/base/src/Geometry.cxx +++ b/Detectors/TRD/base/src/Geometry.cxx @@ -2235,42 +2235,35 @@ void Geometry::createServices(std::vector const& idtmed) // Services in front of the super module // - // Gas in-/outlet pipes (INOX) - parTube[0] = 0.0; - parTube[1] = 0.0; - parTube[2] = 0.0; - createVolume("UTG3", "TUBE", idtmed[8], parTube, 0); - // The gas inside the in-/outlet pipes (Xe) - parTube[0] = 0.0; - parTube[1] = 1.2 / 2.0; - parTube[2] = -1.0; - createVolume("UTG4", "TUBE", idtmed[9], parTube, kNparTube); - xpos = 0.0; - ypos = 0.0; - zpos = 0.0; - TVirtualMC::GetMC()->Gspos("UTG4", 1, "UTG3", xpos, ypos, zpos, 0, "ONLY"); + // Gas in-/outlet pipes (INOX) with the Xe inside them, one per layer. These used to reuse + // the names UTG3/UTG4 of the sector-17 tubes above, which only worked because the two + // registrations happened to land in different TGeo volume lists. for (ilayer = 0; ilayer < NLAYER - 1; ilayer++) { - xpos = 0.0; - ypos = CLENGTH[ilayer][2] / 2.0 + CLENGTH[ilayer][1] + CLENGTH[ilayer][0]; - zpos = 9.0 - SHEIGHT / 2.0 + ilayer * (CH + VSPACE); + snprintf(cTagV, kTag, "UGI%01d", ilayer); parTube[0] = 0.0; parTube[1] = 1.5 / 2.0; parTube[2] = CWIDTH[ilayer] / 2.0 - 2.5; - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1, "UTI1", xpos, ypos, zpos, matrix[2], "ONLY", parTube, kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 1 * NLAYER, "UTI1", xpos, -ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 2 * NLAYER, "UTI2", xpos, ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 3 * NLAYER, "UTI2", xpos, -ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 4 * NLAYER, "UTI3", xpos, ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 5 * NLAYER, "UTI3", xpos, -ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 6 * NLAYER, "UTI4", xpos, ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); - TVirtualMC::GetMC()->Gsposp("UTG3", ilayer + 1 + 7 * NLAYER, "UTI4", xpos, -ypos, zpos, matrix[2], "ONLY", parTube, - kNparTube); + createVolume(cTagV, "TUBE", idtmed[8], parTube, kNparTube); + snprintf(cTagM, kTag, "UGX%01d", ilayer); + parTube[0] = 0.0; + parTube[1] = 1.2 / 2.0; + parTube[2] = CWIDTH[ilayer] / 2.0 - 2.5; + createVolume(cTagM, "TUBE", idtmed[9], parTube, kNparTube); + TVirtualMC::GetMC()->Gspos(cTagM, 1, cTagV, 0.0, 0.0, 0.0, 0, "ONLY"); + } + for (ilayer = 0; ilayer < NLAYER - 1; ilayer++) { + xpos = 0.0; + ypos = CLENGTH[ilayer][2] / 2.0 + CLENGTH[ilayer][1] + CLENGTH[ilayer][0]; + zpos = 9.0 - SHEIGHT / 2.0 + ilayer * (CH + VSPACE); + snprintf(cTagV, kTag, "UGI%01d", ilayer); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1, "UTI1", xpos, ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 1 * NLAYER, "UTI1", xpos, -ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 2 * NLAYER, "UTI2", xpos, ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 3 * NLAYER, "UTI2", xpos, -ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 4 * NLAYER, "UTI3", xpos, ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 5 * NLAYER, "UTI3", xpos, -ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 6 * NLAYER, "UTI4", xpos, ypos, zpos, matrix[2], "ONLY"); + TVirtualMC::GetMC()->Gspos(cTagV, ilayer + 1 + 7 * NLAYER, "UTI4", xpos, -ypos, zpos, matrix[2], "ONLY"); } // Gas distribution box From f21f9f023ab99daf143ad76bcf4f03d5c6b0d867 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 1 Sep 2026 17:11:13 +0200 Subject: [PATCH 7/7] Place the steel tube, not its gas core, on the -x side of the sector-17 gas pipe Co-Authored-By: Claude Opus 5 --- Detectors/TRD/base/src/Geometry.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Detectors/TRD/base/src/Geometry.cxx b/Detectors/TRD/base/src/Geometry.cxx index de84fb97a3140..85cbcb097d553 100644 --- a/Detectors/TRD/base/src/Geometry.cxx +++ b/Detectors/TRD/base/src/Geometry.cxx @@ -1916,7 +1916,9 @@ void Geometry::createServices(std::vector const& idtmed) ypos = -CLENGTH[4][0] / 2.0 - CLENGTH[4][1] - CLENGTH[4][2] / 2.0; zpos = VROCSM + SMPLTT + kCOLhgt / 2.0 - SHEIGHT / 2.0 + 5.0 + 4 * (CH + VSPACE); TVirtualMC::GetMC()->Gspos("UTG3", 1, "UTI4", xpos, ypos, zpos, matrix[4], "ONLY"); - TVirtualMC::GetMC()->Gspos("UTG4", 2, "UTI4", -xpos, ypos, zpos, matrix[4], "ONLY"); + // The mirrored tube is the steel pipe UTG3, not its Xe core UTG4 -- compare the PHOS-hole + // loop above, which places UTG1 on both sides. + TVirtualMC::GetMC()->Gspos("UTG3", 2, "UTI4", -xpos, ypos, zpos, matrix[4], "ONLY"); // // The volumes for the services at the chambers