## main369

Back to index.

    // main369.cc is a part of the PYTHIA event generator.
    // Copyright (C) 2025 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.

    // Authors:
    //            Torbjorn Sjostrand

    // Keywords:
    //            Top
    //            Toponium
    //            Openmp

    // Plot ttbar system properties contrasting six different scenarios:
    // 0 : pure Born baseline.
    // 1 : Coulomb enhancement.
    // 2 : Green's function purely for E > 0.
    // 3 : Green's function, only for the E < 0 part mirrored to E > 0.
    //     Scenario 2 can be added to 3 to give a "complete" scenario.
    // 4 : as 3, but in addition the top mass is reduced to give better <E>.
    // 5 : full Green's for E > and E < 0, the "best bet" scenario.
    // Plots all scenarios in one frame, which may be too much,
    // but also shows how to select and combine as desired.
    // Runs in parallel if Pythia configured with --with-openmp.
    // The same analysis is also provided with main370 but using PythiaParallel.

    // Based on threshold factors according to
    // V. Fadin,  V. Khoze and T. Sjostrand, Z. Phys. C48 (1990) 613.

    #include "Pythia8/Pythia.h"
    using namespace Pythia8;

    // OpenMP includes and user-specifiable number of threads requested.
    // (Will be capped by system maximum. Use -1 to use all available.)
    #ifdef OPENMP
    #include <omp.h>
    const int nThreadsRequested = -1;
    #endif

    //============================================================================

    int main() {

      // Restrict to threshold region only or not.
      bool thresholdOnly = true;

      // Full event generation or only cross-section-oriented studies.
      bool fullEvents = false;

      // Number of events. Fewer in scenarios 3 and 4, since only mirrored part.
      int nEvent       = 1000000;
      int nEventMirror = 0.1 * nEvent;

      // LHC collision energy.
      double eCM = 13000.;

      // Example of possible setup values. Reduced top mass in scenario 4.
      double mt            = 172.5;
      double mtLow         = mt - 3.8;
      double thrWidth      = 10.;
      int    alphasOrder   = 2;
      double alphasValue   = 0.118;
      double ggSingletFrac = 2. / 7.;
      double qqSingletFrac = 0.;

      // Histograms. (Last slot used for E < 0 part of scenario 5.)
      bool separateFrames = true;
      Hist mThrOrig[7], mThrOrigZoom[7],  mHatLow[7];
      for (int scenario = 0; scenario < 7; ++scenario) {
        mThrOrig[scenario].book("original threshold", 100, -20., 80.);
        mThrOrigZoom[scenario].book("original threshold, zoom", 100, -10., 30.);
        mHatLow[scenario].book("ttbar invariant mass, low", 100, 300., 400.);
      }

      // Compare the six scenarios.
      // Automatically run in parallel if Pythia configured with flag
      // --with-openmp.
    #ifdef OPENMP
      int nThreadsSys = omp_get_max_threads();
      int nThreadsMax = min(6, nThreadsSys);
      int nThreads    = nThreadsRequested;
      if (nThreads <= 0 || nThreads > nThreadsMax) nThreads = nThreadsMax;
      cout << " OMP parralelisation using "<<nThreads << " threads."
           << " System maximum = "<<nThreadsSys<<"." << endl;
    #pragma omp parallel for num_threads(nThreads)
    #endif
      for (int scenario = 0; scenario < 6; ++scenario) {

        // Generator.
        Pythia pythia;

        // Feed in desired values.
        int topModel = (scenario < 4) ? scenario : scenario - 1;
        pythia.settings.mode("TopThreshold:model", topModel);
        double mtNow = (scenario == 4) ? mtLow :  mt;
        pythia.particleData.m0(6, mtNow);
        pythia.settings.parm("TopThreshold:width", thrWidth);
        pythia.settings.mode("TopThreshold:alphasOrder", alphasOrder);
        pythia.settings.parm("TopThreshold:alphasValue", alphasValue);
        pythia.settings.parm("TopThreshold:ggSingletFrac", ggSingletFrac);
        pythia.settings.parm("TopThreshold:qqSingletFrac", qqSingletFrac);

        // Process selection and collision energy.
        pythia.readString("Top:gg2ttbar = on");
        pythia.readString("Top:qqbar2ttbar = on");
        pythia.settings.parm("Beams:eCM", eCM);

        // Restrict to threshold region.
        if (thresholdOnly) {
          pythia.readString("PhaseSpace:mHatMin = 300.");
          pythia.readString("PhaseSpace:mHatMax = 400.");
        }
        else pythia.readString("PhaseSpace:mHatMin = 200.");

        // Switch off (most) code parts not relevant here. Reduce printout.
        if (!fullEvents) {
          pythia.readString("PartonLevel:ISR = off");
          pythia.readString("PartonLevel:FSR = off");
          pythia.readString("PartonLevel:MPI = off");
          pythia.readString("HadronLevel:all = off");
        }
        pythia.readString("Next:numberCount = 100000");

        // If Pythia fails to initialize, abort this scenario.
        // Use critical to avoid thread clashes when initializing in parallel.
        bool ok = true;
    #ifdef OPENMP
    #pragma omp critical
    #endif
        {
          cout << " =============================================================="
            "=========\n Initializing PYTHIA for scenario " << scenario
               << " with topModel " << topModel << " and top mass " << mtNow
               << " GeV" << endl;
          ok = pythia.init();
          cout << " Completed initialization of PYTHIA for scenario " << scenario
               << "\n ============================================================"
            "===========\n";
        }
        if (!ok) continue;

        // Begin event loop.
        int nBelow = 0, nBelowgg = 0, nBelowqq = 0, nAbovegg = 0, nAboveqq = 0;
        double eBelow = 0.;
        int nEventNow = (scenario == 3 || scenario == 4) ? nEventMirror : nEvent;
        for (int iEvent = 0; iEvent < nEventNow; ++iEvent) {

          // Generate events.
          if (!pythia.next()) continue;

          // Original threshold value and other properties.
          double eThr = pythia.info.toponiumE;
          if (scenario == 4) eThr -= 2. * (mt - mtLow);
          double mHat = pythia.info.mHat();

          // Histogram threshold value and ttbar invariant mass.
          // Use critical to avoid thread clashes when filling histogram
          // in parallel.
    #ifdef OPENMP
    #pragma omp critical
    #endif
          {
            mThrOrig[scenario].fill( eThr);
            mThrOrigZoom[scenario].fill( eThr);
            mHatLow[scenario].fill( mHat);
            if (scenario == 5 && eThr < 0.) {
              mThrOrig[6].fill( eThr);
              mThrOrigZoom[6].fill( eThr);
              mHatLow[6].fill( mHat);
            }
          }

          // Statistics for below threshold cross sections.
          if (scenario == 3 || scenario == 4 || (scenario == 5 && eThr < 0.)) {
            ++nBelow;
            if (pythia.info.code() == 601) ++nBelowgg;
            else ++nBelowqq;
            eBelow += eThr;
          } else {
            if (pythia.info.code() == 601) ++nAbovegg;
            else ++nAboveqq;
          }

          // End of event loop.
        }

        // Normalization and statistics for this scenario.
        // Use critical to avoid thread clashes.
    #ifdef OPENMP
    #pragma omp critical
    #endif
        {
          cout << "\n ===== End-of-run statistics for scenario " << scenario
               << " with topModel " << topModel << " and top mass " << mtNow
               << " GeV =====" << endl;

          // Normalize histogram to cross section, in pb/GeV.
          double sigmapb = 1e9 * pythia.info.sigmaGen() / nEventNow;
          mThrOrig[scenario]     *= sigmapb;
          mThrOrigZoom[scenario] *= 2.5 * sigmapb;
          mHatLow[scenario]      *= sigmapb;
          if (scenario == 5) {
            mThrOrig[6]          *= sigmapb;
            mThrOrigZoom[6]      *= 2.5 * sigmapb;
            mHatLow[6]           *= sigmapb;
          }

          // Statistics and cross sections.
          pythia.stat();
          double sigAbv   = sigmapb * (nEventNow - nBelow);
          double sigAbvgg = sigmapb * nAbovegg;
          double sigAbvqq = sigmapb * nAboveqq;
          double sigBel   = sigmapb * nBelow;
          double sigBelgg = sigmapb * nBelowgg;
          double sigBelqq = sigmapb * nBelowqq;
          cout << "\n sigma above threshold = " << fixed << setprecision(3)
               << sigAbv << " pb" << endl << "   whereof gg = " << sigAbvgg
               << " and qq = "<< sigAbvqq << endl << " sigma below threshold = "
               << sigBel << " pb" << endl << "   whereof gg = " << sigBelgg
               << " and qq = " << sigBelqq << endl;
          eBelow /= max(1, nBelow);
          cout << " average energy for below-threshold part = " << eBelow
               << " GeV" << endl;
        }
        // End loop over threshold cases.
      }

      // No more parallelism beyond this point.
    #ifdef OPENMP
    #pragma omp barrier
    #endif

      // Histograms with pyplot. Common notation.
      HistPlot hpl("plot369");
      string symbol[6] = {"h,black", "h,olive", "h,blue", "h,orange", "h,magenta",
                          "h,red"};
      string legend[6] = {"Born", "Coulomb", "Green's, $E > 0$",
          "Green's, $E < 0$ mirrored", "Green's, $E < 0$ mirrored + shifted",
          "Green's, all $E$"};

      // The formal E_thr value above/below threshold, key for reweighting.
      hpl.frame("fig369", "Energy above or below threshold",
        "$E$ (GeV)", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)");
      for (int scenario = 0; scenario < 6; ++scenario)
        hpl.add( mThrOrig[scenario], symbol[scenario], legend[scenario]);
      hpl.plot();
      hpl.frame("", "Energy above or below threshold",
        "$E$ (GeV)", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)");
      for (int scenario = 0; scenario < 6; ++scenario)
        hpl.add( mThrOrigZoom[scenario], symbol[scenario], legend[scenario]);
      hpl.plot();

      // Invariant mass of the ttbar pair.
      hpl.frame("", "invariant mass of ttbar pair, near threshold",
        "$m(t+tbar) (GeV)$", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)");
      for (int scenario = 0; scenario < 6; ++scenario)
        hpl.add( mHatLow[scenario], symbol[scenario], legend[scenario]);
      hpl.plot();

      // Zooming in on three specific threshold scenarios (plots for talk).
      string legMirror = (separateFrames) ? "fig369mirror" : "";
      hpl.frame(legMirror, "", "$E$ (GeV)",
        "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)", 4.8, 4.8);
      hpl.add( mThrOrigZoom[0], "h,black", "Born cross section");
      hpl.add( mThrOrigZoom[2], "h,blue", "Green's above threshold");
      hpl.add( mThrOrigZoom[3], "h,magenta", "Green's below threshold mirrored");
      hpl.add( mThrOrigZoom[2] + mThrOrigZoom[3], "h,red", "Green's in total");
      hpl.plot(-10., 30., 0., 3.5);
      string legShift = (separateFrames) ? "fig369shift" : "";
      hpl.frame(legShift, "", "$E$ (GeV)",
        "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)", 4.8, 4.8);
      hpl.add( mThrOrigZoom[0], "h,black", "Born cross section");
      hpl.add( mThrOrigZoom[2], "h,blue", "Green's above threshold");
      hpl.add( mThrOrigZoom[4], "h,magenta",
        "Green's below threshold shifted + mirrored");
      hpl.add( mThrOrigZoom[2] + mThrOrigZoom[4], "h,red", "Green's in total");
      hpl.plot(-10., 30., 0., 3.5);
      string legFull = (separateFrames) ? "fig369full" : "";
      hpl.frame(legFull, "", "$E$ (GeV)",
        "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)", 4.8, 4.8);
      hpl.add( mThrOrigZoom[0], "h,black", "Born cross section");
      hpl.add( mThrOrigZoom[5] - mThrOrigZoom[6], "h,blue",
        "Green's above threshold");
      hpl.add( mThrOrigZoom[6], "h,magenta", "Green's below threshold");
      hpl.plot(-10., 30., 0., 3.5);

      // Done.
      return 0;
    }