File indexing completed on 2026-04-22 08:41:02
0001 import math
0002 import ROOT
0003 import time
0004
0005
0006 class EventSimulator:
0007
0008
0009
0010 def __init__(self, output_file, n_events=10000):
0011 self.output_file = output_file
0012 self.n_events = n_events
0013 self.min_mom = 3.0
0014 self.max_mom = 10.0
0015 self.square_side = 300.0
0016 self.rng = ROOT.TRandom3(0)
0017 self.PARTICLE_DATA = {
0018 "1": {"name": "pion", "pdg_id": 211, "mass": 0.13957039},
0019 "2": {"name": "kaon", "pdg_id": 321, "mass": 0.493677},
0020 "3": {"name": "proton", "pdg_id": 2212, "mass": 0.93827208816},
0021 "5": {"name": "Deuteron", "pdg_id": 1000010020, "mass": 1.87561294257},
0022 "6": {"name": "Alpha Particle", "pdg_id": 1000020040, "mass": 3.7273794066},
0023
0024
0025 }
0026
0027
0028 def get_particle_choice(self):
0029 while True:
0030 print("\nOptions:")
0031 print("1. Pion")
0032 print("2. Kaon")
0033 print("3. Proton")
0034 print("4. Mixed Generation")
0035 print("5. Deuteron (D2 nucleus)")
0036 print("6. Alpha (He4 nucleus)")
0037 choice = input("Enter your choice: ")
0038 if choice in self.PARTICLE_DATA:
0039 return self.PARTICLE_DATA[choice]
0040 if choice == "4":
0041 return "mixed"
0042 print("Invalid input. Please enter 1, 2, or 3.")
0043
0044
0045 def generate_momentum(self):
0046 while True:
0047 p = self.rng.Gaus(6.5, 1.0)
0048 if self.min_mom <= p <= self.max_mom:
0049 break
0050 theta = self.rng.Uniform(0, 0.030)
0051 phi = self.rng.Uniform(0, 2 * math.pi)
0052 px = p * math.sin(theta) * math.cos(phi)
0053 py = p * math.sin(theta) * math.sin(phi)
0054 pz = p * math.cos(theta)
0055 return px, py, pz, p
0056
0057
0058 def generate_vertex(self):
0059 while True:
0060 vx = self.rng.Gaus(0, 50)
0061 vy = self.rng.Gaus(0, 50)
0062 if (abs(vx) <= self.square_side / 2) and (abs(vy) <= self.square_side / 2):
0063 break
0064 vz = 0.0
0065 return vx, vy, vz
0066
0067
0068 def write_event(self, f, event_id, px, py, pz, E, vx, vy, vz, pdg_id):
0069 event_str = (
0070 f"E {event_id} 0 0 0 0 0\n"
0071 "F GenEvent 3 0\n"
0072 "F Units 0 0\n"
0073 "F MomentumUnit GEV\n"
0074 "F LengthUnit MM\n"
0075 f"F GenParticle 1 1 {pdg_id} 1 0 0 0 0 {px:.6f} {py:.6f} {pz:.6f} {E:.6f}\n"
0076 f"F GenVertex 1 -1 -1 -1 {vx:.3f} {vy:.3f} {vz:.3f} 0.0\n"
0077 "F GenParticle 1\n"
0078 "F GenVertex 1\n"
0079 )
0080 f.write(event_str)
0081
0082
0083
0084
0085
0086 def write_event_new(self, f, event_id, px, py, pz, E, mass, vx, vy, vz, pdg_id):
0087 event_str = (
0088 f"E {event_id} 1 2\n"
0089 "U GEV MM\n"
0090 f"P 1 0 {pdg_id} {px:.6f} {py:.6f} {pz:.6f} {E:.6f} {mass:.6f} 1\n"
0091 f"V -1 0 [1] {vx:.3f} {vy:.3f} {vz:.3f} 0.0\n"
0092 f"P 2 -1 {pdg_id} {px:.6f} {py:.6f} {pz:.6f} {E:.6f} {mass:.6f} 1\n"
0093 )
0094 f.write(event_str)
0095
0096 def generate_events(self):
0097 choice_result = self.get_particle_choice()
0098
0099 with open(self.output_file, "w") as f:
0100
0101 f.write("HepMC::Version 3.02.02\n")
0102 f.write("HepMC::Asciiv3-START_EVENT_LISTING\n")
0103
0104 if choice_result != "mixed":
0105 particle = choice_result
0106 mass = particle["mass"]
0107 pdg_id = particle["pdg_id"]
0108 for event_id in range(self.n_events):
0109 px, py, pz, p_mag = self.generate_momentum()
0110 vx, vy, vz = self.generate_vertex()
0111 E = math.sqrt(p_mag**2 + mass**2)
0112 self.write_event_new(f, event_id, px, py, pz, E, mass, vx, vy, vz, pdg_id)
0113
0114
0115 else:
0116
0117 proton = self.PARTICLE_DATA["3"]
0118 pion = self.PARTICLE_DATA["1"]
0119 kaon = self.PARTICLE_DATA["2"]
0120
0121
0122
0123 prob_proton = 5.0 / 9.0
0124 prob_pion_cumulative = prob_proton + (3.0 / 9.0)
0125
0126 for event_id in range(self.n_events):
0127
0128 rand_val = self.rng.Uniform(0, 1)
0129
0130 if rand_val < prob_proton:
0131 particle = proton
0132 elif rand_val < prob_pion_cumulative:
0133 particle = pion
0134 else:
0135 particle = kaon
0136
0137 mass = particle["mass"]
0138 pdg_id = particle["pdg_id"]
0139
0140 px, py, pz, p_mag = self.generate_momentum()
0141 vx, vy, vz = self.generate_vertex()
0142 E = math.sqrt(p_mag**2 + mass**2)
0143 self.write_event_new(f, event_id, px, py, pz, E, mass, vx, vy, vz, pdg_id)
0144
0145
0146 f.write("HepMC::Asciiv3-END_EVENT_LISTING\n")
0147 print(f"Wrote {self.n_events} events to {self.output_file}")
0148
0149
0150
0151
0152
0153 class PlotManager:
0154 def __init__(self, input_file):
0155 self.input_file = input_file
0156 self.hist_vertex_raw = ROOT.TH2F("vertex", "Vertex Distribution;X (mm);Y (mm)", 100, -200, 200, 100, -200, 200)
0157 self.hist_momentum_raw = ROOT.TH1F("momentum", "Momentum Magnitude;|p| (GeV);Events", 100, 0, 12)
0158 self.c_vertex = None
0159 self.c_momentum = None
0160 self.plotted_hist_vertex = None
0161 self.plotted_hist_momentum = None
0162 self.fit_vertex = None
0163 self.fit_momentum = None
0164 self.load_data()
0165
0166
0167 def load_data(self):
0168 with open(self.input_file, "r") as f:
0169 for line in f:
0170 parts = line.strip().split()
0171 if not parts:
0172 continue
0173
0174
0175 if parts[0] == 'V' and len(parts) >= 8:
0176 try:
0177 vx = float(parts[4])
0178 vy = float(parts[5])
0179 self.hist_vertex_raw.Fill(vx, vy)
0180 except (ValueError, IndexError):
0181 pass
0182
0183
0184 elif parts[0] == 'P' and len(parts) >= 8:
0185 try:
0186 px = float(parts[4])
0187 py = float(parts[5])
0188 pz = float(parts[6])
0189 p_mag = math.sqrt(px**2 + py**2 + pz**2)
0190 self.hist_momentum_raw.Fill(p_mag)
0191 except (ValueError, IndexError):
0192 pass
0193
0194
0195 def create_canvases(self, vertex_canvas_name, momentum_canvas_name):
0196 if self.c_vertex:
0197 self.c_vertex.Close()
0198 self.c_vertex = None
0199 if self.c_momentum:
0200 self.c_momentum.Close()
0201 self.c_momentum = None
0202
0203 canvas_width = 800
0204 canvas_height = 600
0205 padding = 30
0206 momentum_x_pos = 50
0207 momentum_y_pos = 50
0208 vertex_x_pos = momentum_x_pos
0209 vertex_y_pos = momentum_y_pos + canvas_height + padding
0210
0211 self.c_momentum = ROOT.TCanvas(momentum_canvas_name, "Momentum Plot",
0212 momentum_x_pos, momentum_y_pos,
0213 canvas_width, canvas_height)
0214
0215 self.c_vertex = ROOT.TCanvas(vertex_canvas_name, "Vertex Plot",
0216 vertex_x_pos, vertex_y_pos,
0217 canvas_width, canvas_height)
0218
0219
0220
0221 def plot_histograms(self, fit=False):
0222
0223 if self.plotted_hist_vertex:
0224 self.plotted_hist_vertex.Delete()
0225 self.plotted_hist_vertex = None
0226 if self.plotted_hist_momentum:
0227 self.plotted_hist_momentum.Delete()
0228 self.plotted_hist_momentum = None
0229 if self.fit_vertex:
0230 self.fit_vertex.Delete()
0231 self.fit_vertex = None
0232 if self.fit_momentum:
0233 self.fit_momentum.Delete()
0234 self.fit_momentum = None
0235
0236
0237 vertex_canvas_name = f"c_vertex_{ROOT.gRandom.Integer(1000000)}"
0238 momentum_canvas_name = f"c_momentum_{ROOT.gRandom.Integer(1000000)}"
0239
0240 self.create_canvases(vertex_canvas_name, momentum_canvas_name)
0241
0242
0243 self.plotted_hist_vertex = self.hist_vertex_raw.Clone(f"vertex_clone_{ROOT.gRandom.Integer(1000000)}")
0244 self.plotted_hist_momentum = self.hist_momentum_raw.Clone(f"momentum_clone_{ROOT.gRandom.Integer(1000000)}")
0245
0246
0247
0248
0249
0250 from ROOT import gStyle
0251 gStyle.SetOptFit(0)
0252 gStyle.SetOptStat(0)
0253 self.plotted_hist_momentum.GetXaxis().CenterTitle()
0254 self.plotted_hist_momentum.GetYaxis().CenterTitle()
0255 self.plotted_hist_vertex.GetXaxis().CenterTitle()
0256 self.plotted_hist_vertex.GetYaxis().CenterTitle()
0257 self.plotted_hist_vertex.GetYaxis().SetTitleOffset(0.9)
0258
0259
0260
0261
0262
0263 self.c_vertex.cd()
0264 self.c_vertex.Clear()
0265 self.plotted_hist_vertex.Draw("COLZ")
0266 self.c_vertex.Update()
0267 if fit:
0268 v_fit_name = f"fit2d_{ROOT.gRandom.Integer(100000)}"
0269 self.fit_vertex = ROOT.TF2(v_fit_name, "[0]*exp(-0.5*((x/[1])**2 + (y/[2])**2))", -150, 150, -150, 150)
0270 self.fit_vertex.SetParameters(1, 50, 50)
0271 self.plotted_hist_vertex.Fit(self.fit_vertex, "RS")
0272 chi2 = self.fit_vertex.GetChisquare()
0273 ndf = self.fit_vertex.GetNDF()
0274 reduced_chi2 = chi2 / ndf if ndf > 0 else float('inf')
0275 print(f"Vertex Fit Reduced χ² = {reduced_chi2:.3f}")
0276 self.c_vertex.Update()
0277 self.c_vertex.RaiseWindow()
0278 time.sleep(0.1)
0279
0280
0281 self.c_momentum.cd()
0282 self.c_momentum.Clear()
0283 self.plotted_hist_momentum.Draw()
0284 self.c_momentum.Update()
0285 if fit:
0286 m_fit_name = f"fit1d_{ROOT.gRandom.Integer(100000)}"
0287 self.fit_momentum = ROOT.TF1(m_fit_name, "gaus", 0, 12)
0288 self.plotted_hist_momentum.Fit(self.fit_momentum, "RS")
0289 chi2 = self.fit_momentum.GetChisquare()
0290 ndf = self.fit_momentum.GetNDF()
0291 print(f"Momentum Fit Reduced χ²/NDF = {chi2 / ndf:.3f}")
0292 self.c_momentum.Update()
0293 self.c_momentum.RaiseWindow()
0294 time.sleep(0.1)
0295
0296
0297
0298
0299
0300
0301 def interactive_plot(self):
0302 while True:
0303 print("\nPlot Options:")
0304 print("1. Unfitted Distributions")
0305 print("2. Fitted Distributions")
0306 print("0. Exit")
0307 choice = input("Enter your choice: ")
0308
0309
0310 if choice == "1":
0311 self.plot_histograms(fit=False)
0312 elif choice == "2":
0313 self.plot_histograms(fit=True)
0314 elif choice == "0":
0315 print("Exiting...")
0316 if self.plotted_hist_vertex:
0317 self.plotted_hist_vertex.Delete()
0318 self.plotted_hist_vertex = None
0319 if self.plotted_hist_momentum:
0320 self.plotted_hist_momentum.Delete()
0321 self.plotted_hist_momentum = None
0322 if self.fit_vertex:
0323 self.fit_vertex.Delete()
0324 self.fit_vertex = None
0325 if self.fit_momentum:
0326 self.fit_momentum.Delete()
0327 self.fit_momentum = None
0328
0329 if self.c_vertex:
0330 self.c_vertex.Close()
0331 self.c_vertex = None
0332 if self.c_momentum:
0333 self.c_momentum.Close()
0334 self.c_momentum = None
0335
0336 ROOT.gSystem.ProcessEvents()
0337 break
0338 else:
0339 print("Invalid input. Please enter 0, 1, or 2.")
0340
0341 def main():
0342 output_file = "flat_particle_ascii.hepmc"
0343 n_events = 10000
0344
0345 simulator = EventSimulator(output_file, n_events)
0346 simulator.generate_events()
0347
0348 plotter = PlotManager(output_file)
0349 plotter.interactive_plot()
0350
0351 if __name__ == "__main__":
0352 main()
0353
0354
0355
0356
0357
0358
0359
0360
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371
0372
0373
0374
0375