Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-01 07:56:15

0001 #!/usr/bin/env ruby
0002 # Copyright 2023, Christopher Dilks
0003 # Subject to the terms in the LICENSE file found in the top-level directory.
0004 
0005 require 'optparse'
0006 require 'ostruct'
0007 require 'fileutils'
0008 
0009 ###################################
0010 # constants:
0011 EtaTestValues = {
0012   :ideal => 2.0,
0013   :min   => 1.6,
0014   :max   => 3.5,
0015 }
0016 IdealEnergy   = 12.0 # [GeV]
0017 IdealParticle = 'pi+'
0018 ###################################
0019 
0020 # setup
0021 # ---------------------------------------------------
0022 
0023 # default opt
0024 opt = OpenStruct.new
0025 opt.sim_mode      = ''
0026 opt.sim_file      = 'out/sim.edm4hep.root'
0027 opt.rec_file      = 'out/rec.edm4hep.root'
0028 opt.ana_file      = 'out/ana.edm4hep.root'
0029 opt.run_sim       = true
0030 opt.run_rec       = true
0031 opt.run_ana       = true
0032 opt.run_rec_down  = false
0033 opt.run_ana_down  = false
0034 opt.num_events    = 10
0035 opt.benchmark_exe = 'benchmark_rich_reconstruction'
0036 opt.algos         = Array.new
0037 opt.plots         = Array.new
0038 opt.verbosity     = 0
0039 opt.dry_run       = false
0040 opt.using_ci      = false
0041 
0042 # available simulation modes
0043 avail_sim_modes = [
0044   'fixedEtaIdeal',
0045   'fixedEtaMin',
0046   'fixedEtaMax',
0047 ]
0048 
0049 # parse options
0050 required_set = false
0051 OptionParser.new do |o|
0052   o.banner = "USAGE: #{$0} [OPTIONS]..."
0053   o.separator ''
0054   o.separator 'required options, one of either:'.upcase
0055   o.separator ''
0056   o.on("-r", "--rec-only", "Run only the reconstruction, then the analysis benchmark") do |a|
0057     opt.run_rec_down = true
0058     required_set = true
0059   end
0060   o.separator ''
0061   o.on("-b", "--ana-only", "Run only the analysis benchmark") do |a|
0062     opt.run_ana_down = true
0063     required_set = true
0064   end
0065   o.separator ''
0066   o.on("-s", "--sim-mode [SIMULATION_MODE]", "Run the simulation, reconstruction, and analysis",
0067        "[SIMULATION_MODE] must be one of:") do |a|
0068     unless avail_sim_modes.include? a
0069       $stderr.puts "ERROR: unknown simulation mode '#{a}'"
0070       exit 1
0071     end
0072     opt.sim_mode = a
0073     required_set = true
0074   end
0075   avail_sim_modes.each{ |it| o.separator ' '*40+it }
0076   o.separator ''
0077   o.separator 'optional options:'.upcase
0078   o.separator ''
0079   o.on("--sim-file [FILE]", "simulation file name",     "default = #{opt.sim_file}") { |a| opt.sim_file=a }
0080   o.on("--rec-file [FILE]", "reconstruction file name", "default = #{opt.rec_file}") { |a| opt.rec_file=a }
0081   o.on("--ana-file [FILE]", "analysis file name",       "default = #{opt.ana_file}") { |a| opt.ana_file=a }
0082   o.separator ''
0083   o.on("--[no-]run-sim", "simulation on/off",         "default = #{opt.run_sim ? 'on' : 'off'}") { |a| opt.run_sim=a }
0084   o.on("--[no-]run-rec", "reconstruction on/off",     "default = #{opt.run_rec ? 'on' : 'off'}") { |a| opt.run_rec=a }
0085   o.on("--[no-]run-ana", "analysis benchmark on/off", "default = #{opt.run_ana ? 'on' : 'off'}") { |a| opt.run_ana=a }
0086   o.separator ''
0087   o.on("-n", "--num-events [NUM_EVENTS]", Integer, "Number of events", "default = #{opt.num_events}") { |a| opt.num_events=a }
0088   o.on("-a", "--algos [ALGORITHMS]...", Array, "List of analysis algorithms to run; default = all",
0089        "delimit by commas, no spaces",
0090        "for more info, run:  #{opt.benchmark_exe}") { |a| opt.algos=a }
0091   o.on("-p", "--plots [PLOTS]...",      Array, "List of plots to draw", 
0092        "delimit by commas, no spaces",
0093        "default = all") { |a| opt.plots=a }
0094   o.on("-x", "--benchmark-exe [EXECUTABLE]", "benchmark executable",
0095        "default = #{opt.benchmark_exe} (from $PATH)") { |a| opt.benchmark_exe=a}
0096   o.separator ''
0097   o.on("-v", "--verbose", "Increase verbosity (-vv for more verbose)") { |a| opt.verbosity+=1 }
0098   o.on("-d", "--dry-run", "Dry run (just print commands)") { |a| opt.dry_run=true }
0099   o.on("--ci", "output plots to ./results, for CI artifact collection") { |a| opt.using_ci=true }
0100   o.separator ''
0101   o.on_tail("-h", "--help", "Show this message") do
0102     puts o
0103     exit 2
0104   end
0105 end.parse!(ARGV.length>0 ? ARGV : ['--help'])
0106 
0107 # print options
0108 puts 'settings: {'.upcase
0109 opt.each_pair { |k,v| puts "#{k.to_s.rjust(20)} => #{v}" }
0110 puts '}'
0111 
0112 # check for required options
0113 unless required_set
0114   $stderr.puts "ERROR: required options have not been set"
0115   $stderr.puts "run '#{$0} --help' for guidance"
0116   exit 1
0117 end
0118 
0119 # figure out which steps to run
0120 run_step = { :sim=>false, :rec=>false, :ana=>false, }
0121 if opt.run_ana_down
0122   run_step[:ana] = true
0123 elsif opt.run_rec_down
0124   run_step[:rec] = true
0125   run_step[:ana] = true
0126 else
0127   run_step[:sim] = opt.run_sim
0128   run_step[:rec] = opt.run_rec
0129   run_step[:ana] = opt.run_ana
0130 end
0131 puts "steps to run: #{run_step}"
0132 
0133 # get compact file
0134 if ENV['DETECTOR_PATH'].nil? or ENV['DETECTOR_CONFIG'].nil?
0135   $stderr.puts "ERROR: unknown DETECTOR_PATH or DETECTOR_CONFIG"
0136   exit 1
0137 end
0138 compact_file = "#{ENV['DETECTOR_PATH']}/#{ENV['DETECTOR_CONFIG']}.xml"
0139 
0140 
0141 # simulation command generators
0142 # ---------------------------------------------------
0143 def theta2xyz(theta)
0144   [ Math.sin(theta), 0.0, Math.cos(theta) ]
0145 end
0146 
0147 def eta2theta(eta)
0148   2 * Math.atan(Math.exp(-eta))
0149 end
0150 
0151 
0152 # fixed angle particle gun
0153 simulate_fixed_angle = Proc.new do |theta, energy, particle|
0154   [
0155     "npsim",
0156     "--runType batch",
0157     "--compactFile #{compact_file}",
0158     "--outputFile #{opt.sim_file}",
0159     "--part.userParticleHandler=''",  # allow opticalphotons in MC particles
0160     "--enableGun",
0161     "--numberOfEvents #{opt.num_events}",
0162     "--gun.particle #{particle}",
0163     "--gun.energy #{energy}*GeV",
0164     "--gun.direction '(#{theta2xyz(theta).join ", "})'",
0165   ]
0166 end
0167 
0168 
0169 # define simulation command
0170 # ---------------------------------------------------
0171 sim_cmd = Array.new
0172 case opt.sim_mode
0173 when /^fixedEta/
0174   key       = opt.sim_mode.sub('fixedEta','').downcase.to_sym
0175   fixed_eta = EtaTestValues[key]
0176   if fixed_eta.nil?
0177     $stderr.puts "ERROR: EtaTestValues[#{key}] is not defined"
0178     exit 1
0179   end
0180   fixed_theta = eta2theta fixed_eta
0181   puts """Simulating fixed-eta events:
0182   - eta      = #{fixed_eta}
0183   - theta    = #{180.0 * fixed_theta / Math::PI} degrees
0184   - energy   = #{IdealEnergy} GeV
0185   - particle = #{IdealParticle}
0186   """
0187   sim_cmd = simulate_fixed_angle.call fixed_theta, IdealEnergy, IdealParticle
0188 else
0189   exit 1 if run_step[:sim]
0190 end
0191 
0192 
0193 # define reconstruction command
0194 # ---------------------------------------------------
0195 output_collections = [
0196   "DRICHHits",
0197   "MCParticles",
0198   "DRICHRawHits",
0199   "DRICHRawHitsAssociations",
0200   "DRICHAerogelTracks",
0201   "DRICHGasTracks",
0202   "DRICHAerogelIrtCherenkovParticleID",
0203   "DRICHGasIrtCherenkovParticleID",
0204   "DRICHMergedIrtCherenkovParticleID",
0205   "ReconstructedChargedParticleAssociationsWithDRICHPID",
0206 ]
0207 recon_cmd = [
0208   'eicrecon',
0209   "-Ppodio:output_collections=\"#{output_collections.join ','}\"",
0210   '-Pjana:nevents="0"',
0211   '-Pjana:debug_plugin_loading="1"',
0212   '-Pacts:MaterialMap="calibrations/materials-map.cbor"',
0213   "-Ppodio:output_file=\"#{opt.rec_file}\"",
0214   '-PDRICH:DRICHIrtCherenkovParticleID:cheatPhotonVertex=true',  # allow knowledge of true photons, for accurate residual determination
0215   opt.sim_file,
0216 ]
0217 
0218 
0219 # define analysis benchmark command
0220 # ---------------------------------------------------
0221 analysis_cmd = [
0222   opt.benchmark_exe,
0223   "-i #{opt.rec_file}",
0224   "-o #{opt.ana_file}",
0225 ]
0226 analysis_cmd.append "-a #{opt.algos.join ' '}" if opt.algos.size > 0
0227 analysis_cmd.append '-' + 'v'*opt.verbosity if opt.verbosity > 0
0228 
0229 # define analysis draw command
0230 # ---------------------------------------------------
0231 draw_cmd = [
0232   "#{__dir__}/draw_benchmark.py",
0233   "-i #{opt.ana_file}",
0234   "-o #{opt.using_ci ? "results/#{opt.sim_mode}" : opt.ana_file.gsub(/edm4hep.root$/,"plots")}"
0235 ]
0236 
0237 # execute commands
0238 # ---------------------------------------------------
0239 
0240 # proc: execute a command; raise runtime exception if failure (exit nonzero)
0241 exe = Proc.new do |cmd_args, name, step|
0242   if run_step[step]
0243     case step
0244     when :sim
0245       FileUtils.mkdir_p File.dirname(opt.sim_file)
0246     when :rec
0247       FileUtils.mkdir_p File.dirname(opt.rec_file)
0248     when :ana
0249       FileUtils.mkdir_p File.dirname(opt.ana_file)
0250     end
0251     cmd = cmd_args.join ' '
0252     puts "benchmark #{name} command:".upcase
0253     cmd_args.each_with_index do |arg,i|
0254       line = i==0 ? '' : '  '
0255       line += arg
0256       line += ' \\' unless i+1==cmd_args.size
0257       puts line
0258     end
0259     unless opt.dry_run
0260       puts '-'*50
0261       puts "#{name} execution:".upcase
0262       system cmd or raise "benchmark #{name} failed!".upcase
0263     end
0264     puts '-'*50
0265   end
0266 end
0267 puts '-'*50
0268 
0269 # execute the commands
0270 exe.call sim_cmd,      'simulation',     :sim
0271 exe.call recon_cmd,    'reconstruction', :rec
0272 exe.call analysis_cmd, 'analysis',       :ana
0273 exe.call draw_cmd,     'draw',           :ana