FairShip
Loading...
Searching...
No Matches
run_fixedTarget.py
Go to the documentation of this file.
1#!/usr/bin/env python
2# SPDX-License-Identifier: LGPL-3.0-or-later
3# SPDX-FileCopyrightText: Copyright CERN for the benefit of the SHiP Collaboration
4
5import os
6
7import geometry_config
8import ROOT
9import shipRoot_conf
10import shipunit as u
11
12mcEngine = "TGeant4"
13simEngine = "Pythia8"
14checkOverlap = True
15outputDir = "."
16dy = 6.0 # 10.
17ds = 8 # 9 # 5=TP muon shield, 6=magnetized hadron, 7=short magnet design, 9=optimised with T4 as constraint, 8=requires config file
18
19# example for primary interaction, nobias: python $FAIRSHIP/muonShieldOptimization/run_fixedTarget.py -n 10000 -e 10 -f -r 10
20# 10000 events, energy cut 10GeV, run nr 10, override existing output folder
21# example for charm decays, python $FAIRSHIP/muonShieldOptimization/run_fixedTarget.py -C -M -n 10000 -e 10 -r 60 -b 50 -f
22# 10000 events, charm decays, energy cut 10GeV, run nr 60, override existing output folder
23# increase di-muon BRs for resonances < 1.1GeV by a factor 50
24
25# ----------------------------- Yandex production ------------------------------
26import argparse
27import logging
28import shutil
29
30logging.info("")
31logger = logging.getLogger(os.path.splitext(os.path.basename(os.sys.argv[0]))[0])
32logger.setLevel(logging.INFO)
33
34
35def get_work_dir(run_number, tag: str | None = None) -> str:
36 import socket
37
38 host = socket.gethostname()
39 job_base_name = os.path.splitext(os.path.basename(os.sys.argv[0]))[0]
40 if tag:
41 out_dir = f"{host}_{job_base_name}_{run_number}_{tag}"
42 else:
43 out_dir = f"{host}_{job_base_name}_{run_number}"
44 return out_dir
45
46
47logger.info("SHiP proton-on-taget simulator (C) Thomas Ruf, 2017")
48
49ap = argparse.ArgumentParser(description='Run SHiP "pot" simulation')
50ap.add_argument("-d", "--debug", action="store_true")
51ap.add_argument("-f", "--force", action="store_true", help="force overwriting output directory")
52ap.add_argument("-r", "--run-number", type=int, dest="runnr", default=1)
53ap.add_argument(
54 "-e", "--ecut", type=float, help="energy cut", default=0.5
55) # GeV with 1 : ~1sec / event, with 2: 0.4sec / event, 10: 0.13sec
56ap.add_argument("-n", "--num-events", type=int, help="number of events to generate", dest="nev", default=100)
57ap.add_argument(
58 "-G",
59 "--G4only",
60 action=argparse.BooleanOptionalAction,
61 default=False,
62 help="Whether or not to use Geant4 directly, no Pythia8 (--no-G4only or --G4only). Default set to False.",
63)
64ap.add_argument(
65 "-P",
66 "--pythiaDecay",
67 action=argparse.BooleanOptionalAction,
68 default=False,
69 help="Whether or not to use Pythia8 for decays (--no-PythiaDecay or --PythiaDecay). Default set to False.",
70)
71ap.add_argument("-t", "--tau-only", action=argparse.BooleanOptionalAction, dest="tauOnly", default=False)
72ap.add_argument("-J", "--Jpsi-mainly", action=argparse.BooleanOptionalAction, dest="JpsiMainly", default=False)
73ap.add_argument("-b", "--boostDiMuon", type=float, default=1.0, help="boost Di-muon branching ratios")
74ap.add_argument("-X", "--boostFactor", type=float, default=1.0, help="boost Di-muon prod cross sections")
75ap.add_argument("-C", "--charm", action=argparse.BooleanOptionalAction, default=False, help="generate charm decays")
76ap.add_argument("-B", "--beauty", action=argparse.BooleanOptionalAction, default=False, help="generate beauty decays")
77ap.add_argument(
78 "-M",
79 "--storeOnlyMuons",
80 action=argparse.BooleanOptionalAction,
81 default=False,
82 help="store only muons, ignore neutrinos",
83)
84ap.add_argument("-N", "--skipNeutrinos", action=argparse.BooleanOptionalAction, default=False, help="skip neutrinos")
85ap.add_argument(
86 "-D",
87 "--4darkPhoton",
88 action=argparse.BooleanOptionalAction,
89 dest="FourDP",
90 default=False,
91 help="enable ntuple production",
92)
93# for charm production
94ap.add_argument("-cc", "--chicc", default=1.7e-3, help="ccbar over mbias cross section")
95ap.add_argument("-bb", "--chibb", default=1.6e-7, help="bbbar over mbias cross section")
96ap.add_argument("-p", "--pot", default=4e13, help="number of protons on target per spill to normalize on")
97ap.add_argument("-S", "--nStart", type=int, help="first event of input file to start", dest="nStart", default=0)
98ap.add_argument(
99 "-I",
100 "--InputFile",
101 type=str,
102 dest="charmInputFile",
103 default=ROOT.gSystem.Getenv("EOSSHIP")
104 + "/eos/experiment/ship/data/Charm/Cascade-parp16-MSTP82-1-MSEL4-76Mpot_1.root",
105 help="input file for charm/beauty decays",
106)
107ap.add_argument("-o", "--output", type=str, help="output directory", dest="work_dir", default=None)
108ap.add_argument(
109 "-rs", "--seed", type=int, help="random seed; default value is 0, see TRrandom::SetSeed documentation", default=0
110)
111ap.add_argument(
112 "--DecayVolumeMedium",
113 help="Set Decay Volume Medium. Choices are helium (default) or vacuums.",
114 default="helium",
115 choices=["helium", "vacuums"],
116)
117ap.add_argument(
118 "--shieldName",
119 help="Name of the shield in the database. New_HA_Design or warm_opt.",
120 default="TRY_2025",
121 choices=["TRY_2025"],
122)
123ap.add_argument(
124 "--AddMuonShield",
125 help="Whether or not to add the muon shield. Default set to False.",
126 default=False,
127 action=argparse.BooleanOptionalAction,
128)
129ap.add_argument(
130 "--AddMuonShieldField",
131 help="Whether or not to add the muon shield magnetic field. Default set to False.",
132 default=False,
133 action=argparse.BooleanOptionalAction,
134)
135ap.add_argument(
136 "--AddHadronAbsorberOnly",
137 help="Whether to only add the hadron absorber part of the muon shield. Default set to True.",
138 default=True,
139 action=argparse.BooleanOptionalAction,
140)
141
142ap.add_argument(
143 "--z-offset", type=float, dest="z_offset", default=-84.0, help="z-offset for the FixedTargetGenerator [mm]"
144)
145ap.add_argument(
146 "--x-offset", type=float, dest="x_offset", default=0.0, help="x-offset for the FixedTargetGenerator [mm]"
147)
148ap.add_argument(
149 "--y-offset", type=float, dest="y_offset", default=0.0, help="y-offset for the FixedTargetGenerator [mm]"
150)
151ap.add_argument(
152 "--beam-smear", type=float, dest="beam_smear", default=16.0, help="beam smearing for the FixedTargetGenerator [mm]"
153)
154ap.add_argument(
155 "--beam-paint",
156 type=float,
157 dest="beam_paint",
158 default=50.0,
159 help="beam painting radius for the FixedTargetGenerator [mm]",
160)
161ap.add_argument(
162 "--TARGET_YAML",
163 dest="TARGET_YAML",
164 help="File for target configuration",
165 default=os.path.expandvars("$FAIRSHIP/geometry/target_config.yaml"),
166)
167
168ap.add_argument(
169 "--AddCylindricalSensPlane",
170 action="store_true",
171 help="Whether or not to add cylindrical sensitive plane around the target. False by default.",
172)
173ap.add_argument(
174 "--AddPostTargetSensPlane",
175 action="store_true",
176 help="Whether or not to add sensitive plane after the target. False by default.",
177)
178
179args = ap.parse_args()
180if args.debug:
181 logger.setLevel(logging.DEBUG)
182
183
184if args.G4only:
185 args.charm = False
186 args.beauty = False
187 withEvtGen = False
188 args.pythiaDecay = False
189elif args.pythiaDecay:
190 withEvtGen = False
191 logger.info("use Pythia8 as primary decayer")
192else:
193 withEvtGen = True
194 logger.info("use EvtGen as primary decayer")
195# withEvtGen = args.withEvtGen
196if args.charm and args.beauty:
197 logger.warning("charm and beauty decays are set! Beauty gets priority")
198 args.charm = False
199charmInputFile = args.charmInputFile
200
201if args.work_dir is None:
202 if args.charm:
203 args.work_dir = get_work_dir(args.runnr, "charm")
204 if args.beauty:
205 args.work_dir = get_work_dir(args.runnr, "beauty")
206 else:
207 args.work_dir = get_work_dir(args.runnr)
208
209logger.debug("work_dir: %s" % args.work_dir)
210logger.debug("command line arguments: %s", args)
211if os.path.exists(args.work_dir):
212 logger.warning("output directory '%s' already exists." % args.work_dir)
213 if args.force:
214 logger.warning("...cleaning")
215 for root, dirs, files in os.walk(args.work_dir):
216 for f in files:
217 os.unlink(os.path.join(root, f))
218 for d in dirs:
219 shutil.rmtree(os.path.join(root, d))
220 else:
221 logger.warning("...use '-f' option to overwrite it")
222else:
223 os.makedirs(args.work_dir)
224
225os.chdir(args.work_dir)
226# -------------------------------------------------------------------
227# PYTHIA8 requires Random:seed to be in range [0, 900000000]
228# When seed=0, ROOT generates a seed from system time which can exceed this limit
229seed = args.seed
230if seed == 0:
231 ROOT.gRandom.SetSeed(0) # Generate time-based seed
232 seed = ROOT.gRandom.GetSeed()
233# Clamp to PYTHIA8's maximum allowed seed value
234if seed > 900000000:
235 seed = seed % 900000000
236ROOT.gRandom.SetSeed(seed)
237shipRoot_conf.configure() # load basic libraries, prepare atexit for python
238ship_geo_kwargs = {
239 "Yheight": dy,
240 "DecayVolumeMedium": args.DecayVolumeMedium,
241 "shieldName": args.shieldName,
242 "TARGET_YAML": args.TARGET_YAML,
243}
244ship_geo = geometry_config.create_config(**ship_geo_kwargs)
245
246txt = "pythia8_Geant4_"
247if withEvtGen:
248 txt = "pythia8_evtgen_Geant4_"
249outFile = f"{outputDir}/{txt}{args.runnr}_{args.ecut}.root"
250parFile = f"{outputDir}/ship.params.{txt}{args.runnr}_{args.ecut}.root"
251
252# -----Timer--------------------------------------------------------
253timer = ROOT.TStopwatch()
254timer.Start()
255
256# -----Create simulation run----------------------------------------
257run = ROOT.FairRunSim()
258run.SetName(mcEngine) # Transport engine
259run.SetSink(ROOT.FairRootFileSink(outFile)) # Output file
260if args.boostFactor > 1:
261 # Turn off UseGeneralProcess to access GammaToMuons directly when cross-sections need to be changed
262 os.environ["SET_GENERAL_PROCESS_TO_FALSE"] = "1"
263run.SetUserConfig("g4Config.C") # user configuration file default g4Config.C
264rtdb = run.GetRuntimeDb()
265
266# -----Materials----------------------------------------------
267run.SetMaterials("media.geo")
268# -----Create geometry----------------------------------------------
269cave = ROOT.ShipCave("CAVE")
270cave.SetGeometryFileName("caveWithAir.geo")
271
272run.AddModule(cave)
273
274TargetStation = ROOT.ShipTargetStation(
275 name="TargetStation",
276 tl=ship_geo.target.length,
277 tz=ship_geo.target.z,
278 nS=ship_geo.target.nS,
279 HeT=ship_geo.target.HeT,
280)
281TargetStation.SetLayerPosMat(
282 d=ship_geo.target.xy,
283 L=ship_geo.target.slices_length,
284 G=ship_geo.target.slices_gap,
285 M=ship_geo.target.slices_material,
286)
287run.AddModule(TargetStation)
288
289
290if args.AddPostTargetSensPlane:
291 sensPlanePostT = ROOT.exitHadronAbsorber()
292 sensPlanePostT.SetEnergyCut(args.ecut * u.GeV)
293 sensPlanePostT.SetVetoPointName("PlanePostT")
294 # by default, if the z-position is not set, the positioning is behind the hadron abosorber and the tracks are stopped when they hit the sens plane
295 # if the z-position is set and has a reasonable value (below 1E8), then the tracks are not stopped and continue to the last plane after the hadron absorber
296 sensPlanePostT.SetZposition(
297 ship_geo.target.length + 7.6 * u.cm + 300 * u.mm
298 ) # target length + vessel shift + shielding length
299 sensPlanePostT.SetUseCaveCoordinates() # position set from the cave to avoid extrusions since the plane is larger than the target vacuum box
300
301 if args.storeOnlyMuons:
302 sensPlanePostT.SetOnlyMuons()
303 if args.skipNeutrinos:
304 sensPlanePostT.SkipNeutrinos()
305 if args.FourDP:
306 sensPlanePostT.SetOpt4DP()
307 run.AddModule(sensPlanePostT)
308
309
310if args.AddMuonShield or args.AddHadronAbsorberOnly:
311 n_params = 15
312 if not args.AddMuonShieldField:
313 for i in range(ship_geo.muShield.nMagnets):
314 ship_geo.muShield.params[i * n_params + 14] = 0 # set B field to 0
315 if args.AddHadronAbsorberOnly:
316 ship_geo.muShield.params = ship_geo.muShield.params[:15] # set dXIn to 0
317
318 MuonShield = ROOT.ShipMuonShield(
319 in_params=list(ship_geo.muShield.params),
320 z=ship_geo.muShield.z,
321 WithConstShieldField=True,
322 SC_key=ship_geo.SC_mag,
323 )
324 # MuonShield.SetSupports(False) # otherwise overlap with sensitive Plane
325 run.AddModule(MuonShield) # needs to be added because of magn hadron shield.
326
327
328sensPlaneHA = ROOT.exitHadronAbsorber()
329sensPlaneHA.SetEnergyCut(args.ecut * u.GeV)
330sensPlaneHA.SetVetoPointName("PlaneHA")
331
332if args.AddCylindricalSensPlane: # add additional sensitive plane around target
333 sensPlaneT = ROOT.exitHadronAbsorber()
334 sensPlaneT.SetEnergyCut(args.ecut * u.GeV)
335 sensPlaneT.SetVetoPointName("PlaneT")
336 sensPlaneT.SetCylindricalPlane()
337 # by default, if the z-position is not set, the positioning is behind the hadron abosorber and the tracks are stopped when they hit the sens plane
338 # if the z-position is set and has a reasonable value (below 1E8), then the tracks are not stopped and continue to the last plane after the hadron absorber
339 sensPlaneT.SetZposition(ship_geo.target.length)
340
341if args.storeOnlyMuons:
342 sensPlaneHA.SetOnlyMuons()
343 if args.AddCylindricalSensPlane:
344 sensPlaneT.SetOnlyMuons()
345if args.skipNeutrinos:
346 sensPlaneHA.SkipNeutrinos()
347 if args.AddCylindricalSensPlane:
348 sensPlaneT.SkipNeutrinos()
349if args.FourDP: # in case a ntuple should be filled with pi0,etas,omega
350 sensPlaneHA.SetOpt4DP()
351 if args.AddCylindricalSensPlane:
352 sensPlaneT.SetOpt4DP()
353
354run.AddModule(sensPlaneHA)
355
356if args.AddCylindricalSensPlane:
357 run.AddModule(sensPlaneT)
358
359# -----Create PrimaryGenerator--------------------------------------
360primGen = ROOT.FairPrimaryGenerator()
361P8gen = ROOT.FixedTargetGenerator()
362P8gen.SetZoffset(args.z_offset * u.mm)
363P8gen.SetXoffset(args.x_offset * u.mm)
364P8gen.SetYoffset(args.y_offset * u.mm)
365P8gen.SetSmearBeam(args.beam_smear * u.mm)
366P8gen.SetPaintRadius(args.beam_paint * u.mm)
367# Use geometry constants instead of fragile TGeo navigation
368P8gen.SetTargetCoordinates(ship_geo.target.z0, ship_geo.target.z0 + ship_geo.target.length)
369P8gen.SetMom(400.0 * u.GeV)
370P8gen.SetEnergyCut(args.ecut * u.GeV)
371P8gen.SetDebug(args.debug)
372P8gen.SetHeartBeat(100000)
373if args.G4only:
374 P8gen.SetG4only()
375if args.JpsiMainly:
376 P8gen.SetJpsiMainly()
377if args.tauOnly:
378 P8gen.SetTauOnly()
379if withEvtGen:
380 P8gen.WithEvtGen()
381if args.boostDiMuon > 1:
382 P8gen.SetBoost(
383 args.boostDiMuon
384 ) # will increase BR for rare eta,omega,rho ... mesons decaying to 2 muons in Pythia8
385 # and later copied to Geant4
386P8gen.SetSeed(seed)
387# for charm/beauty
388# print ' for experts: p pot= number of protons on target per spill to normalize on'
389# print ' : c chicc= ccbar over mbias cross section'
390if args.charm or args.beauty:
391 print("--- process heavy flavours ---")
392 P8gen.InitForCharmOrBeauty(charmInputFile, args.nev, args.pot, args.nStart)
393primGen.AddGenerator(P8gen)
394#
395run.SetGenerator(primGen)
396
397# -----Initialize simulation run------------------------------------
398run.Init()
399
400gMC = ROOT.TVirtualMC.GetMC()
401fStack = gMC.GetStack()
402fStack.SetMinPoints(1)
403fStack.SetEnergyCut(-1.0)
404#
405import AddDiMuonDecayChannelsToG4
406
408
409# boost gamma2muon conversion
410if args.boostFactor > 1:
411 ROOT.gROOT.ProcessLine('#include "Geant4/G4ProcessTable.hh"')
412 ROOT.gROOT.ProcessLine('#include "Geant4/G4AnnihiToMuPair.hh"')
413 ROOT.gROOT.ProcessLine('#include "Geant4/G4GammaConversionToMuons.hh"')
414 gProcessTable = ROOT.G4ProcessTable.GetProcessTable()
415 procAnnihil = gProcessTable.FindProcess(ROOT.G4String("AnnihiToMuPair"), ROOT.G4String("e+"))
416 procGMuPair = gProcessTable.FindProcess(ROOT.G4String("GammaToMuPair"), ROOT.G4String("gamma"))
417 procAnnihil.SetCrossSecFactor(args.boostFactor)
418 procGMuPair.SetCrossSecFactor(args.boostFactor)
419
420# -----Start run----------------------------------------------------
421run.Run(args.nev)
422
423# -----Finish-------------------------------------------------------
424timer.Stop()
425rtime = timer.RealTime()
426ctime = timer.CpuTime()
427print(" ")
428print("Macro finished successfully.")
429print(f"Output file is {outFile}")
430print(f"Real time {rtime} s, CPU time {ctime} s")
431# ---post processing--- remove empty events --- save histograms
432tmpFile = outFile + "tmp"
433if ROOT.gROOT.GetListOfFiles().GetEntries() > 0:
434 fin = ROOT.gROOT.GetListOfFiles()[0]
435else:
436 fin = ROOT.TFile.Open(outFile)
437fHeader = fin["FileHeader"]
438fHeader.SetRunId(args.runnr)
439if args.charm or args.beauty:
440 # normalization for charm
441 poteq = P8gen.GetPotForCharm()
442 info = "POT equivalent = %7.3G" % (poteq)
443else:
444 info = f"POT = {args.nev}"
445
446conditions = " with ecut=" + str(args.ecut)
447if args.JpsiMainly:
448 conditions += " J"
449if args.tauOnly:
450 conditions += " T"
451if withEvtGen:
452 conditions += " V"
453if args.boostDiMuon > 1:
454 conditions += " diMu" + str(args.boostDiMuon)
455if args.boostFactor > 1:
456 conditions += " X" + str(args.boostFactor)
457
458info += conditions
459fHeader.SetTitle(info)
460print(f"Data generated {fHeader.GetTitle()}")
461
462nt = fin.Get("4DP")
463if nt:
464 nt = fin["4DP"]
465 tf = ROOT.TFile("FourDP.root", "recreate")
466 tnt = nt.CloneTree(0)
467 for i in range(nt.GetEntries()):
468 rc = nt.GetEvent(i)
469 rc = tnt.Fill(nt.id, nt.px, nt.py, nt.pz, nt.x, nt.y, nt.z)
470 tnt.Write()
471 tf.Close()
472
473t = fin["cbmsim"]
474fout = ROOT.TFile(tmpFile, "recreate")
475sTree = t.CloneTree(0)
476nEvents = 0
477for n in range(t.GetEntries()):
478 rc = t.GetEvent(n)
479 if (
480 (len(t.PlaneHAPoint) > 0)
481 or (args.AddCylindricalSensPlane and len(t.PlaneTPoint) > 0)
482 or (args.AddPostTargetSensPlane and len(t.PlanePostTPoint) > 0)
483 ):
484 rc = sTree.Fill()
485 nEvents += 1
486fout.cd()
487for k in fin.GetListOfKeys():
488 x = fin.Get(k.GetName())
489 className = x.Class().GetName()
490 if className.find("TTree") < 0 and className.find("TNtuple") < 0:
491 xcopy = x.Clone()
492 rc = xcopy.Write()
493sTree.AutoSave()
494ff = fin["FileHeader"].Clone(fout.GetName())
495fout.cd()
496ff.Write("FileHeader", ROOT.TObject.kSingleKey)
497sTree.Write()
498fout.Close()
499
500rc1 = os.system("rm " + outFile)
501rc2 = os.system("mv " + tmpFile + " " + outFile)
502print("removed out file, moved tmpFile to out file", rc1, rc2)
503fin.SetWritable(False) # bpyass flush error
504
505print(f"Number of events produced with activity after hadron absorber: {nEvents}")
506
507if checkOverlap:
508 sGeo = ROOT.gGeoManager
509 sGeo.CheckOverlaps()
510 sGeo.PrintOverlaps()
511 run.CreateGeometryFile("%s/geofile_full.root" % (outputDir))
512 import saveBasicParameters
513
514 saveBasicParameters.execute("%s/geofile_full.root" % (outputDir), ship_geo)
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)
str get_work_dir(run_number, str|None tag=None)
def execute(f, ox, name="ShipGeo")
None configure(darkphoton=None)