FairShip
Loading...
Searching...
No Matches
geometry_config.py
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 Collaboration
3
4import os
5
6import shipunit as u
7import yaml
8from ShipGeoConfig import AttrDict, Config
9
10# Parameters for geometry configuration are passed to create_config() function
11# nuTargetPassive = 1 #0 = with active layers, 1 = only passive
12
13# targetOpt = 5 # 0=solid >0 sliced, 5: 5 pieces of tungsten, 4 air slits, 17: molybdenum tungsten interleaved with H20
14# strawOpt = 0 # 4=aluminium frame 10=steel frame (default)
15
16# Here you can select the MS geometry, if the MS design is using SC magnet change the hybrid to True
17# The first row is the length of the magnets
18# The other rows are the transverse dimensions of the magnets: dXIn[i], dXOut[i] , dYIn[i], dYOut[i], gapIn[i], gapOut[i].
19shield_db = {
20 "TRY_2025": {
21 "hybrid": False,
22 "WithConstField": False,
23 "params": [
24 [
25 0,
26 115.5,
27 50.00,
28 50.00,
29 119.00,
30 119.00,
31 2.00,
32 2.00,
33 1.00,
34 1.00,
35 50.00,
36 50.00,
37 0.00,
38 0.00,
39 1.90,
40 ],
41 [
42 20,
43 495.00,
44 67.10,
45 79.92,
46 27.00,
47 43.00,
48 5.00,
49 5.00,
50 1.38,
51 1.06,
52 67.10,
53 79.92,
54 0.00,
55 0.00,
56 1.90,
57 ],
58 [
59 10,
60 280.48,
61 53.12,
62 49.56,
63 43.00,
64 56.00,
65 5.03,
66 5.00,
67 2.11,
68 2.40,
69 53.12,
70 49.56,
71 0.00,
72 0.00,
73 1.90,
74 ],
75 [
76 10,
77 232.53,
78 2.73,
79 3.68,
80 56.00,
81 56.00,
82 5.00,
83 5.21,
84 60.44,
85 45.63,
86 2.73,
87 3.68,
88 0.50,
89 0.50,
90 -1.91,
91 ],
92 [
93 10,
94 85.00,
95 31.00,
96 107.12,
97 56.00,
98 56.00,
99 5.27,
100 5.00,
101 4.55,
102 0.63,
103 1.00,
104 77.12,
105 0.00,
106 0.00,
107 -1.91,
108 ],
109 [
110 10,
111 233.82,
112 30.03,
113 40.00,
114 56.00,
115 56.00,
116 5.00,
117 5.01,
118 4.83,
119 3.37,
120 30.03,
121 40.00,
122 0.00,
123 0.00,
124 -1.91,
125 ],
126 ],
127 },
128}
129
130
132 DecayVolumeMedium: str = "helium",
133 Yheight: float = 6.0,
134 strawDesign: int = 10,
135 muShieldGeo=None,
136 shieldName: str = "New_HA_Design",
137 nuTargetPassive: int = 1,
138 SND: bool = True,
139 SND_design=None,
140 TARGET_YAML=None,
141):
142 """
143 Create geometry configuration with specified parameters.
144
145 Args:
146 DecayVolumeMedium: Medium in decay volume ("helium" or "vacuums"), default: "helium"
147 Yheight: Height of vacuum tank in meters, default: 6.0
148 strawDesign: Straw tube design (4=aluminium frame, 10=steel frame), default: 10
149 muShieldGeo: Muon shield geometry file (for experts), default: None
150 shieldName: Name of shield configuration ("warm_opt" or "New_HA_Design"), default: "New_HA_Design"
151 nuTargetPassive: Target type (0=with active layers, 1=only passive), default: 1
152 SND: Enable SND detector, default: True
153 SND_design: SND design options (list of design numbers), default: [2]
154 TARGET_YAML: Path to target YAML configuration file, default: "$FAIRSHIP/geometry/target_config.yaml"
155
156 Returns:
157 Config: Geometry configuration object
158 """
159 # Set defaults for mutable parameters
160 if SND_design is None:
161 SND_design = [2]
162 if TARGET_YAML is None:
163 TARGET_YAML = os.path.expandvars("$FAIRSHIP/geometry/target_config.yaml")
164
165 # Create configuration object
166 c = Config()
167 c.DecayVolumeMedium = DecayVolumeMedium
168 c.SND = SND
169 # Ensure SND_design is always a list
170 if not isinstance(SND_design, list):
171 SND_design = [SND_design]
172 c.SND_design = SND_design
173 c.target_yaml = TARGET_YAML
174 print("Info: Target using configuration:", c.target_yaml)
175
176 if not shieldName:
177 raise ValueError("shieldName must not be empty!")
178
179 c.shieldName = shieldName
180 c.SC_mag = shield_db[shieldName]["hybrid"]
181
182 # global strawDesign, Yheight
183 c.Yheight = Yheight * u.m
184 extraVesselLength = 10 * u.m
185 windowBulge = 25 * u.cm
186 c.strawDesign = strawDesign
187 c.magnetDesign = 4
188 # cave parameters
189 c.cave = AttrDict()
190 c.cave.floorHeightMuonShield = 5 * u.m
191 c.cave.floorHeightTankA = 4.2 * u.m
192 if strawDesign == 10:
193 c.cave.floorHeightMuonShield = c.cave.floorHeightTankA # avoid the gap, for 2018 geometry
194 c.cave.floorHeightTankB = 2 * u.m
195
196 with open(c.target_yaml) as file:
197 targetconfig = yaml.safe_load(file)
198 c.target = AttrDict(targetconfig["target"])
199
200 c.target.slices_length = []
201 c.target.slices_gap = []
202 c.target.slices_material = []
203
204 for i in range(c.target.Nplates):
205 for j in range(c.target.N[i]):
206 if len(c.target.L) == 1:
207 c.target.slices_length.append(c.target.L[0])
208 else:
209 c.target.slices_length.append(c.target.L[i])
210 if len(c.target.G) == 1:
211 c.target.slices_gap.append(c.target.G[0])
212 else:
213 c.target.slices_gap.append(c.target.G[i])
214 if len(c.target.M) == 1:
215 c.target.slices_material.append(c.target.M[0])
216 else:
217 c.target.slices_material.append(c.target.M[i])
218 # Last gap should be 0...
219 c.target.slices_gap[c.target.nS - 1] = 0
220 print(c.target.slices_material, c.target.slices_length, c.target.slices_gap)
221
222 target_length = 0
223 for width, gap in zip(c.target.slices_length, c.target.slices_gap):
224 target_length += width + gap
225 c.target.length = target_length
226 # interaction point, start of target
227
228 c.target.z0 = 0 # Origin of SHiP coordinate system
229 c.target.z = c.target.z0 + c.target.length / 2.0
230
231 c.hadronAbsorber = AttrDict()
232
233 c.hadronAbsorber.z = (
234 c.target.z0
235 + c.target.length
236 + 96.1 * u.mm # Distance between target and proximity shielding
237 + 250 * u.mm # Thickness of proximity shielding
238 + 207.5 * u.mm # Distance between hadron absorber and proximity shielding
239 - 10 * u.cm # Remove spacing internal to hadron absorber
240 )
241
242 # DEFINITION OF THE MUON SHIELD
243 c.muShield = AttrDict()
244
245 c.muShield.z = c.hadronAbsorber.z
246
247 params = shield_db[shieldName]["params"]
248 c.muShield.params = params
249
250 # MS length
251 c.muShield.length = sum(line[0] + line[1] * 2 for line in params)
252 c.muShield.nMagnets = len(params)
253
254 c.muShield.Zgap = []
255 c.muShield.half_length = []
256 c.muShield.Entrance = []
257
258 for line in params:
259 c.muShield.Zgap.append(line[0])
260 c.muShield.half_length.append(line[1])
261
262 # Compute Z position for each magnet
263 for i in range(len(c.muShield.Zgap)):
264 if i == 0:
265 # First magnet uses the initial offset
266 c.muShield.Entrance.append(c.muShield.z + c.muShield.Zgap[i])
267 else:
268 # Subsequent magnets are placed relative to the previous one
269 c.muShield.Entrance.append(
270 c.muShield.Entrance[i - 1] + c.muShield.half_length[i - 1] * 2 + c.muShield.Zgap[i]
271 )
272 # Flatten the params list
273 c.muShield.params = [item for sublist in params for item in sublist]
274
275 c.decayVolume = AttrDict()
276
277 # target absorber muon shield setup, decayVolume.length = nominal EOI length, only kept to define z=0
278 c.decayVolume.length = 50 * u.m
279
280 # make z coordinates for the decay volume and tracking stations relative to T4z
281 # eventually, the only parameter which needs to be changed when the active shielding length changes.
282 c.z = 89.57 * u.m # absolute position of spectrometer magnet
283 c.decayVolume.z = c.z - 31.450 * u.m # Relative position of decay vessel centre to spectrometer magnet
284 c.decayVolume.z0 = c.decayVolume.z - c.decayVolume.length / 2.0
285
286 c.chambers = AttrDict()
287 magnetIncrease = 100.0 * u.cm
288
289 if strawDesign != 4 and strawDesign != 10:
290 raise ValueError(f"straw design {strawDesign} is not supported, use strawDesign = 4 or 10")
291 else:
292 c.chambers.Tub1length = 2.5 * u.m
293 c.chambers.Tub2length = 17.68 * u.m + extraVesselLength / 2.0
294 c.chambers.Tub3length = 0.8 * u.m
295 c.chambers.Tub4length = 2.0 * u.m + magnetIncrease / 2.0
296 c.chambers.Tub5length = 0.8 * u.m
297 c.chambers.Tub6length = 0.1 * u.m + windowBulge / 2.0
298 c.chambers.Rmin = 245.0 * u.cm
299 c.chambers.Rmax = 250.0 * u.cm
300
301 c.xMax = 2 * u.m # max horizontal width at T4
302 TrGap = 2 * u.m # Distance between Tr1/2 and Tr3/4
303 TrMagGap = 3.5 * u.m # Distance from spectrometer magnet centre to the next tracking stations
304 #
305 z4 = c.z + TrMagGap + TrGap
306 c.TrackStation4 = AttrDict(z=z4)
307 z3 = c.z + TrMagGap
308 c.TrackStation3 = AttrDict(z=z3)
309 z2 = c.z - TrMagGap
310 c.TrackStation2 = AttrDict(z=z2)
311 z1 = c.z - TrMagGap - TrGap
312 c.TrackStation1 = AttrDict(z=z1)
313
314 # positions and lengths of vacuum tube segments (for backward compatibility)
315 c.Chamber1 = AttrDict(z=z4 - 4666.0 * u.cm - magnetIncrease - extraVesselLength)
316 c.Chamber6 = AttrDict(z=z4 + 30.0 * u.cm + windowBulge / 2.0)
317
318 c.Bfield = AttrDict()
319 c.Bfield.z = c.z
320 c.Bfield.max = 0 # 1.4361*u.kilogauss # was 1.15 in EOI
321 c.Bfield.y = c.Yheight
322 c.Bfield.x = 2.4 * u.m
323 c.Bfield.fieldMap = "files/MainSpectrometerField.root"
324 if c.magnetDesign > 3: # MISIS design
325 c.Bfield.YokeWidth = 0.8 * u.m # full width 200.*cm
326 c.Bfield.YokeDepth = 1.4 * u.m # half length 200 *cm;
327 c.Bfield.CoilThick = 25.0 * u.cm # thickness
328 c.Bfield.x = 2.2 * u.m # half apertures
329 c.Bfield.y = 3.5 * u.m
330
331 # TimeDet
332 c.TimeDet = AttrDict()
333 c.TimeDet.dzBarRow = 1.2 * u.cm
334 c.TimeDet.dzBarCol = 2.4 * u.cm
335 c.TimeDet.zBar = 1 * u.cm
336 c.TimeDet.DZ = (c.TimeDet.dzBarRow + c.TimeDet.dzBarCol + c.TimeDet.zBar) / 2
337 c.TimeDet.DX = 225 * u.cm
338 c.TimeDet.DY = 325 * u.cm
339 c.TimeDet.z = (
340 37.800 * u.m - c.TimeDet.dzBarRow * 3 / 2 + c.decayVolume.z
341 ) # Relative position of first layer of timing detector to decay vessel centre
342
343 c.HcalOption = -1
344 c.EcalOption = 2
345
346 c.SplitCal = AttrDict()
347 c.SplitCal.ZStart = 38.450 * u.m + c.decayVolume.z # Relative start z of split cal to decay vessel centre
348 c.SplitCal.XMax = 4 * u.m / 2 # half length
349 c.SplitCal.YMax = 6 * u.m / 2 # half length
350 c.SplitCal.Empty = 0 * u.cm
351 c.SplitCal.BigGap = 100 * u.cm
352 c.SplitCal.ActiveECALThickness = 0.56 * u.cm
353 c.SplitCal.FilterECALThickness = 0.28 * u.cm # 0.56*u.cm 1.757*u.cm
354 c.SplitCal.FilterECALThickness_first = 0.28 * u.cm
355 c.SplitCal.ActiveHCALThickness = 90 * u.cm
356 c.SplitCal.FilterHCALThickness = 90 * u.cm
357 c.SplitCal.nECALSamplings = 50
358 c.SplitCal.nHCALSamplings = 0
359 c.SplitCal.ActiveHCAL = 0
360 c.SplitCal.FilterECALMaterial = 3 # 1=scintillator 2=Iron 3 = lead 4 =Argon
361 c.SplitCal.FilterHCALMaterial = 2
362 c.SplitCal.ActiveECALMaterial = 1
363 c.SplitCal.ActiveHCALMaterial = 1
364 c.SplitCal.ActiveECAL_gas_Thickness = 1.12 * u.cm
365 c.SplitCal.num_precision_layers = 1
366 c.SplitCal.first_precision_layer = 6
367 c.SplitCal.second_precision_layer = 10
368 c.SplitCal.third_precision_layer = 13
369 c.SplitCal.ActiveECAL_gas_gap = 10 * u.cm
370 c.SplitCal.NModulesInX = 2
371 c.SplitCal.NModulesInY = 3
372 c.SplitCal.NStripsPerModule = 50
373 c.SplitCal.StripHalfWidth = c.SplitCal.XMax / (c.SplitCal.NStripsPerModule * c.SplitCal.NModulesInX)
374 c.SplitCal.StripHalfLength = c.SplitCal.YMax / c.SplitCal.NModulesInY
375 c.SplitCal.SplitCalThickness = (
376 (c.SplitCal.FilterECALThickness_first - c.SplitCal.FilterECALThickness)
377 + (c.SplitCal.FilterECALThickness + c.SplitCal.ActiveECALThickness) * c.SplitCal.nECALSamplings
378 + c.SplitCal.BigGap
379 )
380
381 c.MuonStation0 = AttrDict(z=c.SplitCal.ZStart + 10 * u.cm + c.SplitCal.SplitCalThickness)
382
383 c.MuonStation1 = AttrDict(z=c.MuonStation0.z + 1 * u.m)
384 c.MuonStation2 = AttrDict(z=c.MuonStation0.z + 2 * u.m)
385 c.MuonStation3 = AttrDict(z=c.MuonStation0.z + 3 * u.m)
386
387 c.MuonFilter0 = AttrDict(z=c.MuonStation0.z + 50.0 * u.cm)
388 c.MuonFilter1 = AttrDict(z=c.MuonStation0.z + 150.0 * u.cm)
389 c.MuonFilter2 = AttrDict(z=c.MuonStation0.z + 250.0 * u.cm)
390
391 c.Muon = AttrDict()
392 c.Muon.XMax = 250.0 * u.cm
393 c.Muon.YMax = 325.0 * u.cm
394
395 c.Muon.ActiveThickness = 0.5 * u.cm
396 c.Muon.FilterThickness = 30.0 * u.cm
397
398 c.hadronAbsorber.WithConstField = shield_db[shieldName]["WithConstField"]
399 c.muShield.WithConstField = shield_db[shieldName]["WithConstField"]
400
401 # for the digitizing step
402 c.strawtubesDigi = AttrDict()
403 c.strawtubesDigi.v_drift = 1.0 / (30 * u.ns / u.mm) # for baseline NA62 5mm radius straws)
404 c.strawtubesDigi.sigma_spatial = 0.012 * u.cm # according to Massi's TP section
405
406 # CAMM - For Nu tau detector, keep only these parameters which are used by others...
407 c.tauMudet = AttrDict()
408 c.tauMudet.Ztot = 3 * u.m # space allocated to Muon spectrometer
409 c.tauMudet.zMudetC = c.muShield.z + c.muShield.length / 2.0 - c.tauMudet.Ztot / 2.0 - 70 * u.cm
410
411 # Upstream Tagger
412 # UpstreamTagger (UBT) - Simplified scoring plane
413 # Note: UBT is implemented as a simple vacuum box
414 # Legacy RPC parameters have been removed as they are not used in the current implementation
415 c.UpstreamTagger = AttrDict()
416 c.UpstreamTagger.BoxX = 4.4 * u.m # X dimension (width)
417 c.UpstreamTagger.BoxY = 6.4 * u.m # Y dimension (height)
418 c.UpstreamTagger.BoxZ = 16.0 * u.cm # Z dimension (thickness)
419 c.UpstreamTagger.Z_Position = -25.400 * u.m + c.decayVolume.z # Relative position of UBT to decay vessel centre
420 c.UpstreamTagger.PositionResolution = 1.0 * u.cm # Position smearing resolution
421 c.UpstreamTagger.TimeResolution = 0.3 # Time resolution in ns
422
423 # Store parameters that might be needed for reference
424 c.muShieldGeo = muShieldGeo
425 c.nuTargetPassive = nuTargetPassive
426
427 return c
def create_config(str DecayVolumeMedium="helium", float Yheight=6.0, int strawDesign=10, muShieldGeo=None, str shieldName="New_HA_Design", int nuTargetPassive=1, bool SND=True, SND_design=None, TARGET_YAML=None)
int open(const char *, int)
Opens a file descriptor.