From 26e1d34d10d567599a3abe4bb832a1822095ef02 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Fri, 13 Mar 2026 17:22:05 +0100 Subject: [PATCH 1/2] adding trapezoidal disk option for FT3; fixing overlaps in FT3 OT modules and TF3 --- .../FT3/base/include/FT3Base/FT3BaseParam.h | 18 +-- .../FT3/base/include/FT3Base/GeometryTGeo.h | 3 +- .../ALICE3/FT3/base/src/GeometryTGeo.cxx | 3 +- .../include/FT3Simulation/FT3Layer.h | 1 + .../ALICE3/FT3/simulation/src/Detector.cxx | 16 +- .../ALICE3/FT3/simulation/src/FT3Layer.cxx | 140 ++++++++++++++++-- .../ALICE3/IOTOF/simulation/src/Detector.cxx | 4 +- .../ALICE3/IOTOF/simulation/src/Layer.cxx | 16 +- 8 files changed, 154 insertions(+), 47 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index 7160067f075f7..67bf42458a88a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -19,20 +19,16 @@ namespace o2 { namespace ft3 { - -// ** -// ** Parameters for FT3 base configuration -// ** - -enum FT3Geometry { - Default = 0, - Telescope = 1 +// Parameters for FT3 (ML and OT disks) +enum eFT3Layout { + kCylindrical = 0, + kTrapezoidal, + kSegmented, }; - struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - - Int_t geoModel = FT3Geometry::Default; + eFT3Layout layoutFT3 = kSegmented; + int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters Int_t nLayers = 10; diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h index 3c78850dffb55..e35a4e06945a4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h @@ -94,6 +94,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static const char* getFT3LayerPattern() { return sLayerName.c_str(); } static const char* getFT3ChipPattern() { return sChipName.c_str(); } static const char* getFT3SensorPattern() { return sSensorName.c_str(); } + static const char* getFT3PassivePattern() { return sPassiveName.c_str(); } static const char* composeSymNameFT3(Int_t d) { return Form("%s_%d", o2::detectors::DetID(o2::detectors::DetID::FT3).getName(), d); } static const char* composeSymNameLayer(Int_t d, Int_t lr); @@ -105,8 +106,8 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static std::string sVolumeName; ///< Mother volume name static std::string sLayerName; ///< Layer name static std::string sChipName; ///< Chip name - static std::string sSensorName; ///< Sensor name + static std::string sPassiveName; ///< Passive material name private: static std::unique_ptr sInstance; ///< singletone instance diff --git a/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx index 6833c4c3d1b89..73b2bc9b94eb8 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx @@ -52,8 +52,9 @@ std::unique_ptr GeometryTGeo::sInstance; std::string GeometryTGeo::sVolumeName = "FT3V"; ///< Mother volume name std::string GeometryTGeo::sInnerVolumeName = "FT3Inner"; ///< Mother inner volume name std::string GeometryTGeo::sLayerName = "FT3Layer"; ///< Layer name -std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Sensor name +std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Chip name std::string GeometryTGeo::sSensorName = "FT3Sensor"; ///< Sensor name +std::string GeometryTGeo::sPassiveName = "FT3Passive"; ///< Passive material name //__________________________________________________________________________ GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : o2::itsmft::GeometryTGeo(DetID::FT3) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h index 44fd8eb08e444..f6acebe80ac33 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h @@ -86,6 +86,7 @@ class FT3Layer : public TObject Double_t mOuterRadius; ///< Outer radius of this layer Double_t mZ; ///< Z position of the layer Double_t mChipThickness; ///< Chip thickness + Double_t mSensorThickness; ///< Sensor thickness Double_t mx2X0; ///< Layer material budget x/X0 ClassDefOverride(FT3Layer, 0); // ALICE 3 EndCaps geometry diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx index 0a93a4061ae44..02aae95daacfe 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx @@ -356,21 +356,7 @@ Detector::Detector(bool active) mTrackData(), mHits(o2::utils::createSimVector()) { - - // FT3 Base configuration parameters - auto& ft3BaseParam = FT3BaseParam::Instance(); - - switch (ft3BaseParam.geoModel) { - case Default: - buildFT3ScopingV3(); // v3 Dec 25 - break; - case Telescope: - buildBasicFT3(ft3BaseParam); // BasicFT3 = Parametrized telescopic detector (equidistant layers) - break; - default: - LOG(fatal) << "Invalid Geometry.\n"; - break; - } + buildFT3ScopingV3(); // v3 Dec 25 } //_________________________________________________________________________________________________ diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 1ad4d1aad1eeb..453d90501802e 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -16,22 +16,17 @@ #include "FT3Simulation/FT3Layer.h" #include "FT3Base/GeometryTGeo.h" - -#include // for LOG +#include "FT3Base/FT3BaseParam.h" #include // for TGeoManager, gGeoManager #include // for TGeoCombiTrans, TGeoRotation, etc #include // for TGeoTube, TGeoTubeSeg +#include // for TGeoTrap #include // for TGeoVolume, TGeoVolumeAssembly #include // for TGeoCompositeShape #include "TMathBase.h" // for Abs #include // for Sin, RadToDeg, DegToRad, Cos, Tan, etc -#include -#include -#include // for snprintf -#include - class TGeoMedium; using namespace TMath; @@ -68,6 +63,7 @@ FT3Layer::FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerNam mOuterRadius = rOut; const double Si_X0 = 9.5; mChipThickness = Layerx2X0 * Si_X0; + mSensorThickness = 0.005; // assume 50 microns of active thickness (for sensor volumes for trapezoidal disks) // Sanity checks if (std::isnan(mZ)) { @@ -232,10 +228,130 @@ void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string void FT3Layer::createLayer(TGeoVolume* motherVolume) { + auto& ft3Params = FT3BaseParam::Instance(); + if (mLayerNumber < 0) { LOG(fatal) << "Invalid layer number " << mLayerNumber << " for FT3 layer."; } - if (mIsMiddleLayer) { // ML disks + + LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3; + + // ### options for ML and OT disk layout + if (ft3Params.layoutFT3 == kTrapezoidal || (mIsMiddleLayer && ft3Params.layoutFT3 == kSegmented)) { + // trapezoidal ML+OT disks + // (disks with TGeoTubes doesn'n work properly in ACTS, due to polar coordinates on TGeoTube sides) + + // (!) Currently (March 12, 2026), only OT disks are segmented --> use Trapezoidal option for ML disks as a simplified segmentation + // To be changed to "true" paving with modules, as for the OT disks + + std::string chipName = o2::ft3::GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber); + std::string sensName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mDirection, mLayerNumber); + std::string passiveName = o2::ft3::GeometryTGeo::getFT3PassivePattern() + std::to_string(mLayerNumber); + + TGeoMedium* medSi = gGeoManager->GetMedium("FT3_SILICON$"); + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + + TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kGray); + + const int NtrapezoidalSegments = ft3Params.nTrapezoidalSegments; + + const double dz = mChipThickness / 2; + const double dzSensor = mSensorThickness / 2; + + const double dphi = 2.0 * TMath::Pi() / NtrapezoidalSegments; + double innerRadiusTrapezoidCorner = mInnerRadius / sin((TMath::Pi() - dphi) / 2); // to ensure that the trapezoid segments do not extend beyond the volume + + const double rc = 0.5 * (innerRadiusTrapezoidCorner + mOuterRadius) * TMath::Cos(0.5 * dphi); // radius of tile center + const double h = 0.5 * (mOuterRadius - innerRadiusTrapezoidCorner) * TMath::Cos(0.5 * dphi); // half radial length + + // chord lengths at inner/outer radii + const double bl = innerRadiusTrapezoidCorner * TMath::Sin(0.5 * dphi); // half lower base + const double tl = mOuterRadius * TMath::Sin(0.5 * dphi); // half upper base + + // create trapezoids + for (int iTr = 0; iTr < NtrapezoidalSegments; ++iTr) { + // chip volume + auto trdShapeChip = new TGeoTrap(dz, + 0.0, 0.0, // theta, phi + h, // h1 + bl, // bl1 + tl, // tl1 + 0.0, // alpha1 + h, // h2 + bl, // bl2 + tl, // tl2 + 0.0); // alpha2 + TGeoVolume* trapezoidChipVolume = new TGeoVolume(chipName.c_str(), trdShapeChip, medSi); + trapezoidChipVolume->SetLineColor(kCyan); + trapezoidChipVolume->SetTransparency(50); + + // sensor volume + auto trdShapeSensor = new TGeoTrap(dzSensor, + 0.0, 0.0, // theta, phi + h, // h1 + bl, // bl1 + tl, // tl1 + 0.0, // alpha1 + h, // h2 + bl, // bl2 + tl, // tl2 + 0.0); // alpha2 + TGeoVolume* trapezoidSensorVolume = new TGeoVolume(sensName.c_str(), trdShapeSensor, medSi); + trapezoidSensorVolume->SetLineColor(kYellow); + + // placing sensor in chip: + const double zSensorInChip = (dz - dzSensor) * (mZ < 0 ? 1 : -1); // place sensor at the outer face of the chip, towards the incoming particles + TGeoCombiTrans* transSens = new TGeoCombiTrans(); + transSens->SetTranslation(0, 0, zSensorInChip); + trapezoidChipVolume->AddNode(trapezoidSensorVolume, iTr, transSens); + + // passive volume + auto trdShapePassive = new TGeoTrap(dz - dzSensor, + 0.0, 0.0, // theta, phi + h, // h1 + bl, // bl1 + tl, // tl1 + 0.0, // alpha1 + h, // h2 + bl, // bl2 + tl, // tl2 + 0.0); // alpha2 + TGeoVolume* trapezoidPassiveVolume = new TGeoVolume(passiveName.c_str(), trdShapePassive, medSi); + trapezoidPassiveVolume->SetLineColor(kGray); + + // placing passive volume in chip: + const double zPassiveInChip = (-dzSensor) * (mZ < 0 ? 1 : -1); // place passive volume at the outer face of the chip, towards the incoming particles + TGeoCombiTrans* transPassive = new TGeoCombiTrans(); + transPassive->SetTranslation(0, 0, zPassiveInChip); + trapezoidChipVolume->AddNode(trapezoidPassiveVolume, iTr, transPassive); + + // prepare placing of chip in layer: + const double phi_c = (iTr + 0.5) * dphi; // sector center + const double phi_deg = phi_c * 180.0 / TMath::Pi(); + + // center of tile + const double x = rc * TMath::Cos(phi_c); + const double y = rc * TMath::Sin(phi_c); + const double z = 0.0; + + // local +Y should point radially outward + auto rot = new TGeoRotation(); + rot->RotateZ(phi_deg - 90.0); + auto transf = new TGeoCombiTrans(x, y, z, rot); + + layerVol->AddNode(trapezoidChipVolume, iTr, transf); + } + + LOG(info) << "Inserting " << NtrapezoidalSegments << " trapezoidal segments (Rmin=" + << mInnerRadius << ", Rmax=" << mOuterRadius << ", z = " << mZ << "cm) inside " << layerVol->GetName(); + + auto* diskRotation = new TGeoRotation("TrapezoidalDiskRotation", 0, 0, 0); + auto* diskCombiTrans = new TGeoCombiTrans(0, 0, mZ, diskRotation); + motherVolume->AddNode(layerVol, 1, diskCombiTrans); + } else if (ft3Params.layoutFT3 == kCylindrical) { + // cylindrical ML+OT disks std::string chipName = o2::ft3::GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber), sensName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mDirection, mLayerNumber); @@ -265,9 +381,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); - - } else { // OT disks - + } else if (ft3Params.layoutFT3 == kSegmented) { FT3Module module; // layer structure @@ -276,7 +390,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); - TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, 10 * mChipThickness / 2); + TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, 12 * mChipThickness / 2); // additional "thickness factor" is to avoid sub-volumes crossing the mother layer TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kYellow + 2); @@ -293,5 +407,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else { + LOG(fatal) << "Unknown FT3 layout option: " << static_cast(ft3Params.layoutFT3); } } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx index d4e34c582bbed..96ce9feff1bee 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx @@ -99,14 +99,14 @@ void Detector::configLayers(bool itof, bool otof, bool ftof, bool btof, std::str if (itof) { // iTOF mITOFLayer = itofSegmented ? ITOFLayer(std::string{GeometryTGeo::getITOFLayerPattern()}, radiusInnerTof, 0.f, lengthInnerTof, 0.f, x2x0, ITOFLayer::kBarrelSegmented, - 24, 5.42, 10.0, 10) + 24, 5.42, 3.0, 10) : ITOFLayer(std::string{GeometryTGeo::getITOFLayerPattern()}, radiusInnerTof, 0.f, lengthInnerTof, 0.f, x2x0, ITOFLayer::kBarrel); } if (otof) { // oTOF mOTOFLayer = otofSegmented ? OTOFLayer(std::string{GeometryTGeo::getOTOFLayerPattern()}, radiusOuterTof, 0.f, lengthOuterTof, 0.f, x2x0, OTOFLayer::kBarrelSegmented, - 62, 9.74, 5.0, 54) + 62, 9.74, 3.0, 54) : OTOFLayer(std::string{GeometryTGeo::getOTOFLayerPattern()}, radiusOuterTof, 0.f, lengthOuterTof, 0.f, x2x0, OTOFLayer::kBarrel); } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx index 1744e4c4510bb..c3612b0276b2e 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx @@ -155,14 +155,17 @@ void ITOFLayer::createLayer(TGeoVolume* motherVolume) case kBarrelSegmented: { // First we create the volume for the whole layer, which will be used as mother volume for the segments const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); - const double staveSizeX = mStaves.second; // cm - const double staveSizeY = mOuterRadius - mInnerRadius; // cm - const double staveSizeZ = mZLength; // cm - const double deltaForTilt = 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves + const double staveSizeX = mStaves.second; // cm + const double staveSizeY = mOuterRadius - mInnerRadius; // cm + const double staveSizeZ = mZLength; // cm + const double rMargin = 0.2; // cm, a small margin to avoid layer extrusion by sub-volumes + const double deltaForTilt = rMargin + 0.5 * (std::sin(TMath::DegToRad() * mTiltAngle) * staveSizeX + std::cos(TMath::DegToRad() * mTiltAngle) * staveSizeY); // we increase the size of the layer to account for the tilt of the staves TGeoTube* layer = new TGeoTube(mInnerRadius - deltaForTilt, mOuterRadius + deltaForTilt, mZLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); setLayerStyle(layerVol); + LOGP(info, "iTOF kBarrelSegmented layout: stave tilt angle {}, layer tube rMin {}, rMax {}", mTiltAngle, mInnerRadius - deltaForTilt, mOuterRadius + deltaForTilt); + // Now we create the volume for a single stave TGeoBBox* stave = new TGeoBBox(staveSizeX * 0.5, staveSizeY * 0.5, staveSizeZ * 0.5); TGeoVolume* staveVol = new TGeoVolume(staveName, stave, medAir); @@ -287,10 +290,13 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) case kBarrelSegmented: { // First we create the volume for the whole layer, which will be used as mother volume for the segments const double avgRadius = 0.5 * (mInnerRadius + mOuterRadius); - TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + const double rMargin = 0.8; // cm, a small margin to avoid layer extrusion by sub-volumes + TGeoTube* layer = new TGeoTube(mInnerRadius - rMargin, mOuterRadius + rMargin, mZLength / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); setLayerStyle(layerVol); + LOGP(info, "oTOF kBarrelSegmented layout: stave tilt angle {}, layer tube rMin {}, rMax {}", mTiltAngle, mInnerRadius - rMargin, mOuterRadius + rMargin); + // Now we create the volume for a single stave const double staveSizeX = mStaves.second; // cm const double staveSizeY = mOuterRadius - mInnerRadius; // cm From 32420a7734a4e18c123254da23e2eb59b6fa60d7 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Fri, 13 Mar 2026 16:28:23 +0000 Subject: [PATCH 2/2] Please consider the following formatting changes --- .../Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h index e35a4e06945a4..1941b543579db 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h @@ -107,7 +107,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static std::string sLayerName; ///< Layer name static std::string sChipName; ///< Chip name static std::string sSensorName; ///< Sensor name - static std::string sPassiveName; ///< Passive material name + static std::string sPassiveName; ///< Passive material name private: static std::unique_ptr sInstance; ///< singletone instance