Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-09-28 07:03:21

0001 #!/usr/bin/env ruby
0002 
0003 # SPDX-License-Identifier: LGPL-3.0-or-later
0004 # Copyright (C) 2023 Christopher Dilks
0005 
0006 require 'optparse'
0007 require 'ostruct'
0008 require 'fileutils'
0009 
0010 # default versions
0011 VersionLatest   = 'epic.23.11.0'
0012 VersionPrevious = 'epic.23.10.0'
0013 
0014 # default CLI options
0015 options = OpenStruct.new
0016 options.version    = VersionLatest
0017 options.energy     = '18x275'
0018 options.locDir     = ''
0019 options.mode       = 's'
0020 options.limit      = 2
0021 options.configFile = ''
0022 options.detector   = 'craterlake'
0023 options.radCor     = false
0024 options.minQ2      = -1
0025 options.maxQ2      = -1
0026 options.numHepmc   = 0
0027 
0028 # global settings
0029 CrossSectionTable = 'datarec/xsec/xsec.dat'
0030 HostURL           = 'https://eics3.sdcc.bnl.gov:9000'
0031 
0032 # helpers
0033 def versionNum(v) # options.version -> version number
0034   v
0035     .split('_').first
0036     .split('.')[1..-1]
0037     .join('.')
0038 end
0039 def ecceQ2range(minQ2,maxQ2) # return file path suffix, for ECCE Q2 ranges
0040   { [1,0]=>'', [1,100]=>'-q2-low', [100,0]=>'-q2-high' }[[minQ2,maxQ2]]
0041 end
0042 
0043 # production specifications, latest first
0044 # PRODUCTION_VERSION => {
0045 #   :comment         => Description about this version
0046 #   :crossSectionID  => Proc(minQ2,maxQ2,radDir) -> label of row of `CrossSectionTable`
0047 #   :releaseSubDir   => Proc() -> Directory for this PRODUCTION_VERSION
0048 #   :energySubDir    => Proc() -> Subdirectory associated to user-specified beam energy
0049 #   :dataSubDir      => Proc(*version dependent*) -> Subdirectory of `:energySubDir`
0050 #   :fileExtension   => File extension (optional, defaults to 'root')
0051 # }
0052 prodSettings = {
0053   'epic.23.11.0' => {
0054     :comment         => 'Pythia 8: high-stats November 2023 production',
0055     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0056     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0057     :energySubDir    => Proc.new { "#{options.energy}" },
0058     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0059   },
0060   'epic.23.10.0' => {
0061     :comment         => 'Pythia 8: high-stats November 2023 production',
0062     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0063     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0064     :energySubDir    => Proc.new { "#{options.energy}" },
0065     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0066   },
0067   'epic.23.09.1' => {
0068     :comment         => 'Pythia 8: high-stats ??? 2023 production',
0069     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0070     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0071     :energySubDir    => Proc.new { "#{options.energy}" },
0072     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0073   },
0074   'epic.23.07.1' => {
0075     :comment         => 'Pythia 8: high-stats July 2023 production',
0076     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0077     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0078     :energySubDir    => Proc.new { "#{options.energy}" },
0079     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0080   },
0081   'epic.23.06.1' => {
0082     :comment         => 'Pythia 8: high-stats June 2023 production',
0083     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0084     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0085     :energySubDir    => Proc.new { "#{options.energy}" },
0086     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0087   },
0088   'epic.23.05.2' => {
0089     :comment         => 'Pythia 8: high-stats May 2023 production',
0090     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0091     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0092     :energySubDir    => Proc.new { "#{options.energy}" },
0093     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0094   },
0095   'epic.23.05.1' => {
0096     :comment         => 'Pythia 8',
0097     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0098     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0099     :energySubDir    => Proc.new { "#{options.energy}" },
0100     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0101   },
0102   'epic.23.03.0_pythia8' => {
0103     :comment         => 'Pythia 8, small sample, 10x100, minQ2=1000 only',
0104     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0105     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0106     :energySubDir    => Proc.new { "#{options.energy}" },
0107     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0108   },
0109   # 'epic.23.03.0_pythia6' => { # FIXME: need cross section for Q2<1 bin
0110   #   :comment         => 'Pythia 6, small sample, 5x41, noradcor only, Q2<1 only',
0111   #   :crossSectionID  => Proc.new { |minQ2,maxQ2,radDir| "pythia6:ep_#{radDir}.#{options.energy}_q2_#{minQ2}_#{maxQ2}" },
0112   #   :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/SIDIS/pythia6" },
0113   #   :energySubDir    => Proc.new { "ep_#{options.energy}" },
0114   #   :dataSubDir      => Proc.new { |radDir| "hepmc_ip6/#{radDir}" },
0115   # },
0116   'epic.23.01.0' => {
0117     :comment         => 'Pythia 8',
0118     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0119     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0120     :energySubDir    => Proc.new { "#{options.energy}" },
0121     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0122   },
0123   'epic.22.11.3' => {
0124     :comment         => 'Pythia 6: high-stats November 2022 production, with & without radiative corrections',
0125     :crossSectionID  => Proc.new { |minQ2,maxQ2,radDir| "pythia6:ep_#{radDir}.#{options.energy}_q2_#{minQ2}_#{maxQ2}" },
0126     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/SIDIS/pythia6" },
0127     :energySubDir    => Proc.new { "ep_#{options.energy}" },
0128     :dataSubDir      => Proc.new { |radDir|
0129       if [options.energy,radDir]==['18x275','noradcor']  # correct for S3 disorganization
0130         "hepmc_ip6"
0131       else
0132         "hepmc_ip6/#{radDir}"
0133       end
0134     },
0135   },
0136   'epic.22.11.2' => {
0137     :comment         => 'Pythia 8: high-stats November 2022 production',
0138     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0139     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/RECO/#{versionNum(options.version)}/epic_#{options.detector}/DIS/NC" },
0140     :energySubDir    => Proc.new { "#{options.energy}" },
0141     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0142   },
0143   'ecce.22.1' => {
0144     :comment         => 'Last ECCE Production, August 2022',
0145     :crossSectionID  => Proc.new { |minQ2,maxQ2| "pythia6:ep-#{options.energy}#{ecceQ2range(minQ2,maxQ2)}" },
0146     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/Campaigns/#{versionNum(options.version)}/SIDIS/pythia6" },
0147     :energySubDir    => Proc.new { "ep-#{options.energy}" },
0148     :dataSubDir      => Proc.new { |minQ2,maxQ2| ecceQ2range(minQ2,maxQ2) }, # (combined with :energySubDir)
0149   },
0150   'athena.deathvalley-v1.0' => {
0151     :comment         => 'ATHENA Proposal production',
0152     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0153     :releaseSubDir   => Proc.new { "S3/eictest/ATHENA/RECO/#{versionNum(options.version)}/DIS/NC" },
0154     :energySubDir    => Proc.new { "#{options.energy}" },
0155     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0156   },
0157   'hepmc.pythia6' => {
0158     :comment         => 'HEPMC files from Pythia 6 for ePIC, with & without radiative corrections',
0159     :crossSectionID  => Proc.new { |minQ2,maxQ2,radDir| "pythia6:ep_#{radDir}.#{options.energy}_q2_#{minQ2}_#{maxQ2}" },
0160     :releaseSubDir   => Proc.new { "S3/eictest/EPIC/EVGEN/SIDIS/pythia6" },
0161     :energySubDir    => Proc.new { "ep_#{options.energy}" },
0162     :dataSubDir      => Proc.new { |radDir| "hepmc_ip6/#{radDir}" },
0163     :fileExtension   => 'hepmc',
0164   },
0165   'hepmc.pythia8' => {
0166     :comment         => 'HEPMC files from Pythia 8, used for ATHENA proposal',
0167     :crossSectionID  => Proc.new { |minQ2| "pythia8:#{options.energy}/minQ2=#{minQ2}" },
0168     :releaseSubDir   => Proc.new { "S3/eictest/ATHENA/EVGEN/DIS/NC" },
0169     :energySubDir    => Proc.new { "#{options.energy}" },
0170     :dataSubDir      => Proc.new { |minQ2| "minQ2=#{minQ2}" },
0171     :fileExtension   => 'hepmc.gz',
0172   },
0173 }
0174 
0175 # additional lists
0176 typicalEnergyList = [
0177   "5x41",
0178   "5x100",
0179   "10x100",
0180   "10x275",
0181   "18x275",
0182 ]
0183 detectorConfigList = [
0184   "arches",
0185   "brycecanyon",
0186   "craterlake",
0187 ]
0188 
0189 # parse options
0190 OptionParser.new do |o|
0191   o.banner = "USAGE: #{$0} [OPTIONS]..."
0192   o.separator ''
0193   o.separator 'OPTIONS:'
0194   o.on("-v", "--version [PRODUCTION_VERSION]",
0195        "Production campaign version",
0196        "Default: #{options.version}",
0197        "Choose one of the following:"
0198       ) do |a|
0199         ver = a
0200         ver = VersionLatest   if a == 'epic.latest'
0201         ver = VersionPrevious if a == 'epic.previous'
0202         unless prodSettings.keys.include? ver
0203           $stderr.puts "ERROR: unknown DETECTOR_VERSION '#{ver}'"
0204           exit 1
0205         end
0206         options.version = ver
0207       end
0208   o.separator ''
0209   o.separator 'epic.latest'.rjust(30)   + '  ::  ' + "Latest production: #{VersionLatest}"
0210   o.separator 'epic.previous'.rjust(30) + '  ::  ' + "Previous production: #{VersionPrevious}"
0211   prodSettings.map{ |k,v| o.separator k.rjust(30) + '  ::  ' + v[:comment] }
0212   o.separator ''
0213   o.separator ' '*20+'NOTE: if the version name starts with \'hepmc\', HEPMC files will be'
0214   o.separator ' '*20+'      downloaded to datagen/ and passed through Delphes fast simulation'
0215   o.separator ''
0216   o.on("-e", "--energy [ENERGY]",
0217        "Energy setting, one of:",
0218        *typicalEnergyList.map{|e|"  #{e}"},
0219        "Default: #{options.energy}",
0220       ) do |a|
0221         $stderr.puts "WARNING: '#{a}' is not a typical beam energy" unless typicalEnergyList.include? a
0222         options.energy = a
0223       end
0224   o.separator ''
0225   o.on("-o", "--output [OUTPUT_DIRECTORY]",
0226        "Local output 'datarec/' subdirectory name",
0227        "Default: [PRODUCTION_VERSION] (-> datarec/[PRODUCTION_VERSION])"
0228       ) do |a|
0229         options.locDir = a
0230       end
0231   o.separator ''
0232   o.on("-m", "--mode [MODE]",
0233        "specify what to do:",
0234        "  s - make config file for streaming from S3",
0235        "  d - download from S3, then make the local config file",
0236        "  c - just make the local config file, for downloaded local files",
0237        "Default: #{options.mode}"
0238       ) do |a|
0239         unless ['s','d','c'].include? a
0240           $stderr.puts "ERROR: unknown MODE '#{a}'"
0241           exit 1
0242         end
0243         options.mode = a
0244       end
0245   o.separator ''
0246   o.on("-l", "--limit [LIMIT]", Integer,
0247        "Specify a limit on the number of files",
0248        "  if an integer>0: take this many per Q2 bin",
0249        "  set to very high number to take all files (a lot of data!)",
0250        "Default: #{options.limit}"
0251       ) do |a|
0252         options.limit = a
0253       end
0254   o.separator ''
0255   o.on("-c", "--config [CONFIG_FILE]",
0256        "Name of config file; if not specified,",
0257        "it will be in datarec/[OUTPUT_DIRECTORY]/"
0258       ) do |a|
0259         options.configFile = a
0260       end
0261   o.separator ''
0262   o.on("-d", "--detector [DETECTOR_VERSION]",
0263        "Detector configuration version, one of:",
0264        *detectorConfigList.map{|e|"  #{e}"},
0265        "Default: #{options.detector}"
0266       ) do |a|
0267         unless detectorConfigList.include? a
0268           $stderr.puts "ERROR: unknown DETECTOR_VERSION '#{a}'"
0269           exit 1
0270         end
0271         options.detector = a
0272       end
0273   o.separator ''
0274   o.on("-r", "--[no-]radcor",
0275        "Decide whether to read radiative-corrected versions:",
0276        "  --radcor:     radiative corrections ON",
0277        "  --no-radcor:  radiative corrections OFF",
0278        "Default: --no-radcor"
0279       ) do |a|
0280         options.radCor = a
0281       end
0282   o.separator ''
0283   o.separator 'Options useful for CI:'
0284   o.on("--minQ2 [MIN_Q2]", Float, "limit to Q2 bins with minQ2=[MIN_Q2]") { |a| options.minQ2 = a }
0285   o.on("--maxQ2 [MAX_Q2]", Float, "limit to Q2 bins with maxQ2=[MAX_Q2]") { |a| options.maxQ2 = a }
0286   o.on("--num-hepmc-events [NUM]", Integer, "limit the number of HEPMC events per file", "(for production version 'hepmc.pythia6' only)") { |a| options.numHepmc = a }
0287   o.separator ''
0288   o.separator ''
0289   o.on_tail("-h", "--help",
0290             "Show this message"
0291            ) do
0292              puts o
0293              exit 2
0294            end
0295 end.parse!( ARGV.length>0 ? ARGV : ['--help'] )
0296 puts "OPTIONS: #{options}"
0297 
0298 # warn about large HEPMC files
0299 if options.version=='hepmc.pythia6' and options.numHepmc<=0
0300   puts """
0301     WARNING: unfortunately, these HEPMC files are very large!!!
0302       Recommendation: limit the number of events per file with the
0303                       `--num-hepmc-events` option
0304   """
0305 end
0306 if options.numHepmc>0 and options.version!='hepmc.pythia6'
0307   $stderr.puts "WARNING: --num-hepmc-events option does not apply to production version '#{options.version}'"
0308 end
0309 
0310 # get release and energy subdirectories, for the user-specified release version
0311 prod = prodSettings[options.version]
0312 prod[:releaseDir] = prod[:releaseSubDir].call
0313 prod[:energyDir]  = prod[:releaseDir] + '/' + prod[:energySubDir].call
0314 puts "Release Dir: #{prod[:releaseDir]}"
0315 puts "Energy Dir:  #{prod[:energyDir]}"
0316 # system "mc tree #{prod[:releaseDir]}"
0317 
0318 # set target `locDir` directory
0319 prod[:targetDir] = "datarec/#{options.locDir.empty? ? options.version : options.locDir}"
0320 
0321 # set the file extension, if specified
0322 ext = prod[:fileExtension].nil? ? 'root' : prod[:fileExtension]
0323 
0324 # settings for handling full simulation output `RECO` vs. fast simulation input `EVGEN` from event generation
0325 readingEvGen = options.version.match? /^hepmc/
0326 delphesCmd   = 's3tools/src/loop_run_delphes.sh'
0327 
0328 ## helper functions
0329 # get a list of files on S3 at `dir`; use `preFilter` to filter out things that are not in the file name (e.g., file size)
0330 def mc_ls(dir, preFilter='')
0331   ls = `mc ls #{dir}`.split(/\n/)
0332   ls = ls.grep_v(preFilter) unless preFilter==''
0333   ls.map{ |line| line.split.last }
0334 end
0335 # download a file from S3 (and do not clobber)
0336 def mc_cp(srcfile,tgtdir)
0337   tgtfile = "#{tgtdir}/#{File.basename srcfile}"
0338   if File.exist? tgtfile
0339     puts "File already exists: #{tgtfile}"
0340   else
0341     system "mc cp '#{srcfile}' #{tgtdir}/"
0342     puts ""
0343   end
0344 end
0345 # get the cross section from `CrossSectionTable`
0346 getCrossSection = Proc.new do |searchPattern|
0347   rows = File.readlines(CrossSectionTable).grep(/^#{searchPattern} /)
0348   if rows.size == 1
0349     rows.first.split[1].to_f
0350   else
0351     $stderr.puts "ERROR: missing cross section for search pattern '#{searchPattern}'"    if rows.size == 0
0352     $stderr.puts "ERROR: duplicated cross section for search pattern '#{searchPattern}'" if rows.size > 1
0353     0.0
0354   end
0355 end
0356 # remove Q2 bins as specified by options.minQ2 and options.maxQ2
0357 cullQ2bins = Proc.new do |q2ranges|
0358   q2ranges.select! do |minQ2,maxQ2|
0359     (minQ2==options.minQ2 or options.minQ2<0) and (maxQ2==options.maxQ2 or options.maxQ2<0)
0360   end
0361 end
0362 
0363 
0364 
0365 # RELEASE VERSION DEPENDENT CODE ###################
0366 # Organize a list of Q2 ranges and associated S3 files for each
0367 # - The file tree layout and naming conventions on S3 varies as a function of productions
0368 # - The following is a chain of `if` statements, grouping together productions with similarly structured file trees
0369 # - Each if-block must fill the following additional elements of `prod`:
0370 #   - prod[:q2ranges]  list of pairs [minQ2,maxQ2] for each Q2 range
0371 #   - prod[:dataDirs]  list of data directories for each Q2 range
0372 #   - prod[:fileLists] list of files for each Q2 range
0373 #   - prod[:radDir]    the name of the radiative corrections directory (if applicable)
0374 ####################################################
0375 
0376 # pattern: "ep_#{energy}/hepmc_ip6/" with Q2 range given in file name as "q2_#{minQ2}_#{maxQ2}"
0377 if [
0378     'epic.23.03.0_pythia6',
0379     'epic.22.11.3',
0380     'hepmc.pythia6',
0381 ].include? options.version
0382   # set source data directory
0383   prod[:radDir] = options.radCor ? 'radcor' : 'noradcor'
0384   dataDir = prod[:energyDir] + '/' + prod[:dataSubDir].call(prod[:radDir])
0385   puts "Data Dir: #{dataDir}"
0386   # set target directory
0387   prod[:targetDir] += "/"+prod[:radDir]
0388   puts "Target Dir: #{prod[:targetDir]}"
0389   # check if there are any files
0390   if mc_ls("#{dataDir} | head").empty?
0391     $stderr.puts "ERROR: no files in this Data Dir"
0392     puts "Available Data Directory Tree (be patient...)"
0393     system "mc tree #{prod[:releaseDir]}"
0394     exit 1
0395   end
0396   # get the Q2 ranges
0397   puts "Getting the full list of files... be patient..."
0398   fullList = mc_ls(dataDir).grep(/\.#{ext}$/)
0399   prod[:q2ranges] = fullList
0400     .map{ |file| file.gsub(/.*_q2_/,'').sub(/_run.*/,'') }
0401     .uniq
0402     .map{ |range| range.split('_').map &:to_i }
0403   cullQ2bins.call prod[:q2ranges]
0404   puts "Q2 ranges: #{prod[:q2ranges]}"
0405   # set dataDirs (the same for each Q2 range)
0406   prod[:dataDirs] = prod[:q2ranges].map{ |q2range| dataDir }
0407   # get a list of files for each Q2 range
0408   puts "File names for each Q2 range:"
0409   prod[:fileLists] = prod[:q2ranges].map do |minQ2, maxQ2|
0410     fileList = fullList
0411       .grep(/q2_#{minQ2}_#{maxQ2}/)
0412       .first(options.limit)
0413     puts "--- #{minQ2} < Q2 < #{maxQ2>0?maxQ2:'inf'}"
0414     fileList.each{ |file| puts "  #{file}" }
0415     fileList
0416   end
0417 
0418 # pattern: "#{energy}/minQ2=#{minQ2}/"
0419 elsif [
0420   'epic.23.11.0',
0421   'epic.23.10.0',
0422   'epic.23.09.1',
0423   'epic.23.07.1',
0424   'epic.23.06.1',
0425   'epic.23.05.2',
0426   'epic.23.05.1',
0427   'epic.23.03.0_pythia8',
0428   'epic.23.01.0',
0429   'epic.22.11.2',
0430   'athena.deathvalley-v1.0',
0431   'hepmc.pythia8',
0432 ].include? options.version
0433   # print target directory
0434   puts "Target Dir: #{prod[:targetDir]}"
0435   # get list of Q2 subdirectories
0436   q2dirList = mc_ls prod[:energyDir]
0437   if q2dirList.empty?
0438     $stderr.puts "ERROR: energy not found"
0439     puts "Available energies"
0440     system "mc ls #{prod[:releaseDir]}"
0441     exit 1
0442   end
0443   puts "Q2 subdirectories: #{q2dirList}"
0444   # get the Q2 ranges
0445   prod[:q2ranges] = q2dirList.map do |dir|
0446     [ dir.split('=').last.sub(/\/$/,'').to_i, 0 ]
0447   end
0448   cullQ2bins.call prod[:q2ranges]
0449   puts "Q2 ranges: #{prod[:q2ranges]}"
0450   # get a list of files for each Q2 range
0451   puts "File names for each Q2 range:"
0452   prod[:dataDirs] = []
0453   prod[:fileLists] = prod[:q2ranges].map do |minQ2, maxQ2|
0454     puts "--- #{minQ2} < Q2 < #{maxQ2>0?maxQ2:'inf'}"
0455     dataDir = prod[:energyDir] + '/' + prod[:dataSubDir].call(minQ2)
0456     prod[:dataDirs] << dataDir
0457     puts "Data Dir: #{dataDir}"
0458     fileList = readingEvGen ?
0459       mc_ls(dataDir,/GiB/) .grep(/\.#{ext}$/) .grep(/vtxfix/) .first(options.limit) :
0460       mc_ls(dataDir)       .grep(/\.#{ext}$/) .first(options.limit)
0461     puts "Files:"
0462     fileList.each{ |file| puts "  #{file}" }
0463     fileList
0464   end
0465   prod[:radDir] = '' # not used
0466 
0467 elsif [
0468   'ecce.22.1'
0469 ].include? options.version
0470   prod[:radDir] = '' # not used
0471   prod[:q2ranges] = mc_ls(prod[:releaseDir]).grep(/#{options.energy}/).grep_v(/Lambda/).map do |dir|
0472     if dir.match? /-q2-high/
0473       [100,0]
0474     elsif dir.match? /-q2-low/
0475       [1,100]
0476     else
0477       [1,0]
0478     end
0479   end
0480   cullQ2bins.call prod[:q2ranges]
0481   prod[:dataDirs] = prod[:q2ranges].map do |minQ2,maxQ2|
0482     prod[:energyDir] + prod[:dataSubDir].call(minQ2,maxQ2)
0483   end
0484   prod[:fileLists] = prod[:dataDirs].map do |dataDir|
0485     mc_ls(dataDir)
0486       .grep(/g4event_eval.root$/)
0487       .first(options.limit)
0488   end
0489 
0490 else
0491   $stderr.puts "\nERROR: production version '#{options.version}' is missing in RELEASE VERSION DEPENDENT part of #{$0}; add it!"
0492   exit 1
0493 end # END RELEASE VERSION DEPENDENT CODE ##################
0494 
0495 
0496 # append the energy to the target directory
0497 prod[:targetDir] += '/'+options.energy
0498 FileUtils.mkdir_p prod[:targetDir]
0499 
0500 # download or stream the files, and build config file lists
0501 localFileTableName = options.configFile.empty? ?
0502   "#{prod[:targetDir]}/files.config.list" :
0503   options.configFile + '.list'
0504 localFileTable = File.open localFileTableName, 'w'
0505 prod[:q2ranges].zip(prod[:dataDirs],prod[:fileLists]).each do |q2range,dataDir,fileList|
0506 
0507   # get the list of file names and set the target directory
0508   minQ2, maxQ2 = q2range
0509   targetDir = "#{prod[:targetDir]}/q2_#{minQ2}_#{maxQ2}"
0510 
0511   # download the RECO files
0512   if options.mode=='d' and !readingEvGen
0513     puts "DOWNLOADING RECO FILES FROM S3..."
0514     FileUtils.mkdir_p targetDir, verbose: true
0515     fileList.each do |file|
0516       mc_cp "#{dataDir}/#{file}", targetDir
0517     end
0518   # or download the EVGEN files
0519   elsif readingEvGen
0520     puts "DOWNLOADING EVGEN FILES FROM S3..."
0521     genDir = targetDir.sub /^datarec/, 'datagen'
0522     delphesCmd += " #{genDir}"
0523     FileUtils.mkdir_p genDir, verbose: true
0524     fileList.each do |file|
0525       if options.version=="hepmc.pythia6" and options.numHepmc>0
0526         # truncate HEPMC file after `options.numHepmc` events (FIXME: could be more efficient)
0527         puts "Downloading #{file}, truncating after #{options.numHepmc} events..."
0528         lineNum = `mc cat #{dataDir}/#{file} | grep -En -m#{options.numHepmc+1} '^E' | tail -n1 | sed 's/:.*//g'`.to_i
0529         puts "  Event #{options.numHepmc} ends on line number #{lineNum-1}; now downloading..."
0530         system "mc head -n #{lineNum-1} #{dataDir}/#{file} > #{genDir}/#{file}"
0531         puts "  ...done"
0532       else
0533         # otherwise get the full file
0534         mc_cp "#{dataDir}/#{file}", genDir
0535       end
0536     end
0537   end
0538 
0539   # get the cross section
0540   crossSection = getCrossSection.call( prod[:crossSectionID].call minQ2, maxQ2, prod[:radDir] )
0541 
0542   # build `localFileTable`
0543   fileList.each do |fileBase|
0544     file = "#{targetDir}/#{fileBase}"
0545     if readingEvGen # if reading EVGEN, be sure to use `.root` extension
0546       file.sub! /\.#{ext}$/, '.root'
0547     elsif options.mode=='s' # if streaming, make URL
0548       file = "#{dataDir.sub(/^S3/,'s3'+HostURL)}/#{fileBase}"
0549     end
0550     localFileTable.puts "#{file} #{crossSection} #{minQ2} #{maxQ2}"
0551   end
0552 
0553 end
0554 localFileTable.close
0555 
0556 # run Delphes, if reading EVGEN
0557 if readingEvGen
0558   puts """
0559 RUNNING DELPHES with the following command:
0560 ```
0561 #{delphesCmd}
0562 ```"""
0563   system delphesCmd
0564 end
0565 
0566 # convert `localFileTable` into a full config file
0567 puts '.'*50
0568 puts "File Config Table: #{localFileTableName}"
0569 system "cat #{localFileTableName}"
0570 puts '\''*50
0571 configFile = localFileTableName.sub(/\.list/,'')
0572 system "s3tools/src/generate-config-file.rb #{configFile} #{options.energy} #{localFileTableName}"