File indexing completed on 2025-01-18 09:27:05
0001 import pyhepmc
0002 from pyhepmc.io import WriterAscii
0003 import argparse
0004
0005 import numpy as np
0006 import pandas as pd
0007
0008 from datetime import datetime
0009 from operator import itemgetter
0010 import time
0011 import sys
0012
0013 import psutil
0014
0015
0016 class signal_background_merger:
0017 """
0018 Combine signal and up to three background HEPMC files.
0019
0020 Typical usage:
0021 python3 signal_background_merger --signalFile dis.hepmc --signalFreq 0 \
0022 --bg1File hgas.hepmc --bg1Freq 1852 \
0023 --bg2File egas.hepmc --bg2Freq 1852 \
0024 --bg3File sr.hepmc
0025 """
0026
0027
0028 def __init__(self):
0029 """ Parse arguments, print banner, open files, initialize rng"""
0030
0031 self.digestArgs()
0032 self.rng=np.random.default_rng(seed=self.args.rngSeed)
0033 self.banner()
0034
0035
0036 def digestArgs(self):
0037 """Handle the command line tedium"""
0038 parser = argparse.ArgumentParser(description='Merge signal events with up to three background sources.')
0039
0040 parser.add_argument('-i','--signalFile', default='pythia_DIS.hepmc',
0041 help='Name of the HEPMC file with the signal events')
0042 parser.add_argument('-sf','--signalFreq', type=float, default=0.0,
0043 help='Poisson-mu of the signal frequency in ns. Default is 0 to have exactly one signal event per slice. Set to the estimated DIS rate to randomize.')
0044
0045
0046 parser.add_argument('-bg1','--bg1File', default='pythia_PROTON.hepmc',
0047 help='Name of the first HEPMC file with background events')
0048 parser.add_argument('-bf1','--bg1Freq', type=float, default=31347.0,
0049 help='Poisson-mu of the first background frequency in ns. Default is the estimated hadron gas rate at 10x100. Set to 0 to use the weights in the corresponding input file.')
0050
0051 parser.add_argument('-bg2','--bg2File', default='pythia_ELECTRON.hepmc',
0052 help='Name of the second HEPMC file with background events')
0053 parser.add_argument('-bf2','--bg2Freq', type=float, default=333.0,
0054 help='Poisson-mu of the second background frequency in ns. Default is the estimated electron gas rate at 10x100. Set to 0 to use the weights in the corresponding input file.')
0055
0056 parser.add_argument('-bg3','--bg3File', default='pythia_SR.hepmc',
0057 help='Name of the third HEPMC file with background events')
0058 parser.add_argument('-bf3','--bg3Freq', type=float, default=0,
0059 help='Poisson-mu of the third background frequency in ns. Default is 0 to use the weights in the corresponding input file. Set to a value >0 to specify a poisson mu instead.')
0060
0061
0062 parser.add_argument('-o','--outputFile', default='',
0063 help='Specify the output file name. By default it will be auto-generated.')
0064
0065 parser.add_argument('-w','--intWindow', type=float, default=2000.0,
0066 help='Length of the integration window in nanoseconds. Default is 2000.')
0067 parser.add_argument('-N','--nSlices', type=int, default=-1,
0068 help='Number of sampled time slices ("events"). Default is 10. If set to -1, all events in the signal file will be used and background files cycled as needed.')
0069 parser.add_argument('--squashTime', action='store_true',
0070 help='Integration is performed but no time information is associated to vertices.')
0071
0072 parser.add_argument('--rngSeed', action='store',type=int, default=None,
0073 help='Random seed, default is None')
0074 parser.add_argument('--atLeastOne', action='store',type=bool, default=False,
0075 help='Assert that at least one Background Event will always be admixed')
0076
0077 parser.add_argument('-v','--verbose', action='store_true',
0078 help='Display details for every slice.')
0079 self.args = parser.parse_args()
0080
0081
0082 def banner(self):
0083 """Print a banner."""
0084 print('==================================================================')
0085 print('=== EPIC HEPMC MERGER ===')
0086 print('authors: Benjamen Sterwerf* (bsterwerf@berkeley.edu), Kolja Kauder** (kkauder@bnl.gov), Reynier Cruz-Torres***')
0087 print('* University of California, Berkeley')
0088 print('** Brookhaven National Laboratory')
0089 print('*** formerly Lawrence Berkeley National Laboratory')
0090
0091 print('\nFor more information, run \npython signal_background_merger.py --help')
0092 print('==================================================================\n')
0093
0094 freqTerm = str(self.args.signalFreq) + " ns" if self.args.signalFreq> 0 else "(one event per time slice)"
0095 print('Signal events file and frequency:')
0096 print('\t- {} \t {}'.format( self.args.signalFile, freqTerm ))
0097
0098 print('\nBackground files and their respective frequencies:')
0099 if self.args.bg1File != "" :
0100 freqTerm = str(self.args.bg1Freq) + " ns" if self.args.bg1Freq> 0 else "(from weights)"
0101 print('\t- {} \t {}'.format( self.args.bg1File, freqTerm ))
0102 if self.args.bg2File != "" :
0103 freqTerm = str(self.args.bg2Freq) + " ns" if self.args.bg2Freq> 0 else "(from weights)"
0104 print('\t- {} \t {}'.format( self.args.bg2File, freqTerm ))
0105 if self.args.bg3File != "" :
0106 freqTerm = str(self.args.bg3Freq) + " ns" if self.args.bg3Freq> 0 else "(from weights)"
0107 print('\t- {} \t {}'.format( self.args.bg3File, freqTerm ))
0108
0109
0110 print()
0111 print('Integration Window:',self.args.intWindow, ' ns')
0112 print('Number of time slices:', self.args.nSlices if self.args.nSlices>0 else ' (as many as needed to exhaust the signal file)')
0113 if self.args.squashTime:
0114 print('No timing information will be attached to vertices')
0115 print('The RNG is seeded to', self.args.rngSeed)
0116 print('==================================================================\n')
0117
0118
0119 def merge( self ):
0120 """Main method to steer the merging."""
0121
0122 t0 = time.time()
0123
0124 try :
0125
0126 sigFile=pyhepmc.io.open(self.args.signalFile)
0127 except IOError as e:
0128 print ('Opening files failed: %s' % e.strerror)
0129 sys.exit()
0130
0131
0132
0133 self.sigDict=dict()
0134 self.freqDict=dict()
0135 self.weightDict=dict()
0136 self.makeDicts ( self.args.signalFile, self.args.signalFreq, signal=True )
0137 self.makeDicts ( self.args.bg1File, self.args.bg1Freq )
0138
0139
0140
0141
0142 if self.args.outputFile != "" :
0143 outputFileName = self.args.outputFile
0144 else :
0145 outputFileName = self.nameGen()
0146
0147 t1 = time.time()
0148 print('Initiation time:',np.round((t1-t0)/60.,2),'min')
0149
0150
0151 nSlices=self.args.nSlices
0152
0153 print()
0154 print('==================================================================')
0155 print()
0156 i=0
0157 with WriterAscii(outputFileName) as f:
0158 while True :
0159 if (i==nSlices) :
0160 print ( "Finished all requested slices." )
0161 break
0162
0163 if i % 100 == 0 or self.args.verbose : self.squawk(i)
0164 hepSlice = self.mergeSlice( i )
0165
0166 try :
0167 if hepSlice==None :
0168 print ( "Exhausted signal source." )
0169 break
0170 except TypeError as e:
0171 pass
0172
0173 hepSlice.event_number=i
0174 f.write_event(hepSlice)
0175 i=i+1
0176
0177 t2 = time.time()
0178 self.slicesDone=i
0179 print('Slice loop time:',np.round((t2-t1)/60.,2),'min')
0180 print(' -- ',np.round((t2-t1) / self.slicesDone ,3),'sec / slice')
0181
0182
0183
0184 File, Freq = self.sigDict[self.args.signalFile]
0185
0186 for fileName in self.freqDict :
0187 File, Freq = self.freqDict[fileName]
0188 File.close()
0189
0190
0191
0192 def makeDicts( self, fileName, freq, signal=False ):
0193 """Create background timeline chunks, open input file, sort into frequency or weight type"""
0194 if fileName == "" : return
0195
0196 try :
0197
0198 File=pyhepmc.open(fileName)
0199 except IOError as e:
0200 print ('Opening {} failed: {}', fileName, e.strerror)
0201 sys.exit()
0202
0203
0204 if signal :
0205 self.sigDict[fileName] = [ File, freq ]
0206 return
0207
0208 if freq<=0 :
0209
0210
0211
0212 print ( "Reading in all events from", fileName )
0213
0214 container = [[event, event.weight()] for event in File if event.weight() > 0]
0215
0216 container = sorted ( container, key=itemgetter(1) )
0217 File.close()
0218 events = np.array([ item[0] for item in container ])
0219 weights = np.array([ item[1] for item in container ])
0220 avgRate = np.average (weights)
0221 avgRate *= 1e-9
0222 print( "Average rate is", avgRate, "GHz")
0223
0224 probs = weights / weights.sum()
0225 self.weightDict[fileName]=[ events, probs, avgRate ]
0226 return
0227
0228 self.freqDict[fileName] = [ File, freq ]
0229
0230 return
0231
0232
0233 def mergeSlice(self, i):
0234 """Arrange the composition of an individual time slice"""
0235
0236 hepSlice = pyhepmc.GenEvent(pyhepmc.Units.GEV, pyhepmc.Units.MM)
0237
0238
0239
0240 try :
0241 hepSlice = self.addFreqEvents( self.args.signalFile, hepSlice, True )
0242 except EOFError :
0243 return
0244
0245
0246 for fileName in self.freqDict :
0247 hepSlice = self.addFreqEvents( fileName, hepSlice )
0248
0249 for fileName in self.weightDict :
0250 hepSlice = self.addWeightedEvents( fileName, hepSlice )
0251
0252 return hepSlice
0253
0254
0255 def addFreqEvents( self, fileName, hepSlice, signal=False ):
0256 """Handles the signal as well as frequency-style backgrounds"""
0257
0258 if signal :
0259 File, Freq = self.sigDict[fileName]
0260 else :
0261 File, Freq = self.freqDict[fileName]
0262
0263
0264 c_light = 299.792458
0265 squash = self.args.squashTime
0266
0267
0268 intTime=self.args.intWindow
0269
0270
0271
0272 if Freq==0:
0273 if not signal :
0274 print( "frequency can't be 0 for background files" )
0275 sys.exit()
0276
0277 slice = np.array([ self.rng.uniform(low=0, high=intTime) ])
0278 else:
0279
0280 slice = self.poissonTimes( Freq, intTime )
0281
0282 if self.args.verbose : print ( "Placing",slice.size,"events from", fileName )
0283 if slice.size == 0 : return hepSlice
0284
0285
0286 for Time in slice :
0287
0288 inevt = File.read()
0289 try :
0290 if inevt==None :
0291 if signal :
0292
0293 raise EOFError
0294 else :
0295
0296 print("Cycling back to the start of ", fileName )
0297 File.close()
0298
0299 File=pyhepmc.io.open(fileName)
0300
0301 self.freqDict[fileName] = [File, Freq]
0302 inevt = File.read()
0303 except TypeError as e:
0304 pass
0305
0306
0307 TimeHepmc = c_light*Time
0308
0309 particles, vertices = [], []
0310
0311 for vertex in inevt.vertices:
0312 position=vertex.position
0313 if not squash:
0314 position=position+pyhepmc.FourVector(x=0,y=0,z=0,t=TimeHepmc)
0315 v1=pyhepmc.GenVertex(position)
0316 vertices.append(v1)
0317
0318
0319 for particle in inevt.particles:
0320
0321 momentum, status, pid = particle.momentum, particle.status, particle.pid
0322 p1 = pyhepmc.GenParticle(momentum=momentum, pid=pid, status=status)
0323 p1.generated_mass = particle.generated_mass
0324 particles.append(p1)
0325 hepSlice.add_particle(p1)
0326
0327
0328 if particle.production_vertex.id < 0:
0329 production_vertex=particle.production_vertex.id
0330 vertices[abs(production_vertex)-1].add_particle_out(p1)
0331
0332
0333
0334
0335 if particle.end_vertex:
0336 end_vertex = particle.end_vertex.id
0337 vertices[abs(end_vertex)-1].add_particle_in(p1)
0338
0339
0340
0341
0342
0343 for vertex in vertices:
0344 hepSlice.add_vertex(vertex)
0345
0346
0347
0348 return hepSlice
0349
0350
0351 def addWeightedEvents( self, fileName, hepSlice, signal=False ):
0352 """Handles weighted backgrounds"""
0353
0354 events, probs, avgRate = self.weightDict[fileName]
0355
0356 c_light = 299.792458
0357 squash = self.args.squashTime
0358 intTime = self.args.intWindow
0359 squash = self.args.squashTime
0360
0361
0362 while True:
0363 nEvents = int( self.rng.exponential( intTime * avgRate ) )
0364 if nEvents > len(events) :
0365 print("WARNING: Trying to place",nEvents,"events from", fileName,
0366 "but the file doesn't have enough. Rerolling, but this is not physical.")
0367 continue
0368 break
0369 if self.args.verbose : print ( "Placing",nEvents,"events from", fileName )
0370
0371
0372 toPlace = self.rng.choice( a=events, size=nEvents, p=probs, replace=False )
0373
0374
0375 if not squash :
0376 times = list(self.rng.uniform(low=0, high=intTime, size=nEvents))
0377
0378 for inevt in toPlace :
0379 Time = 0
0380 if not squash :
0381 Time = times.pop()
0382
0383
0384
0385 particles, vertices = [], []
0386
0387 for vertex in inevt.vertices:
0388 position=vertex.position
0389 if not squash :
0390
0391 TimeHepmc = c_light*Time
0392 position=position+pyhepmc.FourVector(x=0,y=0,z=0,t=TimeHepmc)
0393 v1=pyhepmc.GenVertex(position)
0394 vertices.append(v1)
0395
0396
0397 for particle in inevt.particles:
0398
0399 momentum, status, pid = particle.momentum, particle.status, particle.pid
0400 p1 = pyhepmc.GenParticle(momentum=momentum, pid=pid, status=status)
0401 p1.generated_mass = particle.generated_mass
0402 particles.append(p1)
0403
0404
0405 if particle.production_vertex.id < 0:
0406 production_vertex=particle.production_vertex.id
0407 vertices[abs(production_vertex)-1].add_particle_out(p1)
0408 hepSlice.add_particle(p1)
0409
0410
0411
0412
0413 if particle.end_vertex:
0414 end_vertex = particle.end_vertex.id
0415 vertices[abs(end_vertex)-1].add_particle_in(p1)
0416
0417
0418 for vertex in vertices:
0419 hepSlice.add_vertex(vertex)
0420
0421 return hepSlice
0422
0423
0424 def poissonTimes( self, mu, endTime ):
0425 """Return an np.array of poisson-distributed times."""
0426
0427
0428 t = 0
0429 ret=np.array([])
0430 while True :
0431 delt = self.rng.exponential( mu )
0432 t = t + delt
0433 if t >= endTime :
0434 break
0435 ret = np.append(ret,t)
0436 if self.args.atLeastOne:
0437 if ret.size==0:
0438 while ret.size==0:
0439 delt = self.rng.exponential( mu )
0440 if delt<endTime:
0441 ret=np.append(ret,delt)
0442 return ret
0443
0444
0445 def nameGen(self):
0446
0447
0448
0449
0450 name = self.args.signalFile
0451 if self.args.nSlices > 0 :
0452 name = name.replace(".hepmc","_n_{}.hepmc".format(self.args.nSlices))
0453
0454 name = "bgmerged_"+name
0455 return name
0456
0457 def squawk(self,i) :
0458 print('Working on slice {}'.format( i+1 ))
0459 print('Resident Memory',psutil.Process().memory_info().rss / 1024 / 1024,"MB")
0460
0461
0462 def fileWriter(combo_cont):
0463 numEvents=len(combo_cont)
0464 name=nameGen(numEvents)
0465 Event_ind=0
0466 with WriterAscii(name) as f:
0467 for i in combo_cont:
0468 i.event_number=Event_ind
0469 Event_ind=Event_ind+1
0470 f.write_event(i)
0471 f.close()
0472
0473
0474
0475
0476 if __name__ == '__main__':
0477
0478 t0 = time.time()
0479 sbm = signal_background_merger()
0480 sbm.merge()
0481 print()
0482 print('==================================================================')
0483 print('Overall running time:',np.round((time.time()-t0)/60.,2),'min')
0484
0485
0486