FairShip
Loading...
Searching...
No Matches
MTCDetector.cxx
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-3.0-or-later
2// SPDX-FileCopyrightText: Copyright CERN for the benefit of the SHiP
3// Collaboration
4
5// MTC detector specific headers
6#include "MTCDetector.h"
7
8#include "MTCDetPoint.h"
9#include "ShipDetectorList.h"
10#include "ShipGeoUtil.h"
11#include "ShipStack.h"
12#include "ShipUnit.h"
13
14// ROOT / TGeo headers
15#include "TGeoBBox.h"
16#include "TGeoCompositeShape.h"
17#include "TGeoManager.h"
18#include "TGeoMaterial.h"
19#include "TGeoMedium.h"
20#include "TGeoPara.h"
21#include "TGeoTrd1.h"
22#include "TGeoTrd2.h"
23#include "TGeoTube.h"
24#include "TGeoUniformMagField.h"
25#include "TGeoVolume.h"
26#include "TParticle.h"
27#include "TVector3.h"
28
29// FairROOT headers
30#include "FairGeoBuilder.h"
31#include "FairGeoInterface.h"
32#include "FairGeoLoader.h"
33#include "FairGeoMedia.h"
34#include "FairGeoNode.h"
35#include "FairGeoVolume.h"
36#include "FairRun.h"
37#include "FairRuntimeDb.h"
38#include "FairVolume.h"
39
40// Additional standard headers
41#include "TList.h" // for TListIter, TList (ptr only)
42#include "TObjArray.h" // for TObjArray
43#include "TString.h" // for TString
44#include "TVirtualMC.h"
45
46namespace {
47Double_t ycross(Double_t a, Double_t R, Double_t x) {
48 /*
49 * ycross:
50 * Compute the positive y-coordinate where the vertical line x intersects
51 * the circle of radius R centered at (a, 0). If the line does not intersect
52 * (i.e., (x-a)^2 > R^2), returns -1 as a flag.
53 */
54 Double_t y = -1;
55 Double_t A = R * R - (x - a) * (x - a);
56 if (!(A < 0)) {
57 y = TMath::Sqrt(A);
58 }
59 return y;
60}
61
62Double_t integralSqrt(Double_t ynorm) {
63 /*
64 * integralSqrt:
65 * Compute the analytic integral ∫₀^{ynorm} sqrt(1 - t^2) dt
66 * = ½ [ ynorm * sqrt(1 - ynorm^2) + arcsin(ynorm) ].
67 * This is used for normalizing the circular segment area.
68 */
69 Double_t y =
70 1. / 2. * (ynorm * TMath::Sqrt(1 - ynorm * ynorm) + TMath::ASin(ynorm));
71 return y;
72}
73
74Double_t fraction(Double_t R, Double_t x, Double_t y) {
75 /*
76 * fraction:
77 * Compute the fraction of the circle's total area that lies on one side
78 * of a vertical cut at horizontal distance x from the circle center,
79 * up to the intersection height y = sqrt(R^2 - x^2).
80 * Formula:
81 * F = 2 R^2 ∫₀^{y/R} sqrt(1 - t^2) dt - 2 x y
82 * result = F / (π R^2)
83 */
84 Double_t F = 2 * R * R * (integralSqrt(y / R));
85 F -= (2 * x * y);
86 Double_t result = F / (R * R * TMath::Pi());
87 return result;
88}
89
90Double_t area(Double_t a, Double_t R, Double_t xL, Double_t xR) {
91 /*
92 * area:
93 * Compute the fraction of the full circle (radius R, center at (a,0))
94 * that lies between the vertical boundaries x = xL and x = xR.
95 * Special cases:
96 * - If [xL, xR] fully covers the circle, returns 1.
97 * - If neither boundary intersects the circle, returns -1 (no overlap).
98 * Otherwise, uses ycross() to find intersection heights, fraction()
99 * to get segment areas, and combines them to yield the net fraction.
100 */
101 Double_t fracL = -1;
102 Double_t fracR = -1;
103 if (xL <= a - R && xR >= a + R) {
104 return 1;
105 }
106
107 Double_t leftC = ycross(a, R, xL);
108 Double_t rightC = ycross(a, R, xR);
109 if (leftC < 0 && rightC < 0) {
110 return -1;
111 }
112
113 if (!(rightC < 0)) {
114 fracR = fraction(R, TMath::Abs(xR - a), rightC);
115 }
116 if (!(leftC < 0)) {
117 fracL = fraction(R, TMath::Abs(xL - a), leftC);
118 }
119
120 Double_t theAnswer = 0;
121 if (!(leftC < 0)) {
122 if (xL < a) {
123 theAnswer += 1 - fracL;
124 } else {
125 theAnswer += fracL;
126 }
127 if (!(rightC < 0)) {
128 theAnswer -= 1;
129 }
130 }
131 if (!(rightC < 0)) {
132 if (xR > a) {
133 theAnswer += 1 - fracR;
134 } else {
135 theAnswer += fracR;
136 }
137 }
138 return theAnswer;
139}
140} // namespace
141
142MTCDetector::MTCDetector() : Detector("MTC", kTRUE, kMTC) {}
143
144MTCDetector::MTCDetector(const char* name, Bool_t Active, const char* /*Title*/,
145 Int_t /*DetId*/)
146 : Detector(name, Active, kMTC) {}
147
149 Double_t width, Double_t height, Double_t fiber_tilt_angle,
150 Double_t iron_thickness, Double_t scifi_thickness,
151 Int_t num_of_agg_channels, Double_t scint_cell_size,
152 Double_t scint_thickness, Int_t number_of_layers, Double_t z_position,
153 Double_t field_strength) {
154 fWidth = width;
155 fHeight = height;
156 fSciFiBendingAngle = fiber_tilt_angle;
157 fIronThick = iron_thickness;
158 fSciFiThick = scifi_thickness;
159 fChannelAggregated = num_of_agg_channels;
160 fScintCellSize = scint_cell_size;
161 fScintThick = scint_thickness;
162 fLayers = number_of_layers;
163 fZCenter = z_position;
164 fFieldY = field_strength;
165 fSciFiActiveX = fWidth - fWidth * tan(fSciFiBendingAngle * TMath::DegToRad());
167}
168
169// Updated SciFi module builder with fiber placements
170void MTCDetector::CreateScintModule(const char* name,
171 TGeoVolumeAssembly* modMotherVol,
172 Double_t z_shift, Double_t width,
173 Double_t height, Double_t thickness,
174 Double_t cellSizeX, Double_t cellSizeY,
175 TGeoMedium* material, Int_t color,
176 Double_t transparency, Int_t LayerId) {
177 modMotherVol->SetLineColor(color);
178 modMotherVol->SetTransparency(transparency);
179 auto scint_volume = new TGeoVolumeAssembly(Form("%s_scint", name));
180 modMotherVol->AddNode(scint_volume, 0, new TGeoTranslation(0, 0, z_shift));
181 auto scint_mat = new TGeoVolumeAssembly(Form("%s_scint_mat", name));
182 scint_volume->AddNode(scint_mat, 0, new TGeoTranslation(0, 0, 0));
183 auto cell = new TGeoBBox(Form("%s_cell", name), cellSizeX / 2, cellSizeY / 2,
184 thickness / 2);
185 auto cellVol = new TGeoVolume(Form("%s_cell", name), cell, material);
186 cellVol->SetLineColor(color);
187 cellVol->SetTransparency(transparency);
188 AddSensitiveVolume(cellVol);
189 Int_t nX = Int_t(width / cellSizeX);
190 Int_t nY = Int_t(height / cellSizeY);
191
192 for (Int_t i = 0; i < nX; i++) {
193 for (Int_t j = 0; j < nY; j++) {
194 Double_t x = -width / 2 + cellSizeX * (i + 0.5);
195 Double_t y = -height / 2 + cellSizeY * (j + 0.5);
196 scint_mat->AddNode(cellVol, 1e8 + 1e6 + 2e5 + 0e4 + i * nY + j,
197 new TGeoTranslation(x, y, 0));
198 }
199 }
200}
201
202void MTCDetector::CreateSciFiModule(const char* name,
203 TGeoVolumeAssembly* modMotherVol,
204 Double_t width, Double_t height,
205 Double_t thickness, Int_t LayerId) {
206 // --- Lower Internal Iron ---
207 TGeoBBox* lowerIronBox = new TGeoBBox(Form("%s_lowerIron", name), width / 2,
208 height / 2, lowerIronThick / 2);
209 TGeoVolume* lowerIronVol = new TGeoVolume(
210 Form("%s_lowerIron", name), lowerIronBox, gGeoManager->GetMedium("iron"));
211 lowerIronVol->SetLineColor(kGray + 1);
212 lowerIronVol->SetTransparency(20);
213 modMotherVol->AddNode(lowerIronVol, 0,
214 new TGeoTranslation(0, 0, zLowerIronInt));
215
216 // --- Lower Epoxy matrix (replaces SciFiMat layer) ---
217 TGeoVolumeAssembly* ScifiVolU =
218 new TGeoVolumeAssembly(Form("%s_scifi_U", name));
219 modMotherVol->AddNode(ScifiVolU, 0, new TGeoTranslation(0, 0, 0));
220 TGeoBBox* epoxyMatBoxU = new TGeoBBox(Form("%s_epoxyMat", name), width / 2,
221 height / 2, fiberMatThick / 2);
222 TGeoVolume* ScifiMatVolU = new TGeoVolume(
223 Form("%s_epoxyMat", name), epoxyMatBoxU, gGeoManager->GetMedium("Epoxy"));
224 ScifiMatVolU->SetLineColor(kYellow - 2);
225 ScifiMatVolU->SetTransparency(30);
226 ScifiMatVolU->SetVisibility(kFALSE);
227 ScifiMatVolU->SetVisDaughters(kFALSE);
228 ScifiVolU->AddNode(ScifiMatVolU, 0, new TGeoTranslation(0, 0, zFiberMat1));
229
230 // --- Upper Epoxy matrix (replaces SciFiMat layer) ---
231 TGeoVolumeAssembly* ScifiVolV =
232 new TGeoVolumeAssembly(Form("%s_scifi_V", name));
233 modMotherVol->AddNode(ScifiVolV, 0, new TGeoTranslation(0, 0, 0));
234 TGeoBBox* epoxyMatBoxV = new TGeoBBox(Form("%s_epoxyMat", name), width / 2,
235 height / 2, fiberMatThick / 2);
236 TGeoVolume* ScifiMatVolV = new TGeoVolume(
237 Form("%s_epoxyMat", name), epoxyMatBoxV, gGeoManager->GetMedium("Epoxy"));
238 ScifiMatVolV->SetLineColor(kYellow - 2);
239 ScifiMatVolV->SetTransparency(30);
240 ScifiMatVolV->SetVisibility(kFALSE);
241 ScifiMatVolV->SetVisDaughters(kFALSE);
242 ScifiVolV->AddNode(ScifiMatVolV, 0, new TGeoTranslation(0, 0, zFiberMat2));
243
244 // --- Upper Internal Iron ---
245 TGeoBBox* upperIronBox = new TGeoBBox(Form("%s_upperIron", name), width / 2,
246 height / 2, upperIronThick / 2);
247 TGeoVolume* upperIronVol = new TGeoVolume(
248 Form("%s_upperIron", name), upperIronBox, gGeoManager->GetMedium("iron"));
249 upperIronVol->SetLineColor(kGray + 1);
250 upperIronVol->SetTransparency(20);
251 modMotherVol->AddNode(upperIronVol, 0,
252 new TGeoTranslation(0, 0, zUpperIronInt));
253
254 // -----------------------------
255 // Now build the fibers inside each Epoxy block:
256 // Common fiber parameters (cm)
257 Double_t layerThick = fiberMatThick / numFiberLayers;
258 fFiberLength = fSciFiActiveY / cos(fSciFiBendingAngle * TMath::DegToRad()) -
259 2 * fFiberRadius * sin(fSciFiBendingAngle * TMath::DegToRad());
260 LOG(info) << "Fiber length set to " << fFiberLength << " cm";
261 Int_t fNumFibers = static_cast<Int_t>(fSciFiActiveX / fFiberPitch);
262
263 // --- Define the SciFi fiber volume ---
264 TGeoTube* fiberTube =
265 new TGeoTube("FiberTube", 0, fFiberRadius, fFiberLength / 2);
266 TGeoVolume* fiberVol =
267 new TGeoVolume("FiberVol", fiberTube, gGeoManager->GetMedium("SciFiMat"));
268 AddSensitiveVolume(fiberVol);
269 fiberVol->SetLineColor(kMagenta);
270 fiberVol->SetTransparency(15);
271 fiberVol->SetVisibility(kFALSE);
272
273 // --- Rotations for U/V fibers ---
274 TGeoRotation* rotU = new TGeoRotation();
275 rotU->RotateY(fSciFiBendingAngle);
276 rotU->RotateX(90.);
277 TGeoRotation* rotV = new TGeoRotation();
278 rotV->RotateY(-fSciFiBendingAngle);
279 rotV->RotateX(90.);
280
281 // --- Place U-fibers inside lower Epoxy ---
282 for (int layer = 0; layer < numFiberLayers; ++layer) {
283 Double_t z0 = -fiberMatThick / 2 + (layer + 0.5) * (layerThick);
284 for (int j = 0; j < fNumFibers; ++j) {
285 Double_t x0 = -fSciFiActiveX / 2 + (j + 0.5) * fFiberPitch;
286 if (layer % 2 == 1) {
287 if (j == fNumFibers - 1) {
288 continue; // Skip the last layer for odd layers
289 }
290 x0 += fFiberPitch / 2;
291 }
292 TGeoCombiTrans* ct = new TGeoCombiTrans("", x0, 0, z0, rotU);
293 Int_t copyNo = 100000000 + 1000000 + 0 * 100000 + layer * 10000 + j;
294 ScifiMatVolU->AddNode(fiberVol, copyNo, ct);
295 }
296 }
297
298 // --- Place V-fibers inside upper Epoxy ---
299 for (int layer = 0; layer < numFiberLayers; ++layer) {
300 Double_t z0 = -fiberMatThick / 2 + (layer + 0.5) * (layerThick);
301 for (int j = 0; j < fNumFibers; ++j) {
302 Double_t x0 = -fSciFiActiveX / 2 + (j + 0.5) * fFiberPitch;
303 if (layer % 2 == 1) {
304 if (j == fNumFibers - 1) {
305 continue; // Skip the last layer for odd layers
306 }
307 x0 += fFiberPitch / 2;
308 }
309 TGeoCombiTrans* ct = new TGeoCombiTrans("", x0, 0, z0, rotV);
310 Int_t copyNo = 100000000 + 1000000 + 1 * 100000 + layer * 10000 + j;
311 ScifiMatVolV->AddNode(fiberVol, copyNo, ct);
312 }
313 }
314}
315
317 // Initialize media (using FairROOT's interface)
318 ShipGeo::InitMedium("SciFiMat");
319 ShipGeo::InitMedium("Epoxy");
320 ShipGeo::InitMedium("air");
321 TGeoMedium* air = gGeoManager->GetMedium("air");
322 TGeoMedium* ironMed = gGeoManager->GetMedium("iron");
323 // For the scintillator, you may use the same medium as SciFiMat or another if
324 // defined.
325 TGeoMedium* scintMed = gGeoManager->GetMedium("SciFiMat");
326 ShipGeo::InitMedium("silicon");
327
328 // Define the module spacing based on three sublayers:
329 // fIronThick (outer iron), fSciFiThick (SciFi module/fiber module),
330 // fScintThick (scintillator)
331 Double_t moduleSpacing = fIronThick + fSciFiThick + fScintThick;
332 Double_t totalLength = fLayers * moduleSpacing;
333
334 // --- Create an envelope volume for the detector (green, semi-transparent)
335 // ---
336 auto envBox =
337 new TGeoBBox("MTC_env", fWidth / 2, fHeight / 2, totalLength / 2);
338 auto envVol = new TGeoVolume("MTC", envBox, air);
339 envVol->SetLineColor(kGreen);
340 envVol->SetTransparency(50);
341
342 // --- Outer Iron Layer (gray) ---
343 auto ironBox =
344 new TGeoBBox("MTC_iron", fWidth / 2, fHeight / 2, fIronThick / 2);
345 auto ironVol = new TGeoVolume("MTC_iron", ironBox, ironMed);
346 ironVol->SetLineColor(kGray + 1);
347 ironVol->SetTransparency(20);
348 // Enable the field in the iron volume
349 if (fFieldY != 0) ironVol->SetField(new TGeoUniformMagField(0, fFieldY, 0));
350
351 // --- Assemble the layers into the envelope ---
352 TGeoVolumeAssembly* sensitiveModule = new TGeoVolumeAssembly("MTC_layer");
353 // Define a layer for the SciFi module
354 CreateSciFiModule("MTC", sensitiveModule, fWidth, fHeight, fSciFiThick, 1);
355 CreateScintModule("MTC", sensitiveModule, fSciFiThick / 2 + fScintThick / 2,
356 fWidth, fHeight, fScintThick, fScintCellSize,
357 fScintCellSize, scintMed, kAzure + 7, 30, 1);
358
359 for (Int_t i = 0; i < fLayers; i++) {
360 // Compute the center position (z) for the current module
361 Double_t zPos = -totalLength / 2 + i * moduleSpacing;
362
363 // Place the Outer Iron layer (shifted down by half the SciFi+scint
364 // thickness)
365 envVol->AddNode(ironVol, i,
366 new TGeoTranslation(0, 0, zPos + fIronThick / 2));
367 // Place the sensitive module (SciFi + Scintillator) at the correct z
368 // position
369 envVol->AddNode(
370 sensitiveModule, i,
371 new TGeoTranslation(0, 0, zPos + fIronThick + fSciFiThick / 2));
372 }
373
374 // Finally, add the envelope to the top volume with the global z offset
375 // fZCenter
376 gGeoManager->GetTopVolume()->AddNode(envVol, 1,
377 new TGeoTranslation(0, 0, fZCenter));
378}
379
380Bool_t MTCDetector::ProcessHits(FairVolume* vol) {
382 // Set parameters at entrance of volume. Reset ELoss.
383 if (gMC->IsTrackEntering()) {
384 fELoss = 0.;
385 fTime = gMC->TrackTime() * 1.0e09;
386 fLength = gMC->TrackLength();
387 gMC->TrackPosition(fPos);
388 gMC->TrackMomentum(fMom);
389 TGeoNavigator* nav = gGeoManager->GetCurrentNavigator();
390 Int_t vol_local_id = nav->GetCurrentNode()->GetNumber() %
391 1000000; // Local ID within the mat or scint.
392 Int_t layer_id = nav->GetMother(3)->GetNumber(); // Get layer ID.
393 fVolumeID = 100000000 + layer_id * 1000000 +
394 vol_local_id; // 1e8 + layer_id * 1e6 + fibre_local_id;
395 }
396 // Sum energy loss for all steps in the active volume
397 fELoss += gMC->Edep();
398
399 // Create vetoPoint when exiting active volume
400 if (gMC->IsTrackExiting() || gMC->IsTrackStop() ||
401 gMC->IsTrackDisappeared()) {
402 if (fELoss == 0.) {
403 return kFALSE;
404 } // if you do not want hits with zero eloss
405
406 TParticle* p = gMC->GetStack()->GetCurrentTrack();
407 fTrackID = gMC->GetStack()->GetCurrentTrackNumber();
408 Int_t pdgCode = p->GetPdgCode();
409 TLorentzVector Pos;
410 gMC->TrackPosition(Pos);
411 TLorentzVector Mom;
412 gMC->TrackMomentum(Mom);
413 Double_t x, y, z;
414 if (fVolumeID / 100000 == 3) {
415 x = (fPos.X() + Pos.X()) / 2.;
416 y = (fPos.Y() + Pos.Y()) / 2.;
417 z = (fPos.Z() + Pos.Z()) / 2.;
418 } else {
419 x = fPos.X();
420 y = fPos.Y();
421 z = (fPos.Z() + Pos.Z()) / 2.;
422 }
423
424 AddHit(fTrackID, fVolumeID, TVector3(x, y, z),
425 TVector3(fMom.Px(), fMom.Py(), fMom.Pz()), // entrance momentum
426 fTime, fLength, fELoss, pdgCode);
427 // hit->Print();
428 ShipStack* stack = dynamic_cast<ShipStack*>(gMC->GetStack());
429 stack->AddPoint(kMTC);
430 }
431 return kTRUE;
432}
433
435 if (gGeoManager->FindVolumeFast("SiPMmapVolU") ||
436 gGeoManager->FindVolumeFast("SiPMmapVolV")) {
437 return;
438 }
439 Double_t fLengthScifiMat = fSciFiActiveY;
440 Double_t fWidthChannel = fFiberPitch * fChannelAggregated;
441 fNSiPMChan = std::ceil(fWidth / fWidthChannel);
443 LOG(warn) << "Number of SiPM channels (" << fNSiPMChan
444 << ") exceeds maximum per SiPM (" << kMaxChannelsPerSiPM
445 << "), redistributing across multiple SiPMs";
446
447 fNSiPMs = static_cast<int>(
448 std::ceil(static_cast<double>(fNSiPMChan) / kMaxChannelsPerSiPM));
449
450 LOG(info) << "Increasing number of SiPMs up to " << fNSiPMs;
451
452 // define redistribution of channels among SiPMs
453 fNSiPMChan =
454 static_cast<int>(std::ceil(fNSiPMChan / static_cast<float>(fNSiPMs)));
455
456 LOG(info) << "New fNSiPMChan = " << fNSiPMChan;
457 }
458 Double_t fEdge = (fWidth - fNSiPMs * fNSiPMChan * fWidthChannel) / 2;
459 Double_t firstChannelX = -fWidth / 2;
460
461 LOG(info) << "SiPM Overlap parameters:\n"
462 << " fLengthScifiMat = " << fLengthScifiMat << " cm\n"
463 << " fWidthChannel = " << fWidthChannel << " cm\n"
464 << " fFiberPitch = " << fFiberPitch << " cm\n"
465 << " fChannelAggregated = " << fChannelAggregated << "\n"
466 << " fNSiPMChan = " << fNSiPMChan << "\n"
467 << " fNSiPMs = " << fNSiPMs << "\n"
468 << " fNMats = " << fNMats << "\n"
469 << " fEdge = " << fEdge << " cm\n"
470 << " firstChannelX = " << firstChannelX << " cm";
471 // Contains all plane SiPMs, defined for horizontal fiber plane
472 // To obtain SiPM map for vertical fiber plane rotate by 90 degrees around Z
473 TGeoVolumeAssembly* SiPMmapVolU = new TGeoVolumeAssembly("SiPMmapVolU");
474 TGeoVolumeAssembly* SiPMmapVolV = new TGeoVolumeAssembly("SiPMmapVolV");
475
476 TGeoBBox* ChannelVol_box = new TGeoBBox(
477 "ChannelVol", fWidthChannel / 2, fLengthScifiMat / 2, fiberMatThick / 2);
478 TGeoVolume* ChannelVol = new TGeoVolume("ChannelVol", ChannelVol_box,
479 gGeoManager->GetMedium("silicon"));
480 /*
481 Example of fiberID: 123051820, where:
482 - 1: MTC unique ID
483 - 23: layer number
484 - 0: station type (0 for +5 degrees, 1 for -5 degrees, 2 for scint plane)
485 - 5: z-layer number (0-5)
486 - 1820: local fibre ID within the station
487 Example of SiPM global channel (what is seen in the output file): 123001123,
488 where:
489 - 1: MTC unique ID
490 - 23: layer number
491 - 0: station type (0 for +5 degrees, 1 for -5 degrees)
492 - 0: mat number (only 0 by June 2025)
493 - 1: SiPM number (automatically assigned based on fibre aggregation
494 settings)
495 - 123: number of the SiPM channel (0-N). The channel number depends on the
496 fibre aggregation setting.
497 */
498
499 Double_t pos = fEdge + firstChannelX;
500 for (int imat = 0; imat < fNMats; imat++) {
501 for (int isipms = 0; isipms < fNSiPMs; isipms++) {
502 for (int ichannel = 0; ichannel < fNSiPMChan; ichannel++) {
503 // +5 degrees
504 SiPMmapVolU->AddNode(
505 ChannelVol, 100000 * 0 + imat * 10000 + isipms * 1000 + ichannel,
506 new TGeoTranslation(pos, 0, 0));
507 // -5 degrees
508 SiPMmapVolV->AddNode(
509 ChannelVol, 100000 * 1 + imat * 10000 + isipms * 1000 + ichannel,
510 new TGeoTranslation(-pos, 0, 0));
511 pos += fWidthChannel;
512 }
513 }
514 }
515}
516
517void MTCDetector::GetPosition(Int_t fDetectorID, TVector3& A, TVector3& B) {
518 /*
519 Example of fiberID: 123051820, where:
520 - 1: MTC unique ID
521 - 23: layer number
522 - 0: station type (0 for +5 degrees, 1 for -5 degrees, 2 for scint plane)
523 - 5: z-layer number (0-5)
524 - 1820: local fibre ID within the station
525 Example of SiPM global channel (what is seen in the output file): 123001123,
526 where:
527 - 1: MTC unique ID
528 - 23: layer number
529 - 0: station type (0 for +5 degrees, 1 for -5 degrees)
530 - 0: mat number (only 0 by June 2025)
531 - 1: SiPM number (automatically assigned based on fibre aggregation
532 settings)
533 - 123: number of the SiPM channel (0-N). The channel number depends on the
534 fibre aggregation setting.
535 */
536
537 Int_t station_number = static_cast<int>(fDetectorID / 1e6) % 100;
538 Int_t plane_type = static_cast<int>(fDetectorID / 1e5) %
539 10; // 0 for horizontal, 1 for vertical
540
541 TString sID, stationID;
542 sID.Form("%i", fDetectorID);
543 stationID.Form("%i", station_number);
544 // Basic hierarchy:
545 // /cave/MTC_1/MTC_layer_1/MTC_sciFi_mother_1/MTC_sciFi_epoxyMat_U_1/FiberVol_101010187
546 TString path =
547 "/cave/MTC_1/MTC_layer_" + stationID +
548 ((plane_type == 0) ? "/MTC_scifi_U_0/MTC_epoxyMat_0/FiberVol_1010"
549 : "/MTC_scifi_V_0/MTC_epoxyMat_0/FiberVol_1011");
550 path += sID(4, 5);
551 TGeoNavigator* nav = gGeoManager->GetCurrentNavigator();
552 nav->cd(path);
553 TGeoNode* W = nav->GetCurrentNode();
554 TGeoBBox* S = dynamic_cast<TGeoBBox*>(W->GetVolume()->GetShape());
555
556 Double_t top[3] = {0, 0, (S->GetDZ())};
557 Double_t bot[3] = {0, 0, -(S->GetDZ())};
558 Double_t Gtop[3], Gbot[3];
559 nav->LocalToMaster(top, Gtop);
560 nav->LocalToMaster(bot, Gbot);
561 A.SetXYZ(Gtop[0], Gtop[1], Gtop[2]);
562 B.SetXYZ(Gbot[0], Gbot[1], Gbot[2]);
563}
564
565TVector3 MTCDetector::GetLocalPos(Int_t fDetectorID, TVector3* glob) {
566 Int_t station_number = static_cast<int>(fDetectorID / 1e6) % 100;
567 Int_t plane_type = static_cast<int>(fDetectorID / 1e5) %
568 10; // 0 for horizontal, 1 for vertical
569
570 TString sID, stationID;
571 sID.Form("%i", fDetectorID);
572 stationID.Form("%i", station_number);
573 // Basic hierarchy:
574 // /cave/MTC_1/MTC_layer_1/MTC_sciFi_mother_1/MTC_sciFi_epoxyMat_U_1/FiberVol_101010187
575 TString path = "/cave/MTC_1/MTC_layer_" + stationID +
576 ((plane_type == 0) ? "/MTC_scifi_U_0" : "/MTC_scifi_V_0");
577 TGeoNavigator* nav = gGeoManager->GetCurrentNavigator();
578 nav->cd(path);
579 Double_t aglob[3];
580 Double_t aloc[3];
581 glob->GetXYZ(aglob);
582 nav->MasterToLocal(aglob, aloc);
583 return TVector3(aloc[0], aloc[1], aloc[2]);
584}
585
586void MTCDetector::GetSiPMPosition(Int_t SiPMChan, TVector3& A, TVector3& B) {
587 /* STMRFFF
588 Example of fiberID: 123051820, where:
589 - 1: MTC unique ID
590 - 23: layer number
591 - 0: station type (0 for +5 degrees, 1 for -5 degrees, 2 for scint
592 plane)
593 - 5: z-layer number (0-5)
594 - 1820: local fibre ID within the station
595 Example of SiPM global channel (what is seen in the output file):
596 123001123, where:
597 - 1: MTC unique ID
598 - 23: layer number
599 - 0: station type (0 for +5 degrees, 1 for -5 degrees)
600 - 0: mat number (only 0 by June 2025)
601 - 1: SiPM number (automatically assigned based on fibre aggregation
602 settings)
603 - 123: number of the SiPM channel (0-N). The channel number depends on
604 the fibre aggregation setting.
605 */
606 Int_t locNumber = SiPMChan % 1000000;
607 Int_t station_number = static_cast<int>(SiPMChan / 1e6) % 100;
608 Int_t plane_type = static_cast<int>(SiPMChan / 1e5) %
609 10; // 0 for horizontal, 1 for vertical
610 Float_t locPosition;
611 locPosition =
612 (plane_type == 0 ? SiPMPos_U
613 : SiPMPos_V)[locNumber]; // local position in U/V plane
614 TString stationID;
615 stationID.Form("%i", station_number);
616
617 Double_t loc[3] = {0, 0, 0};
618 TString path = "/cave/MTC_1/MTC_layer_" + stationID +
619 ((plane_type == 0) ? "/MTC_scifi_U_0/MTC_epoxyMat_0"
620 : "/MTC_scifi_V_0/MTC_epoxyMat_0");
621 TGeoNavigator* nav = gGeoManager->GetCurrentNavigator();
622 Double_t glob[3] = {0, 0, 0};
623 loc[0] = locPosition;
624 loc[1] = -fFiberLength / 2;
625 loc[2] = 7.47;
626 nav->cd(path);
627 nav->LocalToMaster(loc, glob);
628 A.SetXYZ(glob[0], glob[1], glob[2]);
629 loc[0] = locPosition;
630 loc[1] = fFiberLength / 2;
631 loc[2] = 7.47; // hardcoded for now, for some reason required to get the
632 // correct local position
633 nav->LocalToMaster(loc, glob);
634 B.SetXYZ(glob[0], glob[1], glob[2]);
635}
636
638 // check if containers are already filled
639 if (!fibresSiPM_U.empty() || !fibresSiPM_V.empty() || !SiPMPos_U.empty() ||
640 !SiPMPos_V.empty()) {
641 LOG(warning) << "SiPM mapping already done, skipping.";
642 return;
643 }
644 Float_t fibresRadius = -1;
645 Float_t dSiPM = -1;
646 TGeoNode* vol;
647 TGeoNode* fibre;
648 SiPMOverlap();
649 // Loop over both U and V planes
650 std::vector<std::pair<const char*, const char*>> sipm_planes = {
651 {"SiPMmapVolU", "MTC_scifi_U"}, {"SiPMmapVolV", "MTC_scifi_V"}};
652 for (const auto& [sipm_vol, scifi_vol] : sipm_planes) {
653 auto sipm = gGeoManager->FindVolumeFast(sipm_vol);
654 if (!sipm) continue;
655 TObjArray* Nodes = sipm->GetNodes();
656 auto plane = gGeoManager->FindVolumeFast(scifi_vol);
657 if (!plane) continue;
658 for (int imat = 0; imat < plane->GetNodes()->GetEntries(); imat++) {
659 auto mat = static_cast<TGeoNode*>(plane->GetNodes()->At(imat));
660 auto vmat = mat->GetVolume();
661 for (int ifibre = 0; ifibre < vmat->GetNodes()->GetEntriesFast();
662 ifibre++) {
663 fibre = static_cast<TGeoNode*>(vmat->GetNodes()->At(ifibre));
664 if (fibresRadius < 0) {
665 auto tmp = fibre->GetVolume()->GetShape();
666 auto S = dynamic_cast<TGeoBBox*>(tmp);
667 fibresRadius = S->GetDX();
668 }
669 Int_t fID =
670 fibre->GetNumber() % 1000000 +
671 imat * 1e4; // local fibre number, global fibre number = SO+fID
672 TVector3 Atop, Bbot;
673 GetPosition(fibre->GetNumber(), Atop, Bbot);
674 Float_t a = Bbot[0];
675
676 // check for overlap with any of the SiPM channels in the same mat
677 for (Int_t nChan = 0; nChan < Nodes->GetEntriesFast();
678 nChan++) { // 7 SiPMs total times 128 channels
679 vol = static_cast<TGeoNode*>(Nodes->At(nChan));
680 Int_t N = vol->GetNumber();
681 Float_t xcentre = vol->GetMatrix()->GetTranslation()[0];
682 if (dSiPM < 0) {
683 TGeoBBox* B = dynamic_cast<TGeoBBox*>(vol->GetVolume()->GetShape());
684 dSiPM = B->GetDX();
685 }
686
687 if (TMath::Abs(xcentre - a) > 3 * dSiPM / 2) {
688 continue;
689 } // no need to check further
690 Float_t W = area(a, fibresRadius, xcentre - dSiPM, xcentre + dSiPM);
691 if (W < 0) {
692 continue;
693 }
694 std::array<float, 2> Wa;
695 Wa[0] = W;
696 Wa[1] = a;
697
698 if (sipm_vol == std::string("SiPMmapVolU")) {
699 fibresSiPM_U[N][fID] = Wa;
700 } else {
701 fibresSiPM_V[N][fID] = Wa;
702 }
703 }
704 }
705 }
706 // calculate also local SiPM positions based on fibre positions and their
707 // fraction probably an overkill, maximum difference between weighted
708 // average and central position < 6 micron.
709 if (sipm_vol == std::string("SiPMmapVolU")) {
710 for (auto [N, it] : fibresSiPM_U) {
711 Float_t m = 0;
712 Float_t w = 0;
713 for (auto [current_fibre, Wa] : it) {
714 m += Wa[0] * Wa[1];
715 w += Wa[0];
716 }
717 SiPMPos_U[N] = m / w;
718 }
719 // make inverse mapping, which fibre is associated to which SiPMs
720 for (auto [N, it] : fibresSiPM_U) {
721 for (auto [nfibre, Wa] : it) {
722 siPMFibres_U[nfibre][N] = Wa;
723 }
724 }
725 } else if (sipm_vol == std::string("SiPMmapVolV")) {
726 for (auto [N, it] : fibresSiPM_V) {
727 Float_t m = 0;
728 Float_t w = 0;
729 for (auto [current_fibre, Wa] : it) {
730 m += Wa[0] * Wa[1];
731 w += Wa[0];
732 }
733 SiPMPos_V[N] = m / w;
734 }
735 // make inverse mapping, which fibre is associated to which SiPMs
736 for (auto [N, it] : fibresSiPM_V) {
737 for (auto [nfibre, Wa] : it) {
738 siPMFibres_V[nfibre][N] = Wa;
739 }
740 }
741 }
742 }
743}
Double_t m
@ kMTC
Definition: diagrams_a.h:3
Definition: diagrams_b.h:4
Double_t fIronThick
Definition: MTCDetector.h:78
std::map< Int_t, std::map< Int_t, std::array< float, 2 > > > siPMFibres_V
mapping of fibres to SiPM channels
Definition: MTCDetector.h:114
std::map< Int_t, std::map< Int_t, std::array< float, 2 > > > fibresSiPM_V
inverse mapping
Definition: MTCDetector.h:112
Double_t fSciFiBendingAngle
Definition: MTCDetector.h:77
std::map< Int_t, std::map< Int_t, std::array< float, 2 > > > fibresSiPM_U
Definition: MTCDetector.h:108
static constexpr Int_t kMaxChannelsPerSiPM
Definition: MTCDetector.h:104
Double_t fFiberLength
Definition: MTCDetector.h:87
void SiPMmapping()
Int_t fChannelAggregated
Definition: MTCDetector.h:102
Double_t fWidth
Definition: MTCDetector.h:73
void ConstructGeometry() override
Int_t numFiberLayers
Definition: MTCDetector.h:100
Double_t fSciFiThick
Definition: MTCDetector.h:79
Double_t fHeight
Definition: MTCDetector.h:74
Double_t upperIronThick
Definition: MTCDetector.h:93
TVector3 GetLocalPos(Int_t fDetectorID, TVector3 *glob)
Double_t fScintCellSize
Definition: MTCDetector.h:81
void SetMTCParameters(Double_t width, Double_t height, Double_t fiber_tilt_angle, Double_t iron_thickness, Double_t scifi_thickness, Int_t num_of_agg_channels, Double_t scint_cell_size, Double_t scint_thickness, Int_t number_of_layers, Double_t z_position, Double_t field_strength)
Double_t zUpperIronInt
Definition: MTCDetector.h:98
virtual void CreateScintModule(const char *name, TGeoVolumeAssembly *modMotherVol, Double_t z_shift, Double_t width, Double_t height, Double_t thickness, Double_t cellSizeX, Double_t cellSizeY, TGeoMedium *material, Int_t color, Double_t transparency, Int_t LayerId)
Int_t fNSiPMs
Definition: MTCDetector.h:103
virtual void CreateSciFiModule(const char *name, TGeoVolumeAssembly *modMotherVol, Double_t width, Double_t height, Double_t thickness, Int_t LayerId)
Double_t lowerIronThick
Definition: MTCDetector.h:91
Double_t fiberMatThick
Definition: MTCDetector.h:86
Double_t fSciFiActiveY
Definition: MTCDetector.h:76
Double_t fScintThick
Definition: MTCDetector.h:80
Double_t zLowerIronInt
Definition: MTCDetector.h:94
Double_t fZCenter
Definition: MTCDetector.h:83
void GetSiPMPosition(Int_t SiPMChan, TVector3 &A, TVector3 &B)
Int_t fLayers
Definition: MTCDetector.h:82
Double_t fFieldY
Definition: MTCDetector.h:84
Double_t fFiberRadius
Definition: MTCDetector.h:99
Double_t fSciFiActiveX
Definition: MTCDetector.h:75
std::map< Int_t, float > SiPMPos_U
inverse mapping
Definition: MTCDetector.h:115
Double_t zFiberMat2
Definition: MTCDetector.h:97
Int_t fNSiPMChan
Definition: MTCDetector.h:101
Double_t fFiberPitch
Definition: MTCDetector.h:88
Int_t fNMats
Definition: MTCDetector.h:106
Double_t zFiberMat1
Definition: MTCDetector.h:95
Bool_t ProcessHits(FairVolume *vol=0) override
std::map< Int_t, float > SiPMPos_V
Definition: MTCDetector.h:115
virtual void SiPMOverlap()
std::map< Int_t, std::map< Int_t, std::array< float, 2 > > > siPMFibres_U
mapping of fibres to SiPM channels
Definition: MTCDetector.h:110
void GetPosition(Int_t fDetectorID, TVector3 &vLeft, TVector3 &vRight)
Int_t fTrackID
event index
Definition: Detector.h:85
Int_t fVolumeID
track index
Definition: Detector.h:86
TLorentzVector fPos
volume id
Definition: Detector.h:87
Double_t fTime
momentum at entrance
Definition: Detector.h:89
MTCDetPoint * AddHit(Args &&... args)
Definition: Detector.h:38
TLorentzVector fMom
position at entrance
Definition: Detector.h:88
Int_t InitMedium(const char *name)
Definition: ShipGeoUtil.h:20