From 90b219debb4bcda3324a7104b655aed132c7cf7e Mon Sep 17 00:00:00 2001 From: Berkin Ulukutlu Date: Mon, 26 Oct 2026 10:53:39 +0100 Subject: [PATCH] ALICE3 TRK update the simplified-realistic OT paving Detailed specifications of the geomtry: https://cernbox.cern.ch/s/HKhKYWesvNdnJZB Module FPC: the flex now extends 2.1 mm beyond the chips on each long side for wire bonding (was 1 mm), so the FPC is 54.4 mm wide (was 52.2 mm). The margin is an OT constant, constants::OT::fpc::wireBondMargin, and the FPC width is derived from it; the ML module edge is unchanged. Paving: L9 and L10 follow the mechanical CAD radii, 620 and 820 mm, with 42 and 56 staves per ring (21 and 28 per quarter barrel). L8 is at 442 mm with 30 staves per ring (15 per quarter barrel), the smallest radius at which 30 staves still give about 1 mm of active overlap for tracks from the interaction point. The border-to-border neighbour overlap is 8.4-9.0 mm in the stave plane. The stave count of the realistic OT layer is now taken from the configuration and must be even; the count derived from the radius is kept as the fallback for nStaves = 0. The builder warns when the resulting active neighbour overlap is below rowActiveOverlap. The OT outer shell moves from 82.0 to 83.0 cm inner radius, since the flipped outermost layer at 820 mm reaches 826 mm. Co-Authored-By: Claude Opus 5.5 --- .../Upgrades/ALICE3/TRKFT3/TRK/README.md | 4 ++-- .../TRKFT3/TRK/base/include/TRKBase/Specs.h | 9 +++---- .../include/TRKSimulation/TRKServices.h | 2 +- .../TRKFT3/TRK/simulation/src/Detector.cxx | 12 ++++------ .../TRKFT3/TRK/simulation/src/TRKLayer.cxx | 24 +++++++++++++------ 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md index 71bbce74dcbb4..0d43e350e7a95 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md @@ -73,7 +73,7 @@ The first 5 valid lines are parsed as `TRKMLLayer` objects. These layers **requi **Outer Layers (OT) - Indices 5 and above** From the 6th valid line onwards, lines are parsed as OT layer objects (`TRKOTLayer` for `kSegmented`, `TRKOTLayerRealistic` for `kSimplifiedRealistic`). These layers do **not** have a staggering offset. The optional mode parameter shifts to the 6th column. * **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `[optional_mode]` -* *(Note: for `kSimplifiedRealistic`, `nStaves` is recomputed internally from the average radius and stave width to guarantee the neighbour overlap; the value in the file is ignored for the OT.)* +* *(Note: for `kSimplifiedRealistic`, `nStaves` is the number of staves per ring and must be even; if it is 0 the count is derived from the radius as the smallest one giving the required neighbour overlap.)* **Example for `kSegmented`:** @@ -111,7 +111,7 @@ The geometry specification is based on [ALICE3 OT WP1 Material (26.06.2025)](htt **End-of-stave card** — one readout PCB per stave (`constants::OT::eosCard`), mounted past the last module at the outer z end, coplanar with the two rows: a 120 × 80 mm, 1.5 mm board carrying four copper planes spread symmetrically through the thickness, the remainder FR4. The copper is what sets the card material budget: at the default 122 µm per plane the card is **4.0 % x/X₀** for a track crossing it perpendicularly (copper 3.40 %, FR4 0.60 %). It is tunable through `TRKBase.otEosCardCuThickness`, which changes only the copper/FR4 split inside the fixed 1.5 mm envelope, so the card dimensions and the layer envelopes stay put and only the radiation length moves. The cards reach |z| ≈ 141 cm, just short of the OT barrel service disk, which places them at |η| ≈ 1.84–1.88 on the innermost OT layer and |η| ≈ 1.27–1.32 on the outermost — inside the tracking acceptance, so they matter for forward-disk performance. -**Barrel** — each layer is built in **four parts**: two z-half-barrels (±η), each split azimuthally into two 180° halves. Both η half-barrels are cut on the **same vertical plane** (x = 0), so the region where the vertical beam-pipe supports run is free of staves over the whole barrel (the supports themselves are not in the geometry). Neighbouring staves overlap (≥ 1 mm active double-coverage); at the cut plane the two azimuthal halves instead leave a gap wide enough for the separation wall plus `barrelWallClearance` on each side. The stave count is the smallest even number that still meets the required overlap at the layer radius, so the OT radii (440, 615, 793 mm) are chosen at the top of a stave-count band, giving 30/42/54 staves per ring with 1.5-1.9 mm of overlap. The cooling pipe faces the larger radius on the inner two OT layers and the smaller radius on the flipped outer layer. +**Barrel** — each layer is built in **four parts**: two z-half-barrels (±η), each split azimuthally into two 180° halves. Both η half-barrels are cut on the **same vertical plane** (x = 0), so the region where the vertical beam-pipe supports run is free of staves over the whole barrel (the supports themselves are not in the geometry). Neighbouring staves overlap (≥ 1 mm active double-coverage); at the cut plane the two azimuthal halves instead leave a gap wide enough for the separation wall plus `barrelWallClearance` on each side. The default paving uses OT radii 442, 620 and 820 mm with 30, 42 and 56 staves per ring (15/21/28 per quarter barrel); L9 and L10 follow the mechanical CAD, L8 is placed as close in as the 1 mm active-overlap requirement allows for 30 staves. This gives 8.4-9.0 mm of border-to-border neighbour overlap in the stave plane. Each module's FPC extends 2.1 mm beyond the chips for wire bonding (`constants::OT::fpc::wireBondMargin`). The cooling pipe faces the larger radius on the inner two OT layers and the smaller radius on the flipped outer layer. **Separation walls** — the quarter barrels are closed on every side but the one carrying the end-of-stave cards (`TRKServices::createOTBarrelWalls`, `kPeacockv1` + `kSimplifiedRealistic`): radially by the ML/OT and outer carbon shells, azimuthally by a rectangular carbon fibre wall in the cut plane, and at mid-rapidity by a half disk at z = 0, one per quarter barrel. diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h index 0042654f92904..9ac1f789d43ba 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h @@ -139,11 +139,13 @@ constexpr int nModulesPerRow{11}; // mod constexpr double interModuleGap{0.2 * mm}; // z-gap between module FPCs // Component dimensions of the simplified-realistic OT stave +constexpr double interChipGap{0.2 * mm}; // gap between chips within a module namespace fpc { -constexpr double length{116.8 * mm}; // z-extent -constexpr double width{52.2 * mm}; // phi-extent -constexpr double thickness{0.200 * mm}; // r-extent, Kapton+Cu stack +constexpr double wireBondMargin{2.1 * mm}; // FPC beyond the chips on each long side, for wire bonding +constexpr double length{116.8 * mm}; // z-extent +constexpr double width{2 * moduleMLOT::chip::width + interChipGap + 2 * wireBondMargin}; // phi-extent +constexpr double thickness{0.200 * mm}; // r-extent, Kapton+Cu stack } // namespace fpc namespace coldPlate { @@ -195,7 +197,6 @@ constexpr double zClearance{1.0 * mm}; // z-clearance to the nearest wall and t } // namespace supportRing constexpr double sensorThickness{moduleMLOT::silicon::thickness}; // pure-silicon chip (no metal stack) -constexpr double interChipGap{0.2 * mm}; // gap between chips within a module constexpr double rowActiveOverlap{1.0 * mm}; // active overlap between the two rows of a stave constexpr double rowRadialStagger{2.0 * mm}; // radial step between any two overlapping rows constexpr double halfBarrelChipGap{1.0 * mm}; // gap between the two azimuthal half-barrels diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h index dcbc2d724a862..3bd2403c46950 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h @@ -64,7 +64,7 @@ class TRKServices : public FairModule // Carbon fibre shells bounding the OT barrel in r; the separation walls span between them. static constexpr float sMLOTShellRMax = 39.5f; // cm, outer radius of the ML/OT separation shell static constexpr float sMLOTShellThickness = 0.2f; // cm - static constexpr float sOTShellRMin = 82.0f; // cm, inner radius of the OT outer shell + static constexpr float sOTShellRMin = 83.0f; // cm, inner radius of the OT outer shell static constexpr float sOTShellThickness = 0.4f; // cm, load bearing, hence thicker // Vacuum diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx index bd34c9109f72b..b7bbdb2e7901e 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx @@ -132,14 +132,12 @@ void Detector::configMLOT() const std::vector nMods{11, 11, 11, 11, 11, 22, 22, 22}; const std::vector stagOffsets{0.f, 0.f, 0.f, 1.17f, 0.89f}; - // OT radii chosen at the top of a stave-count band, where the paving closes with - // ~1.5-1.9 mm of neighbour overlap; the outermost also clears TRKServices::sOTShellRMin. + // OT: L9/L10 radii from the mechanical CAD, L8 at 442 mm; staves per ring (15/21/28 per quarter barrel) std::vector rInnReal = rInn; - rInnReal[constants::ML::nLayers + 0] = 44.0f; - rInnReal[constants::ML::nLayers + 1] = 61.5f; - rInnReal[constants::ML::nLayers + 2] = 79.3f; - // OT counts are informational: TRKOTLayerRealistic derives its own from the radius. - const std::vector nStaves{10, 14, 18, 26, 38, 30, 42, 54}; + rInnReal[constants::ML::nLayers + 0] = 44.2f; + rInnReal[constants::ML::nLayers + 1] = 62.0f; + rInnReal[constants::ML::nLayers + 2] = 82.0f; + const std::vector nStaves{10, 14, 18, 26, 38, 30, 42, 56}; LOGP(warning, "Loading simplified-realistic configuration for ALICE3 TRK"); for (int i{0}; i < constants::ML::nLayers + constants::OT::nLayers; ++i) { diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx index eb33616f8e0db..60128cedc799c 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx @@ -739,7 +739,7 @@ TGeoVolume* TRKOTLayerRealistic::createStave() // Two rows overlapping in phi and staggered in r. They straddle the stave origin, so the // stave is tangent to the barrel circle at its centre and every row-to-row radial step, // within a stave and between neighbours, is rowRadialStagger. - const double edgeDead = constants::moduleMLOT::gaps::outerEdgeLongSide + constants::moduleMLOT::chip::passiveEdgeReadOut; + const double edgeDead = constants::OT::fpc::wireBondMargin + constants::moduleMLOT::chip::passiveEdgeReadOut; const double inStaveOverlap = 2 * edgeDead + constants::OT::rowActiveOverlap; const double rowOffset = constants::OT::fpc::width - inStaveOverlap; @@ -765,7 +765,7 @@ TGeoVolume* TRKOTLayerRealistic::createStave() void TRKOTLayerRealistic::createLayer(TGeoVolume* motherVolume) { - const double edgeDead = constants::moduleMLOT::gaps::outerEdgeLongSide + constants::moduleMLOT::chip::passiveEdgeReadOut; + const double edgeDead = constants::OT::fpc::wireBondMargin + constants::moduleMLOT::chip::passiveEdgeReadOut; const double inStaveOverlap = 2 * edgeDead + constants::OT::rowActiveOverlap; const double staveWidth = 2 * constants::OT::fpc::width - inStaveOverlap; @@ -812,12 +812,18 @@ void TRKOTLayerRealistic::createLayer(TGeoVolume* motherVolume) const double accGap = std::max(constants::OT::halfBarrelChipGap + 2 * constants::moduleMLOT::chip::passiveEdgeReadOut, 2 * (wallThickness / 2 + constants::OT::barrelWallClearance + edgeDead)); - // Smallest even count still leaving rowActiveOverlap between neighbours: the two boundary - // staves take activeStaveWidth + accGap each, so only nStaves - 2 junctions share the rest. + // Staves per ring: configured, or else the smallest even count leaving rowActiveOverlap between + // neighbours (the two boundary staves take activeStaveWidth + accGap, nStaves - 2 junctions share the rest). const double activeStaveWidth = staveWidth - 2 * edgeDead; - int nStavesHalfBarrel = (int)std::ceil(2. + (avgRadius * 2 * TMath::Pi() - 2 * (activeStaveWidth + accGap)) / - (activeStaveWidth - constants::OT::rowActiveOverlap)); - nStavesHalfBarrel += nStavesHalfBarrel % 2; + int nStavesHalfBarrel = mNumberOfStaves; + if (nStavesHalfBarrel <= 0) { + nStavesHalfBarrel = (int)std::ceil(2. + (avgRadius * 2 * TMath::Pi() - 2 * (activeStaveWidth + accGap)) / + (activeStaveWidth - constants::OT::rowActiveOverlap)); + nStavesHalfBarrel += nStavesHalfBarrel % 2; + } + if (nStavesHalfBarrel % 2) { + LOGP(fatal, "OT layer {}: {} staves per ring, must be even (two azimuthal halves)", mLayerNumber, nStavesHalfBarrel); + } const int nHalf = nStavesHalfBarrel / 2; const double thetaGap = (activeStaveWidth + accGap) / avgRadius; @@ -825,6 +831,10 @@ void TRKOTLayerRealistic::createLayer(TGeoVolume* motherVolume) const double overlap = activeStaveWidth - avgRadius * thetaInt; LOGP(info, "Creating realistic OT layer {}: {} staves/half-barrel, internal overlap {} mm, boundary gap {} mm, flipped={}", mLayerNumber, nStavesHalfBarrel, overlap * 10, accGap * 10, mIsFlipped); + if (overlap < constants::OT::rowActiveOverlap) { + LOGP(warning, "OT layer {}: active neighbour overlap {} mm is below the required {} mm", + mLayerNumber, overlap * 10, constants::OT::rowActiveOverlap * 10); + } const int nStaves = nStavesHalfBarrel * 2;