Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-22 08:41:02

0001 import math
0002 import ROOT
0003 import time #optional to make plots not appear behind terminal
0004 
0005 # --- Generation and Assignment ---
0006 class EventSimulator:
0007 
0008     # Variable Dictionary
0009     # Additional particles can be added here, and changing the vertex and momentum constraints can be done here
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 # Lower limit of momentum - 3 G/eV
0014         self.max_mom = 10.0 # Upper limit of momentum - 10 G/eV
0015         self.square_side = 300.0 # Limits vertex region
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     # Prompt User for Particle Species
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     # Randomly Generates x,y,z momentum in a given range
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) #30milliradians
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     # Randomly Generates x,y vertex coordinates in a given range, this range can be editted via self.square_side =... in the variable dictionary
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     # Event details for hepmc output file
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     # New write algorithm tested
0083     # Particle definition: P <barcode> <status> <PDG ID> <px> <py> <pz> <E> <m> <prod_vertex_barcode>
0084     # Vertex definition: V <barcode> <status> [<incoming particles>] <x> <y> <z> <t>
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              # Output the HepMC header
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             #Mixed generation
0115              else:
0116                 # Define particles for the mix
0117                  proton = self.PARTICLE_DATA["3"]
0118                  pion = self.PARTICLE_DATA["1"]
0119                  kaon = self.PARTICLE_DATA["2"]
0120  
0121                 # Define cumulative probabilities for 5:3:1 ratio, edit this ratio as needed.
0122                 # Total parts = 5 + 3 + 1 = 9
0123                  prob_proton = 5.0 / 9.0
0124                  prob_pion_cumulative = prob_proton + (3.0 / 9.0) # Proton + Pion
0125  
0126                  for event_id in range(self.n_events):
0127                      # For each event, randomly select a particle based on the ratio
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             # Output the HepMC tail
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 # Plotting and Fitting
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 # Loads vertex and momentum data by parsing hepmc file
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                 # Format: V barcode status [incoming] x y z t
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                 # Format: P barcode status pdg_id px py pz E m prod_vtx
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     # Plots vertex and momentum histograms
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             #plot styling
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          # 6. Vertex canvas + Fitting
0263          self.c_vertex.cd()
0264          self.c_vertex.Clear() # Clear previous draws on the canvas
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)}" # Unique name for TF2
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() # Optional: Uncomment if you want windows to pop to front
0278              time.sleep(0.1) # Optional: Uncomment for a brief pause after plot update
0279 
0280          # 7. Momentum canvas + Fitting
0281          self.c_momentum.cd()
0282          self.c_momentum.Clear() # Clear previous draws on the canvas
0283          self.plotted_hist_momentum.Draw()
0284          self.c_momentum.Update()
0285          if fit:
0286              m_fit_name = f"fit1d_{ROOT.gRandom.Integer(100000)}" # Unique name for TF1
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() # Optional: Uncomment if you want windows to pop to front
0294              time.sleep(0.1) # Optional: Uncomment for a brief pause after plot update
0295 
0296 
0297 
0298 
0299 
0300 # Allows user to view unfitted or fitted plots continuously
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             #this loop is to make sure if a plot is X'd out, and a replot is attempted, a plot will be generated
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" # Edit name of output file here
0343     n_events = 10000 # Edit the number of events here
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