From 8bd6abebf5754eb6accc2ba42c173f4dd0b647b8 Mon Sep 17 00:00:00 2001 From: scannito Date: Thu, 2 Apr 2026 15:00:06 +0200 Subject: [PATCH 1/6] Update TRK --- .../include/TRKSimulation/TRKLayer.h | 29 ++- .../ALICE3/TRK/simulation/src/Detector.cxx | 90 +++++++-- .../ALICE3/TRK/simulation/src/TRKLayer.cxx | 187 ++++++++++++++---- 3 files changed, 247 insertions(+), 59 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h index 6077d9e5f9839..72456e9fa585e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -12,11 +12,14 @@ #ifndef ALICEO2_TRK_LAYER_H #define ALICEO2_TRK_LAYER_H +#include "TRKBase/Specs.h" +#include "TRKBase/TRKBaseParam.h" #include + #include -#include "TRKBase/TRKBaseParam.h" -#include "TRKBase/Specs.h" +#include +#include namespace o2 { @@ -68,7 +71,7 @@ class TRKSegmentedLayer : public TRKCylindricalLayer { public: TRKSegmentedLayer() = default; - TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); ~TRKSegmentedLayer() override = default; TGeoVolume* createSensor() override; @@ -80,7 +83,10 @@ class TRKSegmentedLayer : public TRKCylindricalLayer void createLayer(TGeoVolume* motherVolume) override = 0; protected: + float mTiltAngle; + int mNumberOfModules; + int mNumberOfStaves; // Fixed parameters for the layer, to be set based on the specifications of the chip and module static constexpr double sChipWidth = constants::moduleMLOT::chip::width; @@ -93,6 +99,12 @@ class TRKSegmentedLayer : public TRKCylindricalLayer // TGeo objects outside logical volumes can cause errors static constexpr float sLogicalVolumeThickness = 1.3; + // For the segmented layers, because of tilting and staggering the bounding radii can be different + // from the inner radius and inner radius + thickness. + // This function calculates the bounding radii based on the geometry of the stave and the tilt angle, + // to ensure that the layer volume is large enough to contain all the staves without overlaps. + virtual std::pair getBoundingRadii(double staveWidth) const; + ClassDefOverride(TRKSegmentedLayer, 0); }; @@ -100,15 +112,20 @@ class TRKMLLayer : public TRKSegmentedLayer { public: TRKMLLayer() = default; - TRKMLLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); ~TRKMLLayer() override = default; TGeoVolume* createStave() override; void createLayer(TGeoVolume* motherVolume) override; private: + float mStaggerOffset; + static constexpr double sStaveWidth = constants::ML::width; + // Override to account for the staggering offset present in specific ML layers + std::pair getBoundingRadii(double staveWidth) const override; + ClassDefOverride(TRKMLLayer, 0); }; @@ -116,7 +133,7 @@ class TRKOTLayer : public TRKSegmentedLayer { public: TRKOTLayer() = default; - TRKOTLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKOTLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); ~TRKOTLayer() override = default; TGeoVolume* createStave() override; @@ -133,4 +150,4 @@ class TRKOTLayer : public TRKSegmentedLayer } // namespace trk } // namespace o2 -#endif // ALICEO2_TRK_LAYER_H \ No newline at end of file +#endif // ALICEO2_TRK_LAYER_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx index 66c02a080e0b6..66ace4746d399 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx @@ -9,18 +9,19 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include - -#include -#include -#include +#include "TRKSimulation/Detector.h" #include "DetectorsBase/Stack.h" -#include "TRKSimulation/Hit.h" -#include "TRKSimulation/Detector.h" + #include "TRKBase/TRKBaseParam.h" +#include "TRKSimulation/Hit.h" #include "TRKSimulation/VDGeometryBuilder.h" #include "TRKSimulation/VDSensorRegistry.h" +#include +#include +#include + +#include #include #include @@ -105,14 +106,21 @@ void Detector::configMLOT() break; } case kSegmented: { + const std::vector tiltAngles{11.2f, 11.9f, 11.4f, 0.f, 0.f, 0.f, 0.f, 0.f}; + // const std::vector tiltAngles{10.f, 16.1f, 19.2f, 0.f, 0.f, 0.f, 0.f, 0.f}; + const std::vector nStaves{10, 14, 18, 26, 38, 32, 42, 56}; + // const std::vector nStaves{10, 16, 22, 26, 38, 32, 42, 56}; const std::vector nMods{10, 10, 10, 10, 10, 20, 20, 20}; + + const std::vector stagOffsets{0.f, 0.f, 0.f, 1.17f, 0.89f}; + LOGP(warning, "Loading segmented configuration for ALICE3 TRK"); for (int i{0}; i < 8; ++i) { std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); - if (i < 4) { - mLayers.push_back(std::make_unique(i, name, rInn[i], nMods[i], thick, MatBudgetParamMode::Thickness)); + if (i < 5) { + mLayers.push_back(std::make_unique(i, name, rInn[i], stagOffsets[i], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); } else { - mLayers.push_back(std::make_unique(i, name, rInn[i], nMods[i], thick, MatBudgetParamMode::Thickness)); + mLayers.push_back(std::make_unique(i, name, rInn[i], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); } } break; @@ -153,16 +161,66 @@ void Detector::configFromFile(std::string fileName) } std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(layerCount); + switch (trkPars.layoutMLOT) { - case kCylindrical: - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], tmpBuff[1], tmpBuff[2], MatBudgetParamMode::Thickness)); + case kCylindrical: { + // Cylindrical requires at least 3 parameters + if (tmpBuff.size() < 3) { + LOGP(fatal, "Invalid configuration for cylindrical layer {}: insufficient parameters.", layerCount); + } + + // Default mode is Thickness + MatBudgetParamMode mode = MatBudgetParamMode::Thickness; + if (tmpBuff.size() >= 4) { + mode = static_cast(static_cast(tmpBuff[3])); + } + + mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], tmpBuff[1], tmpBuff[2], mode)); break; + } case kSegmented: { - int nMods = static_cast(tmpBuff[1]); - if (layerCount < 4) { - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], nMods, tmpBuff[2], MatBudgetParamMode::Thickness)); + // Expected column mapping in the text file (separated by \t): + // tmpBuff[0] = rInn + // tmpBuff[1] = thick + // tmpBuff[2] = tiltAngle + // tmpBuff[3] = nStaves + // tmpBuff[4] = nMods + // tmpBuff[5] = stagOffset (required ONLY for ML) + // tmpBuff[6] = matBudgetMode (optional, default = Thickness) + + // Base parameters for all segmented layers (at least 5 needed) + if (tmpBuff.size() < 5) { + LOGP(fatal, "Invalid configuration for segmented layer {}: missing base parameters.", layerCount); + } + + float rInn = tmpBuff[0]; + float thick = tmpBuff[1]; + float tiltAngle = tmpBuff[2]; + int nStaves = static_cast(tmpBuff[3]); + int nMods = static_cast(tmpBuff[4]); + + // Default mode is Thickness + MatBudgetParamMode mode = MatBudgetParamMode::Thickness; + + if (layerCount < 5) { + // ML layers (0 to 4) require stagOffset (index 5) + if (tmpBuff.size() < 6) { + LOGP(fatal, "Invalid configuration for ML layer {}: stagOffset is missing.", layerCount); + } + float stagOffset = tmpBuff[5]; + + if (tmpBuff.size() >= 7) { + mode = static_cast(static_cast(tmpBuff[6])); + } + + mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, mode)); } else { - mLayers.push_back(std::make_unique(layerCount, name, tmpBuff[0], nMods, tmpBuff[2], MatBudgetParamMode::Thickness)); + // OT layers (5+) do NOT have stagOffset. The optional mode is at index 5. + if (tmpBuff.size() >= 6) { + mode = static_cast(static_cast(tmpBuff[5])); + } + + mLayers.push_back(std::make_unique(layerCount, name, rInn, tiltAngle, nStaves, nMods, thick, mode)); } break; } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 39c7b3598d19b..1d67b57c914a6 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -10,17 +10,21 @@ // or submit itself to any jurisdiction. #include "TRKSimulation/TRKLayer.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/Specs.h" #include "Framework/Logger.h" -#include +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/Specs.h" #include +#include #include - #include +#include +#include +#include +#include + namespace o2 { namespace trk @@ -84,9 +88,10 @@ void TRKCylindricalLayer::createLayer(TGeoVolume* motherVolume) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKSegmentedLayer::TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKCylindricalLayer(layerNumber, layerName, rInn, numberOfModules * sModuleLength, thickOrX2X0, mode), mNumberOfModules(numberOfModules) +TRKSegmentedLayer::TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) + : TRKCylindricalLayer(layerNumber, layerName, rInn, numberOfModules * sModuleLength, thickOrX2X0, mode), mTiltAngle(tiltAngle), mNumberOfStaves(numberOfStaves), mNumberOfModules(numberOfModules) { + assert(numberOfStaves % 2 == 0 && "Error: numberOfStaves must be even!"); } TGeoVolume* TRKSegmentedLayer::createSensor() @@ -124,6 +129,8 @@ TGeoVolume* TRKSegmentedLayer::createMetalStack() TGeoVolume* TRKSegmentedLayer::createChip() { + const int nLayerToSwitchSens = 3; + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); std::string chipName = GeometryTGeo::getTRKChipPattern() + std::to_string(mLayerNumber); TGeoShape* chip = new TGeoBBox(sChipWidth / 2, mChipThickness / 2, sChipLength / 2); @@ -132,22 +139,29 @@ TGeoVolume* TRKSegmentedLayer::createChip() TGeoVolume* sensVol = createSensor(); TGeoCombiTrans* transSens = new TGeoCombiTrans(); - // transSens->SetTranslation(-sDeadzoneWidth / 2, -(mChipThickness - sSensorThickness) / 2, 0); - transSens->SetTranslation(-sDeadzoneWidth / 2, (mChipThickness - sSensorThickness) / 2, 0); - LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); - chipVol->AddNode(sensVol, 1, transSens); TGeoVolume* deadVol = createDeadzone(); TGeoCombiTrans* transDead = new TGeoCombiTrans(); - // transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, -(mChipThickness - sSensorThickness) / 2, 0); - transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, (mChipThickness - sSensorThickness) / 2, 0); - LOGP(debug, "Inserting {} in {} ", deadVol->GetName(), chipVol->GetName()); - chipVol->AddNode(deadVol, 1, transDead); TGeoVolume* metalVol = createMetalStack(); TGeoCombiTrans* transMetal = new TGeoCombiTrans(); - // transMetal->SetTranslation(0, sSensorThickness / 2, 0); - transMetal->SetTranslation(0, -sSensorThickness / 2, 0); + + if (mLayerNumber != nLayerToSwitchSens) { + transSens->SetTranslation(-sDeadzoneWidth / 2, (mChipThickness - sSensorThickness) / 2, 0); + transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, (mChipThickness - sSensorThickness) / 2, 0); + transMetal->SetTranslation(0, -sSensorThickness / 2, 0); + } else { + transSens->SetTranslation(-sDeadzoneWidth / 2, -(mChipThickness - sSensorThickness) / 2, 0); + transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, -(mChipThickness - sSensorThickness) / 2, 0); + transMetal->SetTranslation(0, sSensorThickness / 2, 0); + } + + LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); + chipVol->AddNode(sensVol, 1, transSens); + + LOGP(debug, "Inserting {} in {} ", deadVol->GetName(), chipVol->GetName()); + chipVol->AddNode(deadVol, 1, transDead); + LOGP(debug, "Inserting {} in {} ", metalVol->GetName(), chipVol->GetName()); chipVol->AddNode(metalVol, 1, transMetal); @@ -186,10 +200,51 @@ TGeoVolume* TRKSegmentedLayer::createModule() return moduleVol; } +std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) const +{ + const float avgRadius = 0.5 * (mInnerRadius + mOuterRadius); + const float staveSizeX = staveWidth; + const float staveSizeY = mOuterRadius - mInnerRadius; + + /*const float deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); + + float radiusMin = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY - avgRadius * 2. * deltaForTilt); + float radiusMax = std::sqrt(avgRadius * avgRadius + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadius * 2. * deltaForTilt);*/ + + const double alpha = TMath::DegToRad() * std::abs(mTiltAngle); + + // The maximum distance from the center is always the outer top corner + double u_max = avgRadius * std::sin(alpha) + staveSizeX / 2.0; + double v_max = avgRadius * std::cos(alpha) + staveSizeY / 2.0; + double radiusMax = std::sqrt(u_max * u_max + v_max * v_max); + + // The perpendicular distance from the center to the line where the inner face lies + double perpDistance = avgRadius * std::cos(alpha) - staveSizeY / 2.0; + + // The projection of the center along the width of the stave + double projDistance = avgRadius * std::sin(alpha); + + double radiusMin; + if (projDistance <= staveSizeX / 2.0) { + // The center projects directly inside the flat face. + // The closest point is on the face itself, not on the corner + radiusMin = perpDistance; + } else { + // The center projects outside the face. The closest point is the inner corner + double u_min = projDistance - staveSizeX / 2.0; + radiusMin = std::sqrt(u_min * u_min + perpDistance * perpDistance); + } + + // Add a 10-micron safety margin to prevent false-positive overlaps in ROOT's geometry checker caused by floating-point inaccuracies + const float precisionMargin = 0.001; + + return {radiusMin - precisionMargin, radiusMax + precisionMargin}; +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKSegmentedLayer(layerNumber, layerName, rInn, numberOfModules, thickOrX2X0, mode) +TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) + : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset) { } @@ -215,32 +270,43 @@ TGeoVolume* TRKMLLayer::createStave() void TRKMLLayer::createLayer(TGeoVolume* motherVolume) { + // Retrieve exact bounding boundaries and create the logical container volume + auto [rMin, rMax] = getBoundingRadii(sStaveWidth); + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + // TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + TGeoTube* layer = new TGeoTube(rMin, rMax, mLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow); // Compute the number of staves - int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); - nStaves += nStaves % 2; // Require an even number of staves + // int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); + // nStaves += nStaves % 2; // Require an even number of staves + + // Nominal average radii used as placement barycenters for the staves + const double avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); + const double avgRadiusOuter = avgRadiusInner + mStaggerOffset; // Compute the size of the overlap region - double theta = 2 * TMath::Pi() / nStaves; + double theta = 2. * TMath::Pi() / mNumberOfStaves; double theta1 = std::atan(sStaveWidth / 2 / mInnerRadius); double st = std::sin(theta); double ct = std::cos(theta); double theta2 = std::atan((mInnerRadius * st - sStaveWidth / 2 * ct) / (mInnerRadius * ct + sStaveWidth / 2 * st)); double overlap = (theta1 - theta2) * mInnerRadius; - LOGP(info, "Creating a layer with {} staves and {} mm overlap", nStaves, overlap * 10); + LOGP(info, "Creating a layer with {} staves and {} mm overlap", mNumberOfStaves, overlap * 10); - for (int iStave = 0; iStave < nStaves; iStave++) { + for (int iStave = 0; iStave < mNumberOfStaves; iStave++) { TGeoVolume* staveVol = createStave(); TGeoCombiTrans* trans = new TGeoCombiTrans(); - double theta = 360. * iStave / nStaves; - // TGeoRotation* rot = new TGeoRotation("rot", theta - 90 + 4, 0, 0); - TGeoRotation* rot = new TGeoRotation("rot", theta + 90 + 4, 0, 0); + // If the number of staves is a multiple of 4, rotate by half a stave to avoid having the first one exactly on the x + double phi = (mNumberOfStaves % 4 == 0) ? theta * (iStave + 0.5) : theta * iStave; + double phiDeg = phi * TMath::RadToDeg(); + TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); trans->SetRotation(rot); - trans->SetTranslation(mInnerRadius * std::cos(2. * TMath::Pi() * iStave / nStaves), mInnerRadius * std::sin(2 * TMath::Pi() * iStave / nStaves), 0); + // float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == 0 ? mInnerRadius : mInnerRadius + mStaggerOffset) : mInnerRadius; + float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == 0 ? avgRadiusInner : avgRadiusOuter) : avgRadiusInner; + trans->SetTranslation(trueRadius * std::cos(phi), trueRadius * std::sin(phi), 0); LOGP(debug, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); layerVol->AddNode(staveVol, iStave, trans); } @@ -249,10 +315,49 @@ void TRKMLLayer::createLayer(TGeoVolume* motherVolume) motherVolume->AddNode(layerVol, 1, nullptr); } +std::pair TRKMLLayer::getBoundingRadii(double staveWidth) const +{ + // Get the baseline RMin from the base class (the inner boundary remains the same) + auto [radiusMin, defaultRadiusMax] = TRKSegmentedLayer::getBoundingRadii(staveWidth); + + // If we are not in the staggered layers (3 and 4), return the baseline values + if (mLayerNumber != 3 && mLayerNumber != 4) { + return {radiusMin, defaultRadiusMax}; + } + + /*// For staggered layers, we must recalculate RMax based on the outer shifted row + const float avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); + const float avgRadiusOuter = avgRadiusInner + mStaggerOffset; + + const float staveSizeX = staveWidth; + const float staveSizeY = mOuterRadius - mInnerRadius; + + const float deltaForTiltOuter = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); + + const float radiusMax = std::sqrt(avgRadiusOuter * avgRadiusOuter + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadiusOuter * 2. * deltaForTiltOuter);*/ + + // For staggered layers, we must recalculate ONLY the radiusMax based on the outer shifted row (staggered barycenter) + const float avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); + const float avgRadiusOuter = avgRadiusInner + mStaggerOffset; + + const float staveSizeX = staveWidth; + const float staveSizeY = mOuterRadius - mInnerRadius; + const float alpha = TMath::DegToRad() * std::abs(mTiltAngle); + + // The maximum radius uses avgRadiusOuter instead of avgRadiusInner + float u_max = avgRadiusOuter * std::sin(alpha) + staveSizeX / 2.0; + float v_max = avgRadiusOuter * std::cos(alpha) + staveSizeY / 2.0; + float radiusMax = std::sqrt(u_max * u_max + v_max * v_max); + + const float precisionMargin = 0.001; + + return {radiusMin, radiusMax + precisionMargin}; +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKOTLayer::TRKOTLayer(int layerNumber, std::string layerName, float rInn, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKSegmentedLayer(layerNumber, layerName, rInn, numberOfModules, thickOrX2X0, mode) +TRKOTLayer::TRKOTLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) + : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode) { } @@ -298,8 +403,12 @@ TGeoVolume* TRKOTLayer::createStave() void TRKOTLayer::createLayer(TGeoVolume* motherVolume) { + // Retrieve exact bounding boundaries automatically inherited from TRKSegmentedLayer + auto [rMin, rMax] = getBoundingRadii(sStaveWidth); + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + // TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * sLogicalVolumeThickness, mInnerRadius + 0.667 * sLogicalVolumeThickness, mLength / 2); + TGeoTube* layer = new TGeoTube(rMin, rMax, mLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow); @@ -307,8 +416,11 @@ void TRKOTLayer::createLayer(TGeoVolume* motherVolume) int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / sStaveWidth); nStaves += nStaves % 2; // Require an even number of staves + // Nominal average radius used as the placement barycenter for all staves + const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); + // Compute the size of the overlap region - double theta = 2 * TMath::Pi() / nStaves; + double theta = 2. * TMath::Pi() / nStaves; double theta1 = std::atan(sStaveWidth / 2 / mInnerRadius); double st = std::sin(theta); double ct = std::cos(theta); @@ -319,11 +431,12 @@ void TRKOTLayer::createLayer(TGeoVolume* motherVolume) for (int iStave = 0; iStave < nStaves; iStave++) { TGeoVolume* staveVol = createStave(); TGeoCombiTrans* trans = new TGeoCombiTrans(); - double theta = 360. * iStave / nStaves; - // TGeoRotation* rot = new TGeoRotation("rot", theta - 90, 0, 0); - TGeoRotation* rot = new TGeoRotation("rot", theta + 90, 0, 0); + double phi = theta * iStave; + double phiDeg = phi * TMath::RadToDeg(); + TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); trans->SetRotation(rot); - trans->SetTranslation(mInnerRadius * std::cos(2. * TMath::Pi() * iStave / nStaves), mInnerRadius * std::sin(2 * TMath::Pi() * iStave / nStaves), 0); + // trans->SetTranslation(mInnerRadius * std::cos(phi), mInnerRadius * std::sin(phi), 0); + trans->SetTranslation(avgRadius * std::cos(phi), avgRadius * std::sin(phi), 0); LOGP(debug, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); layerVol->AddNode(staveVol, iStave, trans); } @@ -334,4 +447,4 @@ void TRKOTLayer::createLayer(TGeoVolume* motherVolume) // ClassImp(TRKLayer); } // namespace trk -} // namespace o2 \ No newline at end of file +} // namespace o2 From 53f41d46d3463d2a4a580f7f2cbe3dd77438c7c9 Mon Sep 17 00:00:00 2001 From: scannito Date: Fri, 10 Apr 2026 10:18:01 +0200 Subject: [PATCH 2/6] For L6 the nominal radius corresponds to the outer one --- .../TRK/simulation/include/TRKSimulation/TRKLayer.h | 3 ++- Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h index 72456e9fa585e..692891e41c7c6 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -84,9 +84,9 @@ class TRKSegmentedLayer : public TRKCylindricalLayer protected: float mTiltAngle; - int mNumberOfModules; int mNumberOfStaves; + bool mIsFlipped = false; // Fixed parameters for the layer, to be set based on the specifications of the chip and module static constexpr double sChipWidth = constants::moduleMLOT::chip::width; @@ -122,6 +122,7 @@ class TRKMLLayer : public TRKSegmentedLayer float mStaggerOffset; static constexpr double sStaveWidth = constants::ML::width; + static constexpr int sFlippedLayerNumber = 3; // Override to account for the staggering offset present in specific ML layers std::pair getBoundingRadii(double staveWidth) const override; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 1d67b57c914a6..57f4a2c5bf95c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -146,7 +146,7 @@ TGeoVolume* TRKSegmentedLayer::createChip() TGeoVolume* metalVol = createMetalStack(); TGeoCombiTrans* transMetal = new TGeoCombiTrans(); - if (mLayerNumber != nLayerToSwitchSens) { + if (!mIsFlipped) { transSens->SetTranslation(-sDeadzoneWidth / 2, (mChipThickness - sSensorThickness) / 2, 0); transDead->SetTranslation((sChipWidth - sDeadzoneWidth) / 2, (mChipThickness - sSensorThickness) / 2, 0); transMetal->SetTranslation(0, -sSensorThickness / 2, 0); @@ -246,6 +246,12 @@ std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) c TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset) { + if (mLayerNumber == sFlippedLayerNumber) { + mOuterRadius = rInn; + mInnerRadius = rInn - mChipThickness; + mIsFlipped = true; + LOGP(info, "Layer {} is flipped: sensor and metal stack positions are switched", mLayerNumber); + } } TGeoVolume* TRKMLLayer::createStave() From e8d17cee361a79483b96502a2ab38cc0c2d759a8 Mon Sep 17 00:00:00 2001 From: scannito Date: Fri, 10 Apr 2026 10:20:53 +0200 Subject: [PATCH 3/6] Removed unused variable --- Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx | 2 -- 1 file changed, 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 57f4a2c5bf95c..37ea59f0a6da1 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -129,8 +129,6 @@ TGeoVolume* TRKSegmentedLayer::createMetalStack() TGeoVolume* TRKSegmentedLayer::createChip() { - const int nLayerToSwitchSens = 3; - TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); std::string chipName = GeometryTGeo::getTRKChipPattern() + std::to_string(mLayerNumber); TGeoShape* chip = new TGeoBBox(sChipWidth / 2, mChipThickness / 2, sChipLength / 2); From e0bf0b3e3ce9946daf876c4447568816a89496ba Mon Sep 17 00:00:00 2001 From: scannito Date: Fri, 10 Apr 2026 13:39:34 +0200 Subject: [PATCH 4/6] Fix extrusions for OT layers --- .../TRK/simulation/include/TRKSimulation/TRKLayer.h | 3 +++ .../Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h index 692891e41c7c6..eedb17970d0af 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -146,6 +146,9 @@ class TRKOTLayer : public TRKSegmentedLayer static constexpr double sInStaveOverlap = constants::moduleMLOT::gaps::outerEdgeLongSide + constants::moduleMLOT::chip::passiveEdgeReadOut + 0.1; // 1.5mm outer-edge + 1mm deadzone + 1mm (true) overlap static constexpr double sStaveWidth = constants::OT::width - sInStaveOverlap; + // Override to account for the staggering offset present in OT layers + std::pair getBoundingRadii(double staveWidth) const override; + ClassDefOverride(TRKOTLayer, 0) }; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 37ea59f0a6da1..35d300bd56b1c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -234,7 +234,7 @@ std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) c } // Add a 10-micron safety margin to prevent false-positive overlaps in ROOT's geometry checker caused by floating-point inaccuracies - const float precisionMargin = 0.001; + const float precisionMargin = 0.05f; return {radiusMin - precisionMargin, radiusMax + precisionMargin}; } @@ -353,7 +353,7 @@ std::pair TRKMLLayer::getBoundingRadii(double staveWidth) const float v_max = avgRadiusOuter * std::cos(alpha) + staveSizeY / 2.0; float radiusMax = std::sqrt(u_max * u_max + v_max * v_max); - const float precisionMargin = 0.001; + const float precisionMargin = 0.05f; return {radiusMin, radiusMax + precisionMargin}; } @@ -448,6 +448,13 @@ void TRKOTLayer::createLayer(TGeoVolume* motherVolume) LOGP(debug, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); motherVolume->AddNode(layerVol, 1, nullptr); } + +std::pair TRKOTLayer::getBoundingRadii(double staveWidth) const +{ + auto [radiusMin, radiusMax] = TRKSegmentedLayer::getBoundingRadii(staveWidth); + + return {radiusMin - 0.201f, radiusMax}; +} // ClassImp(TRKLayer); } // namespace trk From 5c367c146b061b557914aa5fe38bdc215ef39601 Mon Sep 17 00:00:00 2001 From: scannito Date: Fri, 10 Apr 2026 14:14:17 +0200 Subject: [PATCH 5/6] Minor --- .../ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h | 2 +- Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h index eedb17970d0af..ef4d5657a1b4f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -149,7 +149,7 @@ class TRKOTLayer : public TRKSegmentedLayer // Override to account for the staggering offset present in OT layers std::pair getBoundingRadii(double staveWidth) const override; - ClassDefOverride(TRKOTLayer, 0) + ClassDefOverride(TRKOTLayer, 0); }; } // namespace trk diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 35d300bd56b1c..9b980368acbc5 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -233,7 +233,7 @@ std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) c radiusMin = std::sqrt(u_min * u_min + perpDistance * perpDistance); } - // Add a 10-micron safety margin to prevent false-positive overlaps in ROOT's geometry checker caused by floating-point inaccuracies + // Add a 0.5 mm safety margin to prevent false-positive overlaps in ROOT's geometry checker caused by floating-point inaccuracies const float precisionMargin = 0.05f; return {radiusMin - precisionMargin, radiusMax + precisionMargin}; From f220c7cabcd8c648c3cc2db07bb31ac49ff33260 Mon Sep 17 00:00:00 2001 From: scannito Date: Fri, 10 Apr 2026 16:05:09 +0200 Subject: [PATCH 6/6] Negative staggering for L6 --- .../ALICE3/TRK/simulation/src/TRKLayer.cxx | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index 9b980368acbc5..7a4b7bef34e03 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -248,6 +248,7 @@ TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float mOuterRadius = rInn; mInnerRadius = rInn - mChipThickness; mIsFlipped = true; + mStaggerOffset = -staggerOffset; LOGP(info, "Layer {} is flipped: sensor and metal stack positions are switched", mLayerNumber); } } @@ -321,12 +322,12 @@ void TRKMLLayer::createLayer(TGeoVolume* motherVolume) std::pair TRKMLLayer::getBoundingRadii(double staveWidth) const { - // Get the baseline RMin from the base class (the inner boundary remains the same) - auto [radiusMin, defaultRadiusMax] = TRKSegmentedLayer::getBoundingRadii(staveWidth); + // Get the baseline RMin from the base class + auto [defaultRadiusMin, defaultRadiusMax] = TRKSegmentedLayer::getBoundingRadii(staveWidth); - // If we are not in the staggered layers (3 and 4), return the baseline values + // If we are not in the staggered layers, return the baseline values if (mLayerNumber != 3 && mLayerNumber != 4) { - return {radiusMin, defaultRadiusMax}; + return {defaultRadiusMin, defaultRadiusMax}; } /*// For staggered layers, we must recalculate RMax based on the outer shifted row @@ -340,22 +341,40 @@ std::pair TRKMLLayer::getBoundingRadii(double staveWidth) const const float radiusMax = std::sqrt(avgRadiusOuter * avgRadiusOuter + 0.25 * staveSizeX * staveSizeX + 0.25 * staveSizeY * staveSizeY + avgRadiusOuter * 2. * deltaForTiltOuter);*/ - // For staggered layers, we must recalculate ONLY the radiusMax based on the outer shifted row (staggered barycenter) const float avgRadiusInner = 0.5 * (mInnerRadius + mOuterRadius); - const float avgRadiusOuter = avgRadiusInner + mStaggerOffset; + const float avgRadiusStaggered = avgRadiusInner + mStaggerOffset; const float staveSizeX = staveWidth; const float staveSizeY = mOuterRadius - mInnerRadius; const float alpha = TMath::DegToRad() * std::abs(mTiltAngle); - // The maximum radius uses avgRadiusOuter instead of avgRadiusInner - float u_max = avgRadiusOuter * std::sin(alpha) + staveSizeX / 2.0; - float v_max = avgRadiusOuter * std::cos(alpha) + staveSizeY / 2.0; - float radiusMax = std::sqrt(u_max * u_max + v_max * v_max); - const float precisionMargin = 0.05f; - return {radiusMin, radiusMax + precisionMargin}; + // If the layer is NOT flipped (e.g., Layer 4), the stagger goes outwards + // Therefore, we must recalculate only the maximum radius based on the outer shifted row + if (!mIsFlipped) { + float u_max = avgRadiusStaggered * std::sin(alpha) + staveSizeX / 2.0; + float v_max = avgRadiusStaggered * std::cos(alpha) + staveSizeY / 2.0; + float radiusMax = std::sqrt(u_max * u_max + v_max * v_max); + + return {defaultRadiusMin, radiusMax + precisionMargin}; + } + // If the layer IS flipped (e.g., Layer 3), the stagger goes inwards + // Therefore, we must recalculate only the minimum radius based on the inner shifted row + else { + double perpDistance = avgRadiusStaggered * std::cos(alpha) - staveSizeY / 2.0; + double projDistance = avgRadiusStaggered * std::sin(alpha); + double newRadiusMin; + + if (projDistance <= staveSizeX / 2.0) { + newRadiusMin = perpDistance; + } else { + double u_min = projDistance - staveSizeX / 2.0; + newRadiusMin = std::sqrt(u_min * u_min + perpDistance * perpDistance); + } + + return {newRadiusMin - precisionMargin, defaultRadiusMax}; + } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////