diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h index f4619c608c099..a4357bf8b8995 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -46,8 +46,13 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { double staveTolOTInner = 0.; double staveTolOTOuter = 0.; - // What to place over x=0 line in case of full outer-outer stave: Gap or Module - bool placeSensorStackInMiddleOfStave = false; + /* + * Place the sensor stacks from the tabulated layout in FT3ModuleConstants.h + * (StaveConfig::exactStaveFills) instead of filling every stave greedily + * with the stack sizes in kSensorsPerStack. The tabulated layout is taken as + * given: none of the radial tolerances above are applied to it. + */ + bool useExactStavePlacement = true; // Draw reference circles at inner and outer radius of stave layer, for visualisation bool drawReferenceCircles = false; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt index 98adea7c6124a..bb5c04de2611b 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt @@ -11,6 +11,7 @@ o2_add_library(FT3Simulation SOURCES + src/FT3Materials.cxx src/FT3Module.cxx src/FT3Layer.cxx src/Detector.cxx diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h index 512326949128b..404bde7895b07 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h @@ -59,25 +59,11 @@ class FT3Layer : public TObject /// \param motherVolume the TGeoVolume owing the volume structure virtual void createLayer(TGeoVolume* motherVolume); - static void initialize_mat(); - // create layer for disk support void createSeparationLayer(TGeoVolume* motherVolume, const std::string& separationLayerName); void createSeparationLayer_waterCooling(TGeoVolume* motherVolume, const std::string& separationLayerName); void createReferenceCircles(TGeoVolume* motherVolume, const std::string& name); - static TGeoMaterial* carbonFiberMat; - static TGeoMedium* medCarbonFiber; - - static TGeoMixture* kaptonMat; - static TGeoMedium* kaptonMed; - - static TGeoMaterial* waterMat; - static TGeoMedium* waterMed; - - static TGeoMaterial* foamMat; - static TGeoMedium* medFoam; - private: Int_t mLayerNumber = -1; ///< Current layer number Int_t mDirection; ///< Layer direction 0=Forward 1 = Backward diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h new file mode 100644 index 0000000000000..06752bf267db1 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Materials.h @@ -0,0 +1,125 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file FT3Materials.h +/// \brief Materials of the FT3 detector, and access to the media made from them + +#ifndef FT3MATERIALS_H +#define FT3MATERIALS_H + +#include +#include +#include + +class TGeoMedium; + +namespace o2::ft3 +{ +namespace Materials +{ +// The name FT3 registers itself under with the MaterialManager. getMedium() +// looks media up under this key, so it has to be the same name the Detector +// hands to DetImpl. +constexpr const char* moduleName = "FT3"; + +/* + * Materials of the FT3 detector. + * + * Everything the simulation needs to know about a material lives in the + * materials map below, keyed by its FT3-local ID: composition, density, + * transport parameters and the colour its volumes are drawn in. + * Detector::createMaterials() registers the whole map with the MaterialManager, + * and FT3Module/FT3Layer reach the media through getMedium() below, so no ID, + * density or colour is ever written out by hand. + */ +enum class MaterialID : unsigned { + Air = 1, + Silicon, + Copper, + Kapton, + CarbonFiber, + Epoxy, + Aluminum, + Foam, + Water +}; + +// Transport parameters of a medium, in the order expected by Detector::Medium() +struct TrackingParams { + float tmaxfd; // maximum field-induced angular deviation per step, degrees + float stemax; // maximum step length, cm + float deemax; // maximum fractional energy loss per step + float epsil; // tracking precision, cm + float stmin; // minimum step length, cm +}; + +constexpr TrackingParams sensitiveTracking = {0.1f, 0.0075f, 0.1f, 1.0e-4f, 0.0f}; +constexpr TrackingParams passiveTracking = {0.1f, 1.0f, 0.1f, 1.0e-4f, 0.0f}; + +// Maximum number of elements any of the mixtures below is built from +constexpr unsigned maxMaterialComponents = 4; +using ComponentArray = std::array; + +struct MaterialProperties { + const char* name; + int colour; // ROOT colour every volume made of this material is drawn in + float density; // g/cm3 + // Radiation and nuclear interaction length, cm. Only single elements carry + // them: Mixture() derives both from the composition and takes no such + // arguments. A non-positive value lets the transport engine compute it. + float radl; + float absl; + int nComponents; // 0: single element; > 0: mixture by weight; < 0: mixture by atom count + ComponentArray a; // mass numbers; only a[0] is used for a single element + ComponentArray z; // atomic numbers; only z[0] is used for a single element + ComponentArray w; // weight fractions or atom counts; unused for a single element + TrackingParams tracking; +}; + +/* + * Silicon, copper and carbon fibre are shared with TRK and are kept numerically + * identical to its SILICON$, COPPER$ and CARBONFIBER$ (TRK Detector::createMaterials()), + * down to the radiation lengths. Kapton, epoxy and aluminium have no TRK + * counterpart: TRK models the flex as the effective FPC$ mixture instead. + */ +inline const std::unordered_map materials = { + // Air volumes get their colour set individually where they are built + {MaterialID::Air, {"Air", kWhite, 1.20479e-3f, 0.0f, 0.0f, 4, {12.0107f, 14.0067f, 15.9994f, 39.948f}, {6.0f, 7.0f, 8.0f, 18.0f}, {0.000124f, 0.755267f, 0.231781f, 0.012827f}, passiveTracking}}, + {MaterialID::Silicon, {"Silicon", kGreen, 2.33f, 9.36f, 999.0f, 0, {28.086f}, {14.0f}, {}, sensitiveTracking}}, + // Copper planes of the end-of-stave cards: X0 = 1.436 cm + {MaterialID::Copper, {"Copper", kOrange, 8.96f, 1.436f, 999.0f, 0, {63.546f}, {29.0f}, {}, passiveTracking}}, + // Kapton: C22 H10 N2 O5, by weight fraction. Also the cooling pipe material. + {MaterialID::Kapton, {"Kapton", kYellow, 1.346f, 0.0f, 0.0f, 4, {12.0107f, 1.00794f, 14.0067f, 15.999f}, {6.0f, 1.0f, 7.0f, 8.0f}, {0.5641f, 0.2564f, 0.0513f, 0.1282f}, passiveTracking}}, + // Carbon fibre: density tuned so X0 ~ 27 cm, as in TRK + // TODO: Check with Rene the exact type of carbon fiber + {MaterialID::CarbonFiber, {"CarbonFiber", kGray + 1, 1.45f, 27.0f, 999.0f, 0, {12.0107f}, {6.0f}, {}, passiveTracking}}, + // Epoxy: C18 H19 O3, by atom count (negative nComponents) + {MaterialID::Epoxy, {"Epoxy", kBlue, 2.186f, 0.0f, 0.0f, -3, {12.0107f, 1.00794f, 15.999f}, {6.0f, 1.0f, 8.0f}, {18.0f, 19.0f, 3.0f}, passiveTracking}}, + // No TRK counterpart; X0 and lambda are left to the transport engine + {MaterialID::Aluminum, {"Aluminum", kBlack, 2.7f, 0.0f, 0.0f, 0, {26.98f}, {13.0f}, {}, passiveTracking}}, + // Carbon foam core of the disk separation layer + {MaterialID::Foam, {"Foam", kBlack, 0.17f, 0.0f, 0.0f, 0, {12.0107f}, {6.0f}, {}, passiveTracking}}, + // Coolant inside the kapton pipes + {MaterialID::Water, {"Water", kBlue, 1.064f, 0.0f, 0.0f, 0, {18.01528f}, {8.0f}, {}, passiveTracking}}}; +// The inactive rim of a sensor is made of silicon as well, but is drawn +// separately so that it can be told apart from the active area. +const int SiInactiveColor = kRed; +} // namespace Materials + +/// Retrieve one of the media registered by Detector::createMaterials(). +/// +/// A free function rather than a member of Detector: the media live in the +/// MaterialManager singleton keyed by Materials::moduleName, not in the +/// detector object, so the lookup needs no Detector instance. +TGeoMedium* getMedium(Materials::MaterialID id); +} // namespace o2::ft3 + +#endif // FT3MATERIALS_H diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h index 71449b008cd80..e071e5cbd521b 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -15,10 +15,12 @@ #ifndef FT3MODULE_H #define FT3MODULE_H +#include #include #include #include +#include "FT3Simulation/FT3Materials.h" #include "FT3Simulation/FT3ModuleConstants.h" // define types for y positions, second element is the stack height @@ -28,25 +30,12 @@ using PosNegPositionTypes = std::pair; // define type of the y position range: First pair is (min, max) for positive y using PositionRangeType = std::pair, std::pair>; namespace Constants = o2::ft3::ModuleConstants; +namespace Materials = o2::ft3::Materials; class FT3Module { public: - static void initialize_materials(); - static TGeoMaterial* siliconMat; - static TGeoMedium* siliconMed; - static TGeoMaterial* copperMat; - static TGeoMedium* copperMed; - static TGeoMixture* kaptonMat; - static TGeoMedium* kaptonMed; - static TGeoMaterial* epoxyMat; - static TGeoMedium* epoxyMed; - static TGeoMaterial* AluminumMat; - static TGeoMedium* AluminumMed; - static TGeoMaterial* carbonFiberMat; - static TGeoMedium* carbonFiberMed; - const char* mDetName; static void createModule( @@ -70,10 +59,39 @@ class FT3Module double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, TGeoVolume* motherVolume); + // Walk every stave of a layer, create its volumes and work out where its + // modules go, leaving the positions in y_positionsPosNeg + void build_staves_exact( + TGeoVolume* motherVolume, int layerNumber, int direction, + const Constants::StaveConfig& staveConfig, + const std::array, 4>& staveTriangles, + double z_offset_to_carbon_face, + std::vector& y_positionsPosNeg, + unsigned& staveVolumeCount); + + void build_staves_greedy( + TGeoVolume* motherVolume, int layerNumber, int direction, double Rin, double Rout, + const Constants::StaveConfig& staveConfig, + const std::array, 4>& staveTriangles, + double z_offset_to_carbon_face, + std::vector& y_positionsPosNeg, unsigned& staveVolumeCount); + + // Shared by both: one stave's carbon shell, plus its mirror where needed + void add_stave_volumes( + TGeoVolume* motherVolume, int layerNumber, int direction, + const Constants::StaveConfig& staveConfig, unsigned i_stave, + const std::array, 4>& staveTriangles, + double z_offset_to_carbon_face, std::pair& absAllowedYRange, + double y_midpoint, bool mirrorStaveAroundX, unsigned* staveVolumeCount); + // Helper functions - void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, - double x_left, unsigned kSensorStack, PositionRangeType y_range, - std::pair& absAllowedYRange); + void fill_stave_greedy( + PosNegPositionTypes& y_positions, unsigned kSensorStack, + PositionRangeType y_range, + std::pair& absAllowedYRange); + + PositionTypes fill_stave_exact(const std::vector& fills); + void addStaveVolume( TGeoVolume* motherVolume, std::string volumeName, int direction, unsigned* volume_count, double staveLength, diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index dd3aa412ad525..c14d5fd42d719 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -11,13 +11,14 @@ /// \file FT3ModuleConstants.h /// \brief Definition of various constants for tiling the modules of sensors +/// +/// The materials themselves live in FT3Materials.h. #ifndef FT3MODULECONSTANTS_H #define FT3MODULECONSTANTS_H #include #include -#include #include namespace o2::ft3::ModuleConstants @@ -29,30 +30,31 @@ namespace o2::ft3::ModuleConstants * When stacking 2x1 modules, there is a 0.2mm gap between them. By default, we assume this * gap to be ABOVE the most recently placed module. * - * |<- 25mm ->|<- 25mm ->| + * |<- 25mm ->||<- 25mm ->| * _______________________ - * ----------------------- 0.2mm gap above - * | | | | | - * | | | | | - * | | | | | - * | | | | | 29mm sensor height - * | | | | | - * | | | | | - * ------------------------ - * + * ------------------------ 0.15mm gap above + * | | || | | + * | | || | | + * | | || | | + * | | || | | 29mm sensor height + * | | || | | + * | | || | | + * ------------------------ 0.15mm gap below + * ^ + * | + * 0.15mm gap in the middle */ // First set all layout constants for the rest of the function const double single_sensor_width = 2.5; const double single_sensor_height = 2.9; -const double inactive_width = 0.2; -const double sensor2x1_gap = 0.02; // both between L&R sensors in 2x1, and between two 2x1s -const double stackGap = sensor2x1_gap; // gap between 2xN module stacks +const double inactive_width = 0.15; +const double sensor2x1_gap = 0.015; // gap between L&R sensors in 2x1, and between sensors in a stack +const double stackGap = 0.035; // gap between 2xN module stacks const double active_width = single_sensor_width - inactive_width; const double active_height = single_sensor_height; -const double sensor2x1_width = 2 * single_sensor_width; -const double sensor2x1_active_width = 2 * active_width; +const double sensor2x1_width = 2 * single_sensor_width + sensor2x1_gap; const double sensor2x1_height = single_sensor_height; const std::vector kSensorsPerStack = {4, 2, 1}; inline const double getStackHeight(unsigned nSensorsPerStack) @@ -70,6 +72,35 @@ inline const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc) return staveIdx - nStavesOneSide + isRight; } +/* + * Stave x midpoints follow from their number and spacing: they are spread + * symmetrically about x=0, so an even count leaves a gap on the axis rather + * than putting a stave on it. Deriving them keeps the spacing and the + * positions from drifting apart. + */ +inline std::vector makeStaveXMidpoints(unsigned nStaves, double spacing) +{ + std::vector midpoints(nStaves); + for (unsigned i = 0; i < nStaves; i++) { + midpoints[i] = (i - (nStaves - 1) / 2.0) * spacing; + } + return midpoints; +} + +/* + * Staves alternate between the front and the back of the disc so that + * neighbours can overlap in x without touching, staggered in z by + * z_offsetStave. Starting at 0 puts the leftmost stave at the back. + */ +inline std::vector makeStaveOnFront(unsigned nStaves) +{ + std::vector staveOnFront(nStaves); + for (unsigned i = 0; i < nStaves; i++) { + staveOnFront[i] = i % 2; + } + return staveOnFront; +} + // material properties const double siliconThickness = 0.01; const double copperThickness = 0.006; @@ -80,8 +111,9 @@ const double effectiveCarbonThickness_Stave = 0.02; // foam + shell const double staveOpeningAngle = 60 * TMath::DegToRad(); const double sinTheta = TMath::Sin(staveOpeningAngle / 2); const double alpha = TMath::Pi() / 2 - staveOpeningAngle / 2; // bottom angles -const double staveSensorGap = 0.1; // 2mm padding on each side when sensor is glued -const double staveTriangleHeight = (sensor2x1_width + 2 * staveSensorGap) / 2.0 / tan(staveOpeningAngle / 2.0); +const double staveSensorGap = 0.1025; // 1025µm padding on each side: 52.2mm stave width +const double staveWidth = sensor2x1_width + 2 * staveSensorGap; +const double staveTriangleHeight = staveWidth / 2.0 / tan(staveOpeningAngle / 2.0); /* * Now describe the offset of every other stave in z to avoid overlaps * ______ ______ @@ -98,12 +130,21 @@ inline const double z_offsetStave(double x_midpoint_spacing) (2 - x_midpoint_spacing / (sensor2x1_width / 2 + staveSensorGap)); } -const int SiColor = kGreen; -const int SiInactiveColor = kRed; -const int glueColor = kBlue; -const int CuColor = kOrange; -const int kaptonColor = kYellow; -const int carbonFiberColor = kGray + 1; +/* + * One uninterrupted fill of 2xN modules along a stave. + * + * yStart is the y of the BOTTOM edge of the first module; modules follow + * upwards, each separated from the previous one by stackGap. Nothing is + * mirrored: the layout is symmetric about the y-axis (stave +-ID) but NOT + * about the x-axis, so every fill states its own y explicitly. + * + * A stave that the beam pipe cuts in two therefore carries two fills, one + * below the hole and one above it, each with its own yStart. + */ +struct StaveFill { + const double yStart; + const std::vector stackHeights; +}; // Struct for stave position configuration (varies between ML/OT) struct StaveConfig { @@ -132,33 +173,88 @@ struct StaveConfig { // kSegmentedStave: staggering staves in z (see z_offsetStave) // accessed via stave index, NOT stave ID const std::vector& staveOnFront; + /* + * Tabulated module layout, used when FT3Base.useExactStavePlacement is set. + * One entry per stave, indexed like x_midpoints (NOT by stave ID), holding + * that stave's fills: one for a stave reaching across y=0, two for a stave + * split by the beam pipe. + */ + const std::vector>& exactStaveFills; }; namespace OT_StavePositions { +/* + * Staves that the beam pipe cuts in two, built as two pieces on +-y_midpoint. + * Do NOT add any zero midpoints, this is taken off separately. + */ const std::map> staveID_to_y_midpoint = { - {-2, {39.0, true}}, - {-1, {41.4, true}}, - {1, {41.4, true}}, - {2, {39.0, true}}}; + {-4, {35.659, true}}, + {-3, {41.735, true}}, + {-2, {42.882, true}}, + {-1, {43.761, true}}, + {1, {43.761, true}}, + {2, {42.882, true}}, + {3, {41.735, true}}, + {4, {35.659, true}}}; +/* + * Length of one stave piece: for a stave in staveID_to_y_midpoint that is one + * of its two pieces, otherwise the whole stave centred on y=0. Trimmed to the + * modules in exactStaveFills, rounded up to the nearest 10 um. + */ const std::vector y_lengths = { - 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, - 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, - 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, - 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8}; -const std::vector x_midpoints = { - -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L - -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L - 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R - 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R -}; -const double x_midpoint_spacing = 4.5; // assume constant for now -const double maxToleranceInner = 0.; // default not allowed inwards + 32.225, 58.401, 73.016, 87.611, + 99.29, 108.055, 113.886, 119.736, + 125.566, 128.481, 61.336, 49.655, + 49.656, 46.818, 46.818, 49.656, + 49.655, 61.336, 128.481, 125.566, + 119.736, 113.886, 108.055, 99.29, + 87.611, 73.016, 58.401, 32.225}; +const unsigned nStaves = 28; // y_lengths, staveOnFront and exactStaveFills follow this +const double x_midpoint_spacing = 4.92; +const std::vector x_midpoints = makeStaveXMidpoints(nStaves, x_midpoint_spacing); +const double maxToleranceInner = 9.; // close but not directly at 10cm yet const double maxToleranceOuter = 3.4; // leave 1mm for layer air encapsulation -const std::vector staveOnFront = - { - 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L - 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R +const std::vector staveOnFront = makeStaveOnFront(nStaves); +/* + * From the disk optimiser: Rin 20, Rout 68, stave width 5.22, overlap 0.30, + * intrusion and extrusion <= 2 with 6 exception staves at 9 and 3, giving + * 99.37% filling with staves and 93.48% with active silicon. One entry per + * stave in x_midpoints order; a stave cut in two by the beam pipe has one + * fill either side of the hole. + * + * yStart is the bottom of the first SENSOR, which is half a module gap above + * the bottom of the module the optimiser reports. + */ +const std::vector> exactStaveFills = { + {{-15.9953, {4, 4, 3}}}, // ID -14 + {{-29.1825, {4, 4, 4, 4, 4}}}, // ID -13 + {{-36.49, {4, 4, 4, 4, 3, 3, 3}}}, // ID -12 + {{-43.7875, {4, 4, 4, 4, 4, 4, 3, 3}}}, // ID -11 + {{-49.6275, {4, 4, 4, 4, 4, 4, 4, 3, 3}}}, // ID -10 + {{-54.01, {4, 4, 4, 4, 4, 4, 4, 3, 3, 3}}}, // ID -9 + {{-56.925, {4, 4, 4, 4, 4, 4, 4, 4, 4, 3}}}, // ID -8 + {{-59.85, {4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3}}}, // ID -7 + {{-62.765, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3}}}, // ID -6 + {{-64.2225, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}}, // ID -5 + {{-66.3087, {4, 4, 4, 3, 3, 3}}, {5.0087, {4, 4, 4, 3, 3, 3}}}, // ID -4 + {{-66.545, {4, 4, 3, 3, 3}}, {16.925, {4, 4, 3, 3, 3}}}, // ID -3 + {{-67.6917, {4, 4, 3, 3, 3}}, {18.0717, {4, 4, 3, 3, 3}}}, // ID -2 + {{-67.0542, {4, 4, 4, 4}}, {20.4669, {4, 4, 4, 4}}}, // ID -1 + {{-67.1519, {4, 4, 4, 4}}, {20.3692, {4, 4, 4, 4}}}, // ID +1 + {{-67.6917, {4, 4, 3, 3, 3}}, {18.0717, {4, 4, 3, 3, 3}}}, // ID +2 + {{-66.545, {4, 4, 3, 3, 3}}, {16.925, {4, 4, 3, 3, 3}}}, // ID +3 + {{-66.3087, {4, 4, 4, 3, 3, 3}}, {5.0087, {4, 4, 4, 3, 3, 3}}}, // ID +4 + {{-64.2225, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}}, // ID +5 + {{-62.765, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3}}}, // ID +6 + {{-59.85, {4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3}}}, // ID +7 + {{-56.925, {4, 4, 4, 4, 4, 4, 4, 4, 4, 3}}}, // ID +8 + {{-54.01, {4, 4, 4, 4, 4, 4, 4, 3, 3, 3}}}, // ID +9 + {{-49.6275, {4, 4, 4, 4, 4, 4, 4, 3, 3}}}, // ID +10 + {{-43.7875, {4, 4, 4, 4, 4, 4, 3, 3}}}, // ID +11 + {{-36.49, {4, 4, 4, 4, 3, 3, 3}}}, // ID +12 + {{-29.1825, {4, 4, 4, 4, 4}}}, // ID +13 + {{-16.0947, {4, 4, 3}}}, // ID +14 }; } // namespace OT_StavePositions @@ -175,18 +271,14 @@ const std::map> staveID_to_y_midpoint = { const std::vector y_lengths = { 30.5, 44.5, 53.6, 60.0, 64.6, 29.5, 25.8, 25.0, 25.0, 25.8, 29.5, 64.6, 60.0, 53.6, 44.5, 30.5}; -const std::vector x_midpoints = { - -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L - 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75 // R -}; +const unsigned nStaves = 16; // y_lengths, staveOnFront and exactStaveFills follow this const double x_midpoint_spacing = 4.5; +const std::vector x_midpoints = makeStaveXMidpoints(nStaves, x_midpoint_spacing); const double maxToleranceInner = 0.; // default not allowed inwards const double maxToleranceOuter = 3.4; // leave 1mm for layer air encapsulation -const std::vector staveOnFront = - { - 1, 0, 1, 0, 1, 0, 1, 0, // L - 1, 0, 1, 0, 1, 0, 1, 0 // R -}; +const std::vector staveOnFront = makeStaveOnFront(nStaves); +// TODO: fill from the disk optimiser output, see OT_StavePositions above. +const std::vector> exactStaveFills = {}; } // namespace ML_StavePositions // Get stave configuration based on tracker type @@ -201,7 +293,8 @@ inline StaveConfig getStaveConfig(bool isInnerDisk) ML_StavePositions::x_midpoint_spacing, ML_StavePositions::maxToleranceInner, ML_StavePositions::maxToleranceOuter, - ML_StavePositions::staveOnFront}; + ML_StavePositions::staveOnFront, + ML_StavePositions::exactStaveFills}; } else { return StaveConfig{ false, // isML @@ -211,7 +304,8 @@ inline StaveConfig getStaveConfig(bool isInnerDisk) OT_StavePositions::x_midpoint_spacing, OT_StavePositions::maxToleranceInner, OT_StavePositions::maxToleranceOuter, - OT_StavePositions::staveOnFront}; + OT_StavePositions::staveOnFront, + OT_StavePositions::exactStaveFills}; } } diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx index 7fe43975934f4..6b07ae3ecd842 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx @@ -20,6 +20,7 @@ #include "FT3Base/FT3BaseParam.h" #include "FT3Base/GeometryTGeo.h" #include "FT3Simulation/FT3Layer.h" +#include "FT3Simulation/FT3Materials.h" // FairRoot includes #include "FairDetector.h" // for FairDetector @@ -39,6 +40,7 @@ #include // for LOG, LOG_IF +#include #include // for NULL, snprintf #define MAX_SENSORS 2000 @@ -54,7 +56,7 @@ using o2::trkft3::Hit; //_________________________________________________________________________________________________ Detector::Detector() - : o2::base::DetImpl("FT3", kTRUE), + : o2::base::DetImpl(Materials::moduleName, kTRUE), mTrackData(), mHits(o2::utils::createSimVector()) { @@ -346,7 +348,7 @@ void Detector::buildFT3Scoping() //_________________________________________________________________________________________________ Detector::Detector(bool active) - : o2::base::DetImpl("FT3", active), + : o2::base::DetImpl(Materials::moduleName, active), mTrackData(), mHits(o2::utils::createSimVector()) { @@ -495,29 +497,26 @@ void Detector::createMaterials() float fieldm = 10.0; o2::base::Detector::initFieldTrackingParams(ifield, fieldm); - float tmaxfdSi = 0.1; // .10000E+01; // Degree - float stemaxSi = 0.0075; // .10000E+01; // cm - float deemaxSi = 0.1; // 0.30000E-02; // Fraction of particle's energy 0(materialID); + if (material.nComponents == 0) { + o2::base::Detector::Material(id, material.name, material.a[0], material.z[0], material.density, + material.radl, material.absl); + } else { + // Mixture() takes non-const pointers, so hand it copies of the table rows + Materials::ComponentArray a = material.a; + Materials::ComponentArray z = material.z; + Materials::ComponentArray w = material.w; + o2::base::Detector::Mixture(id, material.name, a.data(), z.data(), material.density, material.nComponents, w.data()); + } + const auto& tracking = material.tracking; + o2::base::Detector::Medium(id, material.name, id, 0, ifield, fieldm, tracking.tmaxfd, tracking.stemax, + tracking.deemax, tracking.epsil, tracking.stmin); + } } //_________________________________________________________________________________________________ diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx index b85e89f87f099..b70afc9337ab9 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx @@ -17,6 +17,7 @@ #include "FT3Simulation/FT3Layer.h" #include "FT3Base/GeometryTGeo.h" #include "FT3Base/FT3BaseParam.h" +#include "FT3Simulation/FT3Materials.h" #include "FT3Simulation/FT3ModuleConstants.h" #include // for TGeoManager, gGeoManager @@ -37,18 +38,6 @@ ClassImp(FT3Layer); FT3Layer::~FT3Layer() = default; -TGeoMaterial* FT3Layer::carbonFiberMat = nullptr; -TGeoMedium* FT3Layer::medCarbonFiber = nullptr; - -TGeoMixture* FT3Layer::kaptonMat = nullptr; -TGeoMedium* FT3Layer::kaptonMed = nullptr; - -TGeoMaterial* FT3Layer::waterMat = nullptr; -TGeoMedium* FT3Layer::waterMed = nullptr; - -TGeoMaterial* FT3Layer::foamMat = nullptr; -TGeoMedium* FT3Layer::medFoam = nullptr; - FT3Layer::FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerName, Float_t z, Float_t rIn, Float_t rOut, Float_t Layerx2X0, bool partOfMiddleLayers) { // Creates a simple parametrized EndCap layer covering the given @@ -61,7 +50,7 @@ FT3Layer::FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerNam mx2X0 = Layerx2X0; mInnerRadius = rIn; mOuterRadius = rOut; - const double Si_X0 = 9.5; + const double Si_X0 = Materials::materials.at(Materials::MaterialID::Silicon).radl; mChipThickness = Layerx2X0 * Si_X0; mSensorThickness = 0.005; // assume 50 microns of active thickness (for sensor volumes for trapezoidal disks) @@ -78,37 +67,6 @@ FT3Layer::FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerNam LOG(info) << " Layer z = " << mZ << " ; R_in = " << mInnerRadius << " ; R_out = " << mOuterRadius << " ; x2X0 = " << mx2X0 << " ; ChipThickness = " << mChipThickness; } -void FT3Layer::initialize_mat() -{ - - if (carbonFiberMat) { - return; - } - - carbonFiberMat = new TGeoMaterial("CarbonFiber", 12.0, 6.0, 1.6); - medCarbonFiber = new TGeoMedium("CarbonFiber", 1, carbonFiberMat); - - auto* itsC = new TGeoElement("FT3_C", "Carbon", 6, 12.0107); - - auto* itsFoam = new TGeoMixture("FT3_Foam", 1); - itsFoam->AddElement(itsC, 1); - itsFoam->SetDensity(0.17); - - medFoam = new TGeoMedium("FT3_Foam", 1, itsFoam); - foamMat = medFoam->GetMaterial(); - - kaptonMat = new TGeoMixture("Kapton (cooling pipe)", 4, 1.346); // C22 H10 N2 O5 - - kaptonMat->DefineElement(0, 12.0107, 6, 0.5641); // Carbon - kaptonMat->DefineElement(1, 1.00794, 1, 0.2564); // Hydrogen - kaptonMat->DefineElement(2, 14.0067, 7, 0.0513); // Nitrogen - kaptonMat->DefineElement(3, 15.999, 8, 0.1282); // Oxygen - kaptonMed = new TGeoMedium("Kapton (cooling pipe)", 1, kaptonMat); - - waterMat = new TGeoMaterial("Water", 18.01528, 8.0, 1.064); - waterMed = new TGeoMedium("Water", 2, waterMat); -} - static double y_circle(double x, double radius) { return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0; @@ -117,16 +75,14 @@ static double y_circle(double x, double radius) void FT3Layer::createSeparationLayer_waterCooling(TGeoVolume* motherVolume, const std::string& separationLayerName) { - FT3Layer::initialize_mat(); - const double carbonFiberThickness = 0.01; // cm const double foamSpacingThickness = 0.5; // cm TGeoTube* carbonFiberLayer = new TGeoTube(mInnerRadius, mOuterRadius, carbonFiberThickness / 2); // volumes - TGeoVolume* carbonFiberLayerVol1 = new TGeoVolume((separationLayerName + "_CarbonFiber1").c_str(), carbonFiberLayer, medCarbonFiber); - TGeoVolume* carbonFiberLayerVol2 = new TGeoVolume((separationLayerName + "_CarbonFiber2").c_str(), carbonFiberLayer, medCarbonFiber); + TGeoVolume* carbonFiberLayerVol1 = new TGeoVolume((separationLayerName + "_CarbonFiber1").c_str(), carbonFiberLayer, getMedium(Materials::MaterialID::CarbonFiber)); + TGeoVolume* carbonFiberLayerVol2 = new TGeoVolume((separationLayerName + "_CarbonFiber2").c_str(), carbonFiberLayer, getMedium(Materials::MaterialID::CarbonFiber)); carbonFiberLayerVol1->SetLineColor(kGray + 2); carbonFiberLayerVol2->SetLineColor(kGray + 2); @@ -167,16 +123,16 @@ void FT3Layer::createSeparationLayer_waterCooling(TGeoVolume* motherVolume, cons double positiveYLength = yOuter - yInner; - TGeoVolume* kaptonPipePos = new TGeoVolume((separationLayerName + "_KaptonPipePos_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, positiveYLength / 2), kaptonMed); + TGeoVolume* kaptonPipePos = new TGeoVolume((separationLayerName + "_KaptonPipePos_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, positiveYLength / 2), getMedium(Materials::MaterialID::Kapton)); kaptonPipePos->SetLineColor(kGray); - TGeoVolume* waterVolumePos = new TGeoVolume((separationLayerName + "_WaterVolumePos_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, positiveYLength / 2), waterMed); + TGeoVolume* waterVolumePos = new TGeoVolume((separationLayerName + "_WaterVolumePos_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, positiveYLength / 2), getMedium(Materials::MaterialID::Water)); waterVolumePos->SetLineColor(kBlue); motherVolume->AddNode(waterVolumePos, 1, new TGeoCombiTrans(xPos, (yInner + yOuter) / 2.0, mZ, rotation)); - TGeoVolume* kaptonPipeNeg = new TGeoVolume((separationLayerName + "_KaptonPipeNeg_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, positiveYLength / 2), kaptonMed); + TGeoVolume* kaptonPipeNeg = new TGeoVolume((separationLayerName + "_KaptonPipeNeg_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, positiveYLength / 2), getMedium(Materials::MaterialID::Kapton)); kaptonPipeNeg->SetLineColor(kGray); - TGeoVolume* waterVolumeNeg = new TGeoVolume((separationLayerName + "_WaterVolumeNeg_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, positiveYLength / 2), waterMed); + TGeoVolume* waterVolumeNeg = new TGeoVolume((separationLayerName + "_WaterVolumeNeg_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, positiveYLength / 2), getMedium(Materials::MaterialID::Water)); waterVolumeNeg->SetLineColor(kBlue); motherVolume->AddNode(waterVolumeNeg, 1, new TGeoCombiTrans(xPos, -(yInner + yOuter) / 2.0, mZ, rotation)); @@ -190,9 +146,9 @@ void FT3Layer::createSeparationLayer_waterCooling(TGeoVolume* motherVolume, cons yMax = 2 * yOuter; pipeLength = yMax; - TGeoVolume* kaptonPipe = new TGeoVolume((separationLayerName + "_KaptonPipe_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, pipeLength / 2), kaptonMed); + TGeoVolume* kaptonPipe = new TGeoVolume((separationLayerName + "_KaptonPipe_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, pipeLength / 2), getMedium(Materials::MaterialID::Kapton)); kaptonPipe->SetLineColor(kGray); - TGeoVolume* waterVolume = new TGeoVolume((separationLayerName + "_WaterVolume_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, pipeLength / 2), waterMed); + TGeoVolume* waterVolume = new TGeoVolume((separationLayerName + "_WaterVolume_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, pipeLength / 2), getMedium(Materials::MaterialID::Water)); waterVolume->SetLineColor(kBlue); motherVolume->AddNode(waterVolume, 1, new TGeoCombiTrans(xPos, 0, mZ, rotation)); @@ -206,8 +162,6 @@ void FT3Layer::createSeparationLayer_waterCooling(TGeoVolume* motherVolume, cons void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string& separationLayerName) { - FT3Layer::initialize_mat(); - constexpr double carbonFiberThickness = 0.01; // cm constexpr double foamSpacingThickness = 1.0; // cm @@ -215,9 +169,9 @@ void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string TGeoTube* foamLayer = new TGeoTube(mInnerRadius, mOuterRadius, foamSpacingThickness / 2); // volumes - TGeoVolume* carbonFiberLayerVol1 = new TGeoVolume((separationLayerName + "_CarbonFiber1").c_str(), carbonFiberLayer, medCarbonFiber); - TGeoVolume* foamLayerVol = new TGeoVolume((separationLayerName + "_Foam").c_str(), foamLayer, medFoam); - TGeoVolume* carbonFiberLayerVol2 = new TGeoVolume((separationLayerName + "_CarbonFiber2").c_str(), carbonFiberLayer, medCarbonFiber); + TGeoVolume* carbonFiberLayerVol1 = new TGeoVolume((separationLayerName + "_CarbonFiber1").c_str(), carbonFiberLayer, getMedium(Materials::MaterialID::CarbonFiber)); + TGeoVolume* foamLayerVol = new TGeoVolume((separationLayerName + "_Foam").c_str(), foamLayer, getMedium(Materials::MaterialID::Foam)); + TGeoVolume* carbonFiberLayerVol2 = new TGeoVolume((separationLayerName + "_CarbonFiber2").c_str(), carbonFiberLayer, getMedium(Materials::MaterialID::CarbonFiber)); carbonFiberLayerVol1->SetLineColor(kGray + 2); foamLayerVol->SetLineColor(kBlack); @@ -239,9 +193,9 @@ void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::strin TGeoTube* outerCircle = new TGeoTube(mOuterRadius - 0.1, mOuterRadius + 0.1, 0.01); TGeoTube* outerCircleEdge = new TGeoTube(mOuterRadius + 3.3, mOuterRadius + 3.5, 0.01); - TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, gGeoManager->GetMedium("FT3_AIR$")); - TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, gGeoManager->GetMedium("FT3_AIR$")); - TGeoVolume* outerCircleEdgeVol = new TGeoVolume((mLayerName + "_OuterCircleEdge").c_str(), outerCircleEdge, gGeoManager->GetMedium("FT3_AIR$")); + TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, getMedium(Materials::MaterialID::Air)); + TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, getMedium(Materials::MaterialID::Air)); + TGeoVolume* outerCircleEdgeVol = new TGeoVolume((mLayerName + "_OuterCircleEdge").c_str(), outerCircleEdge, getMedium(Materials::MaterialID::Air)); innerCircleVol->SetLineColor(kRed); outerCircleVol->SetLineColor(kBlue); @@ -278,8 +232,8 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) 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$"); + TGeoMedium* medSi = getMedium(Materials::MaterialID::Silicon); + TGeoMedium* medAir = getMedium(Materials::MaterialID::Air); TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); @@ -389,8 +343,8 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) TGeoTube* chip = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); - TGeoMedium* medSi = gGeoManager->GetMedium("FT3_SILICON$"); - TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoMedium* medSi = getMedium(Materials::MaterialID::Silicon); + TGeoMedium* medAir = getMedium(Materials::MaterialID::Air); TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); sensVol->SetLineColor(kYellow); @@ -420,7 +374,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); - TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoMedium* medAir = getMedium(Materials::MaterialID::Air); TGeoVolume* layerVol = nullptr; // Add a little additional room in radius TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); @@ -447,7 +401,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); - TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoMedium* medAir = getMedium(Materials::MaterialID::Air); TGeoVolume* layerVol = nullptr; // set up stave config, differs between ML and OT disks @@ -469,8 +423,12 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) double z_local_offset = z_layer_thickness / 2.0; // ensure staves fully encapsulated in the layer volume, // but don't cross out of max nominal radii of 38.5cm & 71.5cm respectively (3.5cm tolerance) + // Allow 3, 9cm outward, inward for OT: + double innerRadiusForAirTube = mIsMiddleLayer ? mInnerRadius : mInnerRadius - 9.0; + double outerRadiusForAirTube = mIsMiddleLayer ? mOuterRadius + 2.5 : mOuterRadius + 3.0; // MvL: try 70.5 // 2.5 cm tolerance instead - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.2, mOuterRadius + 2.49, z_layer_thickness / 2); + TGeoTube* layer = new TGeoTube(innerRadiusForAirTube - 0.2, outerRadiusForAirTube, + z_layer_thickness / 2); layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); if (ft3Params.drawReferenceCircles) { diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Materials.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Materials.cxx new file mode 100644 index 0000000000000..4097af9d838bb --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Materials.cxx @@ -0,0 +1,29 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file FT3Materials.cxx +/// \brief Access to the media created from the FT3 material map + +#include "FT3Simulation/FT3Materials.h" + +#include "DetectorsBase/MaterialManager.h" + +#include + +TGeoMedium* o2::ft3::getMedium(Materials::MaterialID id) +{ + auto* medium = o2::base::MaterialManager::Instance().getTGeoMedium(Materials::moduleName, static_cast(id)); + if (!medium) { + LOG(fatal) << "FT3: no medium registered for " << Materials::materials.at(id).name + << "; createMaterials() has to run before the geometry is built"; + } + return medium; +} diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx index 00f53c1f693ad..aebab49ba9dcf 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx @@ -14,10 +14,7 @@ #include "FT3Simulation/FT3Module.h" #include "FT3Base/FT3BaseParam.h" -#include "DetectorsBase/MaterialManager.h" -#include "DetectorsBase/Detector.h" #include -#include #include #include #include @@ -26,98 +23,14 @@ #include #include #include +#include +#include #include #include #include #include -TGeoMaterial* FT3Module::siliconMat = nullptr; -TGeoMedium* FT3Module::siliconMed = nullptr; - -TGeoMaterial* FT3Module::copperMat = nullptr; -TGeoMedium* FT3Module::copperMed = nullptr; - -TGeoMixture* FT3Module::kaptonMat = nullptr; -TGeoMedium* FT3Module::kaptonMed = nullptr; - -TGeoMaterial* FT3Module::epoxyMat = nullptr; -TGeoMedium* FT3Module::epoxyMed = nullptr; - -TGeoMaterial* FT3Module::AluminumMat = nullptr; -TGeoMedium* FT3Module::AluminumMed = nullptr; - -TGeoMaterial* FT3Module::carbonFiberMat = nullptr; -TGeoMedium* FT3Module::carbonFiberMed = nullptr; - -void FT3Module::initialize_materials() -{ - LOG(debug) << "FT3Module: initialize_materials"; - if (siliconMat) { - return; - } - - auto& matmgr = o2::base::MaterialManager::Instance(); - - int ifield; // Initialized below - float fieldm; // Initialized below - o2::base::Detector::initFieldTrackingParams(ifield, fieldm); - - float tmaxfdSi = 0.1; - float stemaxSi = 0.0075; - float deemaxSi = 0.1; - float epsilSi = 1.0E-4; - float stminSi = 0.0; - - float tmaxfdPas = 0.1; - float stemaxPas = 1.0; - float deemaxPas = 0.1; - float epsilPas = 1.0E-4; - float stminPas = 0.0; - - // FT3-local material/medium IDs: 10-15. Keep them distinct from IDs 1 and 3, - // which are already used by FT3 Detector::createMaterials() for AIR and SILICON. - // MaterialManager maps these local IDs to globally unique VMC medium IDs. - matmgr.Material("FT3", 10, "Silicon", 28.0855, 14, 2.33, 0, 0); - matmgr.Medium("FT3", 10, "Silicon", 10, 0, ifield, fieldm, tmaxfdSi, stemaxSi, deemaxSi, epsilSi, stminSi); - siliconMed = matmgr.getTGeoMedium("FT3", 10); - siliconMat = siliconMed->GetMaterial(); - - matmgr.Material("FT3", 11, "Copper", 63.546, 29, 8.96, 0, 0); - matmgr.Medium("FT3", 11, "Copper", 11, 0, ifield, fieldm, tmaxfdPas, stemaxPas, deemaxPas, epsilPas, stminPas); - copperMed = matmgr.getTGeoMedium("FT3", 11); - copperMat = copperMed->GetMaterial(); - - // Kapton: C22 H10 N2 O5, by weight fraction - float aKapton[4] = {12.0107, 1.00794, 14.0067, 15.999}; - float zKapton[4] = {6., 1., 7., 8.}; - float wKapton[4] = {0.5641, 0.2564, 0.0513, 0.1282}; - matmgr.Mixture("FT3", 12, "Kapton", aKapton, zKapton, 1.346, 4, wKapton); - matmgr.Medium("FT3", 12, "Kapton", 12, 0, ifield, fieldm, tmaxfdPas, stemaxPas, deemaxPas, epsilPas, stminPas); - kaptonMed = matmgr.getTGeoMedium("FT3", 12); - kaptonMat = dynamic_cast(kaptonMed->GetMaterial()); - - // TODO: Check with Rene the exact type of carbon fiber - matmgr.Material("FT3", 13, "Carbon", 12.0107, 6, 1.8, 0, 0); - matmgr.Medium("FT3", 13, "Carbon", 13, 0, ifield, fieldm, tmaxfdPas, stemaxPas, deemaxPas, epsilPas, stminPas); - carbonFiberMed = matmgr.getTGeoMedium("FT3", 13); - carbonFiberMat = carbonFiberMed->GetMaterial(); - - // Epoxy: C18 H19 O3, by atom count (negative nlmat) - float aEpoxy[3] = {12.0107, 1.00794, 15.999}; - float zEpoxy[3] = {6., 1., 8.}; - float wEpoxy[3] = {18., 19., 3.}; - matmgr.Mixture("FT3", 14, "Epoxy", aEpoxy, zEpoxy, 2.186, -3, wEpoxy); - matmgr.Medium("FT3", 14, "Epoxy", 14, 0, ifield, fieldm, tmaxfdPas, stemaxPas, deemaxPas, epsilPas, stminPas); - epoxyMed = matmgr.getTGeoMedium("FT3", 14); - epoxyMat = epoxyMed->GetMaterial(); - - matmgr.Material("FT3", 15, "Aluminum", 26.98, 13, 2.7, 0, 0); - matmgr.Medium("FT3", 15, "Aluminum", 15, 0, ifield, fieldm, tmaxfdPas, stemaxPas, deemaxPas, epsilPas, stminPas); - AluminumMed = matmgr.getTGeoMedium("FT3", 15); - AluminumMat = AluminumMed->GetMaterial(); - - LOG(debug) << "FT3Module: done initialize_materials"; -} +using o2::ft3::getMedium; double calculate_y_circle(double x, double radius) { @@ -163,25 +76,22 @@ std::pair calculate_y_range( } /* - * This function is a helper function to determine the positions of sensors on the stave + * Greedy fill: determine the positions of sensors on the stave * by adding sensors until there is no more space available. * * Arguments: * y_positions: a pair of vectors, where each vector contains pairs of * y position and stack height for the positive and negative y positions respectively. * This argument will be appended with the new sensor positions and stack heights. - * Rout: the outer radius of the layer - * Rin: the inner radius of the layer - * x_left: the x position of the left edge of the sensor to be placed * kSensorStack: the number of sensors to be stacked on top of each other * y_ranges: the y positions to start and end placing sensors, * for positive and negative y respectively * absAllowedYRange: the absolute y range allowed for placing sensors, * used to cut placement if they go past allowed tolerances */ -void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, - double x_left, unsigned kSensorStack, PositionRangeType y_ranges, - std::pair& absAllowedYRange) +void FT3Module::fill_stave_greedy(PosNegPositionTypes& y_positions, unsigned kSensorStack, + PositionRangeType y_ranges, + std::pair& absAllowedYRange) { // start with upper half of the stave, then mirror to the bottom half // add the height of kSensorStack sensors + the gaps in between them @@ -230,6 +140,33 @@ void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double } } +/* + * Exact fill: read the module positions of one stave straight out of the + * tabulated layout. The counterpart to fill_stave_greedy, without any of its + * checks -- the table is validated before it reaches the geometry, so there is + * nothing here to cut, clip or derive. + * + * fills: the stave's fills, each an uninterrupted stretch of modules. A stave + * split by the beam pipe has one fill below the hole and one above it; + * nothing is mirrored, since the layout is symmetric about the y-axis + * but not about the x-axis. + * + * Returns one list holding every module of the stave, at negative y as well, + * each stored by its BOTTOM edge. + */ +PositionTypes FT3Module::fill_stave_exact(const std::vector& fills) +{ + PositionTypes y_positions; + for (const auto& fill : fills) { + double y_bottom = fill.yStart; + for (unsigned kSensorStack : fill.stackHeights) { + y_positions.emplace_back(y_bottom, kSensorStack); + y_bottom += Constants::getStackHeight(kSensorStack) + Constants::stackGap; + } + } + return y_positions; +} + /* * Create the vertices of the triangles that make up the stave cross section * @@ -355,9 +292,10 @@ void FT3Module::addStaveVolume( TGeoVolume* staveVolume = new TGeoVolume( (volumeName).c_str(), staveShape, - carbonFiberMed); - staveVolume->SetLineColor(Constants::carbonFiberColor); - staveVolume->SetFillColorAlpha(Constants::carbonFiberColor, 0.4); + getMedium(Materials::MaterialID::CarbonFiber)); + const int carbonFiberColor = Materials::materials.at(Materials::MaterialID::CarbonFiber).colour; + staveVolume->SetLineColor(carbonFiberColor); + staveVolume->SetFillColorAlpha(carbonFiberColor, 0.4); TGeoRotation* rot = new TGeoRotation(); rot->RotateX(-90); // lift from xy plane into xz plane @@ -440,7 +378,8 @@ void FT3Module::add2x1GlueVolume( { std::string glue_name = "FT3glue_" + element_glued_to + "_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count); addDetectorVolume( - motherVolume, glue_name, Constants::glueColor, epoxyMed, volume_count, + motherVolume, glue_name, Materials::materials.at(Materials::MaterialID::Epoxy).colour, + getMedium(Materials::MaterialID::Epoxy), volume_count, x_mid, y_mid, z_mid, Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::epoxyThickness / 2); } @@ -455,7 +394,8 @@ void FT3Module::add2x1CopperVolume( { std::string copper_name = "FT3Copper_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count); addDetectorVolume( - motherVolume, copper_name, Constants::CuColor, copperMed, volume_count, + motherVolume, copper_name, Materials::materials.at(Materials::MaterialID::Copper).colour, + getMedium(Materials::MaterialID::Copper), volume_count, x_mid, y_mid, z_mid, Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::copperThickness / 2); } @@ -470,7 +410,8 @@ void FT3Module::add2x1KaptonVolume( { std::string kapton_name = "FT3Kapton_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count); addDetectorVolume( - motherVolume, kapton_name, Constants::kaptonColor, kaptonMed, volume_count, + motherVolume, kapton_name, Materials::materials.at(Materials::MaterialID::Kapton).colour, + getMedium(Materials::MaterialID::Kapton), volume_count, x_mid, y_mid, z_mid, Constants::sensor2x1_width / 2, Constants::sensor2x1_height / 2, Constants::kaptonThickness / 2); } @@ -500,11 +441,12 @@ void FT3Module::addSingleSensorVolume( { TGeoVolume* sensor; TGeoManager* geoManager = gGeoManager; + TGeoMedium* siliconMed = getMedium(Materials::MaterialID::Silicon); // ACTIVE AREA // Sensor thickness is along the Y axis; this convention is used in digitisation by barrels and disks std::string sensor_name = "FT3Sensor_Active_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + "_" + std::to_string(stave_idx) + "_" + std::to_string(volume_count); addDetectorVolume( - motherVolume, sensor_name, Constants::SiColor, siliconMed, + motherVolume, sensor_name, Materials::materials.at(Materials::MaterialID::Silicon).colour, siliconMed, volume_count, active_x_mid, y_mid, z_mid, Constants::active_width / 2, Constants::siliconThickness / 2, Constants::single_sensor_height / 2, 90.); @@ -515,73 +457,148 @@ void FT3Module::addSingleSensorVolume( sensor = geoManager->MakeBox(sensor_inactive_name.c_str(), siliconMed, Constants::inactive_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); addDetectorVolume( - motherVolume, sensor_inactive_name, Constants::SiInactiveColor, siliconMed, + motherVolume, sensor_inactive_name, Materials::SiInactiveColor, siliconMed, volume_count, inactive_x_mid, y_mid, z_mid, Constants::inactive_width / 2, Constants::single_sensor_height / 2, Constants::siliconThickness / 2); } -void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, - double Rin, double Rout, double z_offset_local, - const Constants::StaveConfig& staveConfig, - TGeoVolume* motherVolume) +/* + * Look up a stave's y midpoint and whether it is built as two pieces mirrored + * about the x-axis. Returns false for a stave absent from the map, which sits + * on y=0 in one piece and leaves both outputs untouched. + */ +bool staveMidpointAndMirror(const Constants::StaveConfig& staveConfig, int staveID, + double& y_midpoint, bool& mirrorStaveAroundX) { - LOG(debug) << "FT3Module: create_layout_staveGeo - Direction " - << direction << ", Layer " << layerNumber; + auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID); + if (y_midpoint_it == staveConfig.staveID_to_y_midpoint.end()) { + return false; + } + y_midpoint = y_midpoint_it->second.first; // avoid double map lookup + mirrorStaveAroundX = y_midpoint_it->second.second; + return true; +} - FT3Module::initialize_materials(); - auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); +/* + * Create the carbon shell of one stave, staggered in z, plus the mirrored one + * when the stave is built as two pieces. + */ +void FT3Module::add_stave_volumes( + TGeoVolume* motherVolume, int layerNumber, int direction, + const Constants::StaveConfig& staveConfig, unsigned i_stave, + const std::array, 4>& staveTriangles, + double z_offset_to_carbon_face, std::pair& absAllowedYRange, + double y_midpoint, bool mirrorStaveAroundX, unsigned* staveVolumeCount) +{ + // Get whether the stave is shifted backward or not before creating + double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); + double z_stave_shift_forward = // move staves more inward to fit in layer volume + -z_offset_to_carbon_face + z_stave_shift_abs; + std::string stave_volume_name = + "FT3_Stave_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + + "_" + std::to_string(i_stave); + + addStaveVolume( + motherVolume, stave_volume_name, direction, staveVolumeCount, + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward); + // Now create the mirrored stave + if (mirrorStaveAroundX) { + addStaveVolume( + motherVolume, stave_volume_name + "_mirrored", direction, staveVolumeCount, + staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, + staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward); + } +} - // First let's define some constants used throughout - /* - * we build the volume from the outside in, starting with the silicon, - * then glue & materials towards the stave. Depending on direction, - * the distance from the center will be mirrored. - * - * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | - * ----------------------------------------------------------------> z - * - * Naturally, this will be mirrored for layers in the backwards direction, - * such that the face of the sensors always face the interaction region. - * - * Currently, we stipulate that the default stave face is at local z=0, - * that is then shifted by the half air thickness encapsulating the layer - * to avoid overlaps with the air and services. All offsets are - * calculated for backward direction (since that is a positive shift), - * and then flipped for forward. At that point, the innermost/frontmost - * stave face is at the edge of the air volume, so we shift it back a little - * to make space for the sensor materials and a slight margin. - */ - double totalSensorMaterialThickness = - Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + - Constants::epoxyThickness + Constants::siliconThickness; - double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1; - double z_offset_to_glue_Ka = - z_offset_to_carbon_face + Constants::epoxyThickness / 2; - double z_offset_to_kapton = - z_offset_to_carbon_face + Constants::epoxyThickness + - Constants::kaptonThickness / 2; - double z_offset_to_copper = - z_offset_to_carbon_face + Constants::epoxyThickness + - Constants::kaptonThickness + Constants::copperThickness / 2; - double z_offset_to_glue_Si = - z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness + - Constants::copperThickness + Constants::epoxyThickness / 2; - double z_offset_to_silicon = - z_offset_to_carbon_face + Constants::epoxyThickness + - Constants::kaptonThickness + Constants::copperThickness + - Constants::epoxyThickness + Constants::siliconThickness / 2; +/* + * Exact layout: build every stave of the layer from the tabulated layout in + * exactStaveFills, the counterpart to build_staves_greedy. + * + * Deliberately stupid: the stave is built to the length y_lengths gives it and + * the modules sit exactly where the table says. No tolerance is applied, the + * stave is not cut on the layer radii, no stave is skipped and nothing is + * mirrored, so none of Rin, Rout or the staveTol parameters are needed here. + * Whatever produced the table is responsible for it fitting the layer. + */ +void FT3Module::build_staves_exact( + TGeoVolume* motherVolume, int layerNumber, int direction, + const Constants::StaveConfig& staveConfig, + const std::array, 4>& staveTriangles, + double z_offset_to_carbon_face, + std::vector& y_positionsPosNeg, + unsigned& staveVolumeCount) +{ + // number of modules per stack height, only used for logging. Keyed by height + // rather than indexed by kSensorsPerStack, since the table is free to use + // heights that are not in that list. + std::map nSensorStackTotal; + for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { + const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); - // initialise all y_positions, vector over all staves/columns - std::vector y_positionsPosNeg; - // stave triangle cross sections are the same for every stave (direction based) - std::array, 4> staveTriangles = buildStaveTriangle(direction); + // a stave in the map is built as two pieces, one either side of the beam pipe + double y_midpoint = 0.; + bool mirrorStaveAroundX = false; + staveMidpointAndMirror(staveConfig, staveID, y_midpoint, mirrorStaveAroundX); + + // no radial limit, so addStaveVolume cuts nothing off the stave + std::pair absAllowedYRange = {0., std::numeric_limits::max()}; + + add_stave_volumes(motherVolume, layerNumber, direction, staveConfig, i_stave, + staveTriangles, z_offset_to_carbon_face, absAllowedYRange, + y_midpoint, mirrorStaveAroundX, &staveVolumeCount); + + /* + * Every module goes in the positive-y list, whatever the sign of its y, and + * the negative-y list stays empty. The placement loop reads that list with + * y_sign = +1 and only ever adds to the stored bottom edge, so it does not + * care that some of those edges are negative. + */ + y_positionsPosNeg.emplace_back(fill_stave_exact(staveConfig.exactStaveFills[i_stave]), + PositionTypes{}); + + std::map nSensorStackCount; + for (const auto& [y_bottom, kSensorStack] : y_positionsPosNeg.back().first) { + nSensorStackCount[kSensorStack]++; + nSensorStackTotal[kSensorStack]++; + } + std::string moduleDebugStr = "Module size counts for layer " + std::to_string(layerNumber) + " in direction " + std::to_string(direction) + ":\n"; + for (const auto& [kSensorStack, nModules] : nSensorStackCount) { + moduleDebugStr += "\t" + std::to_string(nModules) + " modules with " + std::to_string(kSensorStack) + " sensors stacked\n"; + } + LOG(debug) << moduleDebugStr; + } + std::string totalModuleInfoStr = + "Total module size counts for layer " + std::to_string(layerNumber) + + " in direction " + std::to_string(direction) + ":\n"; + for (const auto& [kSensorStack, nModules] : nSensorStackTotal) { + totalModuleInfoStr += "\t" + std::to_string(nModules) + " modules with " + std::to_string(kSensorStack) + " sensors stacked\n"; + } + LOG(info) << totalModuleInfoStr; +} + +/* + * Greedy layout: work out the module positions from the layer radii and the + * stave length, filling each stave from the middle outwards with the stack + * sizes in kSensorsPerStack. Since it chooses the positions itself, this is + * the path that applies the radial tolerances. + * + * Extracted verbatim from create_layout_staveGeo; the body is unchanged. + */ +void FT3Module::build_staves_greedy( + TGeoVolume* motherVolume, int layerNumber, int direction, double Rin, double Rout, + const Constants::StaveConfig& staveConfig, + const std::array, 4>& staveTriangles, + double z_offset_to_carbon_face, + std::vector& y_positionsPosNeg, unsigned& staveVolumeCount) +{ + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); // declare vector with number of 2xn sensor stacks (modules) -- only used for logging // each entry is a vector, where each entry is the number of modules of that stack height std::vector> nSensorStackCountPerStave( staveConfig.x_midpoints.size(), std::vector(Constants::kSensorsPerStack.size(), 0)); std::vector nSensorStackTotal(Constants::kSensorsPerStack.size(), 0); - unsigned staveVolumeCount = 0; for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { y_positionsPosNeg.emplace_back(PosNegPositionTypes{PositionTypes{}, PositionTypes{}}); const int staveID = Constants::staveIdxToID(i_stave, staveConfig.x_midpoints.size()); @@ -590,35 +607,14 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction bool mirrorStaveAroundX = false; // default positive and negative starting points has a gap around x-axis for symmetry double stave_half_length = staveConfig.y_lengths[i_stave] / 2; - PositionRangeType y_ranges; - if (ft3Params.placeSensorStackInMiddleOfStave) { - /* - * We want a sensor stack to cross over the x-axis for coverage at y=0 - * N.B. not necessarily exactly mirrored, only if stack gap is the same - * as the gap between sensors in a stack. Since we start filling with the - * first value in the kSensorsPerStack vector, we offset the first position - * by half of that. - * - * NOTE: TODO: in case the stave is too short to fit one full stack over the middle, - * then we will not be able to place anything since the bottom right/left point of - * the module will already be outside of acceptable bounds -- killing further placement. - */ - double stackHeight = Constants::getStackHeight(Constants::kSensorsPerStack[0]); - y_ranges = {{-stackHeight / 2, stave_half_length}, - {-stackHeight / 2 - Constants::stackGap, -stave_half_length}}; - } else { - /* - * Otherwise have a gap around y=0, so sensors are not placed there. - * This means the stave is perfectly mirrored around the x-axis. - */ - y_ranges = {{Constants::stackGap / 2, stave_half_length}, - {-Constants::stackGap / 2, -stave_half_length}}; - } - auto y_midpoint_it = staveConfig.staveID_to_y_midpoint.find(staveID); - if (y_midpoint_it != staveConfig.staveID_to_y_midpoint.end()) { + /* + * Have a gap around y=0, so sensors are not placed there. + * This means the stave is perfectly mirrored around the x-axis. + */ + PositionRangeType y_ranges = {{Constants::stackGap / 2, stave_half_length}, + {-Constants::stackGap / 2, -stave_half_length}}; + if (staveMidpointAndMirror(staveConfig, staveID, y_midpoint, mirrorStaveAroundX)) { // there is a defined midpoint for this stave, use this for starting points - y_midpoint = y_midpoint_it->second.first; // avoid double map lookup - mirrorStaveAroundX = y_midpoint_it->second.second; y_ranges.first = {y_midpoint - stave_half_length, y_midpoint + stave_half_length}; y_ranges.second = {-y_midpoint + stave_half_length, -y_midpoint - stave_half_length}; } @@ -649,10 +645,10 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction * (2) The inner tolerance is large enough to allow stave placement as wished * a) AND the given stave midpoint is above the inner radius */ - double x_left = staveConfig.x_midpoints[i_stave] - Constants::sensor2x1_width / 2; - double x_right = x_left + Constants::sensor2x1_width; + double x_left_stave = staveConfig.x_midpoints[i_stave] - Constants::staveWidth / 2; + double x_right_stave = x_left_stave + Constants::staveWidth; std::pair absAllowedYRange = - calculate_y_range(x_left, x_right, Rin, Rout); + calculate_y_range(x_left_stave, x_right_stave, Rin, Rout); /* * Shift allowed range by tolerance. Note that both values in the range must @@ -672,33 +668,16 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction continue; } - // Get whether the stave is shifted backward or not before creating - double z_stave_shift_abs = staveConfig.staveOnFront[i_stave] ? 0 : Constants::z_offsetStave(staveConfig.x_midpoint_spacing); - double z_stave_shift_forward = // move staves more inward to fit in layer volume - -z_offset_to_carbon_face + z_stave_shift_abs; - std::string stave_volume_name = - "FT3_Stave_" + std::to_string(direction) + "_" + std::to_string(layerNumber) + - "_" + std::to_string(i_stave); - // Create the stave volumes and fill the y positions where to put sensors on the stave - addStaveVolume( - motherVolume, stave_volume_name, direction, &staveVolumeCount, - staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, - staveConfig.x_midpoints[i_stave], y_midpoint, z_stave_shift_forward); - // Now create the mirrored stave - if (mirrorStaveAroundX) { - addStaveVolume( - motherVolume, stave_volume_name + "_mirrored", direction, &staveVolumeCount, - staveConfig.y_lengths[i_stave], staveTriangles, absAllowedYRange, - staveConfig.x_midpoints[i_stave], -y_midpoint, z_stave_shift_forward); - } + add_stave_volumes(motherVolume, layerNumber, direction, staveConfig, i_stave, + staveTriangles, z_offset_to_carbon_face, absAllowedYRange, + y_midpoint, mirrorStaveAroundX, &staveVolumeCount); // now add the sensor positions on the stave for (unsigned i_kSens = 0; i_kSens < Constants::kSensorsPerStack.size(); i_kSens++) { unsigned nModulesCurr = y_positionsPosNeg.back().first.size() + y_positionsPosNeg.back().second.size(); - fill_stave(y_positionsPosNeg.back(), Rin, Rout, x_left, - Constants::kSensorsPerStack[i_kSens], y_ranges, - absAllowedYRange); + fill_stave_greedy(y_positionsPosNeg.back(), Constants::kSensorsPerStack[i_kSens], + y_ranges, absAllowedYRange); unsigned nModulesAdded = y_positionsPosNeg.back().first.size() + y_positionsPosNeg.back().second.size() - nModulesCurr; nSensorStackCountPerStave[i_stave][i_kSens] = nModulesAdded; nSensorStackTotal[i_kSens] += nModulesAdded; @@ -716,6 +695,80 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction totalModuleInfoStr += "\t" + std::to_string(nSensorStackTotal[i_kSens]) + " modules with " + std::to_string(Constants::kSensorsPerStack[i_kSens]) + " sensors stacked\n"; } LOG(info) << totalModuleInfoStr; +} + +void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction, + double Rin, double Rout, double z_offset_local, + const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume) +{ + LOG(debug) << "FT3Module: create_layout_staveGeo - Direction " + << direction << ", Layer " << layerNumber; + + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); + + // First let's define some constants used throughout + /* + * we build the volume from the outside in, starting with the silicon, + * then glue & materials towards the stave. Depending on direction, + * the distance from the center will be mirrored. + * + * | SILICON SENSOR | GLUE | COPPER | KAPTON | GLUE | CARBON STAVE | + * ----------------------------------------------------------------> z + * + * Naturally, this will be mirrored for layers in the backwards direction, + * such that the face of the sensors always face the interaction region. + * + * Currently, we stipulate that the default stave face is at local z=0, + * that is then shifted by the half air thickness encapsulating the layer + * to avoid overlaps with the air and services. All offsets are + * calculated for backward direction (since that is a positive shift), + * and then flipped for forward. At that point, the innermost/frontmost + * stave face is at the edge of the air volume, so we shift it back a little + * to make space for the sensor materials and a slight margin. + */ + double totalSensorMaterialThickness = + Constants::epoxyThickness + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness; + double z_offset_to_carbon_face = z_offset_local - totalSensorMaterialThickness - 0.1; + double z_offset_to_glue_Ka = + z_offset_to_carbon_face + Constants::epoxyThickness / 2; + double z_offset_to_kapton = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness / 2; + double z_offset_to_copper = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness / 2; + double z_offset_to_glue_Si = + z_offset_to_carbon_face + Constants::epoxyThickness + Constants::kaptonThickness + + Constants::copperThickness + Constants::epoxyThickness / 2; + double z_offset_to_silicon = + z_offset_to_carbon_face + Constants::epoxyThickness + + Constants::kaptonThickness + Constants::copperThickness + + Constants::epoxyThickness + Constants::siliconThickness / 2; + + // initialise all y_positions, vector over all staves/columns + std::vector y_positionsPosNeg; + // stave triangle cross sections are the same for every stave (direction based) + std::array, 4> staveTriangles = buildStaveTriangle(direction); + unsigned staveVolumeCount = 0; + /* + * Either read the module positions out of the tabulated layout, or work them + * out here from the layer radii and the stave length. Both create the stave + * volumes and leave the module positions in y_positionsPosNeg, so the sensor + * placement below does not care which of the two ran. + * + * A disc without a tabulated layout falls back to the greedy fill even when + * the parameter is set, so that the discs which do have one can use it. + */ + if (ft3Params.useExactStavePlacement && !staveConfig.exactStaveFills.empty()) { + build_staves_exact(motherVolume, layerNumber, direction, staveConfig, staveTriangles, + z_offset_to_carbon_face, y_positionsPosNeg, staveVolumeCount); + } else { + build_staves_greedy(motherVolume, layerNumber, direction, Rin, Rout, staveConfig, + staveTriangles, z_offset_to_carbon_face, y_positionsPosNeg, + staveVolumeCount); + } // Create volumes for the sensors and the support materials on top of the stave for (unsigned i_stave = 0; i_stave < staveConfig.x_midpoints.size(); i_stave++) { @@ -749,6 +802,8 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction unsigned sensor_count = 0; // reset for each stave for (int y_sign = -1; y_sign < 2; y_sign += 2) { // place sensors at positive and negative y + // recall for exact placement the first entry is filled, second is empty + // (this has no effect on the placement as it loops over all positions) const auto& positions = (y_sign == 1) ? y_positionsPosNeg[i_stave].first : y_positionsPosNeg[i_stave].second; // define starting midpoint: y = y_start +- distance to middle of sensor @@ -761,12 +816,12 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction double z_mid = z_offset_to_silicon * z_offset_multiplier + z_stave_shift; addSingleSensorVolume( motherVolume, layerNumber, direction, i_stave, sensor_count, - x_mid - Constants::active_width / 2 - Constants::sensor2x1_gap / 2, + x_mid - Constants::sensor2x1_gap / 2 - Constants::active_width / 2, y_mid, z_mid, true); // right single sensor of the 2x1: place left edge half of sensor gap from center addSingleSensorVolume( motherVolume, layerNumber, direction, i_stave, sensor_count + 1, - x_mid + Constants::active_width / 2 + Constants::sensor2x1_gap / 2, + x_mid + Constants::sensor2x1_gap / 2 + Constants::active_width / 2, y_mid, z_mid, false); // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; @@ -803,7 +858,11 @@ void FT3Module::create_layout(double mZ, int layerNumber, int direction, double LOG(debug) << "FT3Module: create_layout - Layer " << layerNumber << ", Direction " << direction << ", Face " << face; TGeoManager* geoManager = gGeoManager; - FT3Module::initialize_materials(); + TGeoMedium* siliconMed = getMedium(Materials::MaterialID::Silicon); + TGeoMedium* copperMed = getMedium(Materials::MaterialID::Copper); + TGeoMedium* kaptonMed = getMedium(Materials::MaterialID::Kapton); + TGeoMedium* epoxyMed = getMedium(Materials::MaterialID::Epoxy); + TGeoMedium* AluminumMed = getMedium(Materials::MaterialID::Aluminum); // double sensor_width = 2.5; // double sensor_height = 9.6; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx index c51277665ba72..a5f897ec9ef4f 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx @@ -995,7 +995,7 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) float zLengthOuterBarrelTubeServices = 215.f; // cm, IA, May 11, 2026: temporary length (?) // geometry of service "tubes" for OT disks - float rMinOuterDiskServices = 70.5f; // cm + float rMinOuterDiskServices = 71.f; // cm float zStartOuterDiskServices = 149.f; // cm float zLengthOuterDiskServices = 201.f; // cm