Project
Loading...
Searching...
No Matches
RICHRing.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
15#include "Framework/Logger.h"
16
17#include <TGeoManager.h>
18#include <TGeoTube.h>
19#include <TGeoVolume.h>
20#include <TGeoArb8.h>
21
22#include <cmath>
23#include <limits>
24
25namespace o2
26{
27namespace rich
28{
29
30namespace // quadrant operations
31{
32
33double quadrantDeltaPhiEquation(double x, int nTilesPhi, double rMin, double totalBoundaryWidth)
34{
35 const double argument = totalBoundaryWidth * TMath::Cos(x / 2.0) / (2.0 * rMin);
36 if (TMath::Abs(argument) >= 1.0) {
37 return std::numeric_limits<double>::quiet_NaN();
38 }
39 const double rhs = 2.0 * TMath::Pi() / static_cast<double>(nTilesPhi) - (8.0 / static_cast<double>(nTilesPhi)) * TMath::ASin(argument);
40 return rhs - x;
41}
42
43double solveQuadrantDeltaPhi(int nTilesPhi, double rMin, double totalBoundaryWidth)
44{
45 double lower = 0.0;
46 double upper = 1.1 * 2.0 * TMath::Pi() / static_cast<double>(nTilesPhi);
47 double fLower = quadrantDeltaPhiEquation(lower, nTilesPhi, rMin, totalBoundaryWidth);
48 double fUpper = quadrantDeltaPhiEquation(upper, nTilesPhi, rMin, totalBoundaryWidth);
49 if (!std::isfinite(fLower) || !std::isfinite(fUpper) || fLower * fUpper > 0.0) {
50 return -1.0;
51 }
52 constexpr double tolerance = 1.0e-12;
53 constexpr int maxIterations = 200;
54 for (int iteration = 0; iteration < maxIterations; iteration++) {
55 const double middle = 0.5 * (lower + upper);
56 const double fMiddle = quadrantDeltaPhiEquation(middle, nTilesPhi, rMin, totalBoundaryWidth);
57 if (!std::isfinite(fMiddle)) {
58 return -1.0;
59 }
60 if (TMath::Abs(fMiddle) < tolerance || 0.5 * (upper - lower) < tolerance) {
61 return middle;
62 }
63 if (fLower * fMiddle < 0.0) {
64 upper = middle;
65 fUpper = fMiddle;
66 } else {
67 lower = middle;
68 fLower = fMiddle;
69 }
70 }
71 return 0.5 * (lower + upper);
72}
73
74double quadrantModulePhi(int moduleIndex, int nTilesPhi, double deltaPhi, double extraPhi)
75{
76 const int modulesPerQuadrant = nTilesPhi / 4;
77 const int quadrant = moduleIndex / modulesPerQuadrant;
78 return extraPhi + static_cast<double>(moduleIndex) * deltaPhi - TMath::Pi() / 4.0 + deltaPhi / 2.0 + 2.0 * static_cast<double>(quadrant) * extraPhi;
79}
80
81} // namespace
82
83Ring::Ring(int rPosId,
84 int nTilesPhi,
85 double rMin,
86 double rMax,
87 double radThick,
88 double radYmin,
89 double radYmax,
90 double radZ,
91 double photThick,
92 double photYmin,
93 double photYmax,
94 double photZ,
95 double radRad0,
96 double photR0,
97 double aerDetDistance,
98 double thetaB,
99 const std::string motherName)
100 : mNTiles{nTilesPhi}, mPosId{rPosId}, mRadThickness{radThick}
101{
102 TGeoManager* geoManager = gGeoManager;
103 TGeoVolume* motherVolume = geoManager->GetVolume(motherName.c_str());
104
105 if (!motherVolume) {
106 LOGP(fatal,
107 "RICH: mother volume {} not found while creating ring {}",
108 motherName,
109 rPosId);
110 }
111
112 const auto& richPars = RICHBaseParam::Instance();
113
114 const bool useCylindricalAerogel = richPars.useCylindricalAerogel;
115
116 TGeoMedium* medAerogel = gGeoManager->GetMedium("RCH_AEROGEL$");
117 if (!medAerogel) {
118 LOGP(fatal, "RICH: Aerogel medium not found");
119 }
120
121 TGeoMedium* medSi = gGeoManager->GetMedium("RCH_SILICON$");
122 if (!medSi) {
123 LOGP(fatal, "RICH: Silicon medium not found");
124 }
125
126 TGeoMedium* medCO2 = gGeoManager->GetMedium("RCH_CO2$");
127 if (!medCO2) {
128 LOGP(fatal, "RICH: CO2 medium not found");
129 }
130
131 TGeoMedium* medFR4 = gGeoManager->GetMedium("RCH_FR4$");
132 if (!medFR4) {
133 LOGP(fatal, "RICH: FR4 medium not found");
134 }
135
136 TGeoMedium* medAr = gGeoManager->GetMedium("RCH_ARGON$");
137 if (!medAr) {
138 LOGP(fatal, "RICH: Argon medium not found");
139 }
140
141 TGeoMedium* medAl = gGeoManager->GetMedium("RCH_ALUMINUM$");
142 if (!medAl) {
143 LOGP(fatal, "RICH: Aluminum medium not found");
144 }
145
146 TGeoMedium* medSiAbsorber = gGeoManager->GetMedium("RCH_SILICON_ABSORBER$");
147 if (!medSiAbsorber) {
148 LOGP(fatal, "RICH: Passive silicon absorber medium not found");
149 }
150
151 TGeoMedium* medSilicone = gGeoManager->GetMedium("RCH_SILICONE$");
152 if (!medSilicone) {
153 LOGP(fatal, "RICH: Silicone medium not found");
154 }
155
156 TGeoMedium* medHTCC = gGeoManager->GetMedium("RCH_HTCC$");
157 if (!medHTCC) {
158 LOGP(fatal, "RICH: HTCC medium not found");
159 }
160
161 std::vector<TGeoArb8*> radiatorTiles(nTilesPhi), photoFrames(nTilesPhi), photoTiles(nTilesPhi), gasSectors(nTilesPhi);
162 LOGP(info, "Creating ring: id: {} with {} tiles. ", rPosId, nTilesPhi);
163 LOGP(info, "Rmin: {} Rmax: {} RadThick: {} RadYmin: {} RadYmax: {} RadZ: {} PhotThick: {} PhotYmin: {} PhotYmax: {} PhotZ: {}, zTransRad: {}, zTransPhot: {}, ThetaB: {}",
164 rMin, rMax, radThick, radYmin, radYmax, radZ, photThick, photYmin, photYmax, photZ, radRad0, photR0, thetaB);
165
166 // Use different phi depending on use of quadrants or not
167 const bool flagUseQuadrants = richPars.flagUseQuadrants;
168 if (flagUseQuadrants && (nTilesPhi <= 0 || nTilesPhi % 4 != 0)) {
169 LOGP(fatal, "RICH quadrant geometry requires nTilesPhi to be positive and divisible by four; received {}", nTilesPhi);
170 }
171 const double regularDeltaPhi = 2.0 * TMath::Pi() / static_cast<double>(nTilesPhi);
172 double moduleDeltaPhi = regularDeltaPhi;
173 double quadrantExtraPhi = 0.0;
174
175 if (flagUseQuadrants) {
176 const double totalBoundaryWidth = 2.0 * richPars.vesselThicknessShieldingLateral + richPars.vesselPhiGap;
177 if (totalBoundaryWidth >= 2.0 * rMin) {
178 LOGP(fatal, "RICH quadrant boundary width {} cm is incompatible with rMin={} cm", totalBoundaryWidth, rMin);
179 }
180 moduleDeltaPhi = solveQuadrantDeltaPhi(nTilesPhi, rMin, totalBoundaryWidth);
181
182 quadrantExtraPhi = TMath::ASin(totalBoundaryWidth / (2.0 * rMin));
183
184 if (!(moduleDeltaPhi > 0.0)) {
185 LOGP(fatal, "RICH ring {} could not solve the quadrant angular pitch", rPosId);
186 }
187 }
188
189 auto modulePhiRad = [&](int moduleIndex) {
190 if (!flagUseQuadrants) {
191 // Original placement exactly.
192 return static_cast<double>(moduleIndex) * regularDeltaPhi;
193 }
194 return quadrantModulePhi(moduleIndex, nTilesPhi, moduleDeltaPhi, quadrantExtraPhi);
195 };
196
197 const double thetaBDeg = thetaB * 180.0 / TMath::Pi();
198
199 const double sipmActiveSizeZ = richPars.sipmActiveSizeZ;
200 // const double sipmActiveSizeRPhi = richPars.sipmActiveSizeRPhi;
201 // Select width depending on having quadrants or not (and wall thickness)
202 const double sipmActiveSizeRPhi = flagUseQuadrants ? richPars.quadrantModuleSizeRPhi : richPars.sipmActiveSizeRPhi;
203
204 const double pcb1Thickness = richPars.pcb1Thickness;
205 const double coolingPlateThickness = richPars.coolingPlateThickness;
206 const double pcb2Thickness = richPars.pcb2Thickness;
207 const double pcb3Thickness = richPars.pcb3Thickness;
208
209 const double gapSiPMToPCB1 = richPars.gapSiPMToPCB1;
210 const double gapPCB1ToCoolingPlate = richPars.gapPCB1ToCoolingPlate;
211 const double gapCoolingPlateToPCB2 = richPars.gapCoolingPlateToPCB2;
212 const double gapPCB2ToPCB3 = richPars.gapPCB2ToPCB3;
213
214 const bool oddGeom = richPars.oddGeom;
215 const bool useRectangularModules = richPars.useRectangularModules;
216
217 const int nRings = richPars.nRings;
218
219 const double moduleClearanceZ = richPars.moduleClearanceZ;
220 const double moduleClearanceRPhi = richPars.moduleClearanceRPhi;
221
222 const double siliconeLayerThickness = richPars.siliconeLayerThickness;
223 const double activeSiliconThickness = richPars.activeSiliconThickness;
224 const double passiveSiliconThickness = photThick - activeSiliconThickness;
225
226 const double siliconFrontSurfaceOffset = -photThick / 2.0;
227 const double siliconeCenterOffset = siliconFrontSurfaceOffset - siliconeLayerThickness / 2.0;
228 const double activeSiliconCenterOffset = siliconFrontSurfaceOffset + activeSiliconThickness / 2.0;
229 const double passiveSiliconCenterOffset = siliconFrontSurfaceOffset + activeSiliconThickness + passiveSiliconThickness / 2.0;
230
231 if (siliconeLayerThickness <= 0.0) {
232 LOGP(fatal, "RICH: siliconeLayerThickness must be positive");
233 }
234
235 if (activeSiliconThickness <= 0.0 || activeSiliconThickness >= photThick) {
236 LOGP(fatal, "RICH: activeSiliconThickness={} cm must be larger than zero and smaller than detectorThickness={} cm", activeSiliconThickness, photThick);
237 }
238
239 if (passiveSiliconThickness <= 0.0) {
240 LOGP(fatal, "RICH: passive silicon thickness must be positive");
241 }
242
243 if (moduleClearanceZ < 0.0 || moduleClearanceRPhi < 0.0) {
244 LOGP(fatal, "RICH: module clearances cannot be negative");
245 }
246
247 if (photThick <= 0.0 || sipmActiveSizeZ <= 0.0 || sipmActiveSizeRPhi <= 0.0 || pcb1Thickness <= 0.0 || coolingPlateThickness <= 0.0 || pcb2Thickness <= 0.0 || pcb3Thickness <= 0.0) {
248 LOGP(fatal, "RICH: SiPM and readout-stack dimensions must be positive");
249 }
250
251 if (gapSiPMToPCB1 < 0.0 || gapPCB1ToCoolingPlate < 0.0 || gapCoolingPlateToPCB2 < 0.0 || gapPCB2ToPCB3 < 0.0) {
252 LOGP(fatal, "RICH: readout-stack gaps cannot be negative");
253 }
254
255 const double minimumFrameSizeRPhi = photYmin < photYmax ? photYmin : photYmax;
256
257 if (sipmActiveSizeZ > photZ || sipmActiveSizeRPhi > minimumFrameSizeRPhi) {
258 LOGP(fatal,
259 "RICH: rectangular module {} x {} cm2 does not fit inside the trapezoidal sector {} x [{}, {}] cm2 for ring {}. "
260 "For quadrant mode reduce: quadrantModuleSizeRPhi.",
261 sipmActiveSizeZ, sipmActiveSizeRPhi, photZ, photYmin, photYmax, rPosId);
262 }
263
264 // Number of actual aerogel rows.
265 const int nAerogelRows = oddGeom ? nRings : nRings - 1;
266
267 // Convert the projective-ring ID into a contiguous aerogel-row index.
268 // Example for nRings=11 and even geometry:
269 // projective IDs: 0 1 2 3 4 [5 skipped] 6 7 8 9 10
270 // aerogel index: 0 1 2 3 4 5 6 7 8 9
271 int aerogelRowIndex = rPosId;
272 if (!oddGeom && rPosId > nRings / 2) {
273 --aerogelRowIndex;
274 }
275
276 const double cylindricalAerogelCenterZ = -0.5 * static_cast<double>(nAerogelRows) * radZ + 0.5 * radZ + static_cast<double>(aerogelRowIndex) * radZ;
277
278 int radTileCount{0}, photTileCount{0}; // argSectorsCount{0};
279
280 if (flagUseQuadrants) {
281 LOGP(info, "RICH ring {} quadrant placement: deltaPhi={} deg, boundary half-gap={} deg", rPosId, moduleDeltaPhi * 180.0 / TMath::Pi(), quadrantExtraPhi * 180.0 / TMath::Pi());
282 }
283
284 // Radiator tiles
285 for (auto& radiatorTile : radiatorTiles) {
286 // Local Z is the thin (radial) dimension, looking outward from the IP
287 // (previously this was local X, while for running with ACTS we need local Z).
288 // The placement rotation below is adjusted by +90 deg about Y
289 // to keep the tile in the same physical position.
290 if (useCylindricalAerogel) {
291 // Including gab between adjacent aerogel tiles
292 const double cylindricalTileSizeZ = radZ - moduleClearanceZ;
293 const double cylindricalTileYmin = radYmin - moduleClearanceRPhi;
294 const double cylindricalTileYmax = radYmax - moduleClearanceRPhi;
295 if (cylindricalTileSizeZ <= 0.0 || cylindricalTileYmin <= 0.0 || cylindricalTileYmax <= 0.0) {
296 LOGP(fatal, "RICH: cylindrical-aerogel clearances are larger than the tile dimensions for ring {}", rPosId);
297 }
298 radiatorTile = new TGeoArb8(radThick / 2);
299 radiatorTile->SetVertex(0, cylindricalTileSizeZ / 2, -cylindricalTileYmin / 2);
300 radiatorTile->SetVertex(1, -cylindricalTileSizeZ / 2, -cylindricalTileYmax / 2);
301 radiatorTile->SetVertex(2, -cylindricalTileSizeZ / 2, cylindricalTileYmax / 2);
302 radiatorTile->SetVertex(3, cylindricalTileSizeZ / 2, cylindricalTileYmin / 2);
303 radiatorTile->SetVertex(4, cylindricalTileSizeZ / 2, -cylindricalTileYmin / 2);
304 radiatorTile->SetVertex(5, -cylindricalTileSizeZ / 2, -cylindricalTileYmax / 2);
305 radiatorTile->SetVertex(6, -cylindricalTileSizeZ / 2, cylindricalTileYmax / 2);
306 radiatorTile->SetVertex(7, cylindricalTileSizeZ / 2, cylindricalTileYmin / 2);
307 } else {
308 // Original non-cylindrical tile definition.
309 radiatorTile = new TGeoArb8(radThick / 2);
310 radiatorTile->SetVertex(0, radZ / 2, -radYmin / 2);
311 radiatorTile->SetVertex(1, -radZ / 2, -radYmax / 2);
312 radiatorTile->SetVertex(2, -radZ / 2, radYmax / 2);
313 radiatorTile->SetVertex(3, radZ / 2, radYmin / 2);
314 radiatorTile->SetVertex(4, radZ / 2, -radYmin / 2);
315 radiatorTile->SetVertex(5, -radZ / 2, -radYmax / 2);
316 radiatorTile->SetVertex(6, -radZ / 2, radYmax / 2);
317 radiatorTile->SetVertex(7, radZ / 2, radYmin / 2);
318 }
319
320 TGeoVolume* radiatorTileVol = new TGeoVolume(Form("radTile_%d_%d", rPosId, radTileCount), radiatorTile, medAerogel);
321 radiatorTileVol->SetLineColor(kBlue - 9);
322 radiatorTileVol->SetLineWidth(1);
323
324 // const double phiDeg = static_cast<double>(radTileCount) * deltaPhiDeg;
325 // const double phiRad = static_cast<double>(radTileCount) * 2.0 * TMath::Pi() / static_cast<double>(nTilesPhi);
326
327 const double phiRad = modulePhiRad(radTileCount);
328 const double phiDeg = phiRad * 180.0 / TMath::Pi();
329
330 auto* rotRadiator = new TGeoRotation(Form("radTileRotation_%d_%d", radTileCount, rPosId));
331
332 if (useCylindricalAerogel) {
333 // The TGeoArb8 local Z axis is the thin direction.
334 // RotateY(90 degrees) maps that thin local Z direction onto the global radial direction at phi=0.
335 // There is no thetaB tilt because the cylindrical aerogel tiles are parallel to the beam axis.
336 rotRadiator->RotateY(90.0);
337 } else {
338 // Original projective rotation.
339 rotRadiator->RotateY(90.0 - thetaBDeg);
340 }
341
342 // Rotate the radial tile around the beam axis to its phi sector.
343 rotRadiator->RotateZ(phiDeg);
344
345 const double radiatorCenterZ = useCylindricalAerogel ? cylindricalAerogelCenterZ : radRad0 * TMath::Tan(thetaB);
346
347 auto* rotTransRadiator = new TGeoCombiTrans(radRad0 * TMath::Cos(phiRad), radRad0 * TMath::Sin(phiRad), radiatorCenterZ, rotRadiator);
348
349 motherVolume->AddNode(radiatorTileVol, 1, rotTransRadiator);
350 radTileCount++;
351 }
352
353 // Photosensor tiles: legacy trapezoidal modules and rectangular modules
354 if (!useRectangularModules) {
355 for (auto& photoTile : photoTiles) {
356 const double phiRad = modulePhiRad(photTileCount);
357 const double phiDeg = phiRad * 180.0 / TMath::Pi();
358 // Local Z is the thin (radial) dimension, looking outward from the IP
359 photoTile = new TGeoArb8(photThick / 2);
360 photoTile->SetVertex(0, photZ / 2, -photYmin / 2);
361 photoTile->SetVertex(1, -photZ / 2, -photYmax / 2);
362 photoTile->SetVertex(2, -photZ / 2, photYmax / 2);
363 photoTile->SetVertex(3, photZ / 2, photYmin / 2);
364 photoTile->SetVertex(4, photZ / 2, -photYmin / 2);
365 photoTile->SetVertex(5, -photZ / 2, -photYmax / 2);
366 photoTile->SetVertex(6, -photZ / 2, photYmax / 2);
367 photoTile->SetVertex(7, photZ / 2, photYmin / 2);
368
369 TGeoVolume* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi);
370 photoTileVol->SetLineColor(kOrange + 2);
371 photoTileVol->SetLineWidth(1);
372
373 auto* rotPhoto = new TGeoRotation(Form("photoTileRotation_%d_%d", photTileCount, rPosId));
374 rotPhoto->RotateY(90.0 - thetaBDeg); // +90 compensates the X->Z swap of the tile's local axes
375 // rotPhoto->RotateZ(photTileCount * deltaPhiDeg);
376 rotPhoto->RotateZ(phiDeg);
377 // auto* rotTransPhoto = new TGeoCombiTrans(photR0 * TMath::Cos(photTileCount * TMath::Pi() / (nTilesPhi / 2)),
378 // photR0 * TMath::Sin(photTileCount * TMath::Pi() / (nTilesPhi / 2)),
379 // photR0 * TMath::Tan(thetaB),
380 // rotPhoto);
381 auto* rotTransPhoto = new TGeoCombiTrans(photR0 * TMath::Cos(phiRad), photR0 * TMath::Sin(phiRad), photR0 * TMath::Tan(thetaB), rotPhoto);
382
383 motherVolume->AddNode(photoTileVol, 1, rotTransPhoto);
384 photTileCount++;
385 }
386 } else // <-- New gemetry with rectangular modules
387 {
388 // Photosensor tiles and readout stack
389 for (auto& photoTile : photoTiles) {
390 // const double phiDeg = static_cast<double>(photTileCount) * deltaPhiDeg;
391 // const double phiRad = static_cast<double>(photTileCount) * 2.0 * TMath::Pi() / static_cast<double>(nTilesPhi);
392 const double phiRad = modulePhiRad(photTileCount);
393 const double phiDeg = phiRad * 180.0 / TMath::Pi();
394
395 const double photoCenterR = photR0;
396 const double photoCenterZ = photR0 * TMath::Tan(thetaB);
397
398 // Unit vector normal to the projective plane, pointing away from the IP. Positive offset places layer behind the SiPM.
399 const double normalRadial = TMath::Cos(thetaB);
400 const double normalZ = TMath::Sin(thetaB);
401
402 auto makeProjectiveRotation = [&](const char* prefix) {
403 auto* rotation = new TGeoRotation(Form("%sRotation_%d_%d", prefix, photTileCount, rPosId));
404 rotation->RotateY(90.0 - thetaBDeg); // same orientation as the original photosensor
405 rotation->RotateZ(phiDeg);
406 return rotation;
407 };
408
409 const double frameSizeZ = photZ - moduleClearanceZ;
410 const double frameYmin = photYmin - moduleClearanceRPhi;
411 const double frameYmax = photYmax - moduleClearanceRPhi;
412 // Footprint of the frames with the configured clearances for overlaps
413 auto makeFrameFootprint = [&](double thickness) {
414 auto* shape = new TGeoArb8(thickness / 2.0);
415 shape->SetVertex(0, frameSizeZ / 2.0, -frameYmin / 2.0);
416 shape->SetVertex(1, -frameSizeZ / 2.0, -frameYmax / 2.0);
417 shape->SetVertex(2, -frameSizeZ / 2.0, frameYmax / 2.0);
418 shape->SetVertex(3, frameSizeZ / 2.0, frameYmin / 2.0);
419 shape->SetVertex(4, frameSizeZ / 2.0, -frameYmin / 2.0);
420 shape->SetVertex(5, -frameSizeZ / 2.0, -frameYmax / 2.0);
421 shape->SetVertex(6, -frameSizeZ / 2.0, frameYmax / 2.0);
422 shape->SetVertex(7, frameSizeZ / 2.0, frameYmin / 2.0);
423 return shape;
424 };
425
426 auto makeRectangularFootprint = [&](double thickness) {
427 auto* shape = new TGeoArb8(thickness / 2.0);
428 shape->SetVertex(0, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
429 shape->SetVertex(1, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
430 shape->SetVertex(2, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
431 shape->SetVertex(3, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
432 shape->SetVertex(4, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
433 shape->SetVertex(5, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
434 shape->SetVertex(6, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
435 shape->SetVertex(7, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
436 return shape;
437 };
438
439 auto addReadoutLayer = [&](const char* prefix,
440 double thickness,
441 double centerOffset,
442 TGeoMedium* medium,
443 Color_t lineColor,
444 bool useRectangularFootprint) {
445 auto* shape = useRectangularFootprint ? makeRectangularFootprint(thickness) : makeFrameFootprint(thickness);
446 auto* volume = new TGeoVolume(Form("%s_%d_%d", prefix, rPosId, photTileCount), shape, medium);
447 volume->SetLineColor(lineColor);
448 volume->SetLineWidth(1);
449 const double layerCenterR = photoCenterR + centerOffset * normalRadial;
450 const double layerCenterZ = photoCenterZ + centerOffset * normalZ;
451 auto* transform = new TGeoCombiTrans(layerCenterR * TMath::Cos(phiRad), layerCenterR * TMath::Sin(phiRad), layerCenterZ, makeProjectiveRotation(prefix));
452 motherVolume->AddNode(volume, 1, transform);
453 };
454
455 // ------------------------------------------------------------
456 // Optional trapezoidal frame
457 // ------------------------------------------------------------
458 // This is exactly the old photosensor envelope. It is created for
459 // reference, but deliberately not added to the geometry.
460 photoFrames[photTileCount] = makeFrameFootprint(photThick);
461 auto* photoFrameVol = new TGeoVolume(Form("photoFrame_%d_%d", rPosId, photTileCount), photoFrames[photTileCount], medSi);
462 photoFrameVol->SetLineColor(kGray + 2);
463 photoFrameVol->SetLineWidth(1);
464 // Uncomment only when the mechanical frame material/solid geometry should be included.
465 // This would be a solid trapezoid and would overlap the sensitive silicon: need for opening
466 // motherVolume->AddNode(photoFrameVol, 1, new TGeoCombiTrans(photoCenterR * TMath::Cos(phiRad), photoCenterR * TMath::Sin(phiRad), photoCenterZ, makeProjectiveRotation("photoFrame")));
467
468 // ------------------------------------------------------------
469 // True sensitive silicon: centered 17 x 18 cm2 rectangle
470 // ------------------------------------------------------------
471 // Local X corresponds to the in-plane Z direction after placement.
472 // Local Y corresponds to the in-plane r-phi direction.
473 // Local Z is the 1 mm thickness direction.
474 /*photoTile = new TGeoArb8(photThick / 2.0);
475 photoTile->SetVertex(0, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
476 photoTile->SetVertex(1, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
477 photoTile->SetVertex(2, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
478 photoTile->SetVertex(3, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
479 photoTile->SetVertex(4, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
480 photoTile->SetVertex(5, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0);
481 photoTile->SetVertex(6, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
482 photoTile->SetVertex(7, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0);
483 auto* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi);
484 photoTileVol->SetLineColor(kRed);
485 photoTileVol->SetLineWidth(1);
486 auto* rotTransPhoto = new TGeoCombiTrans(photoCenterR * TMath::Cos(phiRad), photoCenterR * TMath::Sin(phiRad), photoCenterZ, makeProjectiveRotation("photoTile"));
487 motherVolume->AddNode(photoTileVol, 1, rotTransPhoto);*/
488
489 // Silicone resin in front of SiPMs
490 addReadoutLayer("siliconeLayer", siliconeLayerThickness, siliconeCenterOffset, medSilicone, kOrange + 2, useRectangularModules);
491
492 // Active sensitive silicon.
493 photoTile = makeRectangularFootprint(activeSiliconThickness);
494 auto* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi);
495 const double activeSiliconCenterR = photoCenterR + activeSiliconCenterOffset * normalRadial;
496 const double activeSiliconCenterZ = photoCenterZ + activeSiliconCenterOffset * normalZ;
497 auto* rotTransPhoto = new TGeoCombiTrans(activeSiliconCenterR * TMath::Cos(phiRad), activeSiliconCenterR * TMath::Sin(phiRad), activeSiliconCenterZ, makeProjectiveRotation("photoTile"));
498 motherVolume->AddNode(photoTileVol, 1, rotTransPhoto);
499
500 // Passive silicon absorber.
501 addReadoutLayer("siliconAbsorber", passiveSiliconThickness, passiveSiliconCenterOffset, medSiAbsorber, kBlue + 1, true);
502
503 // ------------------------------------------------------------
504 // Stack behind the SiPM
505 // ------------------------------------------------------------
506 // Every gap is surface-to-surface. centerOffset is measured from
507 // the SiPM center along the outward local normal.
508 double outerSurfaceOffset = photThick / 2.0;
509
510 outerSurfaceOffset += gapSiPMToPCB1;
511 const double pcb1CenterOffset = outerSurfaceOffset + pcb1Thickness / 2.0;
512 addReadoutLayer("pcb1", pcb1Thickness, pcb1CenterOffset, medFR4, kGreen + 1, useRectangularModules);
513 outerSurfaceOffset += pcb1Thickness;
514
515 outerSurfaceOffset += gapPCB1ToCoolingPlate;
516 const double coolingPlateCenterOffset = outerSurfaceOffset + coolingPlateThickness / 2.0;
517 addReadoutLayer("coolingPlate", coolingPlateThickness, coolingPlateCenterOffset, medHTCC, kRed, useRectangularModules);
518 outerSurfaceOffset += coolingPlateThickness;
519
520 outerSurfaceOffset += gapCoolingPlateToPCB2;
521 const double pcb2CenterOffset = outerSurfaceOffset + pcb2Thickness / 2.0;
522 addReadoutLayer("pcb2", pcb2Thickness, pcb2CenterOffset, medFR4, kGreen + 2, useRectangularModules);
523 outerSurfaceOffset += pcb2Thickness;
524
525 outerSurfaceOffset += gapPCB2ToPCB3;
526 const double pcb3CenterOffset = outerSurfaceOffset + pcb3Thickness / 2.0;
527 addReadoutLayer("pcb3", pcb3Thickness, pcb3CenterOffset, medFR4, kGreen + 3, useRectangularModules);
528
529 photTileCount++;
530 }
531 }
532
533 // Gas sectors (argon) - legacy code, not used in the current geometry, but kept for reference
534 /*
535 for (auto& gasSector : gasSectors) {
536 double separation{(aerDetDistance - radThick - photThick)};
537 auto* radiator = radiatorTiles[argSectorsCount];
538 auto* photosensor = photoTiles[argSectorsCount];
539 gasSector = new TGeoArb8(separation / 2);
540
541 gasSector->SetVertex(0, -photZ / 2, -photYmin / 2);
542 gasSector->SetVertex(1, -photZ / 2, photYmin / 2);
543 gasSector->SetVertex(2, photZ / 2, photYmax / 2);
544 gasSector->SetVertex(3, photZ / 2, -photYmax / 2);
545 gasSector->SetVertex(4, -radZ / 2, -radYmin / 2);
546 gasSector->SetVertex(5, -radZ / 2, radYmin / 2);
547 gasSector->SetVertex(6, radZ / 2, radYmax / 2);
548 gasSector->SetVertex(7, radZ / 2, -radYmax / 2);
549
550 TGeoVolume* gasSectorVol = new TGeoVolume(Form("gasSector_%d_%d", rPosId, argSectorsCount), gasSector, medCO2);
551 gasSectorVol->SetVisibility(kTRUE);
552 gasSectorVol->SetLineColor(kOrange - 8);
553 gasSectorVol->SetLineWidth(1);
554 auto* rotGas = new TGeoRotation(Form("gasSectorRotation_%d_%d", argSectorsCount, rPosId));
555 rotGas->RotateY(-90 - thetaBDeg);
556 //rotGas->RotateZ(argSectorsCount * deltaPhiDeg);
557 //auto* rotTransGas = new TGeoCombiTrans((radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2) * TMath::Cos(argSectorsCount * TMath::Pi() / (nTilesPhi / 2)),
558 // (radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2) * TMath::Sin(argSectorsCount * TMath::Pi() / (nTilesPhi / 2)),
559 // radRad0 * TMath::Tan(thetaB) + TMath::Sin(thetaB) * (separation + radThick) / 2,
560 // rotGas);
561 const double gasPhiRad = modulePhiRad(argSectorsCount);
562 rotGas->RotateZ(gasPhiRad * 180.0 / TMath::Pi());
563 auto* rotTransGas = new TGeoCombiTrans((radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2.0) * TMath::Cos(gasPhiRad),
564 (radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2.0) * TMath::Sin(gasPhiRad),
565 radRad0 * TMath::Tan(thetaB) + TMath::Sin(thetaB) * (separation + radThick) / 2.0,
566 rotGas);
567 motherVolume->AddNode(gasSectorVol, 1, rotTransGas);
568 argSectorsCount++;
569 }
570 */
571}
572
574 double rMin,
575 double rMax,
576 double zAerogelMin,
577 double dZAerogel,
578 double zArgonMin,
579 double dZArgon,
580 double zSiliconMin,
581 double dZSilicon) : mName{name},
582 mRmin{rMin},
583 mRmax{rMax},
584 mZAerogelMin{zAerogelMin},
585 mDZAerogel{dZAerogel},
586 mZArgonMin{zArgonMin},
587 mDZArgon{dZArgon},
588 mZSiliconMin{zSiliconMin},
589 mDZSilicon{dZSilicon}
590{
591}
592
594 double rMin,
595 double rMax,
596 double zAerogelMin,
597 double dZAerogel,
598 double zArgonMin,
599 double dZArgon,
600 double zSiliconMin,
601 double dZSilicon) : mName{name},
602 mRmin{rMin},
603 mRmax{rMax},
604 mZAerogelMin{zAerogelMin},
605 mDZAerogel{dZAerogel},
606 mZArgonMin{zArgonMin},
607 mDZArgon{dZArgon},
608 mZSiliconMin{zSiliconMin},
609 mDZSilicon{dZSilicon}
610{
611}
612
613void FWDRich::createFWDRich(TGeoVolume* motherVolume)
614{
615 TGeoMedium* medAerogel = gGeoManager->GetMedium("RCH_AEROGEL$");
616 if (!medAerogel) {
617 LOGP(fatal, "RICH: Aerogel medium not found");
618 }
619 TGeoMedium* medSi = gGeoManager->GetMedium("RCH_SILICON$");
620 if (!medSi) {
621 LOGP(fatal, "RICH: Silicon medium not found");
622 }
623 TGeoMedium* medAr = gGeoManager->GetMedium("RCH_ARGON$");
624 if (!medAr) {
625 LOGP(fatal, "RICH: Argon medium not found");
626 }
627
628 // Create the aerogel volume
629 TGeoTube* aerogel = new TGeoTube(mRmin, mRmax, mDZAerogel / 2);
630 TGeoVolume* aerogelVol = new TGeoVolume(mName.c_str(), aerogel, medAerogel);
631 aerogelVol->SetLineColor(kOrange - 8);
632
633 TGeoTranslation* transAerogel = new TGeoTranslation(0, 0, mZAerogelMin + mDZAerogel / 2);
634 motherVolume->AddNode(aerogelVol, 1, transAerogel);
635
636 // Create the argon volume
637 TGeoTube* argon = new TGeoTube(mRmin, mRmax, mDZArgon / 2);
638 TGeoVolume* argonVol = new TGeoVolume(mName.c_str(), argon, medAr);
639 argonVol->SetLineColor(kOrange - 9);
640
641 TGeoTranslation* transArgon = new TGeoTranslation(0, 0, mZArgonMin + mDZArgon / 2);
642 motherVolume->AddNode(argonVol, 1, transArgon);
643
644 // Create the silicon volume
645 TGeoTube* silicon = new TGeoTube(mRmin, mRmax, mDZSilicon / 2);
646 TGeoVolume* siliconVol = new TGeoVolume(mName.c_str(), silicon, medSi);
647 siliconVol->SetLineColor(kOrange - 8);
648
649 TGeoTranslation* transSilicon = new TGeoTranslation(0, 0, mZSiliconMin + mDZSilicon / 2);
650 motherVolume->AddNode(siliconVol, 1, transSilicon);
651}
652
653void BWDRich::createBWDRich(TGeoVolume* motherVolume)
654{
655 TGeoMedium* medAerogel = gGeoManager->GetMedium("RCH_AEROGEL$");
656 if (!medAerogel) {
657 LOGP(fatal, "RICH: Aerogel medium not found");
658 }
659 TGeoMedium* medSi = gGeoManager->GetMedium("RCH_SILICON$");
660 if (!medSi) {
661 LOGP(fatal, "RICH: Silicon medium not found");
662 }
663 TGeoMedium* medAr = gGeoManager->GetMedium("RCH_ARGON$");
664 if (!medAr) {
665 LOGP(fatal, "RICH: Argon medium not found");
666 }
667
668 // Create the aerogel volume
669 TGeoTube* aerogel = new TGeoTube(mRmin, mRmax, mDZAerogel / 2);
670 TGeoVolume* aerogelVol = new TGeoVolume(mName.c_str(), aerogel, medAerogel);
671 aerogelVol->SetLineColor(kOrange - 8);
672
673 TGeoTranslation* transAerogel = new TGeoTranslation(0, 0, -mZAerogelMin - mDZAerogel / 2);
674 motherVolume->AddNode(aerogelVol, 1, transAerogel);
675
676 // Create the argon volume
677 TGeoTube* argon = new TGeoTube(mRmin, mRmax, mDZArgon / 2);
678 TGeoVolume* argonVol = new TGeoVolume(mName.c_str(), argon, medAr);
679 argonVol->SetLineColor(kOrange - 8);
680
681 TGeoTranslation* transArgon = new TGeoTranslation(0, 0, -mZArgonMin - mDZArgon / 2);
682 motherVolume->AddNode(argonVol, 1, transArgon);
683
684 // Create the silicon volume
685 TGeoTube* silicon = new TGeoTube(mRmin, mRmax, mDZSilicon / 2);
686 TGeoVolume* siliconVol = new TGeoVolume(mName.c_str(), silicon, medSi);
687 siliconVol->SetLineColor(kOrange - 8);
688
689 TGeoTranslation* transSilicon = new TGeoTranslation(0, 0, -mZSiliconMin - mDZSilicon / 2);
690 motherVolume->AddNode(siliconVol, 1, transSilicon);
691}
692
693} // namespace rich
694} // namespace o2
int16_t Color_t
Definition GPUQA.h:33
double mZSiliconMin
Definition RICHRing.h:136
void createBWDRich(TGeoVolume *motherVolume)
Definition RICHRing.cxx:653
double mZAerogelMin
Definition RICHRing.h:128
std::string mName
Definition RICHRing.h:123
std::string mName
Definition RICHRing.h:88
double mZSiliconMin
Definition RICHRing.h:101
void createFWDRich(TGeoVolume *motherVolume)
Definition RICHRing.cxx:613
double mZArgonMin
Definition RICHRing.h:97
double mDZAerogel
Definition RICHRing.h:94
double mZAerogelMin
Definition RICHRing.h:93
static const char * getRICHSensorPattern()
Ring()=default
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint const GLchar * name
Definition glcorearb.h:781
D const SVectorGPU< T, D > & rhs
Definition SMatrixGPU.h:193
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...