main280

Back to index.

// main280.cc is a part of PYTHIA 8.
// Copyright (C) 2024 Torbjorn Sjostrand.
// PYTHIA is licenced under the GNU GPL v2 or later, see COPYING for details.
// Please respect the MCnet Guidelines, see GUIDELINES for details.

// Author: Christian T Preuss <preuss@uni-wuppertal.de>

// Keywords:
//            Merging
//            Leading order
//            CKKW‑L
//            Hepmc
//            Vincia

// This is a test program to run Pythia merging and  write HepMC2 files,
// written for Vincia sector merging, see the Sector Merging page in
// the online manual.
// It also redirects Pythia output to a log file, so that
// the terminal output is significantly reduced.
// An example command is
//     ./main280 main280.cmnd vbf_h_ output.hepmc
// where main280.cmnd supplies the commands and vbf_h_ specifies the event
// file path that will be auto-completed to the respective jet multiplicity
// (e.g. vbf_h_1.lhe for the 1-jet sample). The last argument specifies the
// output filename in the HepMC2 format.

// Standard packages.
#include <chrono>
#include <ctime>
#include <fstream>

// PYTHIA
#include "Pythia8/Pythia.h"
#include "Pythia8/Vincia.h"

// HepMC
#ifndef HEPMC2
#include "Pythia8Plugins/HepMC3.h"
#else
#include "Pythia8Plugins/HepMC2.h"
#endif

// Conversion factor mb --> pb.
#define MB_TO_PB 1.e9

using namespace Pythia8;
typedef std::chrono::system_clock Clock;
typedef std::chrono::high_resolution_clock HighResClock;

// Little helper function for execution time as nicely formatted string.
string getTimeAsString(double seconds) {
  stringstream ss;
  if (seconds >= 3600.*24.) {
    int days = seconds / (3600*24);
    ss << days << "d ";
    seconds -= days*(3600.*24.);
  }
  if (seconds >= 3600.) {
    int hours = seconds / 3600;
    ss << hours << "h ";
    seconds -= hours*3600.;
  }
  if (seconds >= 60.) {
    int mins = seconds / 60;
    ss << mins << "m ";
    seconds -= mins*60.;
  }
  ss << int(seconds) << "s";
  return ss.str();
}

// Main Program
int main(int argc, char* argv[]) {

  // Check number of command-line arguments.
  if (argc != 4) {
    cerr << " Unexpected number of command-line arguments. \n"
         << " Please specify:\n"
         << "   1) command file\n"
         << "   2) event file path\n"
         << "   3) output file\n"
         << " \n E.g. run \n"
         << "   ./main280 main280.cmnd w+_production_lhc_ output.hepmc\n"
         << " \n Program stopped! " << endl;
    return 1;
  }

  // Check whether input file exists.
  string cmndFile = argv[1];
  ifstream is(cmndFile);
  if (!is) {
    cerr << " File " << cmndFile << " was not found. \n"
         << " Program stopped! " << endl;
    return 1;
  }

  string evtPath = argv[2];
  string hepMCFile = argv[3];

  //------------------------------------------------------------------------
  // PYTHIA
  //------------------------------------------------------------------------

  Pythia pythia;

  // Add mode to generate only sample for specific jet number.
  pythia.settings.addMode("Merging:nJetReq", -1, true, false, -1, 100);

  // Add flag to decide whether to check all event files exist.
  pythia.settings.addFlag("Merging:checkFilesExist", false);

  // Read user settings from file.
  pythia.readFile(cmndFile);

  //------------------------------------------------------------------------

  // Settings.
  int  nEvent          = pythia.settings.mode("Main:numberOfEvents");
  int  nCount          = pythia.settings.mode("Next:numberCount");
  int  nErr            = pythia.settings.mode("Main:timesAllowErrors");
  int  nMerge          = pythia.settings.mode("Merging:nJetMax");
  int  nReq            = pythia.settings.mode("Merging:nJetReq");
  bool vinciaOn        = (pythia.settings.mode("PartonShowers:model") == 2);
  bool checkFilesExist = pythia.settings.flag("Merging:checkFilesExist");
  bool fsrOn           = pythia.settings.flag("PartonLevel:FSR");
  bool isrOn           = pythia.settings.flag("PartonLevel:ISR");
  bool mpiOn           = pythia.settings.flag("PartonLevel:MPI");
  bool hadLvlOn        = pythia.settings.flag("HadronLevel:all");

  //------------------------------------------------------------------------
  // HepMC
  //------------------------------------------------------------------------

  Pythia8::Pythia8ToHepMC toHepMC(hepMCFile);
  toHepMC.set_print_inconsistency(false);
  toHepMC.set_store_pdf(false);
  toHepMC.set_store_proc(false);
  toHepMC.set_store_xsec(false);

  //------------------------------------------------------------------------

  // Quickly check whether all event files exist.
  if (checkFilesExist) {
    bool allFilesExist = true;
    vector<string> missingFiles;
    for (int i(0); i<=nMerge; ++i) {
      string evtFile = evtPath+std::to_string(i)+".lhe";
      ifstream isNow(evtFile);
      if (!isNow) {
        allFilesExist = false;
        missingFiles.push_back(evtFile);
      }
    }
    if (!allFilesExist) {
      cerr << " Error: the following event files are missing:" << endl;
      for (auto& f : missingFiles) cerr << "  " << f << endl;
      cerr << " Please check the event file path you specified." << endl;
      return EXIT_FAILURE;
    }
  }

  //------------------------------------------------------------------------

  // Redirect cout to log file.

  auto now = Clock::now();
  std::time_t now_c = Clock::to_time_t(now);
  struct tm *parts = std::localtime(&now_c);
  string year  = std::to_string(1900 + parts->tm_year);
  string month = std::to_string(1 + parts->tm_mon);
  string day   = std::to_string(parts->tm_mday);
  string hour  = std::to_string(parts->tm_hour);
  string min   = std::to_string(parts->tm_min);
  string sec   = std::to_string(parts->tm_sec);
  string fname = "main280-"+year+"_"+month+"_"+day+"-"
    +hour+"_"+min+"_"+sec+".log";

  std::ofstream outstream(fname);
  std::streambuf* filebuf = outstream.rdbuf();
  std::streambuf* coutbuf = cout.rdbuf();
  cout.rdbuf(filebuf);

  //------------------------------------------------------------------------

  // Cross section estimation run.
  pythia.settings.flag("Merging:doXSectionEstimate", true);

  // Estimates.
  map<int, double> xSecEst;
  map<int, int> nSelected;
  map<int, int> nAccepted;
  map<int, int> lhaStrategy;

  // Switch hadron-level and shower off.
  pythia.settings.flag("PartonLevel:FSR", false);
  pythia.settings.flag("PartonLevel:ISR", false);
  pythia.settings.flag("HadronLevel:all", false);
  pythia.settings.flag("PartonLevel:MPI", false);
  pythia.settings.mode("Next:numberCount", nEvent);

  // Loop over all samples to merge.
  std::clog << "\n *--------  Merging Info  -----------------------------*\n"
            << " |                                                     |\n"
            << " | Starting cross section estimation for up to "
            << setw(1) << nMerge << " jets  |\n"
            << " |                                                     |\n"
            << " *-----------------------------------------------------*\n\n";
  int nDotCntr = nEvent / 40;
  if (nDotCntr == 0) nDotCntr = 1;
  for (int iMerge(nMerge); iMerge >= 0; --iMerge) {

    if (nReq >= 0 && iMerge != nReq) continue;

    // Read input for this subrun and initialise.
    string lheFileNow = evtPath+std::to_string(iMerge)+".lhe";
    pythia.settings.mode("Beams:frameType", 4);
    pythia.settings.word("Beams:LHEF", lheFileNow);
    if(!pythia.init()) {
      cerr << " Pythia failed initialisation in xSec estimation run "
           << iMerge << "." << endl;
      cerr << "\n Check the log file " << fname << " for details" << endl;
      return EXIT_FAILURE;
    }

    // Event loop.
    std::clog << " Estimating cross section for "
         << iMerge << "-jet sample";
    for(int iEvt(0); iEvt<nEvent; ++iEvt){
      if (iEvt % nDotCntr == 0) {
        std::clog << ".";
      }
      // Generate next event.
      if (!pythia.next()) {
        if (pythia.info.atEndOfFile()) break;
        else continue;
      }
    }
    std::clog << endl;

    pythia.stat();

    // Save estimates.
    xSecEst[iMerge] = pythia.info.sigmaGen();
    nSelected[iMerge] = pythia.info.nSelected();
    nAccepted[iMerge] = pythia.info.nAccepted();
    lhaStrategy[iMerge] =  pythia.info.lhaStrategy();
  }

  // Restore settings.
  pythia.settings.flag("Merging:doXSectionEstimate", false);
  pythia.settings.flag("PartonLevel:FSR", fsrOn);
  pythia.settings.flag("PartonLevel:ISR", isrOn);
  pythia.settings.flag("HadronLevel:all", hadLvlOn);
  pythia.settings.flag("PartonLevel:MPI", mpiOn);
  pythia.settings.mode("Next:numberCount", nCount);

  //------------------------------------------------------------------------

  // Merged total cross section.
  double  sigmaTot = 0.;
  double  errorTot = 0.;
  map<int, double> sigmaSample;
  map<int, double> errorSample;

  // Loop over all samples to merge.
  for (int iMerge(nMerge); iMerge >= 0; --iMerge) {

    if (nReq >= 0 && iMerge != nReq) continue;

    // Initialise cross sections.
    sigmaSample[iMerge] = 0.;
    errorSample[iMerge] = 0.;

    // Get normalisation for HepMC output.
    double normhepmc = (abs(lhaStrategy[iMerge]) == 4) ?
      1./(MB_TO_PB * nSelected[iMerge])
      : xSecEst[iMerge]/nAccepted[iMerge];

    // Read input file for this subrun and initialise.
    string lheFileNow = evtPath+std::to_string(iMerge)+".lhe";
    pythia.settings.mode("Beams:frameType", 4);
    pythia.settings.word("Beams:LHEF", lheFileNow);
    if(!pythia.init()) {
      cerr << " Pythia failed initialisation in run " << iMerge << ".\n"
           << "\n Check the log file " << fname << " for details.\n";
      return EXIT_FAILURE;
    }

    // Debugging.
    ShowerModelPtr showerPtr = pythia.getShowerModelPtr();
    shared_ptr<Vincia> vinciaPtr = dynamic_pointer_cast<Vincia>(showerPtr);
    if(vinciaOn && vinciaPtr==nullptr){
      cerr << "Couldn't fetch Vincia pointer in run "
           << nMerge-iMerge+1 << ".\n";
      return EXIT_FAILURE;
    }

    // Event loop.
    int iErr = 0;
    bool breakMerging = false;
    std::clog << "\n *--------  Merging Info  -----------------------------*\n"
              << " |                                                     |\n"
              << " | Starting event loop for ";
    if (iMerge > 0) std::clog << setw(1) << iMerge << "-jet sample";
    else std::clog << "Born sample ";
    std::clog << "                |\n"
              << " |                                                     |\n"
              << " *-----------------------------------------------------*\n";
    auto evtGenStart = HighResClock::now();
    for(int iEvt(0); iEvt<nEvent; ++iEvt){
      if (iEvt != 0 && iEvt % nCount == 0) {
        auto stopNow = HighResClock::now();
        double timeElapsed
          = std::chrono::duration_cast<std::chrono::seconds>
          (stopNow - evtGenStart).count();
        double timeLeft = timeElapsed * double(nEvent-iEvt) / double(iEvt);
        std::clog << endl << " " << iEvt
                  << " events generated  ("
                  << getTimeAsString(timeElapsed) << " elapsed / "
                  << getTimeAsString(timeLeft) << " left)" << endl;
      }

      // Generate next event.
      // Break out of event loop if at end of LHE file
      // or if too many errors appeared.
      if (!pythia.next()) {
        ++iErr;
        if (pythia.info.atEndOfFile()) break;
        if (iErr >= nErr) break;
        else continue;
      }

      // Get CKKW-L weight of current event.
      double evtweight = pythia.info.weight();
      double weight    = pythia.info.mergingWeight();
      evtweight *= weight;

      // Skip zero-weight events.
      if (evtweight == 0.) continue;

      // Add event weight to total cross section.
      sigmaTot += evtweight*normhepmc;
      errorTot += pow2(evtweight*normhepmc);
      sigmaSample[iMerge] += evtweight*normhepmc;
      errorSample[iMerge] += pow2(evtweight*normhepmc);

      // Write HepMC event and set cross section.
      toHepMC.writeNextEvent(pythia);
    }

    // Statistics (printed to terminal).
    cout.rdbuf(coutbuf);
    pythia.stat();
    cout.rdbuf(filebuf);

    if (breakMerging) break;
  }
  errorTot = sqrt(errorTot);

  // Restore cout.
  cout.rdbuf(coutbuf);

  // Print cross section information.
  cout << "\n *--------  XSec Summary  -----------------------------*\n"
       << " |                                                     |\n"
       << " | Exclusive cross sections:                           |\n";
  for (auto it=sigmaSample.begin(); it!=sigmaSample.end(); ++it) {
    if (it->first==0) cout << " |      Born:  ";
    else cout << " |     " << it->first << "-jet:  ";
    cout << setw(8) << scientific
         << setprecision(6) << it->second << " +- "
         << setw(8) << errorSample.at(it->first) << " mb         |\n";
  }
  cout << " |                                                     |\n"
       << " |- - - - - - - - - - - - - - - - - - - - - - - - - - -|\n"
       << " |                                                     |\n"
       << " | Inclusive cross sections:                           |\n";
  for (auto it=xSecEst.begin(); it!=xSecEst.end(); ++it) {
    if (it->first==0) cout << " |      Born:  ";
    else cout << " |     " << it->first << "-jet:  ";
    cout << setw(8) << scientific
         << setprecision(6) << it->second
         << " mb                         |\n";
  }
  cout << " |                                                     |\n"
       << " | CKKW-L merged inclusive cross section:              |\n"
       << " |             " << setw(8) << scientific << setprecision(6)
       << sigmaTot << " +- " << setw(8) << errorTot << " mb         |\n"
       << " |                                                     |\n"
       << " *-----------------------------------------------------*\n\n"
       << "\n Detailed PYTHIA output has been written to the log file "
       << fname << "\n";

  // Done.
  return 0;
}