FairShip
Loading...
Searching...
No Matches
run_simScript.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 glob
6import os
7import sys
8import uuid
9from argparse import ArgumentParser
10from array import array
11
12import geometry_config
13import ROOT
14import rootUtils as ut
15import shipRoot_conf
16import shipunit as u
17
18DownScaleDiMuon = False
19
20# Default HNL parameters
21theHNLMass = 1.0 * u.GeV
22theProductionCouplings = theDecayCouplings = None
23
24# Default dark photon parameters
25theDPmass = 0.2 * u.GeV
26
27# Alpaca
28# motherMode = True
29
30mcEngine = "TGeant4"
31
32inclusive = "c" # True = all processes if "c" only ccbar -> HNL, if "b" only bbar -> HNL, if "bc" only Bc+/Bc- -> HNL, and for darkphotons: if meson = production through meson decays, pbrem = proton bremstrahlung, qcd = ffbar -> DP.
33
34MCTracksWithHitsOnly = False # copy particles which produced a hit and their history
35MCTracksWithEnergyCutOnly = True # copy particles above a certain kin energy cut
36MCTracksWithHitsOrEnergyCut = False # or of above, factor 2 file size increase compared to MCTracksWithEnergyCutOnly
37
38charmonly = False # option to be set with -A to enable only charm decays, charm x-sec measurement
39HNL = True
40
41inputFile = "$EOSSHIP/eos/experiment/ship/data/Charm/Cascade-parp16-MSTP82-1-MSEL4-978Bpot.root"
42defaultInputFile = True
43
44parser = ArgumentParser()
45
46parser.add_argument("--evtcalc", help="Use EventCalc", action="store_true")
47parser.add_argument("--Pythia6", dest="pythia6", help="Use Pythia6", action="store_true")
48parser.add_argument("--Pythia8", dest="pythia8", help="Use Pythia8", action="store_true")
49parser.add_argument(
50 "--EvtGenDecayer",
51 dest="evtgen_decayer",
52 help="Use TEvtGenDecayer for J/psi and other quarkonium decays",
53 action="store_true",
54)
55subparsers = parser.add_subparsers(dest="command", help="Which mode to run")
56# === PG subcommand ===
57pg_parser = subparsers.add_parser("PG", help="Use Particle Gun")
58
59pg_parser.add_argument(
60 "--multiplePG", help="Multiple particle guns in a x-y plane at a fixed z or in a 3D volume", action="store_true"
61)
62pg_parser.add_argument("--pID", dest="pID", default=22, type=int, help="id of particle used by the gun (default=22)")
63pg_parser.add_argument(
64 "--Estart", default=10, type=float, help="start of energy range of particle gun (default=10 GeV)"
65)
66pg_parser.add_argument("--Eend", default=10, type=float, help="end of energy range of particle gun (default=10 GeV)")
67pg_parser.add_argument("--Vx", dest="Vx", default=0, type=float, help="x position of particle gun (default=0 cm)")
68pg_parser.add_argument("--Vy", dest="Vy", default=0, type=float, help="y position of particle gun (default=0 cm)")
69pg_parser.add_argument("--Vz", dest="Vz", default=0, type=float, help="z position of particle gun (default=0 cm)")
70pg_parser.add_argument(
71 "--Dx", dest="Dx", type=float, help="size of the full uniform spread of PG xpos: (Vx - Dx/2, Vx + Dx/2)"
72)
73pg_parser.add_argument(
74 "--Dy", dest="Dy", type=float, help="size of the full uniform spread of PG ypos: (Vy - Dy/2, Vy + Dy/2)"
75)
76# === End of PG commands ===
77# === Genie subcommand ===
78genie_parser = subparsers.add_parser("Genie", help="Genie for reading and processing neutrino interactions")
79genie_parser.add_argument(
80 "--z_start_nu",
81 dest="z_start_nu",
82 default=2844.2850,
83 type=float,
84 help="Genie neutrino start z start position (default=2844.2850 cm)",
85)
86genie_parser.add_argument(
87 "--z_end_nu",
88 dest="z_end_nu",
89 default=3180.4350,
90 type=float,
91 help="Genie neutrino end z position (default=3180.4350 cm)",
92)
93# === End of Genie subcommand ===
94parser.add_argument("-A", help="b: signal from b, c: from c (default), bc: from Bc, or inclusive", default="c")
95parser.add_argument(
96 "--NuRadio",
97 dest="nuradio",
98 help="misuse GenieGenerator for neutrino radiography and geometry timing test",
99 action="store_true",
100)
101parser.add_argument("--Ntuple", dest="ntuple", help="Use ntuple as input", action="store_true")
102parser.add_argument(
103 "--MuonBack",
104 dest="muonback",
105 help="Generate events from muon background file, --Cosmics=0 for cosmic generator data",
106 action="store_true",
107)
108parser.add_argument(
109 "--FollowMuon", dest="followMuon", help="Make muonshield active to follow muons", action="store_true"
110)
111parser.add_argument(
112 "--FastMuon",
113 dest="fastMuon",
114 help="Only transport muons for a fast muon only background estimate",
115 action="store_true",
116)
117parser.add_argument("--phiRandom", help="only relevant for muon background generator, random phi", action="store_true")
118parser.add_argument(
119 "--SmearBeam", dest="SmearBeam", help="Standard deviation of beam smearing [cm]", default=0.8, type=float
120)
121parser.add_argument("--PaintBeam", dest="PaintBeam", help="Radius of beam painting [cm]", default=5, type=float)
122parser.add_argument(
123 "--Cosmics", dest="cosmics", help="Use cosmic generator, argument switch for cosmic generator 0 or 1", default=None
124) # TODO: Understand integer options, replace with store_true?
125parser.add_argument("--MuDIS", dest="mudis", help="Use muon deep inelastic scattering generator", action="store_true")
126parser.add_argument("--RpvSusy", dest="RPVSUSY", help="Generate events based on RPV neutralino", action="store_true")
127parser.add_argument("--FixedTarget", dest="fixedTarget", help="Enable fixed target simulation", action="store_true")
128parser.add_argument("--DarkPhoton", help="Generate dark photons", action="store_true")
129parser.add_argument("--SusyBench", dest="RPVSUSYbench", help="Generate HP Susy", default=2)
130parser.add_argument(
131 "-m",
132 "--mass",
133 dest="theMass",
134 help=f"Mass of hidden particle, default {theHNLMass} GeV for HNL, {theDPmass} GeV for DP",
135 default=None,
136 type=float,
137)
138parser.add_argument(
139 "-c",
140 "--couplings",
141 "--coupling",
142 dest="thecouplings",
143 help="couplings 'U2e,U2mu,U2tau' or -c 'U2e,U2mu,U2tau' to set list of HNL couplings.\
144 TP default for HNL, ctau=53.3km",
145 default="0.447e-9,7.15e-9,1.88e-9",
146)
147parser.add_argument(
148 "-cp",
149 "--production-couplings",
150 dest="theprodcouplings",
151 help="production couplings 'U2e,U2mu,U2tau' to set the couplings for HNL production only",
152 default=None,
153)
154parser.add_argument(
155 "-cd",
156 "--decay-couplings",
157 dest="thedeccouplings",
158 help="decay couplings 'U2e,U2mu,U2tau' to set the couplings for HNL decay only",
159 default=None,
160)
161parser.add_argument(
162 "-e", "--epsilon", dest="theDPepsilon", help="to set mixing parameter epsilon", default=0.00000008, type=float
163)
164parser.add_argument("-n", "--nEvents", dest="nEvents", help="Number of events to generate", default=100, type=int)
165parser.add_argument("-i", "--firstEvent", help="First event of input file to use", default=0, type=int)
166parser.add_argument(
167 "-s",
168 "--seed",
169 dest="theSeed",
170 help="Seed for random number. Only for experts, see TRrandom::SetSeed documentation",
171 default=0,
172 type=int,
173)
174parser.add_argument(
175 "-S",
176 "--sameSeed",
177 dest="sameSeed",
178 help="can be set to an integer for the muonBackground simulation with specific seed for each muon, only for experts!",
179 default=False,
180 type=int,
181)
182parser.add_argument(
183 "-f",
184 dest="inputFile",
185 action="append",
186 help="Input file (repeat -f for multiple files)",
187 default=None,
188)
189parser.add_argument("--nFiles", dest="nFiles", help="Number of input files to process", default=-1, type=int)
190parser.add_argument("-g", dest="geofile", help="geofile for muon shield geometry, for experts only", default=None)
191parser.add_argument("-o", "--output", dest="outputDir", help="Output directory", default=".")
192parser.add_argument("-Y", dest="dy", help="max height of vacuum tank", default=6.0, type=float)
193parser.add_argument(
194 "--strawDesign",
195 help="Tracker station frame material: 4=aluminium; 10=steel (default)",
196 default=10,
197 type=int,
198 choices=[4, 10],
199)
200parser.add_argument(
201 "-F",
202 dest="deepCopy",
203 help="default = False: copy only stable particles to stack, except for HNL events",
204 action="store_true",
205)
206parser.add_argument("-t", "--test", dest="testFlag", help="quick test", action="store_true")
207parser.add_argument("--dry-run", dest="dryrun", help="stop after initialize", action="store_true")
208parser.add_argument("-D", "--display", dest="eventDisplay", help="store trajectories", action="store_true")
209parser.add_argument(
210 "--shieldName",
211 help="The name of the muon shield in the database to use.",
212 default="TRY_2025",
213 choices=["TRY_2025"],
214)
215parser.add_argument(
216 "--MesonMother", dest="MM", help="Choose DP production meson source: pi0, eta, omega, eta1, eta11", default="pi0"
217)
218parser.add_argument(
219 "--debug",
220 help="Control FairLogger verbosity: 0=info (default), 1=+debug, 2=+debug1, 3=+debug2",
221 default=0,
222 type=int,
223 choices=range(0, 4),
224)
225parser.add_argument("--print-fields", help="Print VMC fields and weights information", action="store_true")
226parser.add_argument("--check-overlaps", help="Perform geometry overlap checking", action="store_true")
227parser.add_argument("--field_map", default=None, help="Specify spectrometer field map.")
228parser.add_argument(
229 "--z-offset", dest="z_offset", help="z-offset for the FixedTargetGenerator [mm]", default=-84.0, type=float
230)
231parser.add_argument(
232 "--helium",
233 dest="decayVolMed",
234 help="Set Decay Volume medium to helium. NOOP, as default is helium",
235 action="store_const",
236 const="helium",
237 default="helium",
238)
239parser.add_argument(
240 "--vacuums",
241 dest="decayVolMed",
242 help="Set Decay Volume medium to vacuum(vessel structure changes)",
243 action="store_const",
244 const="vacuums",
245 default="helium",
246)
247
248parser.add_argument("--SND", dest="SND", help="Activate SND.", action="store_true")
249parser.add_argument(
250 "--SND_design",
251 help="Choose SND design(s) among [1,2,...] or 'all' to enable all. 1: EmulsionTarget, 2: MTC + SiliconTarget",
252 nargs="+",
253 default=[2],
254)
255parser.add_argument(
256 "--noSND", dest="SND", help="Deactivate SND. NOOP, as it currently defaults to off.", action="store_false"
257)
258parser.add_argument(
259 "--target-yaml",
260 help="Path to the yaml target config file",
261 default=os.path.expandvars("$FAIRSHIP/geometry/target_config.yaml"),
262)
263parser.add_argument(
264 "--tag", dest="output_tag", help="Custom tag for output files instead of auto-generated UUID", default=None
265)
266
267
268options = parser.parse_args()
269# Handle SND_design: allow 'all' (case-insensitive) or list of ints
270available_snd_designs = [1, 2] # Extend this list as new designs are added
271if any(str(x).lower() == "all" for x in options.SND_design):
272 options.SND_design = available_snd_designs
273else:
274 try:
275 options.SND_design = [int(x) for x in options.SND_design]
276 except Exception:
277 print("Invalid value for --SND_design. Use integers or 'all'.")
278 sys.exit(1)
279
280if options.A != "c":
281 inclusive = options.A
282 if options.A == "b":
283 inputFile = "$EOSSHIP/eos/experiment/ship/data/Beauty/Cascade-run0-19-parp16-MSTP82-1-MSEL5-5338Bpot.root"
284 if options.A.lower() == "charmonly":
285 charmonly = True
286 HNL = False
287 if options.A not in ["b", "c", "bc", "meson", "pbrem", "qcd"]:
288 inclusive = True
289if options.MM:
290 motherMode = options.MM
291if options.cosmics:
292 Opt_high = int(options.cosmics)
293if options.inputFile:
294 if options.inputFile == ["none"]:
295 options.inputFile = None
296 inputFile = []
297 for _f in options.inputFile:
298 inputFile.extend(glob.glob(_f))
299 inputFile = list(set(inputFile))
300 if options.nFiles > 0:
301 inputFile = inputFile[: options.nFiles]
302 defaultInputFile = False
303if options.RPVSUSY:
304 HNL = False
305if options.DarkPhoton:
306 HNL = False
307if not options.theMass:
308 if options.DarkPhoton:
309 options.theMass = theDPmass
310 else:
311 options.theMass = theHNLMass
312if options.thecouplings:
313 theCouplings = [float(c) for c in options.thecouplings.split(",")]
314if options.theprodcouplings:
315 theProductionCouplings = [float(c) for c in options.theprodcouplings.split(",")]
316if options.thedeccouplings:
317 theDecayCouplings = [float(c) for c in options.thedeccouplings.split(",")]
318if options.testFlag:
319 inputFile = "$FAIRSHIP/files/Cascade-parp16-MSTP82-1-MSEL4-76Mpot_1_5000.root"
320
321
322# sanity check
323if (HNL and options.RPVSUSY) or (HNL and options.DarkPhoton) or (options.DarkPhoton and options.RPVSUSY):
324 print("cannot have HNL and SUSY or DP at the same time, abort")
325 sys.exit(2)
326
327if (options.command == "Genie" or options.nuradio) and defaultInputFile:
328 inputFile = "$EOSSHIP/eos/experiment/ship/data/GenieEvents/genie-nu_mu.root"
329if options.mudis and defaultInputFile:
330 print("input file required if simEngine = muonDIS")
331 print(" for example -f $EOSSHIP/eos/experiment/ship/data/muonDIS/muonDis_1.root")
332 sys.exit()
333
334if (options.ntuple or options.muonback) and defaultInputFile:
335 print("input file required if simEngine = Ntuple or MuonBack")
336 print(" for example -f $EOSSHIP/eos/experiment/ship/data/Mbias/pythia8_Geant4-withCharm_onlyMuons_4magTarget.root")
337 sys.exit()
338# PYTHIA8 requires Random:seed to be in range [0, 900000000]
339# When theSeed=0, ROOT generates a seed from system time which can exceed this limit
340seed = options.theSeed
341if seed == 0:
342 ROOT.gRandom.SetSeed(0) # Generate time-based seed
343 seed = ROOT.gRandom.GetSeed()
344 # Clamp to PYTHIA8's maximum allowed seed value
345if seed > 900000000:
346 seed = seed % 900000000
347ROOT.gRandom.SetSeed(seed)
348shipRoot_conf.configure(0) # load basic libraries, prepare atexit for python
349
350# Configure FairLogger verbosity based on debug level
351ROOT.gInterpreter.ProcessLine('#include "FairLogger.h"')
352if options.debug == 0:
353 ROOT.gInterpreter.ProcessLine('fair::Logger::SetConsoleSeverity("info");')
354elif options.debug == 1:
355 ROOT.gInterpreter.ProcessLine('fair::Logger::SetConsoleSeverity("debug");')
356elif options.debug == 2:
357 ROOT.gInterpreter.ProcessLine('fair::Logger::SetConsoleSeverity("debug1");')
358elif options.debug == 3:
359 ROOT.gInterpreter.ProcessLine('fair::Logger::SetConsoleSeverity("debug2");')
361 Yheight=options.dy,
362 strawDesign=options.strawDesign,
363 muShieldGeo=options.geofile,
364 shieldName=options.shieldName,
365 DecayVolumeMedium=options.decayVolMed,
366 SND=options.SND,
367 SND_design=options.SND_design,
368 TARGET_YAML=options.target_yaml,
369)
370
371if not options.command:
372 for g in ["pythia8", "evtcalc", "pythia6", "nuradio", "ntuple", "muonback", "mudis", "fixedTarget", "cosmics"]:
373 if getattr(options, g):
374 break
375 else:
376 options.pythia8 = True # Ensure Pythia8 is enabled by default
377
378# Output file name
379# Use custom tag if provided, otherwise use random UUID version 4
380run_identifier = options.output_tag if options.output_tag else str(uuid.uuid4())
381if not os.path.exists(options.outputDir):
382 os.makedirs(options.outputDir)
383outFile = f"{options.outputDir}/sim_{run_identifier}.root"
384
385# Parameter file name
386parFile = f"{options.outputDir}/params_{run_identifier}.root"
387
388# In general, the following parts need not be touched
389# ========================================================================
390
391# -----Timer--------------------------------------------------------
392timer = ROOT.TStopwatch()
393timer.Start()
394# ------------------------------------------------------------------------
395# -----Create simulation run----------------------------------------
396run = ROOT.FairRunSim()
397run.SetName(mcEngine) # Transport engine
398run.SetSink(ROOT.FairRootFileSink(outFile)) # Output file
399run.SetUserConfig("g4Config.C") # user configuration file default g4Config.C
400rtdb = run.GetRuntimeDb()
401# -----Create geometry----------------------------------------------
402# import shipMuShield_only as shipDet_conf # special use case for an attempt to convert active shielding geometry for use with FLUKA
403# import shipTarget_only as shipDet_conf
404import shipDet_conf
405
406modules = shipDet_conf.configure(run, ship_geo)
407# -----Create PrimaryGenerator--------------------------------------
408primGen = ROOT.FairPrimaryGenerator()
409if options.pythia8:
410 primGen.SetTarget(ship_geo.target.z0, 0.0)
411 # -----Pythia8--------------------------------------
412 if HNL or options.RPVSUSY:
413 P8gen = ROOT.HNLPythia8Generator()
414 import pythia8_conf
415
416 if HNL:
417 print("Generating HNL events of mass %.3f GeV" % options.theMass)
418 if theProductionCouplings is None and theDecayCouplings is None:
419 print("and with couplings=", theCouplings)
420 theProductionCouplings = theDecayCouplings = theCouplings
421 elif theProductionCouplings is not None and theDecayCouplings is not None:
422 print("and with couplings", theProductionCouplings, "at production")
423 print("and", theDecayCouplings, "at decay")
424 else:
425 raise ValueError("Either both production and decay couplings must be specified, or neither.")
427 P8gen, options.theMass, theProductionCouplings, theDecayCouplings, inclusive, options.deepCopy
428 )
429 if options.RPVSUSY:
430 print("Generating RPVSUSY events of mass %.3f GeV" % theHNLMass)
431 print(f"and with couplings=[{theCouplings[0]:.3f},{theCouplings[1]:.3f}]")
432 print("and with stop mass=%.3f GeV\n" % theCouplings[2])
434 P8gen,
435 options.theMass,
436 [theCouplings[0], theCouplings[1]],
437 theCouplings[2],
438 options.RPVSUSYbench,
439 inclusive,
440 options.deepCopy,
441 )
442 P8gen.SetParameters("ProcessLevel:all = off")
443 if inputFile:
444 ut.checkFileExists(inputFile)
445 # read from external file
446 P8gen.UseExternalFile(inputFile, options.firstEvent)
447 if options.DarkPhoton:
448 P8gen = ROOT.DPPythia8Generator()
449 if inclusive == "qcd":
450 P8gen.SetDPId(4900023)
451 else:
452 P8gen.SetDPId(9900015)
453 import pythia8darkphoton_conf
454
456 P8gen, options.theMass, options.theDPepsilon, inclusive, motherMode, options.deepCopy
457 )
458 if passDPconf != 1:
459 sys.exit()
460 if HNL or options.RPVSUSY or options.DarkPhoton:
461 P8gen.SetSmearBeam(options.SmearBeam * u.cm) # Gaussian beam smearing
462 P8gen.SetPaintRadius(options.PaintBeam * u.cm) # beam painting radius
463 P8gen.SetLmin((ship_geo.Chamber1.z - ship_geo.chambers.Tub1length) - ship_geo.target.z0)
464 P8gen.SetLmax(ship_geo.TrackStation1.z - ship_geo.target.z0)
465 if charmonly:
466 primGen.SetTarget(0.0, 0.0) # vertex is set in pythia8Generator
467 ut.checkFileExists(inputFile)
468 if ship_geo.Box.gausbeam:
469 primGen.SetBeam(0.0, 0.0, 0.5, 0.5) # more central beam, for hits in downstream detectors
470 primGen.SmearGausVertexXY(True) # sigma = x
471 else:
472 primGen.SetBeam(
473 0.0, 0.0, ship_geo.Box.TX - 1.0, ship_geo.Box.TY - 1.0
474 ) # Uniform distribution in x/y on the target (0.5 cm of margin at both sides)
475 primGen.SmearVertexXY(True)
476 P8gen = ROOT.Pythia8Generator()
477 P8gen.UseExternalFile(inputFile, options.firstEvent)
478 # Use geometry constants instead of fragile TGeo navigation
479 P8gen.SetTargetCoordinates(ship_geo.target.z0, ship_geo.target.z0 + ship_geo.target.length)
480 # pion on proton 500GeV
481 # P8gen.SetMom(500.*u.GeV)
482 # P8gen.SetId(-211)
483 primGen.AddGenerator(P8gen)
484if options.fixedTarget:
485 HNL = False
486 P8gen = ROOT.FixedTargetGenerator()
487 P8gen.SetZoffset(options.z_offset * u.mm)
488 # Use geometry constants instead of fragile TGeo navigation
489 P8gen.SetTargetCoordinates(ship_geo.target.z0, ship_geo.target.z0 + ship_geo.target.length)
490 P8gen.SetMom(400.0 * u.GeV)
491 P8gen.SetSmearBeam(options.SmearBeam * u.cm) # Gaussian beam smearing
492 P8gen.SetPaintRadius(options.PaintBeam * u.cm) # beam painting radius
493 P8gen.SetEnergyCut(0.0)
494 P8gen.SetHeartBeat(100000)
495 P8gen.SetG4only()
496 primGen.AddGenerator(P8gen)
497if options.pythia6:
498 # set muon interaction close to decay volume
499 primGen.SetTarget(ship_geo.target.z0 + ship_geo.muShield.length, 0.0)
500 # -----Pythia6-------------------------
501 test = ROOT.TPythia6() # don't know any other way of forcing to load lib
502 P6gen = ROOT.tPythia6Generator()
503 P6gen.SetMom(50.0 * u.GeV)
504 P6gen.SetTarget("gamma/mu+", "n0") # default "gamma/mu-","p+"
505 primGen.AddGenerator(P6gen)
506
507# -----EvtCalc--------------------------------------
508if options.evtcalc:
509 primGen.SetTarget(0.0, 0.0)
510 print(f"Opening input file for EvtCalc generator: {inputFile}")
511 ut.checkFileExists(inputFile)
512 EvtCalcGen = ROOT.EvtCalcGenerator()
513 EvtCalcGen.Init(inputFile, options.firstEvent)
514 EvtCalcGen.SetPositions(zTa=ship_geo.target.z, zDV=ship_geo.decayVolume.z)
515 primGen.AddGenerator(EvtCalcGen)
516 options.nEvents = (
517 EvtCalcGen.GetNevents() if options.nEvents == -1 else min(options.nEvents, EvtCalcGen.GetNevents())
518 )
519 print(f"Generate {options.nEvents} with EvtCalc input. First event: {options.firstEvent}")
520
521# -----Particle Gun-----------------------
522if options.command == "PG":
523 myPgun = ROOT.FairBoxGenerator(options.pID, 1)
524 myPgun.SetPRange(options.Estart, options.Eend)
525 myPgun.SetPhiRange(0, 360) # // Azimuth angle range [degree]
526 myPgun.SetThetaRange(0, 0) # // Polar angle in lab system range [degree]
527 if options.multiplePG:
528 # multiple PG sources in the x-y plane; z is always the same!
529 myPgun.SetBoxXYZ(
530 (options.Vx - options.Dx / 2) * u.cm,
531 (options.Vy - options.Dy / 2) * u.cm,
532 (options.Vx + options.Dx / 2) * u.cm,
533 (options.Vy + options.Dy / 2) * u.cm,
534 options.Vz * u.cm,
535 )
536 else:
537 # point source
538 myPgun.SetXYZ(options.Vx * u.cm, options.Vy * u.cm, options.Vz * u.cm)
539 primGen.AddGenerator(myPgun)
540# -----muon DIS Background------------------------
541if options.mudis:
542 ut.checkFileExists(inputFile)
543 primGen.SetTarget(0.0, 0.0)
544 DISgen = ROOT.MuDISGenerator()
545 # from nu_tau detector to tracking station 2
546 # mu_start, mu_end = ship_geo.tauMudet.zMudetC,ship_geo.TrackStation2.z
547 #
548 # in front of UVT up to tracking station 1
549 mu_start, mu_end = ship_geo.Chamber1.z - ship_geo.chambers.Tub1length - 10.0 * u.cm, ship_geo.TrackStation1.z
550 print("MuDIS position info input=", mu_start, mu_end)
551 DISgen.SetPositions(mu_start, mu_end)
552 DISgen.Init(inputFile, options.firstEvent)
553 primGen.AddGenerator(DISgen)
554 options.nEvents = DISgen.GetNevents() if options.nEvents == -1 else min(options.nEvents, DISgen.GetNevents())
555 print("Generate ", options.nEvents, " with DIS input", " first event", options.firstEvent)
556# -----Neutrino Background------------------------
557if options.command == "Genie":
558 # Genie
559 ut.checkFileExists(inputFile)
560 primGen.SetTarget(0.0, 0.0) # do not interfere with GenieGenerator
561 Geniegen = ROOT.GenieGenerator()
562 Geniegen.Init(inputFile, options.firstEvent)
563 Geniegen.SetPositions(ship_geo.target.z0, options.z_start_nu, options.z_end_nu)
564 primGen.AddGenerator(Geniegen)
565 options.nEvents = Geniegen.GetNevents() if options.nEvents == -1 else min(options.nEvents, Geniegen.GetNevents())
566 run.SetPythiaDecayer("DecayConfigNuAge.C")
567 print("Generate ", options.nEvents, " with Genie input", " first event", options.firstEvent)
568if options.nuradio:
569 ut.checkFileExists(inputFile)
570 primGen.SetTarget(0.0, 0.0) # do not interfere with GenieGenerator
571 Geniegen = ROOT.GenieGenerator()
572 Geniegen.Init(inputFile, options.firstEvent)
573 # Geniegen.SetPositions(ship_geo.target.z0, ship_geo.target.z0, ship_geo.MuonStation3.z)
574 Geniegen.SetPositions(ship_geo.target.z0, ship_geo.tauMudet.zMudetC, ship_geo.MuonStation3.z)
575 Geniegen.NuOnly()
576 primGen.AddGenerator(Geniegen)
577 print("Generate ", options.nEvents, " for nuRadiography", " first event", options.firstEvent)
578 # add tungsten to PDG
579 pdg = ROOT.TDatabasePDG.Instance()
580 pdg.AddParticle("W", "Ion", 1.71350e02, True, 0.0, 74, "XXX", 1000741840)
581 #
582 run.SetPythiaDecayer("DecayConfigPy8.C")
583 # this requires writing a C macro, would have been easier to do directly in python!
584 # for i in [431,421,411,-431,-421,-411]:
585 # ROOT.gMC.SetUserDecay(i) # Force the decay to be done w/external decayer
586if options.ntuple:
587 # reading previously processed muon events, [-50m - 50m]
588 ut.checkFileExists(inputFile)
589 primGen.SetTarget(ship_geo.target.z0 + 50 * u.m, 0.0)
590 Ntuplegen = ROOT.NtupleGenerator()
591 Ntuplegen.Init(inputFile, options.firstEvent)
592 primGen.AddGenerator(Ntuplegen)
593 options.nEvents = Ntuplegen.GetNevents() if options.nEvents == -1 else min(options.nEvents, Ntuplegen.GetNevents())
594 print("Process ", options.nEvents, " from input file")
595#
596if options.muonback:
597 # reading muon tracks from previous Pythia8/Geant4 simulation with charm replaced by cascade production
598 fileType = ut.checkFileExists(inputFile)
599 if fileType == "tree":
600 # 2018 background production
601 primGen.SetTarget(ship_geo.target.z0 + 70.845 * u.m, 0.0)
602 else:
603 primGen.SetTarget(ship_geo.target.z0 + 50 * u.m, 0.0)
604 #
605 MuonBackgen = ROOT.MuonBackGenerator()
606 # MuonBackgen.FollowAllParticles() # will follow all particles after hadron absorber, not only muons
607 MuonBackgen.Init(inputFile, options.firstEvent)
608 MuonBackgen.SetPaintRadius(options.PaintBeam * u.cm)
609 MuonBackgen.SetSmearBeam(options.SmearBeam * u.cm)
610 MuonBackgen.SetPhiRandomize(options.phiRandom)
611 if DownScaleDiMuon:
612 testf = ROOT.TFile.Open(inputFile[0])
613 if not testf.FileHeader.GetTitle().find("diMu100.0") < 0:
614 MuonBackgen.SetDownScaleDiMuon() # avoid interference with boosted channels
615 print("MuonBackgenerator: set downscale for dimuon on")
616 testf.Close()
617 if options.sameSeed:
618 MuonBackgen.SetSameSeed(options.sameSeed)
619 primGen.AddGenerator(MuonBackgen)
620 options.nEvents = (
621 MuonBackgen.GetNevents() if options.nEvents == -1 else min(options.nEvents, MuonBackgen.GetNevents())
622 )
623 MCTracksWithHitsOnly = True # otherwise, output file becomes too big
624 print(
625 "Process ",
626 options.nEvents,
627 " from input file, with 𝜎 = ",
628 options.PaintBeam,
629 ", with smear radius r = ",
630 options.SmearBeam * u.cm,
631 "with MCTracksWithHitsOnly",
632 MCTracksWithHitsOnly,
633 )
634 if options.followMuon:
635 options.fastMuon = True
636 modules["Veto"].SetFollowMuon()
637 if options.fastMuon:
638 modules["Veto"].SetFastMuon()
639
640 # optional, boost gamma2muon conversion
641 # ROOT.kShipMuonsCrossSectionFactor = 100.
642#
643if options.cosmics:
644 primGen.SetTarget(0.0, 0.0)
645 Cosmicsgen = ROOT.CosmicsGenerator()
646 import CMBG_conf
647
648 CMBG_conf.configure(Cosmicsgen, ship_geo)
649 if not Cosmicsgen.Init(Opt_high):
650 print("initialization of cosmic background generator failed ", Opt_high)
651 sys.exit(0)
652 Cosmicsgen.n_EVENTS = options.nEvents
653 primGen.AddGenerator(Cosmicsgen)
654 print("Process ", options.nEvents, " Cosmic events with option ", Opt_high)
655
656#
657run.SetGenerator(primGen)
658# ------------------------------------------------------------------------
659
660# ---Store the visualiztion info of the tracks, this make the output file very large!!
661# --- Use it only to display but not for production!
662if options.eventDisplay:
663 run.SetStoreTraj(ROOT.kTRUE)
664else:
665 run.SetStoreTraj(ROOT.kFALSE)
666
667# -----Configure external decayer globally------------------------------------
668# Override any previous SetPythiaDecayer calls if EvtGenDecayer is requested
669if options.evtgen_decayer:
670 run.SetPythiaDecayer("DecayConfigTEvtGen.C")
671 print("Using TEvtGenDecayer for J/psi and quarkonium decays with EvtGen")
672
673# -----Initialize simulation run------------------------------------
674run.Init()
675if options.dryrun: # Early stop after setting up Pythia 8
676 sys.exit(0)
677gMC = ROOT.TVirtualMC.GetMC()
678fStack = gMC.GetStack()
679
680# -----J/psi external decayer configuration handled in g4config.in------------------------------------
681# VMC command /mcPhysics/setExtDecayerSelection J/psi forces external decayer usage
682EnergyCut = 10.0 * u.MeV if options.mudis else 100.0 * u.MeV
683
684if MCTracksWithHitsOnly:
685 fStack.SetMinPoints(1)
686 fStack.SetEnergyCut(-100.0 * u.MeV)
687elif MCTracksWithEnergyCutOnly:
688 fStack.SetMinPoints(-1)
689 fStack.SetEnergyCut(EnergyCut)
690elif MCTracksWithHitsOrEnergyCut:
691 fStack.SetMinPoints(1)
692 fStack.SetEnergyCut(EnergyCut)
693elif options.deepCopy:
694 fStack.SetMinPoints(0)
695 fStack.SetEnergyCut(0.0 * u.MeV)
696
697if options.eventDisplay:
698 # Set cuts for storing the trajectories, can only be done after initialization of run (?!)
699 trajFilter = ROOT.FairTrajFilter.Instance()
700 trajFilter.SetStepSizeCut(1 * u.mm)
701 trajFilter.SetVertexCut(-20 * u.m, -20 * u.m, ship_geo.target.z0 - 1 * u.m, 20 * u.m, 20 * u.m, 200.0 * u.m)
702 trajFilter.SetMomentumCutP(0.1 * u.GeV)
703 trajFilter.SetEnergyCut(0.0, 400.0 * u.GeV)
704 trajFilter.SetStorePrimaries(ROOT.kTRUE)
705 trajFilter.SetStoreSecondaries(ROOT.kTRUE)
706
707# The VMC sets the fields using the "/mcDet/setIsLocalMagField true" option in "gconfig/g4config.in"
708import geomGeant4
709
710# geomGeant4.setMagnetField() # replaced by VMC, only has effect if /mcDet/setIsLocalMagField false
711
712# Define extra VMC B fields not already set by the geometry definitions, e.g. a global field,
713# any field maps, or defining if any volumes feel only the local or local+global field.
714# For now, just keep the fields already defined by the C++ code, i.e comment out the fieldMaker
715if hasattr(ship_geo.Bfield, "fieldMap"):
716 if options.field_map:
717 ship_geo.Bfield.fieldMap = options.field_map
718 fieldMaker = geomGeant4.addVMCFields(ship_geo, verbose=True)
719
720# Print VMC fields and associated geometry objects
721if options.print_fields:
724 onlyWithField=True, exclude=["DecayVolume", "Tr1", "Tr2", "Tr3", "Tr4", "Veto", "MuonDetector", "SplitCal"]
725 )
726# Plot the field example
727# fieldMaker.plotField(1, ROOT.TVector3(-9000.0, 6000.0, 50.0), ROOT.TVector3(-300.0, 300.0, 6.0), 'Bzx.png')
728# fieldMaker.plotField(2, ROOT.TVector3(-9000.0, 6000.0, 50.0), ROOT.TVector3(-400.0, 400.0, 6.0), 'Bzy.png')
729
730# -----Start run----------------------------------------------------
731run.Run(options.nEvents)
732# -----Runtime database---------------------------------------------
733kParameterMerged = ROOT.kTRUE
734parOut = ROOT.FairParRootFileIo(kParameterMerged)
735parOut.open(parFile)
736rtdb.setOutput(parOut)
737rtdb.saveOutput()
738rtdb.printParamContexts()
739rtdb.print()
740# ------------------------------------------------------------------------
741geofile_name = f"{options.outputDir}/geo_{run_identifier}.root"
742run.CreateGeometryFile(geofile_name)
743# save ShipGeo dictionary in geofile
744import saveBasicParameters
745
746saveBasicParameters.execute(geofile_name, ship_geo)
747
748# checking for overlaps
749if options.check_overlaps:
750 ROOT.gROOT.SetWebDisplay("off") # Workaround for https://github.com/root-project/root/issues/18881
751 fGeo = ROOT.gGeoManager
752 fGeo.SetNmeshPoints(10000)
753 fGeo.CheckOverlaps(0.1) # 1 micron takes 5minutes
754 fGeo.PrintOverlaps()
755 # check subsystems in more detail
756 for x in fGeo.GetTopNode().GetNodes():
757 x.CheckOverlaps(0.0001)
758 fGeo.PrintOverlaps()
759# -----Finish-------------------------------------------------------
760timer.Stop()
761rtime = timer.RealTime()
762ctime = timer.CpuTime()
763print(" ")
764print("Macro finished successfully.")
765if "P8gen" in globals():
766 if HNL:
767 print("number of retries, events without HNL ", P8gen.nrOfRetries())
768 elif options.DarkPhoton:
769 print("number of retries, events without Dark Photons ", P8gen.nrOfRetries())
770 print("total number of dark photons (including multiple meson decays per single collision) ", P8gen.nrOfDP())
771
772print("Output file is ", outFile)
773print("Parameter file is ", parFile)
774print("Geometry file is ", geofile_name)
775print("Real time ", rtime, " s, CPU time ", ctime, "s")
776
777# remove empty events
778if options.muonback:
779 tmpFile = outFile + "tmp"
780 xxx = outFile.split("/")
781 check = xxx[len(xxx) - 1]
782 fin = False
783 for ff in ROOT.gROOT.GetListOfFiles():
784 nm = ff.GetName().split("/")
785 if nm[len(nm) - 1] == check:
786 fin = ff
787 if not fin:
788 fin = ROOT.TFile.Open(outFile)
789 t = fin["cbmsim"]
790 fout = ROOT.TFile(tmpFile, "recreate")
791 fSink = ROOT.FairRootFileSink(fout)
792
793 sTree = t.CloneTree(0)
794 nEvents = 0
795 # Collect branch names containing 'Point' - these are the sensitive detector hit containers
796 pointContainers = []
797 for branch in sTree.GetListOfBranches():
798 name = branch.GetName()
799 if "Point" in name:
800 pointContainers.append(name)
801
802 # Filter out empty events (events with no hits in any Point container)
803 for event in t:
804 empty = True
805 for containerName in pointContainers:
806 container = getattr(sTree, containerName)
807 if container.size() > 0:
808 empty = False
809 break
810
811 if not empty:
812 sTree.Fill()
813 nEvents += 1
814
815 branches = ROOT.TList()
816 branches.SetName("BranchList")
817 branches.Add(ROOT.TObjString("MCTrack"))
818 branches.Add(ROOT.TObjString("vetoPoint"))
819 branches.Add(ROOT.TObjString("ShipRpcPoint"))
820 branches.Add(ROOT.TObjString("TargetPoint"))
821 branches.Add(ROOT.TObjString("TTPoint"))
822 branches.Add(ROOT.TObjString("ScoringPoint"))
823 branches.Add(ROOT.TObjString("strawtubesPoint"))
824 branches.Add(ROOT.TObjString("TimeDetPoint"))
825 branches.Add(ROOT.TObjString("MCEventHeader"))
826 branches.Add(ROOT.TObjString("UpstreamTaggerPoint"))
827 branches.Add(ROOT.TObjString("MTCdetPoint"))
828 branches.Add(ROOT.TObjString("SiliconTargetPoint"))
829 branches.Add(ROOT.TObjString("sGeoTracks"))
830
831 sTree.AutoSave()
832 fSink.WriteObject(branches, "BranchList", ROOT.TObject.kSingleKey)
833 fSink.SetOutTree(sTree)
834
835 fout.Close()
836 print("removed empty events, left with:", nEvents)
837 rc1 = os.system("rm " + outFile)
838 rc2 = os.system("mv " + tmpFile + " " + outFile)
839 fin.SetWritable(False) # bpyass flush error
840
841if options.mudis:
842 temp_filename = outFile.replace(".root", "_tmp.root")
843
844 with (
845 ROOT.TFile.Open(outFile, "read") as f_outputfile,
846 ROOT.TFile.Open(temp_filename, "recreate") as f_temp,
847 ):
848 output_tree = f_outputfile["cbmsim"]
849 muondis_tree = ROOT.TChain("DIS")
850 for _f in inputFile:
851 muondis_tree.Add(_f)
852
853 new_tree = output_tree.CloneTree(0)
854
855 cross_section = array("f", [0.0])
856 cross_section_leaf = new_tree.Branch("CrossSection", cross_section, "CrossSection/F")
857
858 for output_event, muondis_event in zip(output_tree, muondis_tree):
859 mu = muondis_event.InMuon[0]
860 cross_section[0] = mu[10]
861 new_tree.Fill()
862
863 new_tree.Write("", ROOT.TObject.kOverwrite)
864
865 os.replace(temp_filename, outFile)
866 print("Successfully added DISCrossSection to the output file:", outFile)
867
868if options.command == "Genie":
869 # breakpoint()
870 # Copy Genie (gst TTree) information to the output file
871 f_input = ROOT.TFile.Open(inputFile[0], "READ")
872 print("check")
873 gst = f_input.gst
874
875 selection_string = "(Entry$ >= " + str(options.firstEvent) + ")"
876 if (options.firstEvent + options.nEvents) < gst.GetEntries():
877 selection_string += "&&(Entry$ < " + str(options.firstEvent + options.nEvents) + ")"
878
879 # Reopen output file
880 f_output = ROOT.TFile.Open(outFile, "UPDATE")
881
882 # Copy only the events used in this run
883 gst_copy = gst.CopyTree(selection_string)
884 gst_copy.Write()
885
886 f_input.Close()
887 f_output.Close()
888
889# ------------------------------------------------------------------------
890import checkMagFields
891
892
893def visualizeMagFields() -> None:
895
896
898 # after /run/initialize, but prints warning messages, problems with TGeo volume
899 mygMC = ROOT.TGeant4.GetMC()
900 mygMC.ProcessGeantCommand("/geometry/test/recursion_start 0")
901 mygMC.ProcessGeantCommand("/geometry/test/recursion_depth 2")
902 mygMC.ProcessGeantCommand("/geometry/test/run")
def configure(CMBG, ship_geo)
Definition: CMBG_conf.py:5
None printWeightsandFields(bool onlyWithField=True, exclude=None)
Definition: geomGeant4.py:150
def addVMCFields(shipGeo, str controlFile="", bool verbose=False, bool withVirtualMC=True)
Definition: geomGeant4.py:165
None printVMCFields()
Definition: geomGeant4.py:221
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)
None configurerpvsusy(P8gen, mass, couplings, sfermionmass, benchmark, inclusive, bool deepCopy=False, bool debug=True)
Definition: pythia8_conf.py:34
None configure(P8gen, mass, production_couplings, decay_couplings, process_selection, bool deepCopy=False, bool debug=True)
def configure(P8gen, mass, epsilon, inclusive, motherMode, deepCopy=False, debug=True)
None checkOverlapsWithGeant4()
None visualizeMagFields()
def execute(f, ox, name="ShipGeo")
def configure(run, ship_geo)
None configure(darkphoton=None)