Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-09 08:37:06

0001 #ifdef __MACH__
0002 #include <mach/mach.h>
0003 #endif
0004 
0005 #include <iostream>
0006 #include <fstream>
0007 #include <string>
0008 #include <random>
0009 #include <ctime>
0010 #include <cstdlib>
0011 #include <vector>
0012 #include <algorithm>
0013 #include <numeric>
0014 #include <stdexcept>
0015 #include <chrono>
0016 #include <cmath>
0017 #include <random>
0018 #include <tuple>
0019 #include <unistd.h>
0020 #include <sys/resource.h>
0021 
0022 #include <HepMC3/ReaderFactory.h>
0023 #include <HepMC3/ReaderRootTree.h>
0024 #include <HepMC3/WriterAscii.h>
0025 #include "HepMC3/WriterRootTree.h"
0026 #include "HepMC3/GenRunInfo.h"
0027 #include "HepMC3/GenEvent.h"
0028 #include "HepMC3/Print.h"
0029 
0030 #include "argparse/argparse.hpp"
0031 
0032 using std::cout;
0033 using std::cerr;
0034 using std::endl;
0035 using std::string;
0036 
0037 #define _LITERAL_TO_STRING(s) #s
0038 #define _AS_STRING(s) _LITERAL_TO_STRING(s)
0039 const char* hepmc_merger_version = _AS_STRING(HEPMC_MERGER_VERSION_FULL);
0040 #undef _LITERAL_TO_STRING
0041 #undef _AS_STRING
0042 
0043 // =============================================================
0044 /**
0045     Combine signal and up to four background HEPMC files.
0046     
0047     Typical usage:
0048     ./SignalBackgroundMerger --signalFile dis.hepmc3.tree.root --signalFreq 0 \
0049             --bgFile hgas.hepmc3.tree.root 2000 0 2000 \
0050         --bgFile egastouschk.hepmc3.tree.root 20 0 3000 \
0051             --bgFile egascouloumb.hepmc3.tree.root 20 0 4000 \
0052         --bgFile egasbrems.hepmc3.tree.root 20 0 5000 \
0053             --bgFile synrad.hepmc3.tree.root 25 0 6000
0054 **/    
0055 
0056 struct BackgroundConfig {
0057     std::string file;
0058     double frequency=0;
0059     long long skip=0;
0060     int status=0;
0061 } ;
0062 
0063 class SignalBackgroundMerger {
0064 
0065 private:
0066   // more private data at the end; pulling these more complicated objects up for readability
0067   std::shared_ptr<HepMC3::Reader> sigAdapter;
0068   double sigFreq = 0;
0069   int sigStatus = 0;
0070   std::map<std::string, std::shared_ptr<HepMC3::Reader>> freqAdapters;
0071   std::map<std::string, double> freqs;
0072   std::map<std::string, int> baseStatuses;
0073 
0074   std::map<std::string,
0075       std::tuple<std::vector<HepMC3::GenEvent>,
0076               std::piecewise_constant_distribution<>,
0077               double>
0078        > weightDict;
0079 
0080   // just keep count of some numbers, could be more sophisticated
0081   typedef struct{
0082     long eventCount;
0083     long particleCount;
0084   } stats;
0085   std::map<std::string, stats > infoDict;
0086 
0087 public:
0088 
0089   SignalBackgroundMerger(int argc, char* argv[]) {
0090     auto t0 = std::chrono::high_resolution_clock::now();
0091 
0092     // Parse arguments, print banner, open files, initialize rng
0093     digestArgs(argc, argv);
0094     rng.seed( rngSeed );
0095     banner();
0096     if (outputFile != "" ) {
0097       outputFileName = outputFile;
0098     } else {
0099       outputFileName = nameGen();
0100     }
0101     std::cout << "\n==================================================================\n";
0102     cout << "Writing to " << outputFileName << endl;
0103 
0104     PrepData ( signalFile, signalFreq, signalSkip, signalStatus, true );
0105     for (const auto& bg : backgroundFiles) {
0106     PrepData ( bg.file, bg.frequency, bg.skip, bg.status, false );
0107     }
0108     
0109     
0110     auto t1 = std::chrono::high_resolution_clock::now();
0111     std::cout << "Initiation time: " << std::round(std::chrono::duration<double, std::chrono::seconds::period>(t1 - t0).count()) << " sec" << std::endl;
0112     std::cout << "\n==================================================================\n" << std::endl;
0113 
0114   }
0115 
0116   // Helper to parse raw strings into BackgroundConfig structs
0117   std::vector<BackgroundConfig>
0118   parse_backgrounds(const std::vector<std::string> &raw_args_list) {
0119     std::vector<BackgroundConfig> backgrounds;
0120     auto is_pure_integer = [](const std::string &str) {
0121       if (str.empty()) return false;
0122       for (char c : str) {
0123         if (!std::isdigit(c)) return false;
0124       }
0125       return true;
0126     };
0127       
0128     // Group strings into sets of 2-4 arguments per background
0129     for (size_t i = 0; i < raw_args_list.size();) {
0130       // Determine how many arguments this background has
0131       size_t args_count = 2; // minimum
0132 
0133       // Look ahead to see if next strings can be parsed as numbers (skip/status)
0134       if (i + 2 < raw_args_list.size() && is_pure_integer(raw_args_list[i + 2])) {
0135           args_count = 3;
0136 
0137           if (i + 3 < raw_args_list.size() && is_pure_integer(raw_args_list[i + 3])) {
0138               args_count = 4;
0139           }
0140       }
0141 
0142       // Ensure we don't go beyond the vector bounds
0143       if (i + args_count > raw_args_list.size()) {
0144         args_count = raw_args_list.size() - i;
0145       }
0146 
0147       if (args_count < 2) {
0148         throw std::runtime_error("Background file " +
0149                                  std::to_string(backgrounds.size()) +
0150                                  " must have at least 2 arguments");
0151       }
0152 
0153       try {
0154         BackgroundConfig bg;
0155         bg.file = raw_args_list[i];
0156         bg.frequency = std::stod(raw_args_list[i + 1]);
0157         bg.skip = (args_count > 2) ? std::stoll(raw_args_list[i + 2]) : 0;
0158         bg.status = (args_count > 3) ? std::stoi(raw_args_list[i + 3]) : 0;
0159         backgrounds.push_back(bg);
0160       } catch (const std::exception &e) {
0161         throw std::runtime_error("Error parsing background file " +
0162                                  std::to_string(backgrounds.size()) + ": " +
0163                                  e.what());
0164       }
0165 
0166       i += args_count;
0167     }
0168 
0169     return backgrounds;
0170   }
0171 
0172   void merge(){
0173     auto t1 = std::chrono::high_resolution_clock::now();
0174 
0175     // Populate run-level metadata
0176     auto runInfo = std::make_shared<HepMC3::GenRunInfo>();
0177 
0178     runInfo->add_attribute("hepmc_merger_version",
0179         std::make_shared<HepMC3::StringAttribute>(hepmc_merger_version));
0180     runInfo->add_attribute("hepmc_merger_signal_file",
0181         std::make_shared<HepMC3::StringAttribute>(signalFile));
0182     runInfo->add_attribute("hepmc_merger_signal_frequency_kHz",
0183         std::make_shared<HepMC3::DoubleAttribute>(signalFreq));
0184     runInfo->add_attribute("hepmc_merger_signal_skip",
0185         std::make_shared<HepMC3::IntAttribute>(signalSkip));
0186 
0187     std::string bgFiles, bgFreqs, bgAvgRates, bgSkips;
0188     for (size_t bi = 0; bi < backgroundFiles.size(); ++bi) {
0189       if (bi) { bgFiles += ";"; bgFreqs += ";"; bgAvgRates += ";"; bgSkips += ";"; }
0190       bgFiles += backgroundFiles[bi].file;
0191       bgFreqs += std::to_string(backgroundFiles[bi].frequency);
0192       bgSkips += std::to_string(backgroundFiles[bi].skip);
0193       double avgRate = 0.0;
0194       if (backgroundFiles[bi].frequency <= 0.0) {
0195         auto it = weightDict.find(backgroundFiles[bi].file);
0196         if (it != weightDict.end())
0197           avgRate = std::get<2>(it->second) * 1e6; // GHz -> kHz
0198       }
0199       bgAvgRates += std::to_string(avgRate);
0200     }
0201     runInfo->add_attribute("hepmc_merger_background_files",
0202         std::make_shared<HepMC3::StringAttribute>(bgFiles));
0203     runInfo->add_attribute("hepmc_merger_background_frequencies_kHz",
0204         std::make_shared<HepMC3::StringAttribute>(bgFreqs));
0205     runInfo->add_attribute("hepmc_merger_background_avg_rates_kHz",
0206         std::make_shared<HepMC3::StringAttribute>(bgAvgRates));
0207     runInfo->add_attribute("hepmc_merger_background_skips",
0208         std::make_shared<HepMC3::StringAttribute>(bgSkips));
0209 
0210     runInfo->add_attribute("hepmc_merger_integration_window_ns",
0211         std::make_shared<HepMC3::DoubleAttribute>(intWindow));
0212     runInfo->add_attribute("hepmc_merger_n_slices",
0213         std::make_shared<HepMC3::IntAttribute>(nSlices));
0214 
0215     // Open output file — pass runInfo to constructor so it is written to the header
0216     std::shared_ptr<HepMC3::Writer> f;
0217     if (rootFormat)
0218       f = std::make_shared<HepMC3::WriterRootTree>(outputFileName, runInfo);
0219     else
0220       f = std::make_shared<HepMC3::WriterAscii>(outputFileName, runInfo);
0221 
0222     // Slice loop
0223     int i = 0;
0224     for (i = 0; i<nSlices; ++i ) {
0225       if (i % 1000 == 0 || verbose ) squawk(i);
0226       auto hepSlice = mergeSlice(i);
0227       if (!hepSlice) {
0228     std::cout << "Exhausted signal source." << std::endl;
0229     break;
0230       }
0231       hepSlice->set_event_number(i);
0232       f->write_event(*hepSlice);
0233     }
0234     std::cout << "Finished all requested slices." << std::endl;
0235 
0236     int slicesDone = i;
0237     auto t2 = std::chrono::high_resolution_clock::now();
0238 
0239     std::cout << "Slice loop time: " << std::round(std::chrono::duration<double, std::chrono::minutes::period>(t2 - t1).count()) << " min" << std::endl;
0240     std::cout << " -- " << std::round(std::chrono::duration<double, std::chrono::microseconds::period>(t2 - t1).count() / i) << " us / slice" << std::endl;
0241 
0242     for (auto info : infoDict) {
0243       std::cout << "From " << info.first << std::endl;
0244       std::cout << "  placed " << info.second.eventCount << " events" << std::endl; 
0245       std::cout << "  --> on average " << std::setprecision(3) << info.second.eventCount / float(nSlices) << std::endl;
0246       std::cout << "  placed " << info.second.particleCount << " final state particles" << std::endl;
0247       std::cout << "  --> on average " << std::setprecision(3) << info.second.particleCount / float(nSlices) << std::endl;
0248       
0249     }
0250 
0251     struct rusage r_usage;
0252     getrusage(RUSAGE_SELF, &r_usage);
0253 
0254     // NOTE: Reported in kB on Linux, bytes in Mac/Darwin
0255     // Could try to explicitly catch __linux__ as well
0256     // Unclear in BSD, I've seen conflicting reports
0257 #ifdef __MACH__
0258     float mbsize = 1024 * 1024;
0259 #else // Linux
0260     float mbsize = 1024;
0261 #endif
0262   
0263 
0264     std::cout << endl << "Maximum Resident Memory " << r_usage.ru_maxrss / mbsize << " MB" << std::endl;
0265     // clean up, close all files
0266     sigAdapter->close();
0267     for (auto& it : freqAdapters) {
0268       it.second->close();
0269     }
0270     f->close();
0271     
0272   }
0273   
0274   // ---------------------------------------------------------------------------
0275   void digestArgs(int argc, char* argv[]) {
0276     // Handle the command line tedium
0277     // ArgumentParser is meant to be used in a single function.
0278     // ArgumentParser internally uses std::string_views,
0279     // references, iterators, etc.
0280     // Many of these elements become invalidated after a copy or move.
0281     argparse::ArgumentParser args ("Merge signal events with up to four background sources.", hepmc_merger_version);
0282     
0283     args.add_argument("-i", "--signalFile")
0284       .default_value(std::string("root://dtn-eic.jlab.org//volatile/eic/EPIC/EVGEN/SIDIS/pythia6-eic/1.0.0/10x100/q2_0to1/pythia_ep_noradcor_10x100_q2_0.000000001_1.0_run1.ab.hepmc3.tree.root"))
0285       .help("Name of the HEPMC file with the signal events");
0286     
0287     args.add_argument("-sf", "--signalFreq")
0288       .default_value(0.0)
0289       .scan<'g', double>()
0290       .help("Signal frequency in kHz. Default is 0 to have exactly one signal event per slice. Set to the estimated DIS rate to randomize.");
0291 
0292     args.add_argument("-S", "--signalSkip")
0293       .default_value(0)
0294     .scan<'i', int>()
0295     .help("Number of signals events to skip. Default is 0.");
0296 
0297     args.add_argument("-St", "--signalStatus")
0298       .default_value(0)
0299     .scan<'i', int>()
0300     .help("Apply shift on particle generatorStatus code for signal. Default is 0. ");
0301 
0302     args.add_argument("-b","--bgFile")
0303       .nargs(2,4)
0304       .append()
0305       .help("Tuple with name of the HEPMC file with background events, background frequency in kHz, number of background events to skip (default 0), shift on particle generatorStatus code (default 0).");
0306     
0307     args.add_argument("-o", "--outputFile")
0308       .default_value(std::string("bgmerged.hepmc3.tree.root"))
0309       .help("Specify the output file name. By default bgmerged.hepmc3.tree.root is used");
0310 
0311     args.add_argument("-r", "--rootFormat")
0312       .default_value(true)
0313       .implicit_value(true)
0314       .help("Use hepmc.root output format, default is true.");
0315     
0316     args.add_argument("-w", "--intWindow")
0317       .default_value(2000.0)
0318       .scan<'g', double>()
0319       .help("Length of the integration window in nanoseconds. Default is 2000.");
0320     
0321     args.add_argument("-N", "--nSlices")
0322       .default_value(10000)
0323       .scan<'i', int>()
0324       .help("Number of sampled time slices ('events'). Default is 10000. If set to -1, all events in the signal file will be used and background files cycled as needed.");
0325     
0326     args.add_argument("--squashTime")
0327       .default_value(false)
0328       .implicit_value(true)
0329       .help("Integration is performed but no time information is associated to vertices.");
0330     
0331     args.add_argument("--rngSeed")
0332       .default_value(0)
0333       .action([](const std::string& value) { return std::stoi(value); })
0334       .help("Random seed, default is None");
0335     
0336     args.add_argument("-v", "--verbose")
0337       .default_value(false)
0338       .implicit_value(true)
0339       .help("Display details for every slice.");
0340 
0341     try {
0342       args.parse_args(argc, argv);
0343     }
0344     catch (const std::runtime_error& err) {
0345       std::cout << err.what() << std::endl;
0346       std::cout << args;
0347       exit(EXIT_FAILURE);
0348     }
0349     // Access arguments using args.get method
0350     signalFile = args.get<std::string>("--signalFile");
0351     signalFreq = args.get<double>("--signalFreq");
0352     signalSkip = args.get<int>("--signalSkip");
0353     signalStatus = args.get<int>("--signalStatus");
0354     backgroundFiles = parse_backgrounds(args.get<std::vector<std::string>>("--bgFile"));
0355     outputFile = args.get<std::string>("--outputFile");
0356     rootFormat = args.get<bool>("--rootFormat");
0357     intWindow  = args.get<double>("--intWindow");
0358     nSlices    = args.get<int>("--nSlices");
0359     squashTime = args.get<bool>("--squashTime");
0360     rngSeed    = args.get<int>("--rngSeed");
0361     verbose    = args.get<bool>("--verbose");
0362 
0363     
0364   }
0365   
0366   // ---------------------------------------------------------------------------
0367   void banner() {
0368     // Print banner
0369     std::cout << "==================================================================" << std::endl;
0370     std::cout << "=== EPIC HEPMC MERGER ===" << std::endl;
0371     std::cout << "authors: Benjamen Sterwerf* (bsterwerf@berkeley.edu), Kolja Kauder** (kkauder@bnl.gov), Reynier Cruz-Torres***" << std::endl;
0372     std::cout << "* University of California, Berkeley" << std::endl;
0373     std::cout << "** Brookhaven National Laboratory" << std::endl;
0374     std::cout << "*** formerly Lawrence Berkeley National Laboratory" << std::endl;
0375     std::cout << "\nFor more information, run \n./signal_background_merger --help" << std::endl;
0376 
0377     std::string statusMessage = "Shifting all particle status codes from this source by ";
0378     std::vector<int> statusList_stable, statusList_decay;
0379 
0380     std::cout << "Number of Slices:" << nSlices << endl;
0381     std::string freqTerm = signalFreq > 0 ? std::to_string(signalFreq) + " kHz" : "(one event per time slice)";
0382     std::string statusTerm = signalStatus > 0 ? statusMessage + std::to_string(signalStatus): "";
0383     if (signalStatus>0){
0384       statusList_stable.push_back(signalStatus+1);
0385       statusList_decay.push_back(signalStatus+2);
0386     }
0387     std::cout << "Signal events file and frequency:\n";
0388     std::cout << "\t- " << signalFile << "\t" << freqTerm << "\n" << statusTerm << "\n";
0389     
0390     std::cout << "\nBackground files and their respective frequencies:\n";
0391 
0392     for (const auto& bg : backgroundFiles) {
0393       if (!bg.file.empty()) {
0394         freqTerm = bg.frequency > 0 ? std::to_string(bg.frequency) + " kHz" : "(from weights)";
0395         statusTerm = bg.status > 0 ? statusMessage + std::to_string(bg.status) : "";
0396         std::cout << "\t- " << bg.file << "\t" << freqTerm << "\n" << statusTerm << "\n";
0397         if (bg.status>0){
0398           statusList_stable.push_back(bg.status+1);
0399           statusList_decay.push_back(bg.status+2);
0400         }
0401       }
0402     }
0403     
0404     auto join = [](const std::vector<int>& vec) {
0405         return std::accumulate(vec.begin(), vec.end(), std::string(),
0406             [](const std::string& a, int b) {
0407                 return a.empty() ? std::to_string(b) : a + " " + std::to_string(b);
0408             });
0409     };
0410     std::string stableStatuses = join(statusList_stable);
0411     std::string decayStatuses = join(statusList_decay);
0412     std::string message = "\n!!!Attention!!!\n To proceed the shifted particles statuses in DD4hep, please add the following options to ddsim:\n"
0413                           "--physics.alternativeStableStatuses=\"" + stableStatuses + 
0414                           "\"  --physics.alternativeDecayStatuses=\"" + decayStatuses + "\"\n";
0415     std::cout << message<<std::endl;           
0416   }
0417   
0418   // ---------------------------------------------------------------------------  
0419   void PrepData(const std::string& fileName, double freq, int skip=0, int baseStatus=0, bool signal=false) {
0420     if (fileName.empty()) return;
0421 
0422     cout << "Prepping " << fileName << endl;
0423     std::shared_ptr<HepMC3::Reader> adapter;
0424     try {
0425       adapter = openReader(fileName);
0426       if (!adapter) {
0427         throw std::runtime_error("Failed to open file");
0428       }
0429     } catch (const std::runtime_error& e) {
0430       std::cerr << "Opening " << fileName << " failed: " << e.what() << std::endl;
0431       exit(EXIT_FAILURE);
0432     }
0433     
0434     infoDict[fileName] = {0,0};
0435 
0436     if (signal) {
0437       sigAdapter = adapter;
0438       sigFreq = freq;
0439       sigStatus = baseStatus;
0440       applySmartSkip(fileName, sigAdapter, skip);
0441       return;
0442     }
0443 
0444     // Now catch the weighted case
0445     if (freq <= 0) {
0446       std::cout << "Reading in all events from " << fileName << std::endl;
0447       std::vector<HepMC3::GenEvent> events;
0448       std::vector<double> weights;
0449 
0450       while(!adapter->failed()) {
0451         HepMC3::GenEvent evt(HepMC3::Units::GEV,HepMC3::Units::MM);
0452         adapter->read_event(evt);
0453 
0454         // remove events with 0 weight - note that this does change avgRate = <weight> (by a little)
0455         if (double w=evt.weight() > 0){
0456           events.push_back(evt);
0457           weights.push_back(evt.weight());
0458           }
0459       }
0460       adapter->close();
0461       
0462       double avgRate = 0.0;
0463       for ( auto w : weights ){ avgRate += w;}
0464       avgRate /= weights.size();
0465       avgRate *= 1e-9; // convert to 1/ns == GHz
0466       std::cout << "Average rate is " << avgRate << " GHz" << std::endl;
0467 
0468       std::vector<int> indices (weights.size());
0469       std::iota (std::begin(indices), std::end(indices), 0); // [ 0 , ... , N ] <- draw randomly from this
0470       
0471       // Replacing python's compact toPlace = self.rng.choice( a=events, size=nEvents, p=probs, replace=False )
0472       // is tricky. Possibly more elegant or faster versions exist,
0473       // https://stackoverflow.com/questions/42926209/equivalent-function-to-numpy-random-choice-in-c
0474       // we'll do it rather bluntly, since the need for this code should go away soon with new SR infrastructure
0475       // https://stackoverflow.com/questions/1761626/weighted-random-numbers
0476       // Normalizing is not necessary for this method 
0477       // for ( auto& w : weights ) {
0478       //    w /= avgRate;
0479       // }
0480       std::piecewise_constant_distribution<> weightedDist(std::begin(indices),std::end(indices),
0481                               std::begin(weights));
0482       weightDict[fileName] = { std::make_tuple(events, weightedDist, avgRate) };
0483 
0484       return;
0485     }
0486 
0487     // Not signal and not weighted --> prepare frequency backgrounds
0488     applySmartSkip(fileName, adapter, skip);
0489     freqAdapters[fileName] = adapter;
0490     freqs[fileName] = freq;
0491     baseStatuses[fileName] = baseStatus;
0492   }
0493 
0494   // ---------------------------------------------------------------------------
0495   /// Open a HepMC3 file. For .root inputs construct ReaderRootTree directly so
0496   /// we can reach its TTree; deduce_reader wraps it in a ReaderPlugin whose
0497   /// underlying Reader* is private and thus not dynamic_cast'able.
0498   std::shared_ptr<HepMC3::Reader> openReader(const std::string& fileName) {
0499     auto endsWith = [](const std::string& s, const std::string& suffix) {
0500       return s.size() >= suffix.size() &&
0501              s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
0502     };
0503     if (endsWith(fileName, ".root")) {
0504       return std::make_shared<HepMC3::ReaderRootTree>(fileName);
0505     }
0506     return HepMC3::deduce_reader(fileName);
0507   }
0508 
0509   // ---------------------------------------------------------------------------
0510   /// Apply skip, wrapping modulo the file's event count for ROOT TTree inputs.
0511   void applySmartSkip(const std::string& fileName,
0512                       std::shared_ptr<HepMC3::Reader>& adapter,
0513                       long long skip) {
0514     if (skip <= 0) { adapter->skip(0); return; }
0515     if (auto rrt = std::dynamic_pointer_cast<HepMC3::ReaderRootTree>(adapter)) {
0516       Long64_t N = rrt->m_tree ? rrt->m_tree->GetEntries() : 0;
0517       if (N > 0) {
0518         long long wrapped = skip % static_cast<long long>(N);
0519         if (wrapped != skip) {
0520           std::cout << "Wrapping skip " << skip << " -> " << wrapped
0521                     << " (mod " << N << " events) for " << fileName << std::endl;
0522         }
0523         adapter->skip(static_cast<int>(wrapped));
0524         return;
0525       }
0526     }
0527     adapter->skip(static_cast<int>(skip));
0528   }
0529 
0530    // ---------------------------------------------------------------------------
0531   bool hasEnding (std::string const &fullString, std::string const &ending) {
0532     if (fullString.length() >= ending.length()) {
0533       return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
0534     } else {
0535       return false;
0536     }
0537   }
0538   
0539   // ---------------------------------------------------------------------------
0540   std::string nameGen() {
0541     // Generate a name for the output file
0542     // It's too simplistic for input with directories
0543     std::string name = signalFile;
0544     if (nSlices > 0) {
0545         size_t pos = name.find(".hepmc");
0546         if (pos != std::string::npos) {
0547       name.replace(pos, 6, "_n_" + std::to_string(nSlices) + ".hepmc");
0548         }
0549     }
0550 
0551     if ( rootFormat && !hasEnding(name,".root")){
0552       name.append(".root");
0553     }
0554     name = "bgmerged_" + name;
0555 
0556     return name;
0557   }
0558 
0559   // ---------------------------------------------------------------------------
0560   void squawk(int i) {
0561 
0562     // More fine-grained info about current usage
0563 #ifdef __MACH__
0564     task_basic_info_data_t info;
0565     mach_msg_type_number_t size = sizeof(info);
0566     kern_return_t kerr = task_info(mach_task_self(),
0567                                    TASK_BASIC_INFO,
0568                                    (task_info_t)&info,
0569                                    &size);
0570 
0571     long memory_usage = -1;
0572     if (kerr == KERN_SUCCESS) {
0573       memory_usage = info.resident_size  / 1024 / 1024;
0574     }
0575 #else // Linux
0576     std::ifstream statm("/proc/self/statm");
0577     long size, resident, share, text, lib, data, dt;
0578     statm >> size >> resident >> share >> text >> lib >> data >> dt;
0579     statm.close();
0580 
0581     long page_size = sysconf(_SC_PAGESIZE);  // in case x86-64 is configured to use 2MB pages
0582     long memory_usage = resident * page_size  / 1024 / 1024 ;    
0583 #endif
0584   
0585     
0586     std::cout << "Working on slice " << i + 1 << std::endl;
0587     std::cout << "Current memory usage: " << memory_usage << " MB" << std::endl;
0588 
0589   }
0590   // ---------------------------------------------------------------------------
0591 
0592   std::unique_ptr<HepMC3::GenEvent> mergeSlice(int i) {
0593     auto hepSlice = std::make_unique<HepMC3::GenEvent>(HepMC3::Units::GEV, HepMC3::Units::MM);
0594     
0595     addFreqEvents(signalFile, sigAdapter, sigFreq, hepSlice, signalStatus, true);
0596     
0597     for (const auto& freqBgs : freqAdapters) {
0598       auto fileName=freqBgs.first;
0599       addFreqEvents(fileName, freqAdapters[fileName], freqs[fileName], hepSlice, baseStatuses[fileName], false);
0600     }
0601     
0602     for (const auto& fileName : weightDict) {
0603       addWeightedEvents(fileName.first, hepSlice, baseStatuses[fileName.first]);
0604     }
0605 
0606     return hepSlice;
0607   };
0608 
0609   // ---------------------------------------------------------------------------
0610 
0611   void addFreqEvents(std::string fileName, std::shared_ptr<HepMC3::Reader>& adapter, const double freq,
0612              std::unique_ptr<HepMC3::GenEvent>& hepSlice, int baseStatus = 0, bool signal = false) {
0613 
0614     // First, create a timeline
0615     // Signals can be different
0616     std::vector<double> timeline;
0617 
0618     std::uniform_real_distribution<> uni(0, intWindow);
0619     if (freq == 0){
0620       if (!signal) {
0621         std::cerr << "frequency can't be 0 for background files" << std::endl;
0622         exit(EXIT_FAILURE);
0623       }
0624       // exactly one signal event, at an arbitrary point
0625       timeline.push_back(uni(rng));
0626     } else {
0627       // Generate poisson-distributed times to place events
0628       timeline = poissonTimes(freq, intWindow);
0629     }
0630     
0631     if ( verbose) std::cout << "Placing " << timeline.size() << " events from " << fileName << std::endl;
0632 
0633     if (timeline.empty()) return;
0634     long particleCount = 0;
0635 
0636     // Insert events at all specified locations
0637     for (double time : timeline) {
0638       if (adapter->failed()) {
0639         if (signal) {
0640           // Exhausted signal events; stop trying to place more.
0641           break;
0642         }
0643         // background file reached its end, reset to the start and retry this slot
0644         std::cout << "Cycling back to the start of " << fileName << std::endl;
0645         adapter->close();
0646         adapter = openReader(fileName);
0647         if (!adapter || adapter->failed()) {
0648           std::cerr << "Failed to reopen " << fileName << " after cycling." << std::endl;
0649           break;
0650         }
0651         // fall through and read event 0 for this timeline entry
0652       }
0653 
0654       HepMC3::GenEvent inevt;
0655       adapter->read_event(inevt);
0656       if (signal && (signalFreq == 0.0)){
0657         hepSlice->weights() = inevt.weights();
0658       }
0659 
0660       if (squashTime) time = 0;
0661       particleCount += insertHepmcEvent( inevt, hepSlice, time, baseStatus, signal);
0662     }
0663 
0664     infoDict[fileName].eventCount += timeline.size();
0665     infoDict[fileName].particleCount += particleCount;
0666 
0667 
0668     return;
0669   }
0670 
0671   // ---------------------------------------------------------------------------
0672 
0673   void addWeightedEvents(std::string fileName, std::unique_ptr<HepMC3::GenEvent>& hepSlice, int baseStatus=0, bool signal = false) {
0674     auto& [events, weightedDist, avgRate ] = weightDict[fileName];
0675 
0676     // How many events? Assume Poisson distribution
0677     int nEvents;
0678     std::poisson_distribution<> d( intWindow * avgRate );
0679 
0680     // Small SR files may not have enough photons (example or test files). Could use them all or reroll
0681     // Choosing the latter, neither is physical
0682     while (true) {
0683       nEvents = d(rng);
0684       if (nEvents > events.size()) {
0685           std::cout << "WARNING: Trying to place " << nEvents << " events from " << fileName
0686                   << " but the file doesn't have enough. Rerolling, but this is not physical." << std::endl;
0687         continue;
0688       }
0689       break;
0690     }
0691 
0692     if (verbose) std::cout << "Placing " << nEvents << " events from " << fileName << std::endl;
0693     
0694     // Get randomized event indices
0695     // Note: Could change to drawing without replacing ( if ( not in toPLace) ...) , not worth the effort
0696     std::vector<HepMC3::GenEvent> toPlace(nEvents);
0697     for ( auto& e : toPlace ){
0698       auto i = static_cast<int> (weightedDist(rng));
0699       e = events.at(i);
0700     }
0701     
0702     // Place at random times
0703     std::vector<double> timeline;
0704     std::uniform_real_distribution<> uni(0, intWindow);
0705     long particleCount = 0;
0706     if (!squashTime) {
0707       for ( auto& e : toPlace ){
0708           double time = squashTime ? 0 : uni(rng);
0709         particleCount += insertHepmcEvent( e, hepSlice, time, baseStatus, signal);
0710       }
0711     }
0712 
0713     infoDict[fileName].eventCount += nEvents;
0714     infoDict[fileName].particleCount += particleCount;
0715 
0716     return;
0717 }
0718 
0719   // ---------------------------------------------------------------------------
0720   long insertHepmcEvent( const HepMC3::GenEvent& inevt,
0721              std::unique_ptr<HepMC3::GenEvent>& hepSlice, double time=0, int baseStatus=0, bool signal = false) {
0722     // Unit conversion
0723     double timeHepmc = c_light * time;
0724     
0725     std::vector<HepMC3::GenParticlePtr> particles;
0726     std::vector<HepMC3::GenVertexPtr> vertices;
0727 
0728     // Stores the vertices of the event inside a vertex container. These vertices are in increasing order
0729     // so we can index them with [abs(vertex_id)-1]
0730     for (auto& vertex : inevt.vertices()) {
0731       HepMC3::FourVector position = vertex->position();
0732       position.set_t(position.t() + timeHepmc);
0733       auto v1 = std::make_shared<HepMC3::GenVertex>(position);
0734       vertices.push_back(v1);
0735     }
0736       
0737     // copies the particles and attaches them to their corresponding vertices
0738     long finalParticleCount = 0;
0739     for (auto& particle : inevt.particles()) {
0740       HepMC3::FourVector momentum = particle->momentum();
0741       int status = particle->status();
0742       if (status == 1 ) finalParticleCount++;
0743       int pid = particle->pid();
0744       status += baseStatus;
0745       auto p1 = std::make_shared<HepMC3::GenParticle> (momentum, pid, status);
0746       p1->set_generated_mass(particle->generated_mass());
0747       particles.push_back(p1);
0748       // since the beam particles do not have a production vertex they cannot be attached to a production vertex
0749       if (particle->production_vertex()->id() < 0) {
0750           int production_vertex = particle->production_vertex()->id();
0751           vertices[abs(production_vertex) - 1]->add_particle_out(p1);
0752           hepSlice->add_particle(p1);
0753       }
0754     
0755       // Adds particles with an end vertex to their end vertices
0756       if (particle->end_vertex()) {
0757           int end_vertex = particle->end_vertex()->id();
0758           vertices.at(abs(end_vertex) - 1)->add_particle_in(p1);    
0759       }
0760     }
0761 
0762     // Adds the vertices with the attached particles to the event
0763     for (auto& vertex : vertices) {
0764       hepSlice->add_vertex(vertex);
0765     }
0766     
0767     return finalParticleCount;
0768   }
0769 
0770   // ---------------------------------------------------------------------------
0771 
0772   std::vector<double> poissonTimes(double mu, double endTime) {
0773     std::exponential_distribution<> exp(mu);
0774     
0775     double t = 0;
0776     std::vector<double> ret;
0777     while (true) {
0778       double delt = exp(rng)*1e6;
0779       // cout << delt <<endl;
0780       t += delt;
0781       if (t >= endTime) {
0782     break;
0783       }
0784       ret.push_back(t);
0785     }
0786     return ret;
0787 }
0788   // ---------------------------------------------------------------------------
0789 
0790   
0791   // private:
0792   std::mt19937 rng;
0793   string signalFile;
0794   double signalFreq;
0795   int signalSkip;
0796   int signalStatus;
0797   std::vector<BackgroundConfig> backgroundFiles;  
0798   string outputFile;
0799   string outputFileName;
0800   bool rootFormat;
0801   double intWindow;
0802   int nSlices; // should be long, but argparse cannot read that
0803   bool squashTime;
0804   int rngSeed;  // should be unsigned, but argparse cannot read that
0805   bool verbose;
0806   
0807   const double c_light = 299.792458; // speed of light = 299.792458 mm/ns to get mm  
0808 };
0809 
0810 // =============================================================
0811 int main(int argc, char* argv[]) {
0812 
0813   auto t0 = std::chrono::high_resolution_clock::now();
0814   // Create an instance of SignalBackgroundMerger
0815   SignalBackgroundMerger sbm (argc, argv);
0816 
0817 
0818   sbm.merge();
0819 
0820   std::cout << "\n==================================================================\n";
0821   std::cout << "Overall running time: " << std::round(std::chrono::duration<double, std::chrono::minutes::period>(std::chrono::high_resolution_clock::now() - t0).count()) << " min" << std::endl;
0822   
0823   return EXIT_SUCCESS;
0824 }