Skip to content

Commit 9e0fda6

Browse files
[ALICE3] MI3: Add superconducting magnet/cryostat geometry to ALICE3 MID simulation (#15585)
* [MID] Add superconducting magnet/cryostat geometry to simulation * Apply clang-format * [MI3] Add superconducting magnet/cryostat geometry to ALICE3 MID simulation * [ALICE3] Magnet: add WindingPack (NbTi+Cu+Al) coil material option * [ALICE3] Magnet: improve WindingPack material documentation * ICN-UNAM: IanMagnet, IanAbsorber, ICNStepped MID layout — 0 overlaps verified * ICN-UNAM: IanAbsorber — Geometria 6 (Antonio): cara externa plana Rmax=290, escalon hacia adentro * ICN-UNAM: cleanup diagnostic instrumentation and translate comments to English * ICN-UNAM: fix hit production — simple sensor names for VMC resolution and reset mHitStarted on Reset() * ICN-UNAM: fix physLay in ProcessHits — extract layer index from sensor name; remove ITS TrackReference boilerplate; guard against invalid physLay * ICN-UNAM: document CurrentVolOffID limitation; full sensor location available via sensor name pattern * ICN-UNAM: IanAbsorber — correct external Rmin to 245 cm (45 cm thickness per Ian reference code) * ICN-UNAM: rename enum values — IanMagnet->SuperconductingMagnet, IanAbsorber->SteppedAbsorber, ICNStepped->SteppedLayout * ICN-UNAM: fix layer 1 half-length to 525 cm (Ian/SD Table 16); revert absorber to Geometry 6 (Rext=290 const, step in Rmin) * ICN-UNAM: fix ALICE3Field.C — B1 in numerator, Rc=170 cm (R_out_coil per Ian; confirmed by A. Ortiz Aug 2026) * Refactor field function and improve readability Refactored field function to use static constexpr for constants and improved readability. Updated condition checks to use std::abs. * Refactor magnetic field calculation and conditions Refactor magnetic field calculation by defining variables for Rc, R1, R2, B1, and B2. Update conditions for magnetic field assignment based on radius and beam start position. * Fix formatting of B1 assignment in ALICE3Field.C * Refactor member initializer list formatting * Refactor magnet and detector layout enums * Refactor absorber geometry initialization logic * Refactor magnet layout handling in Magnet.cxx * Refactor constants and improve code structure Refactor constants and improve readability in Detector.cxx. --------- Co-authored-by: Nicolò Jacazio <njacazio@users.noreply.github.com>
1 parent e0fefa5 commit 9e0fda6

8 files changed

Lines changed: 194 additions & 77 deletions

File tree

Detectors/Upgrades/ALICE3/MID/base/include/MI3Base/MI3BaseParam.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ namespace mi3
2626

2727
enum MIDLayout : int {
2828
StandardRadius = 0,
29-
ReducedRadius = 1
29+
ReducedRadius = 1,
30+
SteppedLayout = 2
3031
};
3132

3233
struct MIDBaseParam : public o2::conf::ConfigurableParamHelper<MIDBaseParam> {

Detectors/Upgrades/ALICE3/MID/simulation/include/MI3Simulation/MIDLayer.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ class MIDLayer
9292
float staveLength = 500.f,
9393
float staveWidth = 50.f,
9494
float staveThickness = 0.5f,
95-
int nModulesZ = 10);
95+
int nModulesZ = 10,
96+
int nBars = -1);
9697
void createStave(TGeoVolume* motherVolume);
9798

9899
private:
@@ -110,16 +111,20 @@ class MIDLayer
110111

111112
public:
112113
MIDLayer() = default;
113-
MIDLayer(int layerNumber, std::string layerName, float rInn, float length, int nstaves = 16);
114+
MIDLayer(int layerNumber, std::string layerName, float rInn, float length, int nstaves = 16, float zOffset = 0.f, int nModulesZ = 10, float staveWidth = -1.f, int nBars = -1);
114115
void createLayer(TGeoVolume* motherVolume);
115116

116117
private:
117118
std::string mName;
118119
std::vector<Stave> mStaves;
119120
float mRadius;
120121
float mLength;
122+
float mZOffset;
123+
float mStaveWidth;
121124
int mNumber;
122125
int mNStaves;
126+
int mNModulesZ;
127+
int mNBars;
123128
};
124129
} // namespace o2::mi3
125130

Detectors/Upgrades/ALICE3/MID/simulation/src/Detector.cxx

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "DetectorsBase/Stack.h"
2121
#include "ITSMFTSimulation/Hit.h"
2222
#include "MI3Simulation/Detector.h"
23+
#include <set>
2324
#include "MI3Base/MI3BaseParam.h"
2425

2526
using o2::itsmft::Hit;
@@ -92,7 +93,19 @@ void Detector::InitializeO2Detector()
9293
{
9394
LOG(info) << "Initialize MID O2Detector";
9495
mGeometryTGeo = GeometryTGeo::Instance();
95-
// defineSensitiveVolumes();
96+
// Register sensitive volumes
97+
TObjArray* allVols = gGeoManager->GetListOfVolumes();
98+
TString sensorPattern = GeometryTGeo::getMIDSensorPattern();
99+
std::set<TGeoVolume*> registered;
100+
for (int i = 0; i < allVols->GetEntries(); i++) {
101+
TGeoVolume* v = (TGeoVolume*)allVols->At(i);
102+
TString vname = v->GetName();
103+
if (vname.Contains(sensorPattern) && registered.find(v) == registered.end()) {
104+
AddSensitiveVolume(v);
105+
registered.insert(v);
106+
}
107+
}
108+
LOGP(info, "Total MI3 sensitive volumes registered: {}", registered.size());
96109
}
97110

98111
void Detector::EndOfEvent() { Reset(); }
@@ -125,14 +138,36 @@ void Detector::createGeometry()
125138
vMID->SetTitle(vstrng);
126139

127140
// Build the MID
128-
mLayers.resize(2);
129141
auto& midParam = MIDBaseParam::Instance();
130142
const bool standardRadius = (midParam.mLayout == o2::mi3::MIDLayout::StandardRadius);
131143

132144
if (standardRadius) {
145+
mLayers.resize(2);
133146
mLayers[0] = MIDLayer(0, GeometryTGeo::composeSymNameLayer(0), 301.f, 500.f);
134-
mLayers[1] = MIDLayer(1, GeometryTGeo::composeSymNameLayer(1), 311.f, 520.f); // arbitrarily reduced to get multiple of 5.2f
147+
mLayers[1] = MIDLayer(1, GeometryTGeo::composeSymNameLayer(1), 311.f, 525.f); // 10 modules x 52.5 cm = 525 cm — matches Ian ref. code and SD Table 16 (10.5 m)
148+
} else if (midParam.mLayout == o2::mi3::MIDLayout::SteppedLayout) {
149+
// Ian Perez Garcia design (ICN-UNAM) — tesis §3.4.7 Geometria 8
150+
// 11 cm gap from absorber outer face to MID layer
151+
// mLayer index is flat 0-5: even = physical layer 0, odd = physical layer 1
152+
// Module step: layer0=99.8cm (2x49.9), layer1=104cm (2x52=2xsumWidth)
153+
// Central segment: Rmax_abso=290 -> Layer0=301, Layer1=311, nMod=6, semi-dz=299.4/312 at Z=0
154+
// External segments: Rmax_abso=265 -> Layer0=276, Layer1=286, nMod=2, semi-dz=99.8/104 at Z=+-400
155+
constexpr float kAbsGap = 11.f;
156+
constexpr float kPitch = 10.f;
157+
constexpr float kRCen0 = 290.f + kAbsGap; // 301 cm
158+
constexpr float kRCen1 = kRCen0 + kPitch; // 311 cm
159+
constexpr float kRExt0 = 265.f + kAbsGap; // 276 cm
160+
constexpr float kRExt1 = kRExt0 + kPitch; // 286 cm
161+
mLayers.resize(6);
162+
// length = semi-length = nModulesZ x step (layer0: step=49.9cm, layer1: step=52cm)
163+
mLayers[0] = MIDLayer(0, "MIDLayer0_central", kRCen0, 299.4f, 16, 0.f, 6); // 6 modules x 49.9 cm step
164+
mLayers[1] = MIDLayer(1, "MIDLayer1_central", kRCen1, 312.f, 16, 0.f, 6); // 6 modules x 52 cm step
165+
mLayers[2] = MIDLayer(2, "MIDLayer0_forward", kRExt0, 99.8f, 16, +400.f, 2, -1.f, 21); // 2 modules x 49.9 cm step, nBars=21 for R=276 cm
166+
mLayers[3] = MIDLayer(3, "MIDLayer1_forward", kRExt1, 104.f, 16, +405.f, 2); // 2 modules x 52 cm step, +5 cm offset to clear absorber transition
167+
mLayers[4] = MIDLayer(4, "MIDLayer0_backward", kRExt0, 99.8f, 16, -400.f, 2, -1.f, 21); // 2 modules x 49.9 cm step, nBars=21 for R=276 cm
168+
mLayers[5] = MIDLayer(5, "MIDLayer1_backward", kRExt1, 104.f, 16, -405.f, 2); // 2 modules x 52 cm step, -5 cm offset to clear absorber transition
135169
} else {
170+
mLayers.resize(2);
136171
mLayers[0] = MIDLayer(0, GeometryTGeo::composeSymNameLayer(0), 266.f, 500.f);
137172
mLayers[1] = MIDLayer(1, GeometryTGeo::composeSymNameLayer(1), 276.f, 520.f);
138173
}
@@ -147,6 +182,7 @@ void Detector::Reset()
147182
if (!o2::utils::ShmManager::Instance().isOperational()) {
148183
mHits->clear();
149184
}
185+
mTrackData.mHitStarted = false;
150186
}
151187

152188
bool Detector::ProcessHits(FairVolume* vol)
@@ -159,13 +195,16 @@ bool Detector::ProcessHits(FairVolume* vol)
159195
int lay = vol->getVolumeId();
160196
int volID = vol->getMCid();
161197

162-
// Is it needed to keep a track reference when the outer ITS volume is encountered?
198+
// TrackReference block removed: ITS boilerplate whose condition (lay == 0
199+
// against a TGeo volume ID) never fired. No MID reconstruction consumes
200+
// MID track references at present.
163201
auto stack = (o2::data::Stack*)fMC->GetStack();
164-
if (fMC->IsTrackExiting() && (lay == 0)) {
165-
o2::TrackReference tr(*fMC, GetDetId());
166-
tr.setTrackID(stack->GetCurrentTrackNumber());
167-
tr.setUserId(lay);
168-
stack->addTrackReference(tr);
202+
// Extract physical layer index (0 or 1) from sensor name: MIDSensor_L<lay>_S...
203+
int physLay = -1;
204+
const char* volName = fMC->CurrentVolName();
205+
sscanf(volName, "MIDSensor_L%d", &physLay);
206+
if (physLay >= 0) {
207+
physLay = physLay % 2;
169208
}
170209
bool startHit = false, stopHit = false;
171210
unsigned char status = 0;
@@ -213,14 +252,17 @@ bool Detector::ProcessHits(FairVolume* vol)
213252
if (stopHit) {
214253
TLorentzVector positionStop;
215254
fMC->TrackPosition(positionStop);
216-
// Retrieve the indices with the volume path
217-
int stave(0), halfstave(0), chipinmodule(0), module;
218-
fMC->CurrentVolOffID(1, chipinmodule);
219-
fMC->CurrentVolOffID(2, module);
220-
fMC->CurrentVolOffID(3, halfstave);
221-
fMC->CurrentVolOffID(4, stave);
222-
223-
Hit* p = addHit(stack->GetCurrentTrackNumber(), lay, mTrackData.mPositionStart.Vect(), positionStop.Vect(),
255+
// CurrentVolOffID(1..4) yields copy numbers of module/halfstave/stave ancestors.
256+
// With TGeoVolumeAssembly nodes these are always 0 except the stave level.
257+
// Full sensor location (layer, stave, module, bar) is encoded in the sensor
258+
// name (MIDSensor_L<l>_S<s>_M<m>_B<b>) and can be decoded with sscanf if needed.
259+
// Left as future work for hit digitization.
260+
261+
if (physLay < 0) {
262+
LOGP(warn, "MID sensor name {} did not match expected pattern, cannot extract physical layer index", volName);
263+
return false;
264+
} // guard: sensor name did not match expected pattern
265+
Hit* p = addHit(stack->GetCurrentTrackNumber(), physLay, mTrackData.mPositionStart.Vect(), positionStop.Vect(),
224266
mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(),
225267
mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status);
226268
// p->SetTotalEnergy(vmc->Etot());
@@ -241,4 +283,4 @@ o2::itsmft::Hit* Detector::addHit(int trackID, int detID, const TVector3& startP
241283
return &(mHits->back());
242284
}
243285
} // namespace o2::mi3
244-
ClassImp(o2::mi3::Detector);
286+
ClassImp(o2::mi3::Detector);

Detectors/Upgrades/ALICE3/MID/simulation/src/MIDLayer.cxx

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,33 @@ MIDLayer::MIDLayer(int layerNumber,
2626
std::string layerName,
2727
float rInn,
2828
float length,
29-
int nstaves) : mName(layerName),
30-
mRadius(rInn),
31-
mLength(length),
32-
mNumber(layerNumber),
33-
mNStaves(nstaves)
29+
int nstaves,
30+
float zOffset,
31+
int nModulesZ,
32+
float staveWidth,
33+
int nBars) : mName(layerName),
34+
mRadius(rInn),
35+
mLength(length),
36+
mZOffset(zOffset),
37+
mStaveWidth(staveWidth),
38+
mNumber(layerNumber),
39+
mNStaves(nstaves),
40+
mNModulesZ(nModulesZ),
41+
mNBars(nBars)
3442
{
3543
mStaves.reserve(nstaves);
36-
LOGP(debug, "Constructing MIDLayer: {} with inner radius: {}, length: {} cm and {} staves", mName, mRadius, mLength, mNStaves);
44+
LOGP(debug, "Constructing MIDLayer: {} with inner radius: {}, length: {} cm, {} staves and {} modules/stave", mName, mRadius, mLength, mNStaves, mNModulesZ);
3745
for (int iStave = 0; iStave < mNStaves; ++iStave) {
3846
mStaves.emplace_back(GeometryTGeo::composeSymNameStave(layerNumber, iStave),
3947
mRadius,
4048
TMath::TwoPi() / (float)nstaves * iStave,
4149
mNumber,
4250
iStave,
4351
mLength,
44-
!layerNumber ? 59.8f : 61.75f,
45-
0.5f);
52+
!(layerNumber % 2) ? 59.8f : 61.75f,
53+
0.5f,
54+
mNModulesZ,
55+
mNBars);
4656
}
4757
}
4858

@@ -54,27 +64,31 @@ MIDLayer::Stave::Stave(std::string staveName,
5464
float staveLength,
5565
float staveWidth,
5666
float staveThickness,
57-
int nModulesZ) : mName(staveName),
58-
mRadDistance(radDistance),
59-
mRotAngle(rotAngle),
60-
mLength(staveLength),
61-
mWidth(staveWidth),
62-
mThickness(staveThickness),
63-
mLayer(layer),
64-
mNumber(number),
65-
mNModulesZ(nModulesZ)
67+
int nModulesZ,
68+
int nBars) : mName(staveName),
69+
mRadDistance(radDistance),
70+
mRotAngle(rotAngle),
71+
mLength(staveLength),
72+
mWidth(staveWidth),
73+
mThickness(staveThickness),
74+
mLayer(layer),
75+
mNumber(number),
76+
mNModulesZ(nModulesZ)
6677
{
78+
// nBars=-1 uses default calibrated for standard radii
79+
int effNBars = (nBars < 0) ? (!(mLayer % 2) ? 23 : 20) : nBars;
80+
float moduleOffset = -effNBars * 5.2f / 2.f; // 5.2 = 2*barWidth + barSpacing
6781
// Staves are ideal shapes made of air including the modules, for now.
68-
LOGP(debug, "\t\tConstructing MIDStave: {} layer: {} at angle {}", mName, mLayer, mRotAngle * TMath::RadToDeg());
82+
LOGP(debug, "\t\tConstructing MIDStave: {} layer: {} at angle {} nBars={}", mName, mLayer, mRotAngle * TMath::RadToDeg(), effNBars);
6983
mModules.reserve(nModulesZ);
7084
for (int iModule = 0; iModule < mNModulesZ; ++iModule) {
7185
mModules.emplace_back(GeometryTGeo::composeSymNameModule(mLayer, mNumber, iModule),
7286
mLayer,
7387
mNumber,
7488
iModule,
75-
!mLayer ? 23 : 20,
89+
effNBars,
7690
-staveLength,
77-
!mLayer ? 49.9f : 61.75f);
91+
!(mLayer % 2) ? 49.9f : 61.75f);
7892
}
7993
}
8094

@@ -106,8 +120,8 @@ MIDLayer::Stave::Module::Module(std::string moduleName,
106120
mStave,
107121
mNumber,
108122
iBar,
109-
!mLayer ? -59.8f : -52.f, // offset
110-
!mLayer ? 49.9f : 61.75f); // sensor length
123+
-mNBars * 5.2f / 2.f, // moduleOffset derived from nBars
124+
!(mLayer % 2) ? 49.9f : 61.75f); // sensor length
111125
}
112126
}
113127

@@ -136,9 +150,13 @@ MIDLayer::Stave::Module::Sensor::Sensor(std::string sensorName,
136150

137151
void MIDLayer::createLayer(TGeoVolume* motherVolume)
138152
{
139-
LOGP(debug, "Creating MIDLayer: {}", mName);
153+
LOGP(debug, "Creating MIDLayer: {} at zOffset={} cm", mName, mZOffset);
140154
TGeoVolumeAssembly* layerVolume = new TGeoVolumeAssembly(mName.c_str());
141-
motherVolume->AddNode(layerVolume, 0);
155+
if (mZOffset != 0.f) {
156+
motherVolume->AddNode(layerVolume, 0, new TGeoTranslation(0, 0, mZOffset));
157+
} else {
158+
motherVolume->AddNode(layerVolume, 0);
159+
}
142160
for (auto& stave : mStaves) {
143161
stave.createStave(layerVolume);
144162
}
@@ -172,7 +190,7 @@ void MIDLayer::Stave::Module::createModule(TGeoVolume* motherVolume)
172190
sensor.createSensor(moduleVolume);
173191
}
174192
TGeoCombiTrans* modTrans = nullptr;
175-
if (!mLayer) {
193+
if (!(mLayer % 2)) {
176194
modTrans = new TGeoCombiTrans(0, 0, mZOffset + mNumber * 2 * mBarLength + mBarLength, nullptr);
177195
} else {
178196
modTrans = new TGeoCombiTrans(0, 0, mZOffset + mNumber * 2 * sumWidth + sumWidth, nullptr);
@@ -184,17 +202,19 @@ void MIDLayer::Stave::Module::Sensor::createSensor(TGeoVolume* motherVolume)
184202
{
185203
LOGP(debug, "\t\t\t\tCreating MIDSensor: {}", mName);
186204
TGeoBBox* sensor = nullptr;
187-
if (!mLayer) {
205+
if (!(mLayer % 2)) {
188206
sensor = new TGeoBBox(mName.c_str(), mWidth, mThickness, mLength);
189207
} else {
190208
sensor = new TGeoBBox(mName.c_str(), mLength, mThickness, mWidth);
191209
}
192210
auto* polyMed = gGeoManager->GetMedium("MI3_POLYSTYRENE");
193-
TGeoVolume* sensorVolume = new TGeoVolume(mName.c_str(), sensor, polyMed);
211+
// Simple unique name without slashes so gMC->VolId() resolves correctly during stepping
212+
auto volName = Form("MIDSensor_L%d_S%d_M%d_B%d", mLayer, mStave, mNumber, mNumber);
213+
TGeoVolume* sensorVolume = new TGeoVolume(volName, sensor, polyMed);
194214
sensorVolume->SetVisibility(true);
195215
auto totWidth = mWidth + mSpacing / 2;
196216
TGeoTranslation* sensorTrans = nullptr;
197-
if (!mLayer) {
217+
if (!(mLayer % 2)) {
198218
sensorTrans = new TGeoTranslation(mModuleOffset + 2 * totWidth * mNumber + totWidth, 0, 0);
199219
sensorVolume->SetLineColor(kAzure + 4);
200220
sensorVolume->SetTransparency(50);
@@ -205,4 +225,4 @@ void MIDLayer::Stave::Module::Sensor::createSensor(TGeoVolume* motherVolume)
205225
}
206226
motherVolume->AddNode(sensorVolume, 0, sensorTrans);
207227
}
208-
} // namespace o2::mi3
228+
} // namespace o2::mi3

Detectors/Upgrades/ALICE3/Passive/include/Alice3DetectorsPassive/PassiveBaseParam.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,24 @@ namespace passive
2424
// ** Parameters for Passive base configuration
2525
// **
2626

27-
enum MagnetLayout : int {
28-
AluminiumStabilizer = 0,
29-
CopperStabilizer = 1
27+
enum MagnetType : int {
28+
AluminiumStabilizer = 0, // Using Aluminium stabilizer for the magnet
29+
CopperStabilizer = 1, // Using Copper stabilizer for the magnet
30+
WindingPack = 2, // Using Winding Pack for the magnet
31+
SuperconductingMagnet = 3 // Using Superconducting magnet (NbTi+Cu+Al) for the magnet
3032
};
3133

32-
enum DetLayout : int {
33-
StandardRadius = 0,
34-
ReducedRadius = 1
34+
enum MagnetAbsorberLayout : int {
35+
StandardRadius = 0, // Using standard radius for the magnet and absorber
36+
ReducedRadius = 1, // Using reduced radius for the magnet and absorber
37+
SteppedAbsorber = 2 // Using stepped absorber for the magnet and absorber
3538
};
3639

3740
struct Alice3PassiveBaseParam : public o2::conf::ConfigurableParamHelper<Alice3PassiveBaseParam> {
3841
// Geometry Builder parameters
3942

40-
int mLayout = MagnetLayout::AluminiumStabilizer;
41-
int mDetLayout = DetLayout::StandardRadius;
43+
MagnetType mMagType = MagnetType::AluminiumStabilizer; // Magnet type: as in MagnetType enum
44+
MagnetAbsorberLayout mMagAbsLayout = o2::passive::MagnetAbsorberLayout::SteppedAbsorber; // Magnet and absorber layout: as in MagnetAbsorberLayout enum
4245

4346
O2ParamDef(Alice3PassiveBaseParam, "Alice3PassiveBase");
4447
};

0 commit comments

Comments
 (0)